Spring 未在运行时发现新上下文的 @RequestMapping 注释

Spring does not discover @RequestMapping annotation for a new context at runtime

我刚刚尝试为我的 spring 引导 Web 应用程序开发一个插件系统。该应用程序使用根上下文路径部署在 tomcat 服务器上。插件系统允许我在运行时加载专门准备的 jar 文件。系统还应该能够在运行时取消部署插件。这些罐子包含在当前工作目录的插件文件夹中。我希望每个插件都有自己的 spring 上下文来操作。依赖注入按预期工作,但 spring 没有发现我的插件上下文的 @RequestMapping 注释。所以我的问题是:如何让 spring 发现我的插件的那些 @RequestMapping 注释(在运行时)?

我正在使用最新的 spring 引导版本和以下 application.yml:

# Server
server:
  error:
    whitelabel:
      enabled: true
  session:
    persistent: true
  tomcat:
    uri-encoding: UTF-8
# Spring
spring:
  application:
    name: Plugins
  mvc:
    favicon:
      enabled: false
  favicon:
    enabled: false
  thymeleaf:
    encoding: UTF-8
# Logging
logging:
  file: application.log
  level.: error

这是加载插件的代码:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
URLClassLoader urlClassLoader = URLClassLoader.newInstance(new URL[] { plugin.getPluginURL() }, getClass().getClassLoader()); // plugin.getPluginURL will refer to a jar file with the plugin code (see below).
context.setClassLoader(urlClassLoader);
context.setParent(applicationContext); // applicationContext is the the context of the original spring application. It was autowired.
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, true);
scanner.scan("my.plugin.package");
context.refresh();

控制器代码(在我的插件中):

@Controller
public class PluginTestController {
    @PostConstruct
    private void postContruct() {
        System.out.println("Controller ready.");
    }

    @RequestMapping(value = "/test", method = RequestMethod.GET)
    public ResponseEntity<String> doGet() {
        return new ResponseEntity<>("Hello!", HttpStatus.OK);
    }
}

当我启动应用程序并加载插件时,我可以在控制台中看到 "Controller ready."。但是,当我尝试访问 url (localhost:8080/test) 时,我只能看到 404 错误页面。非插件 spring 上下文控制器的每个 url 都被正确映射(例如,我可以访问 localhost:8080/index)。我发现它可能与 RequestMappingHandlerMapping 有关。但是我真的不明白如何利用它来使注释再次工作。

编辑:我找到了一种使用以下代码使 @RequestMapping 注释适用于我的控制器的方法:

// context is the Plugins context, that i just created earlier.
for (Map.Entry < String, Object > bean: context.getBeansWithAnnotation(Controller.class).entrySet()) {
    Object obj = bean.getValue();
    // From 
    // As you are using AOP check for AOP proxying. If you are proxying with Spring CGLIB (not via Spring AOP)
    // Use org.springframework.cglib.proxy.Proxy#isProxyClass to detect proxy If you are proxying using JDK
    // Proxy use java.lang.reflect.Proxy#isProxyClass
    Class < ? > objClz = obj.getClass();
    if (org.springframework.aop.support.AopUtils.isAopProxy(obj)) {
        objClz = org.springframework.aop.support.AopUtils.getTargetClass(obj);
    }
    for (Method m: objClz.getDeclaredMethods()) {
        if (m.isAnnotationPresent(RequestMapping.class)) {
            RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(m, RequestMapping.class);
            RequestMappingInfo requestMappingInfo = RequestMappingInfo
                .paths(requestMapping.path())
                .methods(requestMapping.method())
                .params(requestMapping.params())
                .headers(requestMapping.headers())
                .consumes(requestMapping.consumes())
                .produces(requestMapping.produces())
                .mappingName(requestMapping.name())
                .customCondition(null)
                .build();
            // This will register the actual mapping, so that the Controller can handle the Request
            requestMappingHandlerMapping.registerMapping(requestMappingInfo, obj, m);
        }
    }
}

但是我仍在寻找一种使用 spring 方式使其工作的方法:https://github.com/spring-projects/spring-framework/blob/fb7ae010c867ae48ab51f48cce97fe2c07f44115/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java

感谢阅读。

您是否在 context:component-scan base-package="location" 中添加了控制器位置 spring_servlet.xml

SpringMvc有自己的@RequestMapping缓存,详细代码是RequestMappingHandlerMapping。如您所见,显示了init方法,也许您可​​以在加载新插件后调用init方法。

protected void initHandlerMethods() {
        if (logger.isDebugEnabled()) {
            logger.debug("Looking for request mappings in application context: " + getApplicationContext());
        }

        String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
                BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
                getApplicationContext().getBeanNamesForType(Object.class));

        for (String beanName : beanNames) {
            if (isHandler(getApplicationContext().getType(beanName))){
                detectHandlerMethods(beanName);
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }

这是spring3的RequestMappingHandlerMapping代码,可能spring4的impl有一些改动

我认为,您需要添加

<context:component-scan base-package="package_name" />
<mvc:annotation-driven />
<mvc:default-servlet-handler />

在你的 xxx-servlet.xml 文件中。