当前位置: 首页 > 工具软件 > Forest > 使用案例 >

Forest的快速入门以及源码解析

胡修伟
2023-12-01

0x00:Forest源码分析(1)

又名: 怎么使用Spring的Scanner去扫描加载使用了自定义注解的Bean
又名: 怎么构建一个spring-boot-starter将自定义扫描注册Bean
又名: 我没理解这个框架的设计模式

0x01:简单使用

  • 导入 starter, 然后注解, 结束
  • 这里是官方文档: 中文文档.

简单来说, forest 是一个很方便的使用的调用第三方URL的工具. 可以帮你从 httpClient, okHttp3的具体细节中解放出来…
同时也不至于像 openFeign 那么重量级.
非常轻便, 易于自定义扩展

0x02:当你导入starter的时候, 它干了些啥

resources/META-INF/spring.factories

引入了 com.dtflys.forest.springboot.ForestAutoConfiguration
为什么起作用, 在启动spring-boot时 @SpringBootApplication->@EnableAutoConfiguration会扫描所有依赖的 resources/META-INF/spring.factories 将加载对应的文件中写的bean;

参考此篇 如何激活自动装配

接下来就会发现此类会被装配; (只粘贴关键代码)


@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ComponentScan("com.dtflys.forest.springboot.properties")
@EnableConfigurationProperties({ForestConfigurationProperties.class})
@Import({ForestScannerRegister.class})
public class ForestAutoConfiguration {

    @Autowired(required = false)
    private ConfigurableApplicationContext applicationContext;

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

}

1, 首先@Import({ForestScannerRegister.class}) 此类会将所有准备好所有需要扫描的包名 List<String> basePackages

public class ForestScannerRegister implements BeanFactoryAware, ImportBeanDefinitionRegistrar {

... 略
        // 若 @ForestScan 注解未定义扫描包名,则扫描整个项目
        if (basePackages.isEmpty()) {
            basePackages.addAll(AutoConfigurationPackages.get(beanFactory));
        }
        
 ... 略
}
public class ForestScannerRegister implements BeanFactoryAware, ImportBeanDefinitionRegistrar

实现了ImportBeanDefinitionRegistrar;
方法 void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)
第一个参数是元注解;指的是 ` ForestAutoConfiguration ` 调用者的注解信息;
第二个参数是bean定义的注册工厂;

需要关注的是:这个能获取到Spring容器启动会扫描的所有的路径; beanFactory 是 spring的BeanFactory
如果之前写了扫描的基础路径, 就能节省很多扫描的时间;

2, 然后开始往容器里注入bean; 都是对基础配置属性的加载, 还有默认值的设置, 略
3, register.registerScanner(forestConfigurationProperties);

public ClassPathClientScanner registerScanner(ForestConfigurationProperties forestConfigurationProperties) {
    // 之前在 @Import({ForestScannerRegister.class}) 里注册的所有 路径
    List<String> basePackages = ForestScannerRegister.getBasePackages();
    String configurationId = ForestScannerRegister.getConfigurationId();

    BeanDefinitionRegistry registry = (BeanDefinitionRegistry) applicationContext.getBeanFactory();

    // 这是自定义的 beanScanner
    ClassPathClientScanner scanner = new ClassPathClientScanner(configurationId, registry);
    // this check is needed in Spring 3.1
    if (resourceLoader != null) {
        scanner.setResourceLoader(resourceLoader);
    }
//        scanner.registerFilters();
    if (basePackages == null || basePackages.size() == 0) {
        return scanner;
    }
    // 最终加载bean的操作在这里;
    scanner.doScan(org.springframework.util.StringUtils.toStringArray(basePackages));
    return scanner;
}

那么doScan做了些什么呢

    /**
     * 重写扫描逻辑
     * @param basePackages 请求接口类所在的包路径,只能是第一层的包,不包含子包
     * @return BeanDefinitionHolder实例集合
     */
    @Override
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        if (beanDefinitions.isEmpty()) {
            logger.warn("[Forest] No Forest client is found in package '" + Arrays.toString(basePackages) + "'.");
        }
        processBeanDefinitions(beanDefinitions);
        return beanDefinitions;
    }

调用了 super.doScan(); 查询所有能被查询到 beanDefinitions
链接: DoScan详解.

其中的
findCandidateComponents(basePackage) 是扫描目标bean的关键;
一路往下我们最终调用 scanCandidateComponents(String basePackage)
其中有一个关键的判断

MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// 首先判断这个路径在不在 白名单/黑名单 上.
if (isCandidateComponent(metadataReader)) {
    ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
    // 然后再判断, 这个bean是否是 只有接口没有实现的类
    // 此方法被 forest 重写了. 原本的判断条件更多;
    if (isCandidateComponent(sbd)) {
        candidates.add(sbd);
    }
}

### 注册黑名单和白名单的时间在Sacnner::New的时候, 会调用 registerFilters

/**
 * 注册过滤器
 */
public void registerFilters() {
    if (allInterfaces) {
        // include all interfaces
        addIncludeFilter((metadataReader, metadataReaderFactory) ->
                interfaceFilter(metadataReader, metadataReaderFactory));
    }

    // exclude package-info.java
    addExcludeFilter((metadataReader, metadataReaderFactory) -> {
        String className = metadataReader.getClassMetadata().getClassName();
        return className.endsWith("package-info");
    });
}

### 至于白名单
private boolean interfaceFilter(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) {
    ClassMetadata classMetadata = metadataReader.getClassMetadata();
    if (!classMetadata.isInterface() || classMetadata.isFinal()) {
        return false;
    }

    String[] superClassNames = metadataReader.getClassMetadata().getInterfaceNames();
    boolean hasSuperForestClient = false;
    for (String superClassName : superClassNames) {
        try {
            MetadataReader superMetaReader = metadataReaderFactory.getMetadataReader(superClassName);
            hasSuperForestClient = interfaceFilter(superMetaReader, metadataReaderFactory);
        } catch (IOException e) {
        }
        if (hasSuperForestClient) {
            return true;
        }
    }

    AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
    for (String annotationTypeName : annotationMetadata.getAnnotationTypes()) {
        if ("com.dtflys.forest.annotation.ForestClient".equals(annotationTypeName)) {
            return true;
        }
        if ("com.dtflys.forest.annotation.BaseRequest".equals(annotationTypeName)) {
            return true;
        }
    }
    for (String methodAnnName : FOREST_METHOD_ANNOTATION_NAMES) {
        if (annotationMetadata.hasAnnotatedMethods(methodAnnName)) {
            return true;
        }
    }

    return false;
}
### 也就是判断此Class里面有没有使用了forest注解的方法, 如果有就是白名单, 会被代理;
### 黑名单, 排除java的Class

注册完成了之后执行
processBeanDefinitions(beanDefinitions);

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");

    }
}
最终则是

private final static Class CLIENT_FACTORY_BEAN_CLASS = ClientFactoryBean.class;

public static void setupClientFactoryBean(AbstractBeanDefinition beanDefinition, String configurationId, String clientClassName) {
    beanDefinition.setBeanClass(CLIENT_FACTORY_BEAN_CLASS);
    if (configurationId != null && configurationId.length() > 0) {
        beanDefinition.getPropertyValues().add("forestConfiguration", new RuntimeBeanReference(configurationId));
    }
    beanDefinition.getPropertyValues().add("interfaceClass", clientClassName);
}

也就是将写了forest注解的接口的beanDefintion,注册了一个对应的FactoryBean
简单来说,FactoryBean#GetObject()方法会给容器里注入Bean, bean的类型是FactoryBean#getObjectType()
在这里它的类型是传入的 clientClassName;(虽然传入的是String, 但是后续Spring的容器会将它加载为Class对象)

链接: FactoryBean的作用.

0x03 FactoryBean做了什么


    @Override
    public T getObject() {
        // 单例模式的典型双重检测写法 https://www.cnblogs.com/tangZH/p/10031337.html
        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);
    }

    @Override
    public Class<?> getObjectType() {
        return interfaceClass;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

也即是最终调用了 forestConfiguration.createInstance(interfaceClass); 去给需要实现的接口实现代理;

    /**
     * 创建请求接口的动态代理实例
     *
     * @param clazz 请求接口类
     * @param <T>   请求接口类泛型
     * @return 动态代理实例
     * @see ForestConfiguration#client(Class)
     */
    public <T> T createInstance(Class<T> clazz) {
        ProxyFactory<T> proxyFactory = getProxyFactory(clazz);
        return proxyFactory.createInstance();
    }

最终就是走的代理工厂; 这是JDK的动态代理实现;

public class ProxyFactory<T> {

    private ForestConfiguration configuration;
    private Class<T> interfaceClass;

    public ProxyFactory(ForestConfiguration configuration, Class<T> interfaceClass) {
        this.configuration = configuration;
        this.interfaceClass = interfaceClass;
    }

    public Class<T> getInterfaceClass() {
        return interfaceClass;
    }

    public void setInterfaceClass(Class<T> interfaceClass) {
        this.interfaceClass = interfaceClass;
    }

    public T createInstance() {
        T instance = (T) configuration.getInstanceCache().get(interfaceClass);
        boolean cacheEnabled = configuration.isCacheEnabled();
        if (cacheEnabled && instance != null) {
            return instance;
        }
        synchronized (configuration.getInstanceCache()) {
            instance = (T) configuration.getInstanceCache().get(interfaceClass);
            if (cacheEnabled && instance != null) {
                return instance;
            }
            InterfaceProxyHandler<T> interfaceProxyHandler = new InterfaceProxyHandler<T>(configuration, this, interfaceClass);
            instance = (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass, ForestClientProxy.class}, interfaceProxyHandler);
            if (cacheEnabled) {
                configuration.getInstanceCache().put(interfaceClass, instance);
            }
            return instance;
        }
    }

}

使用JDK代理, 然后将其加入缓存中;
synchronized (configuration.getInstanceCache()) 锁住防止可能的并发问题;
具体代理内容就是 new InterfaceProxyHandler()

链接: Forest源码探析.

InterfaceProxyHandler 生成了代理; 然后构建ForestRequest去执行请求

0x05总结

  • 使用自定义的Scanner扫描框架内的Bean
  • 使用beanDefinition往Spring内注册修改内容
  • 最终则是JDK动态代理,将一个接口代理生成了一个Bean;去执行对应的方法;
 类似资料: