Controller類的方法上的RequestMapping必定要寫在Controller類裏嗎?

使用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 }

這就是我要尋找的代碼片斷,驗證了個人猜想。

寫這篇文章我想告訴讀者兩件事:

  • 能夠將服務的對外類進行一個模塊的拆分,好比不少服務都須要用的FeignClient、model、dto、常量信息等,這些信息單獨打Jar,其餘服務須要使用,引用下便可。
  • url映射不必定要寫在Contreller類的方法上,也能夠寫在它實現的接口裏面。貌似並無是luan用,哈。

原文連接:https://blog.csdn.net/forezp/article/details/80069961

相關文章
相關標籤/搜索