Controller類的方法上的RequestMapping一定要寫在Controller類里嗎?


使用Spring Cloud做項目的同學會使用Feign這個組件進行遠程服務的調用,Feign這個組件采用模板的方式,有着優雅的代碼書寫規范。核心原理對Feign等相關注解進行解析,並提取信息,在Spring Boot工程啟動時,通過反射生產Request的bean,並將提取的信息,設置到bean中,最后注入到ioc容器中。

現在有這樣的場景,服務A提高RestApi接口,服務B、C、D等服務需要調用服務A提供的RestApi接口,這時最常見的做法是在服務B、C、D分別寫一個FeignClient,並需要寫RestApi接口的接收參數的實體和接收響應的實體DTo類。這樣的做法就是需要不停復制代碼。

有沒有辦法簡潔上面的操作呢?有一種最常見的做法是將將服務A進行模塊拆分,將FeignClient和常見的model、dto對外輸出的類單獨寫一個模塊,可以類似於取名a-service-open_share。這樣將服務A服務分為兩個模塊,即A服務的業務模塊和A服務需要被其他服務引用的公共類的模塊。服務B、C、D只需要引用服務A的a-service-open_share就具備調用服務A的能力。

筆者在這里遇到一個有趣的其問題。首先看問題:

寫一個FeignClient:

1 @FeignClient(name = "user-service")
2 public interface UserClient {
3   
4     @GetMapping("/users")
5     List<User> getUsers();
6 }

寫一個實現類:

 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這樣的注解。為何能正確的映射?!

帶着這樣的疑問,我進行了一番的分析和探索!

首先就是自己寫了一個demo,首先創建一個接口類:

1 public interface ITest {
2     @GetMapping("/test/hi")
3     public String hi();
4 }

寫一個Controller類TestController

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM