简体   繁体   中英

Why I can't inject this bean annoted with @Service into a bean declared into the spring-security.xml configuration file?

I am pretty new in Spring and I have some problem trying to inject a bean into a class named CustomUserDetailsMapper (I think that it could depend by something like scoping issue).

So this is the code of my CustomUserDetailsMapper , this class extends LdapUserDetailsMapper and it is automatically called after an LDAP authentication and it is used by the LDAP authentication provider to create an LDAP user object. (but it is not important, the only important thing is that it is automatically call by Spring)

package it.myCompany.miur.gestioneUtenze.security;

// IMPORT REMOVED FROM THE CODE SNIPPET

public class CustomUserDetailsMapper extends LdapUserDetailsMapper  {

    private static final Logger _logger = Logger.getLogger(CustomUserDetailsMapper.class.getName());

 @Autowired
    private ProgettoService progettoService;


    @Override
    public UserDetails mapUserFromContext(DirContextOperations dirContextOperations, String userName, Collection<? extends GrantedAuthority> authorities) {
        ...............................................
        ...............................................
        ...............................................
        USE progettoService VARIABLE
        ...............................................
        ...............................................
        ............................................... 
    }

}

As you can see in the previous code I am trying to inject the ProgettoService (that is a service that perform some queries on my DB) object by:

@Autowired
private ProgettoService progettoService;

The problem is that, when the mapUserFromContext() method is performed this field is null and I can't use it inside this method.

It seem to me strange because I inject this bean in a similar way (constructor injection) into a controller class, in this way (constructor injection):

package it.myCompany.miur;

@Controller
public class HomeController {

    @Autowired
    public HomeController(ProgettoService progettoService,ScuolaService scuolaService) {
        this.progettoService = progettoService;
        this.scuolaService=scuolaService;
    }

    ...........................................
    ...........................................
    ...........................................
}

And in this second case it works fine and I can correctly use my ProgettoService object.

So, the configuration of my application is done by these XML files:

1) web.xml :

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <listener>
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>
    <context-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>classpath:/META-INF/log4j.properties</param-value>
    </context-param>


    <!-- Processes application requests -->
    <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</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>

    <!-- Creates the Spring Container shared by all Servlets and Filters -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/spring/spring-security.xml
            /WEB-INF/spring/root-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>   

</web-app>

As you can see inside it are defined the following other XML configurations file:

2) servlet-context.xml : for the MVC configuration:

<?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/" />

    <!-- 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>

    <context:component-scan base-package="it.myCompany.miur" />

</beans:beans>

As you can see in this file are defined the:

<annotation-driven />

to enables the Spring MVC @Controller programming model, and I specify the package to scan (where search the @Controller and derived annotation):

<context:component-scan base-package="it.myCompany.miur" />

Is it this reasoning correct?

3) spring-security.xml : where is defined the security configurations:

<?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-4.0.xsd
                        http://www.springframework.org/schema/security 
                        http://www.springframework.org/schema/security/spring-security-4.0.xsd">

    <ldap-server id="ldapServer"
        url="${ldap.connection.url}"
        manager-dn="${ldap.connection.admin.user}"
        manager-password="${ldap.connection.admin.password}"/>

    <http auto-config="true"  use-expressions="true" authentication-manager-ref="authenticationManager">
        <intercept-url pattern="/" access="permitAll" />
    <!--<intercept-url pattern="/resetPassword" access="permitAll" />
        <intercept-url pattern="/nuovaPassword" access="permitAll" />
        <intercept-url pattern="resources/css/style.css" access="permitAll"/>
        <intercept-url pattern="resources/img/*" access="permitAll"/>-->
        <intercept-url pattern="/home" access="isAuthenticated()" /> 
        <logout logout-success-url="/" logout-url="/logout" />
        <form-login  login-page="/"  
                     authentication-failure-url="/?error=true"
                    default-target-url="/home"
                    username-parameter="username"
                    password-parameter="password"
                    login-processing-url="/j_spring_security_check"/>
        <csrf disabled="true"/>

    </http> 

    <!-- Authenticator -->
    <beans:bean class="org.springframework.security.ldap.authentication.BindAuthenticator" id="ldapBindAuthenticator">
        <beans:constructor-arg ref="ldapServer"/>
        <beans:property name="userDnPatterns">
            <beans:list><beans:value>cn={0},ou=Users,dc=miur,dc=it</beans:value>
                <beans:value>ou=mailusers,o=mpi.it</beans:value>
            </beans:list>
        </beans:property>
    </beans:bean>

    <beans:bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
        <beans:constructor-arg>
            <beans:list>
                <beans:ref bean="ldapAuthProvider" />
            </beans:list>
        </beans:constructor-arg>
    </beans:bean>

    <beans:bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
        <beans:constructor-arg ref="ldapBindAuthenticator"/>
        <beans:property name="userDetailsContextMapper" ref="customUserContextMapper"/>
    </beans:bean>

    <beans:bean id="customUserContextMapper" class="it.myCompany.miur.gestioneUtenze.security.CustomUserDetailsMapper"/>

</beans:beans>

As you can see in this XML configuration file is defined my CustomUserDetailsMapper (the bean where I can't inject the ProgettoService bean), by:

<beans:bean id="customUserContextMapper" class="it.myCompany.miur.gestioneUtenze.security.CustomUserDetailsMapper"/>

3) Finally I have the root-context.xml configuration file:

<?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:context="http://www.springframework.org/schema/context"
    xmlns:ldap="http://www.springframework.org/schema/ldap"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.1.xsd
        http://www.springframework.org/schema/data/repository http://www.springframework.org/schema/data/repository/spring-repository-1.7.xsd
        http://www.springframework.org/schema/ldap http://www.springframework.org/schema/ldap/spring-ldap-2.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd
        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
    <!-- Root Context: defines shared resources visible to all other web components -->

<!--<context:property-placeholder
    location="file:///${jboss.modules.dir}/system/layers/base/it/myCompany/gestioneUtenze/main/gestioneUtenze.properties classpath*:gestioneUtenze_local.properties" />-->
<context:property-placeholder
    location="classpath*:wifi.properties" />

 <jpa:repositories base-package="it.myCompany.miur.wifipnsd.repository"
    entity-manager-factory-ref="entityManagerFactory"
    transaction-manager-ref="transactionManager"/> 

<bean id="entityManagerFactory" name="wifipnsd"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="datasource" /> 
    <property name="persistenceUnitName" value="wifipnsdPU" />
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" >             
            <property name="showSql" value="false" />
            <property name="generateDdl" value="false" />
            <property name="database" value="MYSQL"/>
            <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
        </bean>
    </property>
</bean> 

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- Data Source -->
<jee:jndi-lookup jndi-name="java:jboss/datasources/wifiDS"
    id="datasource" expected-type="javax.sql.DataSource" />


<bean id="messageSource"
    class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename" value="message" />
</bean>

where is configured the datasource and the hibernate configuration.

So why, using @Autowired , I can inject the ProgettoService object into the HomeController controller class but I can't do it into the ProgettoService class? What am I missing? How can I solve this issue and inject it also into the ProgettoService class?

Maybe the problem could depend by the fact that beans declared in a child context (servlet-context) can see beans declared in parent context but not viceversa? I have no idea about how can I try to solve it. Can you help me?

You have to add <context:component-scan ...> to root-context.xml and annotate the ProgettoService implementation as @Service .

The <context:component.scan ../> is needed by spring to know where is has to look for annotation. The @Service annotation is needed that spring manage the lifecycle of the bean and the bean can be autowired.

For more information see the javadoc of @Service

What you need to understand you have two different contexts

the root context thats created by ContextLoaderListener , as well as sub context created by the DispatcherServlet .

Since CustomUserDetailsMapper does not have any annotation for eg: @Service its not picked by scan the mentioned in servlet-context.xml componentscan. Hence in not in the sub context of dispatcher servlet. But in case of Homecontroller you have @Controller as well your ProgettoService is in the component scan package path.

The CustomUserDetailsMapper is present in the root context due the bean definition in the spring-security.xml . This is the reason the bean was not injected initially.

I would suggest to remove the bean definition from spring-security.xml and annotate the CustomUserDetailsMapper class with @Service or @Component what ever is appropriate. This will work as CustomUserDetailsMapper is in the component scan path.

By implementing the answer by Jens, you have basically duplicated the bean in both context.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM