Feign & 注解

注解

@Autowired一个接口有多个实现类

@Autowired是spring的注解,默认使用的是byType的方式向Bean里面注入相应的Bean。例如:

@Autowired
private UserService userService;

这段代码会在初始化的时候,在spring容器中寻找一个类型为UserService的bean实体注入,关联到userService的引入上。

但是如果UserService这个接口存在多个实现类的时候,就会在spring注入的时候报错,具体如下:

public class UserService1 implements UserService
public class UserService2 implements UserService

抛出了org.springframework.beans.factory.BeanCreationException,而原因是注入的时候发现有2个匹配的bean,但是不知道要注入哪一个:expected single matching bean but found 2: userService1,userService2

那么如何应对多个实现类的场景呢,看一下代码:

@Autowired
private UserService userService1;


@Autowired
private UserService userService2;


@Autowired
@Qualifier(value = "userService2")
private UserService userService3;

@Test
public void test(){
         System.out.println(userService1.getClass().toString());
         System.out.println(userService2.getClass().toString());
         System.out.println(userService3.getClass().toString());
}

运行结果:

class yjc.demo.serviceImpl.UserService1
class yjc.demo.serviceImpl.UserService2
class yjc.demo.serviceImpl.UserService2

运行结果成功,说明了2种处理多个实现类的方法:
1.变量名用userService1,userService2,而不是userService。
通常情况下@Autowired是通过byType的方法注入的,可是在多个实现类的时候,byType的方式不再是唯一,而需要通过byName的方式来注入,而这个name默认就是根据变量名来的。

2.通过@Qualifier注解来指明使用哪一个实现类,实际上也是通过byName的方式实现。

由此看来,@Autowired注解到底使用byType还是byName,其实是存在一定策略的,也就是有优先级。优先用byType,而后是byName。

@RestController:@ResponseBody + @Controller 返回Json对象

@Controller 用来响应页面,@Controller必须配合模版来使用,如webapp下的index.html文件

@RequestMapping此注解即可以作用在控制器的某个方法上,也可以作用在此控制器类上。

当控制器在类级别上添加@RequestMapping注解时,这个注解会应用到控制器的所有处理器方法上。处理器方法上的@RequestMapping注解会对类级别上的@RequestMapping的声明进行补充。

@Mapper

MapStruct可以帮助我们自动根据一个添加@Mapper注解的接口生成一个实现类,

@Mapper注解的componentModel属性componentModel属性用于指定自动生成的接口实现类的组件类型。这个属性支持四个值:

default: 这是默认的情况,mapstruct不使用任何组件类型, 可以通过Mappers.getMapper(Class)方式获取自动生成的实例对象。

cdi: the generated mapper is an application-scoped CDI bean and can be retrieved via @Inject

spring: 生成的实现类上面会自动添加一个@Component注解,可以通过Spring的 @Autowired方式进行注入

jsr330: 生成的实现类上会添加@javax.inject.Named 和@Singleton注解,可以通过 @Inject注解获取。

@Component: 标注Spring管理的Bean,使用@Component注解在一个类上,表示将此类标记为Spring容器中的一个Bean。

基于@Component扩展的注解

  • @Repository

@Repository本身是基于@Component注解的扩展,被@Repository注解的POJO类表示DAO层实现,从而见到该注解就想到DAO层实现,使用方式和@Component相同;

  • @Service

@Service本身是基于@Component注解的扩展,被@Service注解的POJO类表示Service层实现,从而见到该注解就想到Service层实现,使用方式和@Component相同;

  • @Controller

@Controller本身是基于@Component注解的扩展,被@Controller注解的类表示Web层实现,从而见到该注解就想到Web层实现,使用方式和@Component相同;

@Configuration 从Spring3.0,@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。

OpenFeign相关

Spring Cloud OpenFeign 工作原理解析

参考文章

深入理解 Feign 之源码解析

背景

OpenFeign 是 Spring Cloud 家族的一个成员, 它最核心的作用是为 HTTP 形式的 Rest API 提供了非常简洁高效的 RPC 调用方式。 如果说 Spring Cloud 其他成员解决的是系统级别的可用性,扩展性问题, 那么 OpenFeign 解决的则是与开发人员利益最为紧密的开发效率问题。

使用方式

在介绍 OpenFeign 的工作原理之前, 首先值得说明的是使用了 Open Feign 后, 开发人员的效率是如何得到提升的。 下面展示在使用了 OpenFeign 之后, 一个接口的提供方和消费方是如何快速高效地完成代码开发。

接口提供方

接口提供方的形式为 RestApi, 这个在 spring-web 框架的支持下, 编写起来非常简单

@RestController
@RequestMapping(value = "/api")
public class ApiController {

 @RequestMapping(value = "/demoQuery", method = {RequestMethod.POST}, consumes = MediaType.APPLICATION_JSON_VALUE)
 public ApiBaseMessage<DemoModel> demoQuery(@RequestBody DemoQryRequest request){
 return new ApiBaseMessage(new DemoModel());
 }
}

如上, 除去请求 DemoQryRequest 和响应 DemoModel 类的定义, 这个接口就已经快速地被完成了。 在这里 Feign 不需要发挥任何作用。

注意该接口的入参是 json 格式, 框架会自动帮我们反序列化到对应的 DemoQryRequest 类型的入参对象里。

返回值 ApiBaseMessage<DemoModel> 也会被框架自动序列化为 json 格式

接口使用方

在接口的使用者一端, 首先需要引入 SpringFeign 依赖(为简化篇幅, 只展示 build.gradle 中添加 Feign 的依赖, 没有展示其他的 spring cloud 依赖添加)

implementation('org.springframework.cloud:spring-cloud-starter-openfeign')

@Component
@FeignClient(name = "${feign.demoApp.name}")
@RequestMapping("/api")
public interface DemoService {
 @RequestMapping(value = "/demoQuery", method = RequestMethod.POST,
 consumes = MediaType.APPLICATION_JSON_VALUE)
 ApiBaseMessage<DemoModel> demoQuery(@RequestBody DemoQryRequest request);
}

再直接利用 spring 的自动注入功能, 就可以使用服务端的接口了

@Component
public class DemoServiceClient
{
 private final DemoService demoService;

 @Autowired
 public DemoServiceClient(DemoService demoService) {
 this.demoService= demoService;
 }

 public void useDemoService(DemoQryRequest request){
 // 直接像调用一个本地方法一样, 调用远端的 Rest API 接口, 完全是 RPC 形式
 ApiBaseMessage<DemoModel> result = demoService.demoQuery(request);
 }
}

通过上面的例子可以看到, Feign 正如同其英文含义 "假装" 一样, 能够让我们装作调用一个本地 java 方法一样去调用基于 HTTP 协议的 Rest API 接口。 省去了我们编写 HTTP 连接,数据解析获取等一系列繁琐的操作

工作原理

在展开讲解工作原理前, 首先捋一下上文中, 我们完成 Feign 调用前所进行的操作:

  1. 添加了 Spring Cloud OpenFeign 的依赖

  2. 在 SpringBoot 启动类上添加了注解 @EnableFeignCleints

  3. 按照 Feign 的规则定义接口 DemoService, 添加@FeignClient 注解

  4. 在需要使用 Feign 接口 DemoService 的地方, 直接利用 @Autowire 进行注入

  5. 使用接口完成对服务端的调用

可以根据上面使用 Feign 的步骤大致猜测出整体的工作流程:

  1. SpringBoot 应用启动时, 由针对 @EnableFeignClient 这一注解的处理逻辑触发程序扫描 classPath 中所有被@FeignClient 注解的类, 这里以 DemoService 为例, 将这些类解析为 BeanDefinition 注册到 Spring 容器中

  2. Sping 容器在为某些用的 Feign 接口的 Bean 注入 DemoService 时, Spring 会尝试从容器中查找 DemoService 的实现类

  3. 由于我们从来没有编写过 DemoService 的实现类, 上面步骤获取到的 DemoService 的实现类必然是 feign 框架通过扩展 spring 的 Bean 处理逻辑, 为 DemoService 创建一个动态接口代理对象, 这里我们将其称为 DemoServiceProxy 注册到 spring 容器中。

  4. Spring 最终在使用到 DemoService 的 Bean 中注入了 DemoServiceProxy 这一实例。

  5. 当业务请求真实发生时, 对于 DemoService 的调用被统一转发到了由 Feign 框架实现的 InvocationHandler 中, InvocationHandler 负责将接口中的入参转换为 HTTP 的形式, 发到服务端, 最后再解析 HTTP 响应, 将结果转换为 Java 对象, 予以返回。

上面整个流程可以进一步简化理解为:

  1. 我们定义的接口 DemoService 由于添加了注解 @FeignClient, 最终产生了一个虚假的实现类代理

  2. 使用这个接口的地方, 最终拿到的都是一个假的代理实现类 DemoServiceProxy

  3. 所有发生在 DemoServiceProxy 上的调用, 都被转交给 Feign 框架, 翻译成 HTTP 的形式发送出去, 并得到返回结果, 再翻译回接口定义的返回值形式。

所以不难发现, Feign 的核心实现原理就是 java 原生支持的基于接口的动态代理

总结

Spring Cloud OpenFeign 的核心工作原理经上文探究可以非常简单的总结为:

  1. 通过 @EnableFeignCleints 触发 Spring 应用程序对 classpath 中 @FeignClient 修饰类的扫描

  2. 解析到 @FeignClient 修饰类后, Feign 框架通过扩展 Spring Bean Deifinition 的注册逻辑, 最终注册一个 FeignClientFacotoryBean 进入 Spring 容器

  3. Spring 容器在初始化其他用到 @FeignClient 接口的类时, 获得的是 FeignClientFacotryBean 产生的一个代理对象 Proxy.

  4. 基于 java 原生的动态代理机制, 针对 Proxy 的调用, 都会被统一转发给 Feign 框架所定义的一个 InvocationHandler , 由该 Handler 完成后续的 HTTP 转换, 发送, 接收, 翻译 HTTP 响应的工作

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容