@Autowired object gets a null value in one class,

2020-07-02 08:18发布

问题:

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);

  }
}

回答1:

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:

public class UserDaoFactory {

private static UserDao userDao;

public static UserDao getUserDao() {
    return UserDaoFactory.userDao;
}

public void setUserDao(UserDao userDao) {
    UserDaoFactory.userDao = userDao;
}
}

Then put this and your dao two objects into the spring container:

<bean id="userDao" class="com.package.UserDaoImpl">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

<bean id="userDaoFactory" class="com.package.UserDaoFactory">
    <property name="userDao" ref="userDao" />
</bean>

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:

userDao = UserDaoFactory.getUserDao();

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.



回答2:

Since the second bean is not in the specified bean annotation package component scan as specified in project-servlet.xml:

<context:component-scan base-package="com.project" />

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:

<context:component-scan base-package="com" />


回答3:

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



回答4:

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.



回答5:

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.:

private Service service = new ServiceImpl();

instead of:

@Autowired private Service service;

This was not in the Service using the Repository injection, but in a controller on top of that.