功能需求
-
提供一個公共的jar包給其他業務模塊依賴,需要在這個公共的jar中暴露一個restful API
-
采用spring auto config機制,在公共jar包中定義spring.factories文件,將jar包需要注入到spring容器中的bean定義好,業務模塊依賴后直接使用,不需要額外定義bean,也不需要指定ComponentScan
之前做法:根據spring文檔給的方案調用RequestMappingHandlerMapping的registerMapping方法手動注冊一個mapping,可以不使用@Controller注解就可以追加一個rest 接口,可是spring 5之后,spring推出了spring web flux,而RequestMappingHandlerMapping也分成了是spring webmvc版和spring webflux兩個,我們給定的jar又不能限定業務模塊使用spring web還是spring web flux開發,所以這種方式就不適用了。
解決方式
我們知道,無論是webmvc還是webflux中的RequestMappingHandlerMapping類,都是在afterPropertiesSet方法中查找所有帶有Controller或者RequestMapping注解的類,再把對應類中的帶有RequestMapping注解的方法解析后注冊到對應的RequestMappingHandlerMapping中的,其中判斷一個類是否帶有@Controller或@RequestMapping的方法如下
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
對應的AnnotatedElementUtils.hasAnnotation方法,最終會調用到AnnotatedElementUtils.searchWithFindSemantics方法,代碼片段如下
else if (element instanceof Class) {
Class<?> clazz = (Class<?>) element;
if (!Annotation.class.isAssignableFrom(clazz)) {
// Search on interfaces 在實現接口中查找
for (Class<?> ifc : clazz.getInterfaces()) {
T result = searchWithFindSemantics(ifc, annotationTypes, annotationName,
containerType, processor, visited, metaDepth);
if (result != null) {
return result;
}
}
// Search on superclass 在父類中查找
Class<?> superclass = clazz.getSuperclass();
if (superclass != null && superclass != Object.class) {
T result = searchWithFindSemantics(superclass, annotationTypes, annotationName,
containerType, processor, visited, metaDepth);
if (result != null) {
return result;
}
}
}
}
發現這個地方查找是否有指定注解時,如果繼承的類或實現的接口有相應的注解也是可以的,利用這個特性,我們可以采用如下思路來實現。
-
定義一個標記Controller,里面什么方法也沒有,僅僅追加了一個注解
@RestController public class MarkController { }
-
定義具體的Controller繼承這個標記類,注意這個類不需要用RestController注解
public class HelloController extends MarkController {
@RequestMapping("/hello")
public String hello() {
return "hello";
}
}
3. 在一個Configuration類中用@Bean注解聲明這個類
```
@Configuration
public class BeanConfig {
@Bean
public HelloController helloController() {
return new HelloController();
}
}
```
這樣我們就可以通過@Bean的形式聲明Controller,之后把這個BeanConfig直接追加到spring.factories中,其他模塊依賴這個jar之后,自動就會有一個/hello的接口了。