4

I have simple Controller class:

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

I want to integration-test my controllers with Spring Security. Based on this article I have created the following test:

@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
}

However, inside the @Before method, when build() is being called, the following exception is being thrown:

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

Seems like Spring is trying to instantiate the controller twice. Why?

Webapp config:

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

Full stack trace:

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

I'm able to run this test in the MockMvc standalone setup, but it seems that I can't use spring data web integration then, which is equally annoying.

Community
  • 1
  • 1
fracz
  • 20,536
  • 18
  • 103
  • 149
  • I believe that when you use the annotation Before it will execute that method everytime over each test, you would need BeforeClass to prevent it to execute it twice. – Ringo Nov 04 '14 at 23:37
  • In order to help you figure out why Spring is trying to map your UserController twice, we would need to see (1) your ApplicationContext configuration and (2) the full stack trace. – Sam Brannen Nov 20 '14 at 17:47
  • By the way, is the code listing for your `UserController` identical to what's in your code base? I ask because the exception shows that Spring created a CGLIB-based proxy for it, and based on your example I cannot readily deduce why Spring would want to proxy that controller. – Sam Brannen Nov 20 '14 at 17:49
  • I have updated the listing so it contains `@PreAuthorize` annotation that is cause of proxy creation. – fracz Jun 19 '15 at 08:06
  • @fracz just to be sure that its due to unit test try to run your app inside a container and see the logs (Spring logs) if its creating your UserController object twice then there is something wrong with your spring configuration. – Sarfaraz Khan Jun 19 '15 at 09:30
  • @Sarfaraz I have verified it by adding a default construtor to the controller. Then I added breakpoint for it and launched the app in the debug mode. Breakpoint has been hit only once during application startup. – fracz Jun 19 '15 at 09:34
  • @fracz do you have more than one test case? – Sarfaraz Khan Jun 19 '15 at 09:39
  • Yes, many of them actually. – fracz Jun 19 '15 at 09:53
  • Can you post your ApplicationContext? I think the controller is getting loaded twice. – We are Borg Jun 22 '15 at 08:18
  • 1
    The code you have posted doesn't produce the exception you're getting, so it is really hard to figure out what is wrong. Could you please update your example code so that it actually produces the error? – Raniz Jun 25 '15 at 02:35

2 Answers2

2

This is an assumption, since you don't provide other parts of the system that might seem not related, but they actually might, because of @ComponentScan("com.example") annotation on WebappConfig class. This is usually the most common reason in my experience.

If you do have other @Configuration classes in the package "com.example" or any of its subpackages, they are going to be loaded in the Context aswell.

Now if the other @Configuration classes also have a @ComponentScan, annotation, that is going to be probably the problem of components being scanned twice.

So if this is your case, the solution to avoid this scenario, is to organize your packages in such a way that classes that use @ComponentScan annotations never scan other classes that also use @ComponentScan.

saljuama
  • 2,906
  • 1
  • 20
  • 42
  • Unfortunately, I don't have any other configurations annotated with `@ComponentScan`. Code samples origin from much more complicated project, indeed, but I tried to include as many details as possible. – fracz Jun 23 '15 at 13:44
  • Having multiple `@ComponentScan` that overlap is *not* an issue - Spring will not instantiate 2 singletons just because it encounters the same class twice when scanning. – Raniz Jun 25 '15 at 02:38
  • it does for me, once for root context, and another one for servlet context, haven't tried for 3 or more overlappings though – saljuama Jun 25 '15 at 08:13
  • I reward your answer as it help me to track down the real issue with my code. Unfortunately, I did not include all relevant aspects of my configuration but you have pointed me to the right direction. I will update my question shortly. – fracz Jun 26 '15 at 08:35
  • glad it could help, i'll review your edited question aswell and edit my answer accordingly – saljuama Jun 26 '15 at 12:29
-1

Try not to scan com.projectname.* , in general it's a bad practice which could lead to problematic bean initialization, not only because on errors at startup, it could also lead to load beans that shouldn't be part of your current context.

Try to use correct package naming standards and scan only services, components and repositories packages... one time.

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

Component scan configuration file:

@Configuration
@ComponentScan(basePackages = { 
        "com.example.serviceModule.service",
        "com.example.webModule.controller"
})
public class ComponentScanConfig { }
Javier Sánchez
  • 984
  • 6
  • 7
  • If you have a sane project setup and stick to either manually registering beans or having them scanned there is absolutely no problem with scanning your base package. – Raniz Jun 25 '15 at 02:42
  • Indeed it will work, but in general it's a bad practice. Imagine you are working on multi modular project com.projectname.module1, com.projectname.module2... each one with its own spring configuration this type of scan can go crazy very quickly. Also take into account that on large projects Spring will have to scan a lots of undesired packages, packages with no Spring component classes (Service, Controller...), this could have some unnecessary time penalty during spring beans initialization. – Javier Sánchez Jun 25 '15 at 07:36