Reputation: 20063
I've written a Spring MVC application with a custom user details service.
@Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService{
@Autowired
private UserAccountDao userAccountDao;
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
UserDetails user = null;
try {
UserAccount dbUser = (UserAccount) userAccountDao.getByUsername(username);
At this point, userAccountDao is null, so it's throwing a null pointer exception on the above line, which would mean the autowiring isn't injecting this Dao within this service. Now the Dao itself has been autowired as such...
@Repository("userAccountDao")
public class UserAccountDaoImpl extends UserDaoImpl implements UserAccountDao {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private SessionFactory sessionFactory;
@Override
public void addUserAccount(final UserAccount userAccount) {
userAccount.setPassword(passwordEncoder.encodePassword(userAccount.getPassword(), "salt"));
sessionFactory.getCurrentSession().save(userAccount);
}
}
@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Autowired
private SessionFactory sessionFactory;
@Override
public User getByUsername(final String username) {
return (User) sessionFactory.getCurrentSession()
.createQuery("from User where username = :username")
.setParameter("username", username).uniqueResult();
}
Now this does work fine when I create users, get users from any other object, it's just this CustomUserDetailsService that's not being injected properly. It's in the same package com.securetest.app.service
as other services that are able to use @Autowired fine.
I have 3 context.xml files - below is my web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml
/WEB-INF/spring/appServlet/security-context.xml</param-value>
</context-param>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml
/WEB-INF/spring/appServlet/persistence-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
This is my security-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<http use-expressions="true">
<intercept-url pattern="/exam" access="isAuthenticated()" />
<intercept-url pattern="/" access="permitAll" />
<intercept-url pattern="/**" access="denyAll" />
<form-login />
<logout invalidate-session="true" logout-success-url="/"
logout-url="/logout" />
</http>
<beans:bean id="CustomUserDetailsService"
class="com.securetest.app.service.CustomUserDetailsService" />
<beans:bean
class="org.springframework.security.authentication.encoding.ShaPasswordEncoder"
id="passwordEncoder" />
<authentication-manager>
<authentication-provider user-service-ref='CustomUserDetailsService'>
<password-encoder ref="passwordEncoder" />
</authentication-provider>
</authentication-manager>
</beans:beans>
and finally just to make sure I've not missed anything, my servlet-context.xml - as you can see, here I'm using context-component-scan and it should inject everything in com.securetest.app
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing
infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving
up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<context:component-scan base-package="com.securetest.app." />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources
in the /WEB-INF/views directory -->
<beans:bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<beans:bean
class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<beans:property name="mediaTypes">
<beans:map>
<beans:entry key="html" value="text/html" />
<beans:entry key="json" value="application/json" />
</beans:map>
</beans:property>
<beans:property name="defaultViews">
<beans:list>
<beans:bean
class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
<beans:property name="prefixJson" value="true" />
</beans:bean>
</beans:list>
</beans:property>
</beans:bean>
</beans:beans>
I should mention, I'm pretty sure it's something like I've ordered my web.xml incorrectly or vice versa as almost exactly the same code works for another project but I can't see the difference between the two.
Why do I not get a autowire failure and just a nullPointerException?
EDIT : root-context.xml added below..
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
Upvotes: 2
Views: 5311
Reputation: 15109
I think, your security context loads before your servlet-context
, which holds the configuration for your DAOs. When security loads, DAOs haven't been scanned, hence nothing to inject! Use @Required
to check.
I do the following for my project :
in root-context.xml
<context:component-scan use-default-filters="true"
base-package="com.trelta.accountmanagement, com.trelta.commons">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
in servlet-context :
<context:component-scan use-default-filters="false"
base-package="com.trelta.accountmanagement, com.trelta.commons">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
</context:component-scan>
I put the root-context.xml
& security.xml in context-param
and servlet-context.xml
in the DispatcherServlet
's init-param
. This method also adds modularity. It keeps your WebApplicationContext
related beans in one file which is then passed to your DispatcherServlet
and your other ApplicationContext
beans in a separate file.
Upvotes: 1
Reputation: 11363
Your web.xml is creating two Spring application contexts. Let's call them Security (after security-context.xml) and Servlet (after servlet-context.xml). Security is created by the ContextLoaderListener listener, and Servlet is created by the DispatcherServlet servlet. Security is the parent of Servlet. That means that the beans in Security are only visible to other beans in Security, and the beans in Servlet can see both the beans in Security and in Servlet.
You're defining the CustomUserDetailsService (CUDS) bean in Security, and the UserAccountDao and UserDao beans in Servlet, so the CUDS bean can't see them. You need to add a component scan in Security for the DAO beans if you want them wired in to CUDS.
I'm not sure about the NPE.
Upvotes: 3