简体   繁体   中英

Inheritance in Spring RestController

I have a Spring RestController which handles version 1 of API calls.

package rest.v1;

@RestController
@RequestMapping("v1/someResource")
public class Controller_V1 {

    @RequestMapping(value = "/{resourceName}", method = RequestMethod.GET)
    public Object retrieve() throws Exception {
        ....
    }
}

What I want to do is create another controller which handles version 2 requests. At the same time I want to inherit the implementation which are already in version 1 controller. This is because the implementations which are unchanged from v1 to v2 will just be inherited as it is from v1.

So something like this:

package rest.v2;

@RestController("controllerV2")
@RequestMapping("v2/someResource")
public class Controller_v2 extends Controller_v1 {

    @RequestMapping(value = "/{resourceName}", method = RequestMethod.GET)
    public Object retrieve() throws Exception {
        //implementation overridden from v1
    }
}

Doing it results in ConflictingBeanDefinitionException. I am also not sure if extending the controllers like above is supposed to work.

I am aware that I can use multiple paths in RequestMapping of the same controller like this:

@RestController
@RequestMapping(value = { "v1/someResource", "v1/someResource" })
public class Controller_V1 {

}

But the above is not going to work for my scenario.

Here is the stack trace when the controllers are extended:

 [java] org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exception parsing XML document from class path resource [rest-context.xml]; nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'controller_V1' for bean class [rest.v2.Controller_V2] conflicts with existing, non-compatible bean definition of same name and class [rest.v1.Controller_V1]
     [java]     at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:413)
     [java]     at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:335)
     [java]     at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:303)
     [java]     at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:180)
     [java]     at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:216)
     [java]     at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:187)
     [java]     at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:125)
     [java]     at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:94)
     [java]     at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:129)
     [java]     at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:540)
     [java]     at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:454)
     [java]     at org.springframework.web.servlet.FrameworkServlet.configureAndRefreshWebApplicationContext(FrameworkServlet.java:658)
     [java]     at org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:624)
     [java]     at org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:672)
     [java]     at org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:543)
     [java]     at org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:484)
     [java]     at org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:136)
     [java]     at javax.servlet.GenericServlet.init(GenericServlet.java:158)

The problem is clear in the first line of the stacktrace:

org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exception parsing XML document from class path resource [rest-context.xml]; nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException : Annotation-specified bean name 'controller_V1' for bean class [rest.v2.Controller_V2] conflicts with existing, non-compatible bean definition of same name and class [rest.v1.Controller_V1]

This is because you're letting Spring give a name for your beans. By default, Spring will use as bean name the name of the interface or super class of the current class in camelCase.

So, you have something like this (names given by Spring):

@RestController("controller_V1") //<-- that's the name of the Spring bean
@RequestMapping("v1/someResource")
public class Controller_V1

@RestController("controller_V1") //<-- that's the name of the Spring bean
@RequestMapping("v2/someResource")
public class Controller_V2 extends Controller_V1

As you can see, you only need to rename your second bean:

@RestController("controller_V2")
@RequestMapping("v2/someResource")
public class Controller_V2 extends Controller_V1

IMO you should use better names for your classes.

A @RestController extension is currently available. I needed to extend a @RestController due to a new method implementation version. I tried to extend a @RestController and it works perfectly without specifying the name of the spring bean. Here is a simple example:

@RestController
@RequestMapping("/v1/mycontroller")
public class MyControllerV1 {
    
    @RequestMapping("/mymethod1")
    public Object mymethod1(@RequestBody Object myRequest) {
        <my v1 implementation>
        return .....
    }
    
    @RequestMapping("/mymethod2")
    public Object mymethod2(@RequestBody Object myRequest) {
        <my v1 implementation>
        return .....
    }
}



@RestController
@RequestMapping("/v2/mycontroller")
public class MyControllerV2 extends MyControllerV1 {
    
    @RequestMapping("/mymethod1")
    public Object mymethod1(@RequestBody Object myRequest) {
        <my v2 implementation>
        return .....
    }
    
}

In this case you can call "mymethod1" and "mymethod2" in two different ways, which are

http://hostname/v1/mycontroller/mymethod1 <-- first implementation -->

http://hostname/v2/mycontroller/mymethod1 <-- second implementation -->

http://hostname/v1/mycontroller/mymethod2 <-- same implementation -->

http://hostname/v2/mycontroller/mymethod2 <-- same implementation -->

"mymethod1" has two different implementation and "mymethod2" has only one implemation that "MyControllerV1" has inherited from "MyControllerV2"

Not only can you not share mappings this way, but inheritance does not seem to work for @RestController at all. Doing this will cause your controllers to silently fail to pick up your any @RequestMapping annotated methods. Figuring this out took quite some time, but you can inspect the output of the log files to get a hint that it isn't working. Specifically, you'll know that your method handling isn't working if you fail to see the following log lines printed inside AbstractHandlerMethodMapping for your @RestController :

    if (logger.isDebugEnabled()) {
        logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM