MySQL 数据库 day-05

下载网页视频到本地

  返回  

Forest源码分析:如何注入将代理类注入Spring容器以及代理生成

2021/8/21 21:44:37 浏览:

前言

本文源码基于Forest 1.5.2-BETA Spring boot starter

该篇文章主要探究Forest的两个问题

  • Forest如何将代理对象注入到Spring容器中
  • Forest注入的代理对象是如何生成的

背景

什么是Forest?
Forest 是一个开源的 Java HTTP 客户端框架,它能够将 HTTP 的所有请求信息(包括 URL、Header 以及 Body 等信息)绑定到您自定义的 Interface 方法上,能够通过调用本地接口方法的方式发送 HTTP 请求。—引用自官网

架构图

架构图
面向使用者,我们拿到的是一个接口,该接口的实现实际上是一个Java原生动态代理。
代理大致分为两个部分,根据官方的命名分别为前端、后端
前端工作:

  1. Forest 配置: 负责管理 HTTP 发送请求所需的配置。
  2. Forest 注解: 用于定义 HTTP 发送请求的所有相关信息,一般定义在 interface 上和其方法上。
  3. 动态代理: 用户定义好的 HTTP 请求的interface将通过动态代理产生实际执行发送请求过程的代理类。
  4. 模板表达式: 模板表达式可以嵌入在几乎所有的 HTTP 请求参数定义中,它能够将用户通过参数或全局变量传入的数据动态绑定到 HTTP 请求信息中。
  5. 数据转换: 此模块将字符串数据和JSON或XML形式数据进行互转。目前 JSON 转换器支持Jackson、Fastjson、Gson三种,XML 支持JAXB一种。
  6. 拦截器: 用户可以自定义拦截器,拦截指定的一个或一批请求的开始、成功返回数据、失败、完成等生命周期中的各个环节,以插入自定义的逻辑进行处理。
  7. 过滤器: 用于动态过滤和处理传入 HTTP 请求的相关数据。
  8. SSL: Forest 支持单向和双向验证的 HTTPS 请求,此模块用于处理 SSL 相关协议的内容。
    后端的工作:
    为实际执行 HTTP 请求发送过程的第三方 HTTP API,目前支持okHttp3和httpclient两种后端 API。

上手展示

以调用百度搜索API为例
编写接口

public interface BaiduApi {
    /**
     * 百度搜索API
     * @param keyWord 关键词
     * @return 网页
     */
    @Get(url = "http://www.baidu.com/s?wd=${keyWord}")
    String search(@Var("keyWord") String keyWord);
}

编写测试用例

@SpringBootTest
public class BaiduApiTest {
    @Autowired
    BaiduApi baiduApi;

    @Test
    public void testSearch(){
        try {
            String search = baiduApi.search("什么时候取消大小周?");
            Assertions.assertNotNull(search,"测试失败");
        }catch (ForestNetworkException e){
            e.printStackTrace();
        }
    }
}

至此,开发者完全不需要关注底层HttpClient的实现,不必关心具体发送 HTTP 请求的细节,同时将 HTTP 请求信息与业务代码解耦,只需要调用接口即可,同时引入该框架的Spring boot starter可以自动将该类注入Spring容器,不需要额外操作,使用起来十分方便。

引出问题

如上面所示,接口BaiduApi并没有加上任何Spring的任何与注入Bean相关的注解,但是我们在测试代码中可以直接将其注入,这是如何做到?
以及,注入的对象毫无疑问是是一个代理,问题是这个代理是什么生成的?
带着这两个问题,我们开始进行源码分析,来寻找答案。

1. 如何将代理注入Spring容器

一般Spring boot starter都是使用了Spring 的SPI机制,前往引入的包中,查看spring.properties,可以发现他在其中注入com.dtflys.forest.springboot.ForestAutoConfiguration ForestAutoConfiguration基本上完成了所有工作:包括配置读取,构建代理,将代理注入Spring容器。

@Configuration
//该扫描实际上全是读取配置,生成配置类
@ComponentScan("com.dtflys.forest.springboot.properties")
//读取配置
@EnableConfigurationProperties({ForestConfigurationProperties.class})
@Import({ForestScannerRegister.class})
public class ForestAutoConfiguration {

    @Autowired
    private ConfigurableApplicationContext applicationContext;

    @Bean
    public ForestBeanRegister forestBeanRegister(ForestConfigurationProperties forestConfigurationProperties) {
        ForestBeanRegister forestBeanRegister = new ForestBeanRegister(applicationContext, forestConfigurationProperties);
        forestBeanRegister.registerForestConfiguration(forestConfigurationProperties);
        forestBeanRegister.registerScanner(forestConfigurationProperties);
        return forestBeanRegister;
    }

}

核心步骤:forestBeanRegister.registerScanner(forestConfigurationProperties);其大概内容如下:
在这里插入图片描述
1:ClassPathClientScanner实际上是ClassPathBeanDefinitionScanner的子类,最后读取 BeanDefinition都是委托它来做
2:该方法实际上调用ClassPathBeanDefinitionScanner的doScan
3-5:读取传入路径下接口,并且设置了过滤,只扫描类上面有BaseLifeCycle注解或者接口方法上MethodLifeCycle注解的类
6:对得到的BeanDefinition进行设置,将其BeanClass设置为ClientFactoryBean.class,该类为工厂类,之后对BeanDefinition进行实例化获取对象时,会调用ClientFactoryBean的工厂方法创建代理对象,创建代理对象的逻辑,下节叙述。

在这里插入图片描述

2. 如何生成代理类

紧接上节,Forest获取到BeanDefinition后,将其BeanClass设置为ClientFactoryBean.class,ClientFactoryBean实际上继承了Spring的FactoryBean接口,Spring的单例bean是会在启动时出发实例化,因此才Spring boot启动实例化单例bean时会触发ClientFactoryBean类的工厂方法getObject()

@Override
public T getObject() throws Exception {
    if (forestConfiguration == null) {
        synchronized (this) {
            if (forestConfiguration == null) {
                try {
                    forestConfiguration = applicationContext.getBean(ForestConfiguration.class);
                } catch (Throwable th) {
                }
                if (forestConfiguration == null) {
                    forestConfiguration = ForestConfiguration.getDefaultConfiguration();
                }
            }
        }
    }
    return forestConfiguration.createInstance(interfaceClass);
} 

可见最后又委托给了ForestConfiguration对象的createInstance(interfaceClass)方法,下面是该方法主要做的事
在这里插入图片描述
到此代理创建完毕,我们调用代理接口实际上调用的就是InterfaceProxyHandler该类的invoke(Object proxy, Method method, Object[] args)方法,里面就是封装了Forest的前端、后端的逻辑。
核心类InterfaceProxyHandler在实例化的时候,就会对传入的目标接口Class对象,利用反射,读取注解信息,并储存起来,供后面调用方法使用。

总结

总的来说,这部分源码还是非常好懂的,了解完Forest如何生成代理以及将代理注入Spring容器中去后,接下来我们开始了解InterfaceProxyHandler里面的源码,也是最复杂的部分,它封装了前后端的逻辑。

联系我们

如果您对我们的服务有兴趣,请及时和我们联系!

服务热线:18288888888
座机:18288888888
传真:
邮箱:888888@qq.com
地址:郑州市文化路红专路93号