“Ambiguous mapping found” when one @Controller ext

2019-06-07 02:57发布

I have a ImportAction class which serves as a parent class for several type-specific import controllers, such as ImportClientsAction and ImportServicesAction.

ImportAction is a Spring MVC annotated @Controller class and has @RequestMapping-annotated methods to pull up a menu of import options and enter each of the type-specific import controllers.

Each child class, e.g. ImportClientsAction is also annotated @Controller and has type-specific @RequestMappings for their type's specific import process.

None of the @RequestMappings in any of the child classes should ever collide with the parent or with each other; each has a different path/value and different params.

From what I've come across in questions like this one and this one, it sounds like Spring counts each child class as having a duplicate of the parent class's @RequestMapping-annotated methods, even if the child class does not override the parent's methods.

Is there a way to have an @Controller-annotated parent class with @RequestMappings, and have @Controller-annotated child classes, without Spring seeing the child classes as duplicating the parent's @RequestMapping-annotated methods?

Bonus question, why can't Spring recognize a @RequestMapping "duplicate" on a child class and just ignore all but the parent's version? Has this simply not been implemented, or is there a fundamental problem in Java that makes this impossible?


EDIT: Example code

Parent class example:

@Controller
public class ImportAction {

    @RequestMapping(value = "/import", params = "m=importMenu", method = RequestMethod.GET)
    public String importMenu(HttpServletRequest request) throws Exception {
        return TilesConstants.IMPORT_MENU;
    }

    @RequestMapping(value = "/import", params = "m=importClients", method = RequestMethod.GET)
    public String importClients(@ModelAttribute("ImportUploadForm") ImportUploadForm theForm, HttpServletRequest request) throws Exception {
        retrieveReturnPage(request);
        theForm.setSomeBoolean(true);
        return TilesConstants.IMPORT_CLIENTS_UPLOAD;
    }

    @RequestMapping(value = "/import", params = "m=importServices", method = RequestMethod.GET)
    public String importServices(@ModelAttribute("ImportUploadForm") ImportUploadForm theForm, HttpServletRequest request) throws Exception {
        retrieveReturnPage(request);
        theForm.setSomeBoolean(false);
        return TilesConstants.IMPORT_SERVICES_UPLOAD;
    }

    /* etc 7 more almost identical methods */

}

Child class example:

@Controller
public class ImportClientsAction extends ImportAction {

    @RequestMapping(value = "/importClients", params = "m=uploadClients", method = RequestMethod.POST)
    public String uploadClients(@ModelAttribute("ImportUploadForm") ImportUploadForm theForm, BindingResult errors, HttpServletRequest request) throws Exception {
        if (!parseAndStoreUploadedFile(theForm, errors, request)) {
            return TilesConstants.IMPORT_CLIENTS_UPLOAD;
        }

        return "redirect:/importClients?m=enterMapClientsUpload";
    }

    /* etc other "client" type-specific import methods */

}

1条回答
男人必须洒脱
2楼-- · 2019-06-07 03:39

(Hat tip to Sotirios Delimanolis for helping me learn & understand this)

One @Controller-annotated class should not extend another @Controller-annotated class, because the parent class's methods also exist on the child class.

Each @Controller-annotated class is instantiated as a bean in the servlet context (?), and that instance of the class (ie that bean) is then used to call @RequestMapping-annotated methods according to the mapping provided when a user makes a request to the servlet.

When you have two @Controller-annotated classes, one a child of the other, the child class tries to register the mappings on the parent a second time. "No big deal!" you say. The problem is that Spring has no way to definitively decide which instance to use to call the mapped method, even if it is exactly the same method.

The same problem applies if you register two beans of the same type/class, where both try to register identical mappings.

There's a couple ways that Spring using the wrong instance would be problematic:

  1. The child overrides the parent's methods. It doesn't even have to override the mapped methods, just a method called from a mapped method. The behavior for the child instance would be different than for the parent instance, so they can't have the same mapping.
  2. The class has non-static fields. This applies even if two beans are the same class. One instance/bean can have different values and thus different behavior due to the values of the instance fields.

Because of these problems (and probably several others), Spring cannot ignore or work around duplicate mappings, even if the method that's mapped to is the same method.

In a related question, I tried working around this by making the @RequestMapping-annotated methods static. Problem 1 still applies, so simply making each mapped method static doesn't fix or work around the problem.

查看更多
登录 后发表回答