转载请标明出处:

https://blog.csdn.net/forezp/article/details/80069961

本文出自方志朋的博客

使用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用,哈。




扫码关注公众号有惊喜

(转载本站文章请注明作者和出处 方志朋的博客

Controller类的方法上的RequestMapping一定要写在Controller类里吗?的更多相关文章

  1. SpringMVC常用注解@Controller,@Service,@repository,@Component,@Autowired,@Resource,@RequestMapping

    1.controller层使用@Controller注解-用于呈现层,(spring-mvc) @Controller 用于标记在一个类上,使用它标记的类就是一个SpringMVC Controlle ...

  2. 【WPF/MVVM】把鼠标事件写到Controller层

    要使用Mouse Event,最快捷的方法便是前台控件直接绑定事件,然后再后台代码中实现. 在MVVM中,View层的后台代码无法调用Contrller层的函数.(反过来可以Controller –& ...

  3. fastjson简单使用demo,@JSONField注解属性字段上与set、get方法上。实体类toString(),实体类转json的区别;_下划线-减号大小写智能匹配

    一.demo代码 @JSONField注解属性字段上与set.get方法上.使用@Data注解(lombok插件安装最下方),对属性“笔名”[pseudonym]手动重写setter/getter方法 ...

  4. Spring的annotation用在set方法上 hibernate的annotation用get方法上

    1.Spring的annotation用在set方法上 2.hibernate的annotation用在get方法上

  5. spring中的传播性 个人认为就是对方法的设置 其作用能传播到里面包含的方法上

    spring中的传播性 个人认为就是对方法的设置 其作用能传播到里面包含的方法上

  6. JVM源码分析之深入分析Object类finalize()方法的实现原理

      原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 ​“365篇原创计划”第十篇. 今天呢!灯塔君跟大家讲: 深入分析Object类finalize()方法的实现原理 finalize 如果 ...

  7. Conccrent中 Unsafe类原理 以及 原子类AutomicXX的原理以及对Unsafe类的使用

    Unsafe类的介绍 Java中基于操作系统级别的原子操作类sun.misc.Unsafe,它是Java中对大多数锁机制实现的最基础类.请注意,JDK 1.8和之前JDK版本的中sun.misc.Un ...

  8. vs2010 C# 如何将类做成DLL 再从另一个项目中使用这个类

    vs2010 C# 如何将类做成DLL 再从另一个项目中使用这个类 2011-10-20 12:00 486人阅读 评论(0) 收藏 举报 一.将类做成DLL 方法一: 你可以通过在命令行下用命令将以 ...

  9. js 一个自写的 监测类

    自从认识了jQuery后,很多页面加载入口,都放在document.ready里面.但是有时候这个觉得ready加载太慢, 这个[监测类 ]就开始产生了 效果类似这个. 每10毫秒检查一次,直到加载了 ...

随机推荐

  1. 1.Vue.js的常用指令

      Vue.js介绍 Vue.js是当下很火的一个JavaScript MVVM库,它是以数据驱动和组件化的思想构建的.相比于Angular.js,Vue.js提供了更加简洁.更易于理解的API,使得 ...

  2. 深入理解JavaScript系列(25):设计模式之单例模式

    介绍 从本章开始,我们会逐步介绍在JavaScript里使用的各种设计模式实现,在这里我不会过多地介绍模式本身的理论,而只会关注实现.OK,正式开始. 在传统开发工程师眼里,单例就是保证一个类只有一个 ...

  3. Java网络编程三--基于TCP协议的网络编程

    ServerSocket对象用于监听来自客户端的Socket连接,如果没有连接,它将一直处于等待状体 Socket accept():如果接收到客户端的连接请求,该方法返回一个与客户端对应Socket ...

  4. WebGrease—异常来自 HRESULT:0x80131040

    一.错误源: 未能加载文件或程序集“WebGrease, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”或它的某一 ...

  5. 【Q】类和对象:游戏角色开发(角色介绍)

    案例情景:某公司要开发新游戏,请用面向对象的思想设计英雄类.怪物类和武器类. 编写测试类,创建英雄对象.怪物对象和武器对象,并输出各自的信息. 其中设定分别如下: 1.英雄类 属性:英雄名字.生命值. ...

  6. win10下MySQL 5.7.20解压版安装步骤

    1.从官网下载MySQL5.7.20解压版64位:https://dev.mysql.com/downloads/file/?id=473309. 2.解压(我的解压路径为:E:\mysql-5.7. ...

  7. vue——指令系统

    指令系统,可以联想咱们的cmd命令行工具,只要我输入一条正确的指令,系统就开始干活了. 在vue中,指令系统,设置一些命令之后,来操作我们的数据属性,并展示到我们的DOM上. 在vue中提供了一套为数 ...

  8. Python爬虫之requests模块(1)

    一.引入 Requests 唯一的一个非转基因的 Python HTTP 库,人类可以安全享用. 警告:非专业使用其他 HTTP 库会导致危险的副作用,包括:安全缺陷症.冗余代码症.重新发明轮子症.啃 ...

  9. ref 和 out 的区别

    ref和out都是C#中的关键字,所实现的功能也差不多,都是指定一个参数按照引用传递. 对于编译后的程序而言,它们之间没有任何区别,也就是说它们只有语法区别. 总结起来,他们有如下语法区别: 1.re ...

  10. H5手机页面剖析

    <!--强制使用webkit内核进行渲染--><meta http-equiv="X-UA-COMPATIBLE" content="IE=edge, ...