草庐IT

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

秋装什么 2024-06-27 原文

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());
            }
        }
    }

有关【开源项目】Springboot整合Forest的快速入门及源码解析的更多相关文章

  1. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  2. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  3. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  4. ruby - 用逗号、双引号和编码解析 csv - 2

    我正在使用ruby​​1.9解析以下带有MacRoman字符的csv文件#encoding:ISO-8859-1#csv_parse.csvName,main-dialogue"Marceu","Giveittohimóhe,hiswife."我做了以下解析。require'csv'input_string=File.read("../csv_parse.rb").force_encoding("ISO-8859-1").encode("UTF-8")#=>"Name,main-dialogue\r\n\"Marceu\",\"Giveittohim\x97he,hiswife.\"\

  5. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

    我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

  6. ruby-on-rails - 新 Rails 项目 : 'bundle install' can't install rails in gemfile - 2

    我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="

  7. Ruby 从大范围中获取第 n 个项目 - 2

    假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit

  8. ruby-on-rails - 我更新了 ruby​​ gems,现在到处都收到解析树错误和弃用警告! - 2

    简而言之错误:NOTE:Gem::SourceIndex#add_specisdeprecated,useSpecification.add_spec.Itwillberemovedonorafter2011-11-01.Gem::SourceIndex#add_speccalledfrom/opt/local/lib/ruby/site_ruby/1.8/rubygems/source_index.rb:91./opt/local/lib/ruby/gems/1.8/gems/rails-2.3.8/lib/rails/gem_dependency.rb:275:in`==':und

  9. UE4 源码阅读:从引擎启动到Receive Begin Play - 2

    一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame

  10. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

随机推荐