2014-11-04 6 views
4

У меня есть простой класс контроллера:Не удается построить MockMvc (Существует уже обработчик типа X отображенного)

@Controller 
public class UserController { 
    @RequestMapping("/user") 
    @ResponseBody 
    @PostAuthorize("hasPermission(returnObject, 'VIEW')") 
    public User getUser(Long id) { 
     // fetch user from DB 
    } 
} 

Я хочу интеграцию-тестирования моих контроллеров с Spring Security. На основе this article я создал следующий тест:

@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(loader = AnnotationConfigWebContextLoader.class, classes = WebappConfig.class) 
@WebAppConfiguration 
public class UserControllerTest { 
    @Autowired 
    private WebApplicationContext context; 

    @Autowired 
    private Filter springSecurityFilterChain; 

    protected MockMvc mvc; 

    @Before 
    public void initializeMvc() { 
     mvc = MockMvcBuilders 
       .webAppContextSetup(context) 
       .addFilters(springSecurityFilterChain) 
       .build(); 
    } 

    // tests 
} 

Однако внутри метода @Before, когда build() вызывается следующее исключение броска:

java.lang.IllegalStateException: 
Cannot map handler 'userController' to URL path [/user]: 
There is already handler of type 
[class com.example.UserController$$EnhancerBySpringCGLIB$$adfe5ea8] mapped. 

Похоже, весна пытается дважды запрограммируйте контроллер. Зачем?

Webapp конфигурации:

@Configuration 
@EnableWebMvc 
@ComponentScan("com.example") 
@EnableGlobalMethodSecurity(prePostEnabled = true) 
@EnableSpringDataWebSupport 
public class WebappConfig extends WebMvcConfigurerAdapter { } 

Полный трассировки стека:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping': Initialization of bean failed; nested exception is java.lang.IllegalStateException: Cannot map handler 'userController' to URL path [/user]: There is already handler of type [class com.example.rest.controller.UserController$$EnhancerBySpringCGLIB$$adfe5ea8] mapped. 
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:547) ~[spring-beans-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476) ~[spring-beans-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:291) ~[spring-beans-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.web.servlet.DispatcherServlet.createDefaultStrategy(DispatcherServlet.java:849) ~[spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.web.servlet.DispatcherServlet.getDefaultStrategies(DispatcherServlet.java:818) ~[spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.web.servlet.DispatcherServlet.initHandlerMappings(DispatcherServlet.java:588) ~[spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.web.servlet.DispatcherServlet.initStrategies(DispatcherServlet.java:482) ~[spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.web.servlet.DispatcherServlet.onRefresh(DispatcherServlet.java:471) ~[spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:555) ~[spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:489) ~[spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:136) [spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at javax.servlet.GenericServlet.init(GenericServlet.java:244) [javax.servlet-api-3.1.0.jar:3.1.0] 
    at org.springframework.test.web.servlet.MockMvcBuilderSupport.createMockMvc(MockMvcBuilderSupport.java:52) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.test.web.servlet.setup.AbstractMockMvcBuilder.build(AbstractMockMvcBuilder.java:146) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at com.example.UserControllerTest.initializeMvc(UserControllerTest .java:40) [test/:na] 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_45] 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_45] 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_45] 
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_45] 
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) [junit-4.12.jar:4.12] 
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) [junit-4.12.jar:4.12] 
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) [junit-4.12.jar:4.12] 
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) [junit-4.12.jar:4.12] 
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) [junit-4.12.jar:4.12] 
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:73) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55) [junit-4.12.jar:4.12] 
    at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55) [junit-4.12.jar:4.12] 
    at org.junit.rules.RunRules.evaluate(RunRules.java:20) [junit-4.12.jar:4.12] 
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:73) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) [junit-4.12.jar:4.12] 
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:224) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:83) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) [junit-4.12.jar:4.12] 
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) [junit-4.12.jar:4.12] 
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) [junit-4.12.jar:4.12] 
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) [junit-4.12.jar:4.12] 
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) [junit-4.12.jar:4.12] 
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:68) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363) [junit-4.12.jar:4.12] 
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:163) [spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137) [junit-4.12.jar:4.12] 
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78) [junit-rt.jar:na] 
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212) [junit-rt.jar:na] 
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68) [junit-rt.jar:na] 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_45] 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_45] 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_45] 
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_45] 
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140) [idea_rt.jar:na] 
Caused by: java.lang.IllegalStateException: Cannot map handler 'userController' to URL path [/user]: There is already handler of type [class com.example.rest.controller.UserController$$EnhancerBySpringCGLIB$$adfe5ea8] mapped. 
    at org.springframework.web.servlet.handler.AbstractUrlHandlerMapping.registerHandler(AbstractUrlHandlerMapping.java:295) ~[spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.web.servlet.handler.AbstractUrlHandlerMapping.registerHandler(AbstractUrlHandlerMapping.java:265) ~[spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping.detectHandlers(AbstractDetectingUrlHandlerMapping.java:82) ~[spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping.initApplicationContext(AbstractDetectingUrlHandlerMapping.java:58) ~[spring-webmvc-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.context.support.ApplicationObjectSupport.initApplicationContext(ApplicationObjectSupport.java:120) ~[spring-context-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.web.context.support.WebApplicationObjectSupport.initApplicationContext(WebApplicationObjectSupport.java:76) ~[spring-web-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.context.support.ApplicationObjectSupport.setApplicationContext(ApplicationObjectSupport.java:74) ~[spring-context-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.context.support.ApplicationContextAwareProcessor.invokeAwareInterfaces(ApplicationContextAwareProcessor.java:119) ~[spring-context-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.context.support.ApplicationContextAwareProcessor.postProcessBeforeInitialization(ApplicationContextAwareProcessor.java:94) ~[spring-context-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:408) ~[spring-beans-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1566) ~[spring-beans-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539) ~[spring-beans-4.1.6.RELEASE.jar:4.1.6.RELEASE] 
    ... 51 common frames omitted 

Я могу запустить этот тест в установке автономного MockMvc, но мне кажется, что I can't use spring data web integration then, который в равной степени раздражает.

+0

Я считаю, что, когда вы используете аннотацию Прежде чем он будет выполнять этот метод каждый раз над каждым испытанием, вам нужно будет BeforeClass, чтобы предотвратить его, чтобы выполнить его в два раза. –

+0

Чтобы помочь вам понять, почему Spring пытается сопоставить свой UserController дважды, нам нужно будет увидеть (1) вашу конфигурацию ApplicationContext и (2) полную трассировку стека. –

+0

Кстати, является ли код вашего 'UserController' идентичным тому, что находится в вашей базе кода? Я спрашиваю, потому что исключение показывает, что Spring создала для него прокси-сервер на основе CGLIB, и на основе вашего примера я не могу легко понять, почему Spring захочет прокси-сервера этого контроллера. –

ответ

2

Это предположение, поскольку вы не предоставляете другие части системы, которые могут показаться не связанными, но на самом деле они могут быть из-за аннотации @ComponentScan («com.example») класса WebappConfig. Это, как правило, самая распространенная причина в моем опыте.

Если у вас есть другие классы @Configuration в пакете com.example или любой из его подпакетов, они также будут загружены в Контекст.

Теперь, если в других классах @Configuration также есть аннотация @ComponentScan, это будет, вероятно, проблема проверки компонентов в два раза.

Так что, если это ваш случай, решение избежать этого сценария - организовать ваши пакеты таким образом, чтобы классы, использующие аннотации @ComponentScan, никогда не проверяли другие классы, которые также используют @ComponentScan.

+0

К сожалению, у меня нет других конфигураций, аннотированных с помощью '@ ComponentScan'. Образцы кода происходили из гораздо более сложного проекта, но я попытался включить как можно больше деталей. – fracz

+0

Наличие нескольких '@ ComponentScan', которые перекрываются * не * проблема. Spring не будет создавать экземпляры двух синглтонов только потому, что он дважды сталкивается с тем же классом при сканировании. – Raniz

+0

он для меня, один раз для корневого контекста и еще один для контекста сервлета, не пробовал для 3 или более перекрытий, хотя – saljuama

-1

Не пытайтесь сканировать имя com.project. *, В общем, это плохая практика, которая может привести к проблемной инициализации компонента, а не только потому, что при ошибках при запуске она также может привести к загрузке компонентов, которые не должны быть частью ваш текущий контекст.

Попробуйте использовать правильные стандарты именования пакетов и сканировать только сервисы, компоненты и пакеты репозиториев ... один раз.

@Configuration 
@EnableWebMvc 
@EnableGlobalMethodSecurity(prePostEnabled = true) 
@EnableSpringDataWebSupport 
@Import(value = ComponentScanConfig.class) 
public class WebappConfig extends WebMvcConfigurerAdapter { } 

компонент сканирования файла конфигурации:

@Configuration 
@ComponentScan(basePackages = { 
     "com.example.serviceModule.service", 
     "com.example.webModule.controller" 
}) 
public class ComponentScanConfig { } 
+0

Если у вас есть разумная настройка проекта и ручка регистрации вручную или их сканирование, абсолютно никаких проблем при сканировании базового пакета абсолютно нет. – Raniz

+0

Действительно, это сработает, но в целом это плохая практика. Представьте, что вы работаете над многомодульным проектом com.projectname.module1, com.projectname.module2 ... каждый со своей собственной конфигурацией пружин этот тип сканирования может сходить с ума очень быстро. Также учтите, что Spring на больших проектах придется сканировать множество нежелательных пакетов, пакетов без классов компонентов Spring (Service, Controller ...), это может привести к ненужному временному штрафу при инициализации весенних бобов. –