Spring MVC Смешивание xml и java @ContextConfiguration в интеграционном тесте

Я пытаюсь настроить тест интеграции Spring MVC, используя комбинацию конфигурации XML и @Configuration аннотированных классов.

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@TestPropertySource({"/spring-security.properties",
                     "/rabbitmq-default.properties",
                     "/mongodb-default.properties",
                     "/webapp-override.properties"})
@ContextHierarchy({
    @ContextConfiguration("classpath:**/security-config.xml"),
    @ContextConfiguration(classes = RootConfig.class),
    @ContextConfiguration(classes = SpringMvcConfig.class)
})
public class BaseConfiguredMvcIntegrationTest {
}

Конфигурации Java инициализированы правильно. Проблема в том, что файл "**/security-config.xml" найден и проанализирован во время инициализации... все bean-компоненты безопасности spring, определенные в нем, никогда не регистрируются в файле WebApplicationContext.

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1301)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1047)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:533)

Итак, мой вопрос: как вы используете конфигурацию на основе XML и на основе аннотаций в тесте интеграции Spring MVC?

Я мог бы изменить конфигурацию безопасности spring на java/аннотированную... Я бы предпочел этого не делать. Я нахожу использование пространства имен безопасности spring более читаемым и лаконичным, чем использование конфигурации java.

Также обратите внимание, что эта комбинированная конфигурация XML/Java отлично работает в нетестовой среде.

  • Весна v4.1.6
  • Весенняя безопасность v4.0.1

Конфигурация контекста веб-приложения:

package com.gggdw.web.config;

import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.DispatcherServlet;


@Configuration
public class GGGWebInitializer implements WebApplicationInitializer {

    public static final String SERVLET_NAME = "ggg";

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        // Create the 'root' Spring application context
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(RootConfig.class);

        // Manage the lifecycle of the root application context
        servletContext.addListener(new ContextLoaderListener(rootContext));

        // Create the dispatcher servlet's Spring application context
        AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
        dispatcherContext.register(SpringMvcConfig.class);

        // Register and map the dispatcher servlet
        ServletRegistration.Dynamic dispatcher = servletContext.addServlet(SERVLET_NAME, new DispatcherServlet(dispatcherContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");

        //Spring security config
        FilterRegistration.Dynamic springSecurityFilterChain = servletContext.addFilter(
                                                    "securityFilter", new DelegatingFilterProxy("springSecurityFilterChain"));

        springSecurityFilterChain.addMappingForServletNames(null, false, SERVLET_NAME);
        //springSecurityFilterChain.setAsyncSupported(true);

        servletContext.addFilter("hiddenHttpMethodFilter", HiddenHttpMethodFilter.class);

    }

}

RootConfig.class

@Configuration
@Import({WebPropertiesConfig.class,                     // loads all properties files on class path from resources folder
         MongoConfig.class                              // init mongodb connection 
         })                     
@ImportResource({"classpath:**/security-config.xml"})   // spring security xml config (java config not as readable)
public class RootConfig {

}

security-config.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:security="http://www.springframework.org/schema/security"
       xmlns:context="http://www.springframework.org/schema/context"
       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.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- <context:property-placeholder location="classpath:spring-security.properties" /> -->

    <security:global-method-security pre-post-annotations="enabled" secured-annotations="enabled">
       <security:expression-handler ref="expressionHandler"/>
    </security:global-method-security>

    <bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
        <property name="permissionEvaluator" ref="permissionEvaluator"/>
    </bean>

    <bean id="permissionEvaluator"
          class="com.my.local.package.security.GenericPermissionEvaluator">
    </bean>

    <!-- Configure Spring Security -->
    <security:http auto-config="true" use-expressions="true" >
        <security:form-login login-page="${spring.security.login-page}" 
                             login-processing-url="${spring.security.login-processing-url}" 
                             default-target-url="${spring.security.default-target-url}" 
                             authentication-failure-url="${spring.security.authentication-failure-url}" 
                             username-parameter="${spring.security.username-parameter}"
                             password-parameter="${spring.security.password-parameter}"
        />
        <security:logout logout-url="${spring.security.logout-url}" 
                 logout-success-url="${spring.security.logout-success-url}" />

        <security:intercept-url pattern="/**" requires-channel="https" />
        <security:intercept-url pattern="/s/**" access="isAuthenticated()" requires-channel="https" />
        <security:custom-filter ref="log4JMDCFilter" after="SECURITY_CONTEXT_FILTER"/>
        <security:access-denied-handler error-page="${spring.security.access-denied-handler-error-page}" />
        <!-- <security:session-management invalid-session-url="${spring.security.invalid-session-url}"/> 
              2 types of invalid session, brand new user and a timeout of a previous logged in user
              both need to be handled differently -->
    </security:http>

    <bean id="customUserDetailsService" class="com.my.local.package.CustomUserDetailsService" depends-on="userRepository"/>   

    <bean id="bCryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />


   <!--  log4j filter to add userName and ipAddress into logging on a per request/thread basis -->
    <bean id="log4JMDCFilter" class="com.my.local.package.filter.Log4JMDCFilter"/>

    <security:authentication-manager>
       <security:authentication-provider user-service-ref="customUserDetailsService">
           <security:password-encoder ref="bCryptPasswordEncoder"/>
       </security:authentication-provider>
    </security:authentication-manager>


</beans>

person Selwyn    schedule 02.06.2015    source источник
comment
Я начал мучительный процесс перехода на конфигурацию Java Spring Security... 6 часов :(   -  person Selwyn    schedule 03.06.2015


Ответы (2)


ОБНОВЛЕНИЕ: при дальнейшем рассмотрении и на основе ваших последних отзывов поведение, с которым вы сталкиваетесь, может быть результатом ошибки, появившейся в Spring Framework 4.1.4 (см. SPR-13075 для подробностей).

Попробуйте перейти на Spring Framework 4.1.3 и дайте мне знать, если вы по-прежнему сталкиваетесь с нежелательным поведением.


обратите внимание, что эта комбинированная конфигурация XML/Java отлично работает в нетестовой среде.

Как же так?

У вас есть буквально три (3) контекста, загруженных в иерархию в производстве?

Я сомневаюсь, что. Скорее, я предполагаю, что вы каким-то образом загружаете один корень WebApplicationContext из "classpath:**/security-config.xml" и RootConfig.class.

Таким образом, самый важный вопрос: Как вы настраиваете корневой WebApplicationContext в рабочей среде?

Как только вы ответите на этот вопрос, я могу рассказать вам, как добиться того же в вашей тестовой конфигурации. ;)

С уважением,

Сэм (автор Spring TestContext Framework)

person Sam Brannen    schedule 03.06.2015
comment
нет, у меня нет 3. **/security-config.xml действительно находится внутри класса RootConfig. Я просто вынул его, чтобы прояснить проблему. Я расширяю WebApplicationInitializer и вставляю RootConfig.class в rootContext и SpringMvcConfig.class в контекст сервлета. Я обновлю исходный пост с этой конфигурацией. Это сервлет 3.1+, поэтому файл web.xml не используется. - person Selwyn; 03.06.2015
comment
extension* = реализация WebApplicationInitializer - person Selwyn; 03.06.2015
comment
переход на 4.1.3 НЕ решил проблему. Компоненты по-прежнему не загружаются в контекст, если они находятся в файле xml. Я уже перешел на конфигурацию безопасности на основе аннотаций... и во время этой миграции у меня возникла другая проблема/ошибка stackoverflow.com/questions/30613148/... что, как ни странно, было исправлено, когда я понизил версию до 4.1.3, ха-ха. Можете ли вы воспроизвести это на своей стороне, чтобы проверить, является ли это ошибкой? - person Selwyn; 03.06.2015
comment
Пожалуйста, покажите нам, как конфигурация XML находится внутри RootConfig.class. - person Sam Brannen; 04.06.2015

Обратите внимание на примечание от PathMatchingResourcePatternResolver:

Обратите внимание, что «classpath*:» в сочетании с шаблонами в стиле Ant будет надежно работать только по крайней мере с одним корневым каталогом до запуска шаблона, если только фактические целевые файлы не находятся в файловой системе. Это означает, что шаблон типа "classpath*:*.xml" не будет извлекать файлы из корня jar-файлов, а только из корня расширенных каталогов. Это происходит из-за ограничения метода ClassLoader.getResources() JDK, который возвращает местоположения файловой системы только для переданной пустой строки (указывающей потенциальные корни для поиска).

person MamOn    schedule 02.12.2015