@Autowired 对象在一个类中获取空值,而在另一个类中成功连接
我正在开发一个使用 Spring 3 和 Spring Security 的项目。我的问题是 IoC 容器。当我为 Spring Security-3 编写自己的 UserDetailsService
实现时,问题就开始了。我检查了其他问题,但仍然无法解决问题。
问题的定义是:
我有两个单独的类(一个是UsersController.java
,它扩展了@Controller
,以及ProjectUserDetailsService
,它扩展了@Service
),使用通用对象进行自动装配。但是,虽然对象在 UsersController
中自动装配成功,但在 ProjectUserDetailsService
类中它是 null
,尽管该类的对象(ProjectUserDetailsService
>)已成功创建(我通过调试验证了这一点)。
有什么建议如何解决这个问题吗?
以下是我的 web.xml
、project-servlet.xml
和 project-security.xml
文件和相关类。
Web.xml`
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Tutorial web application
-
-->
<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">
<display-name>Ecognitio with Spring Security</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/ecognitio-servlet.xml
/WEB-INF/ecognitio-security.xml
</param-value>
</context-param>
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>tutorial.root</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>
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
<servlet>
<servlet-name>ecognitio</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>project</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>project</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
project-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<!-- Scans the classpath of this application for @Components to deploy as beans -->
<context:component-scan base-package="com.project" />
<!-- Configures the @Controller programming model -->
<mvc:annotation-driven />
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource"
p:basename="Messages"/>
<!-- misc -->
<!-- <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="suffix" value=".jsp"/>
</bean> -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass">
<value>
org.springframework.web.servlet.view.tiles2.TilesView
</value>
</property>
</bean>
<bean id="tilesConfigurer"
class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/tiles.xml</value>
</list>
</property>
</bean>
<!-- Configures Hibernate - Database Config -->
<import resource="db-config.xml" />
</beans>
project-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Sample namespace-based configuration
-
-->
<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-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<debug />
<global-method-security pre-post-annotations="enabled">
<!-- AspectJ pointcut expression that locates our "post" method and applies security that way
<protect-pointcut expression="execution(* bigbank.*Service.post*(..))" access="ROLE_TELLER"/>
-->
</global-method-security>
<http pattern="/loggedout.jsp" security="none"/>
<http use-expressions="true" >
<intercept-url pattern="/secure/extreme/**" access="hasRole('ROLE_SUPERVISOR')"/>
<intercept-url pattern="/secure/**" access="isAuthenticated()" />
<!--
Allow all other requests. In a real application you should
adopt a whitelisting approach where access is not allowed by default
-->
<intercept-url pattern="/login.jsp*" access="isAuthenticated()==false"/>
<intercept-url pattern="/timeout.jsp*" access="isAuthenticated()==false"/>
<intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
<!-- <intercept-url pattern="/**" access="permitAll" /> -->
<form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?login_error=1" default-target-url="/dashboard.html" />
<logout logout-success-url="/login.jsp" delete-cookies="JSESSIONID"/>
<remember-me />
<!--
Uncomment to enable X509 client authentication support
<x509 />
-->
<!-- Uncomment to limit the number of sessions a user can have
<session-management invalid-session-url="/login.jsp">
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
-->
</http>
<!-- HERE IS WHERE I USE an object of ProjectUserDetailsService -->
<authentication-manager>
<authentication-provider user-service-ref="userDetailsService" />
</authentication-manager>
<!--
</beans:beans>
UsersController.java
(UsersDAO 类的对象自动装配为此成功class)
package com.project.users;
//required imports
@Controller
public class UsersControllers
{
@Autowired
private UsersDAO usersDAO;
//Some more autowires and some class specific code
}
ProjectUserDetailsService.java
(其中 UsersDAO 的自动装配不起作用)
package com.project.security;
import java.util.ArrayList;
import java.util.Collection;
//required imports
@SuppressWarnings("deprecation")
@Service("userDetailsService")
public class ProjectUserDetailsService implements UserDetailsService {
@Autowired
private UsersDAO usersDAO;
@Autowired private Assembler assembler;
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
UserDetails userDetails = null;
//For debugging purposes
if(usersDAO==null){
System.out.println("DAO IS NULL");
System.out.println("DAO IS NULL");
System.out.println("DAO IS NULL");
}
User userEntity = usersDAO.findUserbyEmail("'"+username+"'");
if (userEntity == null)
throw new UsernameNotFoundException("user not found");
return assembler.buildUserFromUserEntity(userEntity);
}
}
I am working on a project using Spring 3, and Spring Security. My problem is with IoC container. Problem started when I wrote my own implementation of UserDetailsService
for Spring Security-3. I checked the other questions but still could not solve the problem.
Definition of the problem is:
I have two seperate classes(One is UsersController.java
which extends @Controller
, and ProjectUserDetailsService
which extends @Service
) that uses a common object to be autowired. But while object is autowired successfully in UsersController
, it is null
in ProjectUserDetailsService
class altough the object of this class(ProjectUserDetailsService
) is successfully created(I verified this by debugging).
Any suggestions how to solve this?
Here are my web.xml
, project-servlet.xml
and project-security.xml
files and related classes.
Web.xml`
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Tutorial web application
-
-->
<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">
<display-name>Ecognitio with Spring Security</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/ecognitio-servlet.xml
/WEB-INF/ecognitio-security.xml
</param-value>
</context-param>
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>tutorial.root</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>
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
<servlet>
<servlet-name>ecognitio</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>project</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>project</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
project-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<!-- Scans the classpath of this application for @Components to deploy as beans -->
<context:component-scan base-package="com.project" />
<!-- Configures the @Controller programming model -->
<mvc:annotation-driven />
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource"
p:basename="Messages"/>
<!-- misc -->
<!-- <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="suffix" value=".jsp"/>
</bean> -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass">
<value>
org.springframework.web.servlet.view.tiles2.TilesView
</value>
</property>
</bean>
<bean id="tilesConfigurer"
class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/tiles.xml</value>
</list>
</property>
</bean>
<!-- Configures Hibernate - Database Config -->
<import resource="db-config.xml" />
</beans>
project-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Sample namespace-based configuration
-
-->
<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-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<debug />
<global-method-security pre-post-annotations="enabled">
<!-- AspectJ pointcut expression that locates our "post" method and applies security that way
<protect-pointcut expression="execution(* bigbank.*Service.post*(..))" access="ROLE_TELLER"/>
-->
</global-method-security>
<http pattern="/loggedout.jsp" security="none"/>
<http use-expressions="true" >
<intercept-url pattern="/secure/extreme/**" access="hasRole('ROLE_SUPERVISOR')"/>
<intercept-url pattern="/secure/**" access="isAuthenticated()" />
<!--
Allow all other requests. In a real application you should
adopt a whitelisting approach where access is not allowed by default
-->
<intercept-url pattern="/login.jsp*" access="isAuthenticated()==false"/>
<intercept-url pattern="/timeout.jsp*" access="isAuthenticated()==false"/>
<intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
<!-- <intercept-url pattern="/**" access="permitAll" /> -->
<form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?login_error=1" default-target-url="/dashboard.html" />
<logout logout-success-url="/login.jsp" delete-cookies="JSESSIONID"/>
<remember-me />
<!--
Uncomment to enable X509 client authentication support
<x509 />
-->
<!-- Uncomment to limit the number of sessions a user can have
<session-management invalid-session-url="/login.jsp">
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
-->
</http>
<!-- HERE IS WHERE I USE an object of ProjectUserDetailsService -->
<authentication-manager>
<authentication-provider user-service-ref="userDetailsService" />
</authentication-manager>
<!--
</beans:beans>
UsersController.java
(autowiring of an object of UsersDAO class is successful for this class)
package com.project.users;
//required imports
@Controller
public class UsersControllers
{
@Autowired
private UsersDAO usersDAO;
//Some more autowires and some class specific code
}
ProjectUserDetailsService.java
(where autowiring of UsersDAO does not work)
package com.project.security;
import java.util.ArrayList;
import java.util.Collection;
//required imports
@SuppressWarnings("deprecation")
@Service("userDetailsService")
public class ProjectUserDetailsService implements UserDetailsService {
@Autowired
private UsersDAO usersDAO;
@Autowired private Assembler assembler;
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
UserDetails userDetails = null;
//For debugging purposes
if(usersDAO==null){
System.out.println("DAO IS NULL");
System.out.println("DAO IS NULL");
System.out.println("DAO IS NULL");
}
User userEntity = usersDAO.findUserbyEmail("'"+username+"'");
if (userEntity == null)
throw new UsernameNotFoundException("user not found");
return assembler.buildUserFromUserEntity(userEntity);
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
是的,似乎不可能将对象自动装配到从 Spring 安全类继承的 bean 中。我不知道这是否是 Spring Security 中的一个错误,或者它是否是为了安全或什么而做的。如果有人有解释,我有兴趣听听。您可以通过 xml 配置手动注入 beans(而不是使用 @Autowired 注释)来解决您的问题,然后它们就会出现。不过,请注意……
我这样做了,我注意到我的 userDao 上有注释(特别是 @Transactional),不再在事务中操作。我的 userDao 已在多个地方使用。如果我将它注入到我的自定义 AbstractUserDetailsAuthenticationProvider 中,它就不再在使用它的任何其他类的事务中运行。删除对自定义 AbstractUserDetailsAuthenticationProvider 的注入,将事务功能恢复到我的 userDao 中,当其他接收它的对象使用它时(通过 @Autowired 或手动 xml 注入)。
那么我如何让我的 userDao 进入我的 Spring Security 上下文并仍然保持它 @Transactional ?我必须创建一个 Factory 类:
然后将其和您的 dao 两个对象放入 spring 容器中:
这样 userDao 将自动连接到您的 userDaoFactory 中。它将拥有所有 @Transactional 功能(因为 Spring Security 还没有将其剥离?)。然后在您的 Spring Security 对象中,您可以执行以下操作:
我在自定义 AbstractUserDetailsAuthenticationProvider 对象中实现了 ServletContextAware,以便在初始化期间执行上述操作一次,以及 viola。
因此请注意,虽然您可以通过 xml 配置手动将 bean 注入到 spring security 对象中以克服 @Autowired 问题,但如果您尝试将该 DAO 包装在 @Transactional 中,您最终会遇到一个新问题。
现在也许有人知道为什么会发生这种情况。这可能是我的配置错误(我承认我不是 Spring 专家),或者它可能是 Spring 的一个功能。我很想听听大家的意见以及如何改进这一点。
yes, it seems to be impossible to autowire objects into beans that inherit from spring security classes. i don't know if this is a bug in spring security, or if it's done for security or what. if anyone has an explanation I would be interested in hearing it. You can solve your problem though by manually injecting the beans via xml configuration (as opposed to using the @Autowired annotation) and then they will be present. One word of caution though..
I did this, and i noticed that my userDao which had annotations on it (specifically @Transactional) was no longer operating in a transaction. My userDao was being used in multiple places. If I injected it into my custom AbstractUserDetailsAuthenticationProvider though, it no longer operated in a transaction for any other class that used it. Removing the injection into the custom AbstractUserDetailsAuthenticationProvider restored the transaction functionality to my userDao when used by other objects which were receiving it (either via @Autowired or manual xml injection).
So how did I get my userDao into my Spring Security context and still keep it @Transactional? I had to create a Factory class:
Then put this and your dao two objects into the spring container:
So the userDao will get autowired into your userDaoFactory. It will have all of it's @Transactional capability (because spring security hasn't stripped it off?). Then in your spring security object you can do a:
I implemented ServletContextAware in my custom AbstractUserDetailsAuthenticationProvider object to do the above once during initialization, and viola.
So note, while you can manually inject your bean via xml configuration into the spring security object to overcome the @Autowired problem, you will end up with a new problem if you are trying to wrap that DAO in @Transactional.
now maybe someone knows why this may be happening. it could be a mis-configuration on my part (i admit i'm not a spring expert), or it could be a feature of spring. i would love to hear what anyone has to say and how to improve this though.
由于第二个 bean 不在
project-servlet.xml
中指定的指定 bean 注释包组件扫描中:它不认为它是一个服务,也不翻译注释。
您需要进一步扩展它或将其移动到以 com.project 开头的包中,或者像这样:
Since the second bean is not in the specified bean annotation package component scan as specified in
project-servlet.xml
:it does not consider it to be a service and does not translate the annotations.
You need to extend it further or move it to a package starting with com.project or else like this:
我不知道为什么:(但是从project-security.xml中删除标签就可以了!
http://forum.springsource.org/showthread.php?115561-Autowire-on-custom-authentication-provider-doesn-t-work
I don't know why :( but removing the tag from project-security.xml and will work!
http://forum.springsource.org/showthread.php?115561-Autowire-on-custom-authentication-provider-doesn-t-work
这可能是由于 SEC-1911 导致依赖 BeanPostProcessor (如
) 时出现问题AutowiredAnnotationBeanPostProcessor
并使用
元素。尝试删除
并查看 @Autowired 是否再次工作。请注意,第一个问题与 SEC-1885 重复,这是修复此问题的地方但第一个问题的症状更符合这个问题。This is likely due to SEC-1911 which causes issues when relying on BeanPostProcessors like
AutowiredAnnotationBeanPostProcessor
and using the<debug />
element. Try removing<debug />
and seeing if @Autowired works again. Note that this first issue is a duplicate of SEC-1885, which is where this is fixed but the first issue's symptoms better match up with this issue.我也有同样的问题。尽管这种情况可以通过多种方式发生,但在我的例子中,我创建了一个新对象,而不是使用
autowired
对象。即:而不是:
这不是在使用存储库注入的服务中,而是在其之上的控制器中。
I had the same problem. Although this can happen in many ways, in my case I was creating a new object, instead of using the
autowired
one. i.e.:instead of:
This was not in the Service using the Repository injection, but in a controller on top of that.