文章目录
- 前言
- Openfeign实现思路
- 前期准备
- 基本依赖项
- 开始实现
- 自定义注解
- 自定义代理类
- 定义创建代理对象的工厂
- InstantiationAwareBeanPostProcessor实现bean的注入
- OpenInstantiationAwareBeanPostProcessor 自定义
- feign接口
- 启动类
- 小结
- 踩坑记录
- @Import
- @Component和@Configuration区别是什么
- 总结
前言
最近开发cloud项目,里面涉及到服务间调用,最后使用的openfeign解决的,于是对于openfeign的底层原理有些兴趣了,提前透露一下底层无非就是使用一些http调用的工具帮助我们实现了请求调用
Openfeign实现思路
前期准备
基本依赖项
- 首先创建一个springboot项目
- 有一个发送请求的工具这里使用的ribbon
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.9.RELEASE</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
开始实现
自定义注解
创建两个自定义注解,分别用于在启动类和接口上添加
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@Import(OpenInstantiationAwareBeanPostProcessor.class)//这个类重点注意
public @interface EnableRemoteClient {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface CustomProxy {
String servie();
String address();
}
自定义代理类
代理类中有一个RestTemplate 用于发送请求
public class CustomProxyHandler implements InvocationHandler {
RestTemplate restTemplate=new RestTemplate();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//首先获取对象
Class<?> declaringClass = method.getDeclaringClass();
//判断当前的被代理对象是否使用了自定的注解
if(declaringClass.isAnnotationPresent(CustomProxy.class)){
CustomProxy annotation = declaringClass.getAnnotation(CustomProxy.class);
String servie = annotation.servie();
String address = annotation.address();
String url=address+servie;
RequestMapping annotation1 = method.getAnnotation(RequestMapping.class);
url=url+annotation1.value()[0];
//判断请求方法是否入参
if(args!=null&&args.length!=0){
return restTemplate.getForObject(url,method.getReturnType(),args);
}else {
return restTemplate.getForObject(url,method.getReturnType());
}
}else{
return null;
}
}
}
定义创建代理对象的工厂
用于创建代理对象使用,
public class ProxyFactory {
/**
* @description: 创建具体的代理对象
* @author:
* @date: 2023/9/1 17:30
* @param: [targetInterface]
* @return: T
**/
public static <T> T createProxy(Class<T> targetInterface) {
return (T) Proxy.newProxyInstance(
targetInterface.getClassLoader(),
new Class[]{targetInterface},
new CustomProxyHandler()
);
}
}
InstantiationAwareBeanPostProcessor实现bean的注入
InstantiationAwareBeanPostProcessor
是 Spring 框架提供的一个扩展接口,它在 Spring 容器实例化 bean 之前和之后对 bean 进行处理。其主要功能如下:
- 实例化前置处理(Before Instantiation):在 Spring 容器实例化 bean 之前,可以通过这个接口来自定义 bean 的实例化方式。可以在此处进行一些特殊的准备工作,比如使用自定义的实例化逻辑或者切入点的选择。
- 实例化后置处理(After Instantiation):在 Spring 容器实例化 bean 之后,可以通过这个接口对实例化后的 bean 进行处理。可以在此处做一些初始化的操作,比如对属性进行赋值、调用初始化方法等。
- 属性设置前置处理(Before Property Set):在 Spring 容器对 bean 的属性进行设置之前,可以通过这个接口来自定义属性的设置方式。可以在此处对属性进行修改或者校验操作。
- 属性设置后置处理(After Property Set):在 Spring 容器对 bean 的属性进行设置之后,可以通过这个接口对属性设置后的 bean 进行处理。可以在此处做一些属性设置后的额外操作。
- 初始化前置处理(Before Initialization):在 Spring 容器对 bean 进行初始化之前,可以通过这个接口来自定义初始化的方式。可以在此处添加一些额外的初始化逻辑。
- 初始化后置处理(After Initialization):在 Spring 容器对 bean 进行初始化之后,可以通过这个接口对初始化后的 bean 进行处理。可以在此处做一些初始化后的额外操作。
通过实现 InstantiationAwareBeanPostProcessor
接口,并重写其中的方法,可以在 Spring 容器实例化和初始化 bean 的各个阶段进行自定义处理,从而灵活地对 bean 进行定制化的操作。
OpenInstantiationAwareBeanPostProcessor 自定义
public class OpenInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
/**
* 实例化前,后面的方法可以不用看了
* @param beanClass
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
log.info("正在为:{}生成代理对象,被代理的类为:{}",beanName,beanClass.getName());
if(!beanClass.isAnnotationPresent(CustomProxy.class)){
return null;
}
//动态代理里面需要实现的方法,本文采用的是jdk动态代理
//返回代理对象
Object object = ProxyFactory.createProxy(beanClass);
return object;
}
/**
* 实例化后
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
return true;
}
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
return pvs;
}
/**
* 初始化钱
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
/**
* 初始化后
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
}
feign接口
这里的servie没有参数是因为我的另一个服务中没有配置context-path,如果你们自己的被调用的那个demo中有配置一定要加上哦
@CustomProxy(servie = "",address = "http://localhost:8082/")
public interface MyService {
@RequestMapping(value = "/QuerySubselect",method = RequestMethod.GET)
String test();
}
启动类
@SpringBootApplication
@EnableRemoteClient
@Import({MyService.class})//接口注册,注意接口上面加@Se
public class OpenfeignDemoApplication {
public static void main(String[] args) {
SpringApplication.run(OpenfeignDemoApplication.class, args);
}
}
小结
这个版本的实现目前spring还不能识别我添加了注解的这个接口,需要在启动类中使用@Import
注解手动配置,所以还不完善,不过后期会继续完善的,目前博主也在学习这个,这个思路就是基于我们正常的发送请求,然后有一个人帮助我们去发送请求这个思路。
踩坑记录
@Import
将接口 MyService
标识为 @Import
的目的是为了在启动类中将该接口的实现类(动态代理对象)注册到 Spring 容器中。
当你使用 @Import({MyService.class})
注解时,Spring 在启动时会扫描被注解的类,并根据其类型进行相应的处理。在这种情况下,MyService
类型是一个接口,而不是一个具体的类。
由于接口无法直接实例化,因此你需要在某处提供对 MyService
接口的实现类的创建逻辑。通常情况下,使用动态代理是一种常见的方式。
动态代理是一种在运行时生成代理类的机制,可以在不修改原始接口源代码的情况下,通过代理类来增强接口的功能。你可以编写一个动态代理类,实现 MyService
接口,并在动态代理类中实现你所需的增强逻辑。
然后,在启动类上使用 @Import
注解,并传入该动态代理类的类型,告诉 Spring 在启动时需要将该动态代理类注册到容器中。
这样,当其他组件需要使用 MyService
接口时,Spring 容器会从容器中获取该接口的实例,而实际上获得的是动态代理对象,该代理对象会在实际调用接口方法时根据你的增强逻辑进行处理。
- 因为openfeign的标注的接口没有实现类所以需要这个
@Import
注解帮助我们告诉spring容器可以去容器中去这个类型的代理对象,目前还没有想到其他的方式不用使用@Import
注解声明的。后续再想想办法
@Component和@Configuration区别是什么
@Configuration
注解不能直接添加到接口上,因为 @Configuration
注解是用于标识一个类为配置类的注解,它主要用于定义和配置 Bean 对象以及其他的配置信息。
一开始使用这个注解导致使用了自定义注解的接口一直扫描不到,后来修改为@Component
注解后就可以了。
@Component
是 Spring Framework 中的一个核心注解之一,用于将一个普通的 Java 类标识为一个可以被 Spring 自动扫描并管理的组件。
具体来说,@Component
注解可以应用于以下场景:
- Bean 的自动扫描和注册:通过在类上添加
@Component
注解,Spring 容器会自动扫描并将该类作为 Bean 进行注册,使得我们可以通过依赖注入等方式方便地使用该类的实例。 - 类型指定的自动装配:当需要进行自动装配时,Spring 容器会检测所有被
@Component
注解标记的类,并根据类型进行自动装配。
@Component
注解是一个通用的注解,如果对应的类有更具体的角色或作用,还可以使用其他派生注解,例如:
-
@Controller
:标识一个类是控制器(Controller)组件,用于接收和处理用户请求。 -
@Service
:标识一个类是服务(Service)组件,用于封装业务逻辑。 -
@Repository
:标识一个类是数据访问仓库(Repository)组件,用于访问持久化数据。
这些派生注解都是基于 @Component
注解的,继承了 @Component
的功能,并且更明确地表示了对应类的角色和用途。
总结
对于这次去实现这么一个框架的小demo自己思考了一天,不能说很长时间吧,主要是因为对于spring中提供的一些扩展机制还不是很了解,但是我相信它一定提供了能够解决我上面问题的一些api的,只是我还没有发现而已。对于这些技术的应用底层原理还是很简单的,可以发现无非就是没让你做的事情有人帮你做了这么一个过程,只不过这个过程被人封装起来以后就是一个技术或者一个框架了。