1

JDK Version: 1.7 (latest update) Spring: 3.2.16-Release

I have a generic controller class, that can be reused for multiple functionality. Due to the limitations of annotation-based approach for such requirements, I am using the XML-based configuration. Also, I have disabled the component scan in XML.

I have configured multiple bean instances of the same class and used SimpleUrlHandlerMapping for mapping URLs to controller. If I test the project with one controller enabled at a time, it works fine. However, when I enable the second instance, spring complains with following error:

ERROR: org.springframework.web.servlet.DispatcherServlet - Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0': Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping found. Cannot map 'deviceController' bean method 
public java.lang.String com.smvc.pr05.controllers.SearchController.search(java.util.Locale,org.springframework.ui.ModelMap)
to {[],methods=[POST],params=[],headers=[],consumes=[],produces=[],custom=[]}: There is already 'searchController' bean method
public java.lang.String com.smvc.pr05.controllers.SearchController.search(java.util.Locale,org.springframework.ui.ModelMap) mapped.
...
Caused by: java.lang.IllegalStateException: Ambiguous mapping found. Cannot map 'installerController' bean method 
public java.lang.String com.smvc.pr05.controllers.SearchController.search(java.util.Locale,org.springframework.ui.ModelMap)
to {[],methods=[POST],params=[],headers=[],consumes=[],produces=[],custom=[]}: There is already 'deviceController' bean method
public java.lang.String com.smvc.pr05.controllers.SearchController.search(java.util.Locale,org.springframework.ui.ModelMap) mapped.
...

I have tried it with scope=singleton and scope=prototype for the controller bean definition. I have tried with enabling component scan (keeping manually defined bean in XML) and disabling the same. The error persists.

While this may be fixed, if I create concrete class per instance, I really want to keep it as last option. I have a strong belief in Spring capabilities, as I have used similar technique for non-controller classes.

Please let me know, what is that I am missing.

The spring configuration (EDITED with controller as singleton)

...
<beans:bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
   <beans:property name="mappings">
      <beans:props>
     <beans:prop key="/">homeController</beans:prop>
     <beans:prop key="/deviceSearch/">deviceController</beans:prop>
     <beans:prop key="/installerSearch/">installerController</beans:prop>
     <beans:prop key="/customerSearch/">customerController</beans:prop>
      </beans:props>
   </beans:property>
</beans:bean>
...
<beans:bean id="homeController" class="com.smvc.pr05.controllers.HomeController" >
</beans:bean>
<beans:bean id="deviceController" class="com.smvc.pr05.controllers.SearchController">
    <beans:property name="metaModel" ref="deviceModel"/>
    <beans:property name="searchService" ref="deviceService" />
</beans:bean>
<beans:bean id="installerController" class="com.smvc.pr05.controllers.SearchController" >
    <beans:property name="metaModel" ref="installerModel"/>
    <beans:property name="searchService" ref="installerService" />
</beans:bean>
<beans:bean id="customerController" class="com.smvc.pr05.controllers.SearchController" >
    <beans:property name="metaModel" ref="customerModel"/>
    <beans:property name="searchService" ref="customerService" />
</beans:bean>

The Java Controller Class:

...
@Controller
public class SearchController {

    private static final Logger LOG = LoggerFactory.getLogger(SearchController.class);

    private SearchService searchService;    //Has explicit set() method

    private MetaModel metaModel;    //Has explicit set() method

    @SuppressWarnings({ "unchecked" })
    @RequestMapping(method = RequestMethod.POST)
    public String search(Locale locale, ModelMap modelMap) {
        ...
    }

    public void setSearchService(SearchService searchService) {
        this.searchService = searchService;
    }

    public void setMetaModel(MetaModel metaModel) {
        this.metaModel = metaModel;
    }
}
ajoshi
  • 349
  • 2
  • 10
  • Why you do that? Controllers are meant to be singletons. What you're trying to achieve? – Evgeni Dimitrov Mar 09 '16 at 10:00
  • In the above case, please ignore the attribute scope. It was added for the testing purpose. Basically, the controller is singleton. I can use the same controller to server multiple functionality, based on the properties set by the bean definition. For example, if "deviceModel" is set, then the controller would search for devices. – ajoshi Mar 09 '16 at 11:29
  • Do you use `` in your xml config? – Ken Bekov Mar 09 '16 at 11:37
  • Yes, annotation-driven is enabled. – ajoshi Mar 09 '16 at 14:07

3 Answers3

1

The main issue is that when using @Controller and <mvc:annotation-driven /> is that the RequestMappingHandlerMapping and RequestMappingHandlerAdapter will kick in. The first will detect all @Controller annotated beans and based on the @RequestMapping create a mapping for it.

As you have registered 3 beans of the same type it will result in 3 of the same mappings and thus it will stop with an exception telling you that. Basically with the introduction of RequestMappingHandlerAdapter/RequestMappingHandlerMapping the ability to use a SimpleUrlHandlerMapping and an annotation way of selecting the method was lost.

You could however remove the <mvc:annotation-driven /> and add the AnnotationMethodHandlerAdapter however that class is more or less deprecated (and will at least be removed in future versions of Spring).

I would suggest to use the old trusty Controller interface instead of the annotation. You only have a single method you want to use and hence using the old support classes is a viable option.

public class SearchController extends AbstractController {

    private static final Logger LOG = LoggerFactory.getLogger(SearchController.class);

    private SearchService searchService;    //Has explicit set() method

    private MetaModel metaModel;    //Has explicit set() method

    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception;

        if (!("post".equalsIgnoreCase(request.getMethod()))) {
            return null; // or throw exception or ....
        }
        final Locale locale = LocaleContextHolder.getLocale(); // retrieve current locale.

        ModelAndView mav = new ModelAndView("your-view");
        // prepare your model instead of adding to ModelMap
        mav.addObject("name", object);
        return mav;
    }
    // Omitted setters.
}

This will prevent the annotation scanning from kicking in and saves you from refactoring (again) when you upgrade to a version of Spring that removed the deprecated classes.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
0

Seems to be component scan still working. Because somebody created instance of SearchController depending on @Controller annotation. That is why you get Cannot map 'deviceController' bean method.

Another problem, if you use <mvc:annotation-driven/> in xml config, mvc engine will look for all beans marked with @Controller annotation, and will attempt to map this beans depending on methods annotation. Because you have three controllers of same class, and this class marked with @Controller, mvc engine will try to map all of this controllers. Since they will have same methods annotation they will be mapped to same path (in your case it is empty path). That is why you get Cannot map 'installerController' bean method.

Solution for both cases: remove @Controller annotation from SearchController class.

Ken Bekov
  • 13,696
  • 3
  • 36
  • 44
  • Thanks Ken. I have disabled the tag and added AnnotationMethodHandlerAdapter as specified by ekem chitsiga, below. It is working now. – ajoshi Mar 09 '16 at 14:27
0

The Controller is just a stereotype annotation which works in conjuction with component scanning. THe culprit here is @RequestMapping which maps all the methods to the same url. For your configuration to work remove <mvc:annotation-driven/> element which registers a RequestMappingHandlerMapping bean which uses @RequestMapping for url mapping. Now your SimpleUrlHandlerMapping will be used instead of the one configured through mvc:annotation-driven or @EnableWebMvc

However you will need to register a HandlerAdapter which knows how to handler @RequestMapping methods i.e org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

as follows

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
Mr Lister
  • 45,515
  • 15
  • 108
  • 150
ekem chitsiga
  • 5,523
  • 2
  • 17
  • 18
  • Thank you for the help. After adding this, it is working now. However, in Spring 3.2.16, this class appears to be deprecated. Is there an alternate class for this? – ajoshi Mar 09 '16 at 14:29
  • AnnotationMethodHandlerAdapter was replaced by org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter – ekem chitsiga Mar 09 '16 at 15:22