了解OpenFeign实现原理,实现过程中学习了两个Spring扩展接口
-
定义接口,添加自定义注解,注解的属性值代表请求地址
-
接口内定义方法,方法上加@RequestMapping注解属性值是请求路径
-
自定义注解,注解属性值可以设置为请求地址
-
给添加了自定义注解的接口创建代理对象,创建时机在bean实例化之前
-
设置代理对象的invoke方法,发送http请求
具体实现:
实现过程为,首先创建自定义注解,这个注解的作用是加了这个注解的接口会被创建一个代理对象,当代理对象的方法被调用时,会发送一个HTTP请求,并返回请求的结果,相当于模拟@FeignClient注解的作用
|
/**
* 这个注解的作用是,加了这个注解的类会被创建一个代理对象,当代理对象的方法被调用时,会发送一个HTTP请求,并返回请求的结果,就相当于@FeignClient注解的作用
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
//@Component这个注解没有用,只有在启动类上写@Import才能将这个接口的代理对象导入到spring容器中,就离谱
public @interface CustomProxy {
String service();
String address();
}
|
然后创建InvocationHandler对象,当调用代理对象的方法时,这个对象的 invoke 方法会被调用,invoke 方法内部逻辑是利用反射获取代理方法所在类的class对象,判断是否包含目标注解,包含目标注解则获取注解的属性值,进行url拼接,然后利用RestTemplate发送http请求。
|
/**
* 本类的作用是为使用了CustomProxy注解的类创建代理对象,当代理对象的方法被调用时,会发送一个HTTP请求,并返回请求的结果
*/
public class CustomProxyHandler implements InvocationHandler {
//RestTemplate是Spring提供的用于发送HTTP请求的工具
RestTemplate restTemplate = new RestTemplate();
//代理对象的方法被调用时,准确的说是当调用MyService接口中的test方法时就会执行invoke()方法
//proxy:代表代理对象,method:代表被代理的方法对象,args:代表被代理方法的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//首先获取被代理方法所在类的class对象
Class<?> declaringClass = method.getDeclaringClass();
//判断当前的被代理对象是否使用了自定义的CustomProxy注解,没有使用则直接返回 null
if (declaringClass.isAnnotationPresent(CustomProxy.class)) {
//如果使用了自定义注解,那么就获取自定义注解
CustomProxy annotation = declaringClass.getAnnotation(CustomProxy.class);
//获取注解中的属性值
String service = annotation.service();
String address = annotation.address();
String url = address + service;
//获取方法上的注解,获取请求路径,拼接url
RequestMapping annotation1 = method.getAnnotation(RequestMapping.class);
url = url + annotation1.value()[0];
//判断方法是否有参数,如果方法有参数,就将参数传递给 getForObject 方法;如果没有参数,就只传递 URL 和返回类型。
if (args != null && args.length != 0) {
System.out.println("代理对象的方法被调用了");
return restTemplate.getForObject(url, method.getReturnType(), args);
} else {
System.out.println("代理对象的方法被调用了");
return restTemplate.getForObject(url, method.getReturnType());
}
} else {
return null;
}
}
}
|
然后定义创建代理对象的工厂方法,工厂方法接收一个class对象,利用Proxy.newProxyInstance传入类加载器,代理类 Class 对象和InvocationHandler对象,通过调用静态工厂方法即可创建目标接口的代理对象
|
/**
* 定义创建代理对象的工厂,用于创建代理对象使用,<T>就是起到泛型约束的作用,有了这个才可以用T来代替具体的类型,更通用
*/
public class ProxyFactory {
//创建具体的代理对象,用泛型就更方便了,会自动强转,且返回值类型不会写死,更通用
public static <T> T createProxy(Class<T> targetInterface) {
return (T) Proxy.newProxyInstance(
targetInterface.getClassLoader(),
new Class[]{targetInterface},
new CustomProxyHandler()
);
}
}
|
创建时机在bean实例化之前,因此需要实现Spring 框架提供的一个扩展接口,InstantiationAwareBeanPostProcessor,重写其中的方法,可以在 Spring 容器实例化和初始化 bean 的各个阶段进行自定义处理,从而灵活地对 bean 进行定制化的操作,然后postProcessBeforeInstantiation在实例化前的方法中创建目标接口的代理对象。
|
@Slf4j
@Component("openInstantiationAwareBeanPostProcessor")
public class OpenInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor{
/**
* 只用到了实例化前的方法,后面的方法可以不用看了
* @param beanClass
* @param beanName
* @return
* @throws BeansException
*/
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
//创建任何一个bean对象之前,都会自动调用这个方法,但是我们只对使用了自定义注解的类才创建代理对象,没有使用的直接返回null
if(!beanClass.isAnnotationPresent(CustomProxy.class)){
return null;
}
log.info("正在为:{}生成代理对象,被代理的类为:{}",beanName,beanClass.getName());
//动态代理里面需要实现的方法,本文采用的是jdk动态代理
//实例化每一个bean之前都来判断一下是否有自定义注解,如果有的话就创建代理对象并返回
Object object = ProxyFactory.createProxy(beanClass);
return object;
}
|
启动类
|
@SpringBootApplication //构建springboot项目的第三步
@Import({MyService.class}) //导入一个可以远程调用的接口的实现类对象,也就是动态代理对象,正常导入一个接口没有实现类对象的话是会报错的,但是现在并不正常,因为这个接口的实现类对象是在运行时动态生成的
public class LaunchApplication {
public static void main(String[] args) {
SpringApplication.run(LaunchApplication.class, args);
}
}
|