手写OpenFeign的demo

了解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);
    }
}

 

 

posted @   complexlong  阅读(62)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示