注解
@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 工作原理解析
参考文章
背景
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 调用前所进行的操作:
添加了 Spring Cloud OpenFeign 的依赖
在 SpringBoot 启动类上添加了注解
@EnableFeignCleints
按照 Feign 的规则定义接口
DemoService
, 添加@FeignClient
注解在需要使用 Feign 接口 DemoService 的地方, 直接利用 @Autowire 进行注入
使用接口完成对服务端的调用
可以根据上面使用 Feign 的步骤大致猜测出整体的工作流程:
SpringBoot 应用启动时, 由针对
@EnableFeignClient
这一注解的处理逻辑触发程序扫描 classPath 中所有被@FeignClient
注解的类, 这里以DemoService
为例, 将这些类解析为 BeanDefinition 注册到 Spring 容器中Sping 容器在为某些用的 Feign 接口的 Bean 注入
DemoService
时, Spring 会尝试从容器中查找 DemoService 的实现类由于我们从来没有编写过
DemoService
的实现类, 上面步骤获取到的 DemoService 的实现类必然是 feign 框架通过扩展 spring 的 Bean 处理逻辑, 为DemoService
创建一个动态接口代理对象, 这里我们将其称为DemoServiceProxy
注册到 spring 容器中。Spring 最终在使用到
DemoService
的 Bean 中注入了DemoServiceProxy
这一实例。当业务请求真实发生时, 对于
DemoService
的调用被统一转发到了由 Feign 框架实现的InvocationHandler
中,InvocationHandler
负责将接口中的入参转换为 HTTP 的形式, 发到服务端, 最后再解析 HTTP 响应, 将结果转换为 Java 对象, 予以返回。
上面整个流程可以进一步简化理解为:
我们定义的接口
DemoService
由于添加了注解@FeignClient
, 最终产生了一个虚假的实现类代理使用这个接口的地方, 最终拿到的都是一个假的代理实现类
DemoServiceProxy
所有发生在
DemoServiceProxy
上的调用, 都被转交给 Feign 框架, 翻译成 HTTP 的形式发送出去, 并得到返回结果, 再翻译回接口定义的返回值形式。
所以不难发现, Feign 的核心实现原理就是 java 原生支持的基于接口的动态代理
总结
Spring Cloud OpenFeign 的核心工作原理经上文探究可以非常简单的总结为:
通过 @EnableFeignCleints 触发 Spring 应用程序对 classpath 中 @FeignClient 修饰类的扫描
解析到 @FeignClient 修饰类后, Feign 框架通过扩展 Spring Bean Deifinition 的注册逻辑, 最终注册一个 FeignClientFacotoryBean 进入 Spring 容器
Spring 容器在初始化其他用到 @FeignClient 接口的类时, 获得的是 FeignClientFacotryBean 产生的一个代理对象 Proxy.
基于 java 原生的动态代理机制, 针对 Proxy 的调用, 都会被统一转发给 Feign 框架所定义的一个 InvocationHandler , 由该 Handler 完成后续的 HTTP 转换, 发送, 接收, 翻译 HTTP 响应的工作