功能需求 1. 提供一個公共的jar包給其他業務模塊依賴,需要在這個公共的jar中暴露一個restful API 1. 採用spring auto config機制,在公共jar包中定義spring.factories文件,將jar包需要註入到spring容器中的bean定義好,業務模塊依賴後直接使 ...
功能需求
提供一個公共的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"; } }
在一個Configuration類中用@Bean註解聲明這個類
@Configuration public class BeanConfig { @Bean public HelloController helloController() { return new HelloController(); } }
這樣我們就可以通過@Bean的形式聲明Controller,之後把這個BeanConfig直接追加到spring.factories中,其他模塊依賴這個jar之後,自動就會有一個/hello的介面了。