1. 首页
  2. 微服务

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:

@FeignClient(name = "user-service")
public interface UserClient {

    @GetMapping("/users")
    List<User> getUsers();

写一个实现类:


@RestController public class UserController implements UserClient { @Autowired UserService userService; @OverRide List<User> getUsers(){ return userService.getUsers(); }

启动工程,浏览器访问接口localhost:8008/users,竟然能正确访问?!明明我在UserController类的getUsers方法没有加RequestMapping这样的注解。为何能正确的映射?!

带着这样的疑问,我进行了一番的分析和探索!

首先就是自己写了一个demo,首先创建一个接口类:

public interface ITest {

    @GetMapping("/test/hi")
    public String hi();
}

写一个Controller类TestController

@RestController
public class TestController implements ITest {
    @Override
    public String hi() {
        return "hi you !";
    }

启动工程,浏览器访问:http://localhost:8762/test/hi,浏览器显示:

hi you !

我去,TestController类的方法 hi()能够得到ITest的方法hi()的 @GetMapping("/test/hi")注解吗? 答案肯定是获取不到的。

特意编译了TestController字节码文件:
javap -c TestController

 public class com.example.demo.web.TestController implements com.example.demo.web.ITest {
  public com.example.demo.web.TestController();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public java.lang.String hi();
    Code:
       0: ldc           #2                  // String hi you !
       2: areturn
}

上面的字节码没有任何关于@GetMapping("/test/hi")的信息,可见TestController直接获取不到@GetMapping("/test/hi")的信息。

那应该是Spring MVC在启动时在向容器注入Controller的Bean(HandlerAdapter)时做了处理。初步判断应该是通过反射获取到这些信息,并组装到Controller的Bean中。首先看通过反射能不能获取ITest的注解信息:

 public static void main(String[] args) throws ClassNotFoundException {
    Class c = Class.forName("com.example.demo.web.TestController");
    Class[] i=c.getInterfaces();
    System.out.println("start interfaces.."  );
    for(Class clz:i){
        System.out.println(clz.getSimpleName());
        Method[] methods = clz.getMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(GetMapping.class)) {
                GetMapping w = method.getAnnotation(GetMapping.class);
                System.out.println("value:" + w.value()[0]  );
            }
        }
    }
    System.out.println("end interfaces.."  );

    Method[] methods = c.getMethods();
    for (Method method : methods) {
        if (method.isAnnotationPresent(GetMapping.class)) {
            GetMapping w = method.getAnnotation(GetMapping.class);
            System.out.println("value:" + w.value());
        }
    }
}

允运行上面的代码:

start interfaces..

ITest

value:/test/hi

end interfaces..

可见通过反射是TestController类是可以获取其实现的接口的注解信息的。为了验证Spring Mvc 在注入Controller的bean时通过反射获取了其实现的接口的注解信息,并作为urlMapping进行了映射。于是查看了Spring Mvc 的源码,经过一系列的跟踪在RequestMappingHandlerMapping.java类找到了以下的方法:

protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
   RequestMappingInfo info = createRequestMappingInfo(method);
   if (info != null) {
      RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
      if (typeInfo != null) {
         info = typeInfo.combine(info);
      }
   }
   return info;
}

继续跟踪源码在AnnotatedElementUtils 类的searchWithFindSemantics()方法中发现了如下代码片段:

// Search on methods in interfaces declared locally
Class<?>[] ifcs = method.getDeclaringClass().getInterfaces();
result = searchOnInterfaces(method, annotationType, annotationName, containerType, processor,
      visited, metaDepth, ifcs);
if (result != null) {
   return result;
}

这就是我要寻找的代码片段,验证了我的猜测。

写这篇文章我想告诉读者两件事:

  • 可以将服务的对外类进行一个模块的拆分,比如很多服务都需要用的FeignClient、model、dto、常量信息等,这些信息单独打Jar,其他服务需要使用,引用下即可。
  • url映射不一定要写在Contreller类的方法上,也可以写在它实现的接口里面。貌似并没有是luan用,哈。

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「方志朋」,公众号后台回复「666」 免费领取我精心整理的进阶资源教程
  4. JS中文网,Javascriptc中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,是给开发者用的 Hacker News,技术文章由为你筛选出最优质的干货,其中包括:Android、iOS、前端、后端等方面的内容。目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。

    本文著作权归作者所有,如若转载,请注明出处

    转载请注明:文章转载自「 Java极客技术学习 」https://www.javajike.com

    标题:RequestMapping一定要写在Controller上吗

    链接:https://www.javajike.com/article/2278.html

« JWT如何在Spring Cloud微服务系统中在服务相互调时传递
kong api gateway 初体验»

相关推荐

QR code