【开源项目】Springboot整合Forest的快速入门及源码解析 - Go语言中文社区

【开源项目】Springboot整合Forest的快速入门及源码解析


Springboot整合Forest的快速入门及源码解析

项目介绍

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

源码地址

https://gitee.com/dromara/forest

在这里插入图片描述

快速入门

  1. 引入依赖
        <dependency>
            <groupId>com.dtflys.forest</groupId>
            <artifactId>forest-spring-boot-starter</artifactId>
            <version>1.5.28</version>
        </dependency>
  1. 暴露接口
@RestController
public class IndexController {
    @Value(value = "${spring.application.name}")
    private String applicationName;     //demo

    @GetMapping("/index")
    public String index() {
        return "您好,欢迎访问【" + applicationName + "】";
    }

    @GetMapping("/hello")
    public String hello(@RequestParam(required = false) String msg) throws InterruptedException {
        // 模拟业务耗时处理流程
//        Thread.sleep(2 * 1000L);
        return "hello: " + msg;
    }
}
  1. 写Http接口
@Address(host = "127.0.0.1", port = "9098")
public interface MyForestClient {
    /**
     * 本地测试接口
     */
    @Get(url = "/index")
    String index();

    @Get(url = "/hello?msg=${msg}")
    String hello(@DataVariable("msg") String msg);
}
  1. 测试运行
@SpringBootTest
public class ForestTest {

    @Autowired
    private MyForestClient myClient;

    @Test
    public void testForest() throws Exception {
        // 调用接口
        String index = myClient.index();
        System.out.println(index);
        String hello = myClient.hello("测试...");
        System.out.println(hello);
    }
}

源码解析

  1. 导入starter后,会扫描所有依赖的 resources/META-INF/spring.factories 将加载对应的文件中写的bean,ForestAutoConfiguration。该类会引入ForestBeanRegister
@Configuration
@EnableConfigurationProperties({ForestConfigurationProperties.class})
@Import({ForestScannerRegister.class})
public class ForestAutoConfiguration {
    
	//...
    @Bean
    @DependsOn("forestBeanProcessor")
    @ConditionalOnMissingBean
    public ForestBeanRegister forestBeanRegister(SpringForestProperties properties,
                                                    SpringForestObjectFactory forestObjectFactory,
                                                    SpringInterceptorFactory forestInterceptorFactory,
                                                    ForestConfigurationProperties forestConfigurationProperties) {
        ForestBeanRegister register = new ForestBeanRegister(
                applicationContext,
                forestConfigurationProperties,
                properties,
                forestObjectFactory,
                forestInterceptorFactory);
        register.registerForestConfiguration();
        register.registerScanner();
        return register;
    }
  1. 首先@Import({ForestScannerRegister.class}) ,会找到所有匹配的包,若 @ForestScan 注解未定义扫描包名,则扫描整个项目。
  2. ForestBeanRegister#registerScanner会进行扫描注册,使用了自定义的扫描器ClassPathClientScanner,扫描匹配的方法是ClassPathClientScanner#interfaceFilter,将扫描好的匹配类生成对应的实体Bean,注册到容器中。ClassPathClientScanner#processBeanDefinitions。往容器中注入Class为ClientFactoryBean的Bean。
    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition;
        for (BeanDefinitionHolder holder : beanDefinitions) {
            definition = (GenericBeanDefinition) holder.getBeanDefinition();
            String beanClassName = definition.getBeanClassName();
            ClientFactoryBeanUtils.setupClientFactoryBean(definition, configurationId, beanClassName);
            logger.info("[Forest] Created Forest Client Bean with name '" + holder.getBeanName()
                    + "' and Proxy of '" + beanClassName + "' client interface");

        }
    }
  1. ClientFactoryBean#getObject的实体类。返回的是ForestConfiguration#createInstance。生成代理类,使用的拦截拦截类是InterfaceProxyHandler。
    public <T> T createInstance(Class<T> clazz) {
        ProxyFactory<T> proxyFactory = this.getProxyFactory(clazz);
        return proxyFactory.createInstance();
    }
    //ProxyFactory#createInstance
	public T createInstance() {
        T instance = this.configuration.getInstanceCache().get(this.interfaceClass);
        boolean cacheEnabled = this.configuration.isCacheEnabled();
        if (cacheEnabled && instance != null) {
            return instance;
        } else {
            synchronized(this.configuration.getInstanceCache()) {
                instance = this.configuration.getInstanceCache().get(this.interfaceClass);
                if (cacheEnabled && instance != null) {
                    return instance;
                } else {
                    InterfaceProxyHandler<T> interfaceProxyHandler = new InterfaceProxyHandler(this.configuration, this, this.interfaceClass);
                    instance = Proxy.newProxyInstance(this.interfaceClass.getClassLoader(), new Class[]{this.interfaceClass, ForestClientProxy.class}, interfaceProxyHandler);
                    if (cacheEnabled) {
                        this.configuration.getInstanceCache().put(this.interfaceClass, instance);
                    }

                    return instance;
                }
            }
        }
    }
  1. InterfaceProxyHandler该拦截类主要根据拦截方法,解析对应的注解信息,进行http请求,最核心的就是调用okhttp3的请求。
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if (method.isDefault()) {
            return this.invokeDefaultMethod(proxy, method, args);
        } else {
            ForestMethod forestMethod = (ForestMethod)this.forestMethodMap.get(method);
            if (forestMethod != null) {
                return forestMethod.invoke(args);
            } else {
                if (args == null || args.length == 0) {
                    InterfaceProxyHandler.NonParamsInvocation invocation = getNonParamsInvocation(methodName);
                    if (invocation != null) {
                        return invocation.invoke(this, proxy);
                    }
                }

                if (args != null && args.length == 1) {
                    if ("equals".equals(methodName)) {
                        Object obj = args[0];
                        if (Proxy.isProxyClass(obj.getClass())) {
                            InvocationHandler h1 = Proxy.getInvocationHandler(proxy);
                            InvocationHandler h2 = Proxy.getInvocationHandler(obj);
                            return h1.equals(h2);
                        }

                        return false;
                    }

                    if ("wait".equals(methodName) && args[0] instanceof Long) {
                        proxy.wait((Long)args[0]);
                    }
                }

                if (args != null && args.length == 2 && args[0] instanceof Long && args[1] instanceof Integer && "wait".equals(methodName)) {
                    proxy.wait((Long)args[0], (Integer)args[1]);
                }

                throw new NoSuchMethodError(method.getName());
            }
        }
    }
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_42985872/article/details/128457211
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2022-12-31 20:03:56
  • 阅读 ( 331 )
  • 分类:Go开源项目

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢