在Spring boot中类似于spring cloud 一样的使用OpenFeign(1)
一、前言
在文章开始之前,首先得了解下为什么?不然我自己搞不明白,看这篇文章的人也搞不明白
为什么在spring boot中(非spring cloud环境下)以spring cloud的方式使用openFeign?
首先项目不是很大,使用spring cloud有种杀鸡用牛刀的感觉,所以该项目并没有使用spring cloud,但是项目中做了一定的拆分,主要是微信小程序需要去调用一些业务性的服务,接口不算太多,但也不是很少。spring cloud提供了很多的功能,比如总线、网关之类的,暂时还用不上,所以没有使用spring cloud。也许有更好得解决方式,但是我受限于自身得知识结构,想不到啊,所以,谁有更好的方式告诉我啊
为什么非得使用OpenFeign?
在web环境中去调用其它的http服务有很多的方式,比如apacheHttpClient,或者直接使用spring提供的RestTemplate,都非常的简单易用,原本也是这样做的,但是当你调用的http接口很多的时候,你就发现很多的代码都是重复性的代码,都是格式化的代码,而真正需要改变的永远只是接口地址、请求方法、请求参数、请求头等那么几个。
OpenFeign把一个HTTP请求中经常会变的几个参数交给用户以接口+注解的形式去声明,而那些重复性的工作(发起请求,解析数据等)则自己去做了,使用起来,至少在结构上非常的简单清晰明了。
二、OpenFeign的使用
openFeign的使用非常简单,简单来说就两个步骤:
- 创建一个接口,声明请求
- 构建接口的实例,调用接口
1)定义一个接口
openFeign要求用户在接口类中声明请求,使用@RequestLine注解,声明请求方法及地址,@Headers声明请求的请求头,@Param参数设置请求参数的值,@QueryMap处理动态参数。
具体的使用,可以参照openFeign的github
openFeign使用说明
public interface HxGpsService {
@RequestLine("GET /location/traceReplay/{vehicleId}?startTime={startTime}&endTime={endTime}")
@Headers("Token: {token}")
public List<Gps> traceReplay(@Param("token") String token,
@Param("vehicleId") String vehicleId,
@Param("startTime") String startTime,
@Param("endTime") String endTime);
}
2)使用
构建者模式创建接口代理对象
HxGpsService hxGpsService =
Feign.builder()
.client(new OkHttpClient())
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.target(HxGpsService.class, "http://localhost:8383/dispatch");
List<Gps> gpsLists = hxGpsService.traceReplay("XXXX","XX","XXXX-XX-XX ZZ:ZZ:ZZ","XXXX-XX-XX ZZ:ZZ:ZZ")
三、在spring boot中使用OpenFeign
1)创建@Configuration配置类
声明请求的接口类定义好之后,使用Feign代理创建接口的实例,如果该实例其它的地方需要引用,我们可以把Feign代理创建的对象交给spring 容器来管理,那使用的地方就只需要注入即可
@Configuration
public class OpenFeignInterfaceConfiguration {
@Bean
public HxGpsService hxGpsService(){
Feign.builder()
.client(new OkHttpClient())
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.target(HxGpsService.class, "http://localhost:8383/dispatch");
}
}
http://localhost:8383/dispatch在上面示例中硬编码了,这个地址我们可以在yml文件中定义,在这里注入即可,但我这个只是示例,各位将就着看下。
2)使用
@Service("gpsService")
public GpsService {
@Autowired
private HxGpsService hxGpsService;
public List<Gps> getGps(String token,String vehicleId,String startTime,String endTime){
return hxGpsService.traceReplay(token,vehicleId,startTime,endTime);
}
}
这样我们就可以非常方便的在任意地方调用远程接口,就像调用本地方法一样。如果接口不多的情况下,我们自己去配置一个Bean没有什么很大的难度,也不会觉得繁琐,但是如果接口比较多的情况下,我们就发现这些都是重复性的工作,mybatis的mapper接口我们只需要定义然后配置扫描就可以交给spring容器来管理,那openFeign接口有没有可能像mybatis一样的使用呢?答案是肯定的,只要肯钻研,办法还是有的。
四、向IOC容器中自动注入接口类
在介绍之前,我们先得了解一下spring boot是怎么把对象搞到IOC容器中的?,因为要把OpenFeign声明的请求接口自动注入到IOC容器中,需要知道下面这些原理。
在springboot中有些类我们不需要配置,只需要引入jar包,spring boot就能帮我们把对象弄到IOC容器中。还有些组件,我们只要加一个@EnableXXXX的注解,spring 就可以帮我们把该组件需要注入到IOC容器中的类自动注入,使用起来非常方便,怎么做到的?
1)第一种引入jar包自动注入
这种jar一般都是starter,有些starter是spring官方实现的,有些则是各个组件厂商自己实现的。starter的原理,一般是在jar包的META-INFO目录下定义一个spring.factories文件,在这个spring.fatories文件中配置一个XXXAutoConfiguration的类,然后再在这个类中定义哪些对象交给spring自动注入。
spring boot在启动时会扫描所有引入的jar包是否含有spring.factories文件,然后解析文件,判断是否存在这些jar,如果存在则自动加载,不存在就不加载
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration{
.....
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
}
......
@ConditionalXXXX注解是spring的条件注解,意思只有满足某些条件我才加载或不加载,比如上面的例子中只有项目中存在SqlSessionFactory.class, SqlSessionFactoryBean.class两个类时才加载对象到spring容器中,感兴趣的可以单独去搜搜,这里不展开讨论。
2)EnableXXXX
还有些类也会被spring自动加载,但前提条件是需要我们自己去把开关打开,在spring boot的启动类中加@EnableXXXX注解,spring就会自动帮我把要注入的对象注入到容器中。那spring又是怎么做到的?
这里就需要了解spring @Import注解注入对象的三种模式,spring在扫描到@Import注解时,会去执行@Import注解中引入的类,本文的要实现的目标,其原理就是这个。
1、以XXXXConfiguration结尾
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}
在XXXXConfiguration类上添加@Configuration注解,然后在类中创建bean
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}
这种模式可直接创建要注入到容器中的对象
2、以XXXXSelector结尾
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching
Selector模式一般根据用户的配置来选择要以什么方式,把哪些类加载到容器中,需要实现ImportSelector接口,在selectImports方法中返回要注入的类的完全类名,spring根据类名实现注入
public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector{
....
@Override
public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
Class<?> annoType = GenericTypeResolver.resolveTypeArgument(getClass(), AdviceModeImportSelector.class);
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType);
if (attributes == null) {
throw new IllegalArgumentException(String.format(
"@%s is not present on importing class '%s' as expected",
annoType.getSimpleName(), importingClassMetadata.getClassName()));
}
AdviceMode adviceMode = attributes.getEnum(this.getAdviceModeAttributeName());
String[] imports = selectImports(adviceMode);
if (imports == null) {
throw new IllegalArgumentException(String.format("Unknown AdviceMode: '%s'", adviceMode));
}
return imports;
}
......
}
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching>{
....
@Override
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return getProxyImports();
case ASPECTJ:
return getAspectJImports();
default:
return null;
}
}
.....
}
3、以XXXXRegistrar结尾
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan
这种模式最复杂,可以动态的注入bean。
我去、前面废话了太多,还没有开始正题,文章就已经很长了,好吧,正题下一篇
在Spring boot中类似于spring cloud 一样的使用OpenFeign(2)