使用Spring Cloud作項目的同窗會使用Feign這個組件進行遠程服務的調用,Feign這個組件採用模板的方式,有着優雅的代碼書寫規範。核心原理對Feign等相關注解進行解析,並提取信息,在Spring Boot工程啓動時,經過反射生產Request的bean,並將提取的信息,設置到bean中,最後注入到ioc容器中。java
如今有這樣的場景,服務A提升RestApi接口,服務B、C、D等服務須要調用服務A提供的RestApi接口,這時最多見的作法是在服務B、C、D分別寫一個FeignClient,並須要寫RestApi接口的接收參數的實體和接收響應的實體DTo類。這樣的作法就是須要不停複製代碼。web
有沒有辦法簡潔上面的操做呢?有一種最多見的作法是將將服務A進行模塊拆分,將FeignClient和常見的model、dto對外輸出的類單獨寫一個模塊,能夠相似於取名a-service-open_share。這樣將服務A服務分爲兩個模塊,即A服務的業務模塊和A服務須要被其餘服務引用的公共類的模塊。服務B、C、D只須要引用服務A的a-service-open_share就具有調用服務A的能力。瀏覽器
筆者在這裏遇到一個有趣的其問題。首先看問題:app
寫一個FeignClient:ide
1 @FeignClient(name = "user-service") 2 public interface UserClient { 3 4 @GetMapping("/users") 5 List<User> getUsers(); 6 }
寫一個實現類:lua
1 @RestController 2 public class UserController implements UserClient { 3 @Autowired 4 UserService userService; 5 6 @OverRide 7 List<User> getUsers(){ 8 return userService.getUsers(); 9 } 10 }
啓動工程,瀏覽器訪問接口localhost:8008/users,居然能正確訪問?!明明我在UserController類的getUsers方法沒有加RequestMapping這樣的註解。爲什麼能正確的映射?!url
帶着這樣的疑問,我進行了一番的分析和探索!spa
首先就是本身寫了一個demo,首先建立一個接口類:.net
1 public interface ITest { 2 @GetMapping("/test/hi") 3 public String hi(); 4 }
寫一個Controller類TestControllercode
1 @RestController 2 public class TestController implements ITest { 3 @Override 4 public String hi() { 5 return "hi you !"; 6 } 7 }
啓動工程,瀏覽器訪問:http://localhost:8762/test/hi,瀏覽器顯示:
hi you !
我去,TestController類的方法 hi()可以獲得ITest的方法hi()的 @GetMapping("/test/hi")註解嗎? 答案確定是獲取不到的。
特地編譯了TestController字節碼文件:
javap -c TestController
1 public class com.example.demo.web.TestController implements com.example.demo.web.ITest { 2 public com.example.demo.web.TestController(); 3 Code: 4 0: aload_0 5 1: invokespecial #1 // Method java/lang/Object."<init>":()V 6 4: return 7 8 public java.lang.String hi(); 9 Code: 10 0: ldc #2 // String hi you ! 11 2: areturn 12 }
上面的字節碼沒有任何關於@GetMapping("/test/hi")的信息,可見TestController直接獲取不到@GetMapping("/test/hi")的信息。
那應該是Spring MVC在啓動時在向容器注入Controller的Bean(HandlerAdapter)時作了處理。初步判斷應該是經過反射獲取到這些信息,並組裝到Controller的Bean中。首先看經過反射能不能獲取ITest的註解信息:
1 public static void main(String[] args) throws ClassNotFoundException { 2 Class c = Class.forName("com.example.demo.web.TestController"); 3 Class[] i=c.getInterfaces(); 4 System.out.println("start interfaces.." ); 5 for(Class clz:i){ 6 System.out.println(clz.getSimpleName()); 7 Method[] methods = clz.getMethods(); 8 for (Method method : methods) { 9 if (method.isAnnotationPresent(GetMapping.class)) { 10 GetMapping w = method.getAnnotation(GetMapping.class); 11 System.out.println("value:" + w.value()[0] ); 12 } 13 } 14 } 15 System.out.println("end interfaces.." ); 16 17 Method[] methods = c.getMethods(); 18 for (Method method : methods) { 19 if (method.isAnnotationPresent(GetMapping.class)) { 20 GetMapping w = method.getAnnotation(GetMapping.class); 21 System.out.println("value:" + w.value()); 22 } 23 } 24 }
允運行上面的代碼:
start interfaces…
ITest
value:/test/hi
end interfaces…
可見經過反射是TestController類是能夠獲取其實現的接口的註解信息的。爲了驗證Spring Mvc 在注入Controller的bean時經過反射獲取了其實現的接口的註解信息,並做爲urlMapping進行了映射。因而查看了Spring Mvc 的源碼,通過一系列的跟蹤在RequestMappingHandlerMapping.java類找到了如下的方法:
1 protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { 2 RequestMappingInfo info = createRequestMappingInfo(method); 3 if (info != null) { 4 RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType); 5 if (typeInfo != null) { 6 info = typeInfo.combine(info); 7 } 8 } 9 return info; 10 }
繼續跟蹤源碼在AnnotatedElementUtils 類的searchWithFindSemantics()方法中發現了以下代碼片斷:
1 // Search on methods in interfaces declared locally 2 Class<?>[] ifcs = method.getDeclaringClass().getInterfaces(); 3 result = searchOnInterfaces(method, annotationType, annotationName, containerType, processor, 4 visited, metaDepth, ifcs); 5 if (result != null) { 6 return result; 7 }
這就是我要尋找的代碼片斷,驗證了個人猜想。
寫這篇文章我想告訴讀者兩件事:
url映射不必定要寫在Contreller類的方法上,也能夠寫在它實現的接口裏面。貌似並無是luan用,哈。