spring源码扩展点与实战(一)

前言

我们在使用 spring 框架的时候,有时候需要做一些定制化开发,这个时候就有必要对 spring 进行一些个性化扩展。spring 的代码本身就是一门艺术,可以非常方便进行扩展,但是有时候应用场景比较复杂,可能会觉得无从下手,笔者也曾有这样的困惑,因此,本文总结了一些常用的扩展点,希望能起到抛砖引玉的作用,开拓大家的思路。

spring 扩展点

BeanPostProcessor

首先,我们来看下官方的注释,解释得非常到位

Factory hook that allows for custom modification of new bean instances, e.g. checking for marker interfaces or wrapping them with proxies. ApplicationContexts can autodetect BeanPostProcessor beans in their bean definitions and apply them to any beans subsequently created. Plain bean factories allow for programmatic registration of post-processors, applying to all beans created through this factory.

BeanPostProcessor 这是一个勾子,允许对 bean 的实例进行个些自定义的个性,比如检查标记接口、使用代理包装 bean 实例。spring 可以自动检测容器中定义的 BeanPostProcessor,后续创建的 bean 便会被该 BeanPostProcessor 处理。此外,BeanFactory 还允许通过编码的方式注册 BeanPostProcessor

BeanPostProcessor 接口定义如下所示:

public interface BeanPostProcessor {

    // 初始化之后被调用,已完成注入,但是尚未执行 InitializingBean#afterPropertiesSet() 方法,或者自定义的 init 方法
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;

    // 初始化之后被调用
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;

}

由于 BeanPostProcessor 可以修改 bean 的实例对象,因此在 spring 框架中得到了非常广泛的应用,比如注解注入、AOP 等等。这是一个非常重要的扩展点,就拿注解注入来说吧,spring 根据 BeanDefinition 对 bean 进行实例化之后,会遍历容器内部注册的 InstantiationAwareBeanPostProcessor(BeanPostProcessor的子类)进行属性填充,其中就包括 AutowiredAnnotationBeanPostProcessor,它会处理 @Autowired@Value 注解,从而完成注入的功能,而 CommonAnnotationBeanPostProcessor 也是如此,只是处理不同的注解而已

spring 有很多内置的 BeanPostProcessor,我们来看个简单的例子,帮助我们理解这个接口的作用。

我们知道 spring 提供了很多 Aware 帮助我们注入 spring 内置的 bean,比如 ApplicationContextAwareResourceLoaderAware,那么 spring 又是如何实现该功能的呢?非常的简单,只要写一个 BeanPostProcessor,判断下这个 bean 是否实现了该 Aware 接口,如果是则调用 setXXX() 方法进行赋值即可。这个类在 spring-context 模块,叫做 ApplicationContextAwareProcessor,我们来看下源代码

class ApplicationContextAwareProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
        // 省略 AccessControlContext 处理的代码
        invokeAwareInterfaces(bean);
        return bean;
    }

    private void invokeAwareInterfaces(Object bean) {
        if (bean instanceof Aware) {
            if (bean instanceof ApplicationEventPublisherAware) {
                ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
            }
            // 省略部分代码……
        }
    }
}

通过这个简单的例子,我们可以感受到 BeanPostProcessor 的强大,不过,这仅仅是 spring 的冰山一角,还有很多更高级的应用,比如 AnnotationAwareAspectJAutoProxyCreator 在 bean 的实例化、或者初始化之后创建代理,从而实现 aop 功能。再比如,spring 对注解注入的检查机制是由 RequiredAnnotationBeanPostProcessor 完成的,也是一个 InstantiationAwareBeanPostProcessor 实现,只不过它执行的优先级较低(通过 Ordered 控制),因为需要等待所有的 InstantiationAwareBeanPostProcessor 完成注入之后才能进行注入检查,否则会有逻辑上的问题

BeanFactoryPostProcessor

BeanFactoryPostProcessor 允许对 Bean 的定义进行修改,还可以往 ConfigurableListableBeanFactory 中注册一些 spring 的组件,比如添加 BeanPostProcessor、ApplicationListener、类型转换器等等,接口定义如下:

public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

spring 利用 BeanFactoryPostProcessor 来处理占位符 ${...},关键的实现类是 PropertySourcesPlaceholderConfigurer,它会遍历所有的 BeanDefinition,如果 PropertyValue 存在这样的占位符,则会进行解析替换,下面我们来简单的分析下实现原理。

我们可以看到 PropertySourcesPlaceholderConfigurer 重写了 BeanFactoryPostProcessorpostProcessBeanFactory 方法,遍历了 BeanFactory 内部注册的 BeanDefinition,并且使用 BeanDefinitionVisitor 修改其属性值,从而完成占位符的替换。如果,我们要对某些配置进行加解密处理,也可以使用这种方式进行处理

PropertySourcesPlaceholderConfigurer.java

public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport implements EnvironmentAware {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // 省略对 PropertySources 的处理逻辑......
        processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
        this.appliedPropertySources = this.propertySources;
    }

    protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
            final ConfigurablePropertyResolver propertyResolver) throws BeansException {

        propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
        propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
        propertyResolver.setValueSeparator(this.valueSeparator);
        StringValueResolver valueResolver = new StringValueResolver() {
            @Override
            public String resolveStringValue(String strVal) {
                String resolved = (ignoreUnresolvablePlaceholders ?
                        propertyResolver.resolvePlaceholders(strVal) :
                        propertyResolver.resolveRequiredPlaceholders(strVal));
                if (trimValues) {
                    resolved = resolved.trim();
                }
                return (resolved.equals(nullValue) ? null : resolved);
            }
        };

        // 调用父类 PlaceholderConfigurerSupport 进行 properties 处理
        // 遍历每一个 BeanDefinition,并将其交给 BeanDefinitionVisitor 修改内部的属性
        doProcessProperties(beanFactoryToProcess, valueResolver);
    }
}

BeanDefinitionRegistryPostProcessor

BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor 的子类,可以通过编码的方式,改变、新增类的定义,甚至删除某些 bean 的定义

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
    void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}

这也是一个非常有用的扩展点,比如 spring 集成 mybatis,我们可以使用 spring 提供的扫描功能,为我们的 Dao 接口生成实现类而不需要编写具体的实现类,简化了大量的冗余代码,mybatis-spring 框架就是利用 BeanDefinitionRegistryPostProcessor 通过编码的方式往 spring 容器中添加 bean。

关键代码如下所示,MapperScannerConfigurer 重写了 postProcessBeanDefinitionRegistry 方法,扫描 Dao 接口的 BeanDefinition,并将 BeanDefinition 注册到 spring 容器中。我们也可以借鉴这种思路,通过具体的应用场景,从 spring 容器中删除或者新增 BeanDefinition

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, 
    InitializingBean, ApplicationContextAware, BeanNameAware {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        if (this.processPropertyPlaceHolders) {
            processPropertyPlaceHolders();
        }

        // ClassPathMapperScanner 持有 BeanDefinitionRegistry 引用,可以添加 BeanDefinition
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

        // 省略部分代码……,设置 ClassPathMapperScanner 各种属性

        // 扫描并注册 Dao 接口的 BeanDefinition,当然是通过动态代理实现
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
    }
}

ApplicationListener

用于接收spring的事件通知,比如常用的 ContextRefreshedEvent 事件,spring 在成功完成 refresh 动作之后便会发出该事件,代表 spring 容器已经完成初始化了,可以做一些额外的处理了,比如开启 spring 定时任务、拉取 MQ 消息,等等。

下面列出了 spring 处理 @Scheduled 注解的部分实现,在收到 Refresh 事件之后对 ScheduledTaskRegistrar 进行额外的设置,并开启定时任务

public class ScheduledAnnotationBeanPostProcessor
        implements MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor,
        Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,
        SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (event.getApplicationContext() == this.applicationContext) {
            finishRegistration();   // 对 ScheduledTaskRegistrar 进行额外的设置,并开启定时任务
        }
    }

    // 其它代码......
}

ApplicationContextInitializer

ApplicationContext#refresh() 之前调用,用于对 ApplicationContext 进行一些初始化设置,也可以做一些前期的初始化工作,比如设置环境变量

我们知道,dubbo 可以通过注册中心发现服务,也可以通过直连的方式,直连的方式通过设置环境变量即可,下面的代码演示了通过 dubbo-resolve-local.properties 文件指定的直连配置

public class DubboContextInitializer implements ApplicationContextInitializer {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        String[] activeProfiles = applicationContext.getEnvironment().getActiveProfiles();
        if (activeProfiles.length == 0) {
            return;
        }

        // 初始化Dubbo直连文件
        initResolveEnv(activeProfiles[0]);
    }

    private void initResolveEnv(String activeProfile) {
        // 省略获取配置文件的代码......
        Properties properties = new Properties();
        properties.load(inputStream);
        for (String name : properties.stringPropertyNames()) {
            // dubbo 指定直连 url 
            System.setProperty(name, properties.getProperty(name));
        }
    }
}

dubbo-resolve-local.properties 文件指定了直连的配置信息:

net.dwade.dubbo.UserService=dubbo://127.0.0.1:20881
net.dwade.dubbo.ItemService=dubbo://127.0.0.1:20881

上面只是实现了 ApplicationContextInitializer 的具体逻辑,还需要告诉 spring 有这么个 DubboContextInitializer 实现类
- 如果是 spring boot,则新增个 META-INF/spring.factories 文件,并且指定实现类即可

org.springframework.context.ApplicationContextInitializer=net.dwade.spring.dubbo.DubboContextInitializer
  • 如果是 web 环境,还可以通过配置 ServletContext 参数的方式,参数名为 "contextInitializerClasses",例如 web.xml 配置
<context-param>
    <param-name>contextInitializerClasses</param-name>
    <param-value>net.dwade.spring.dubbo.DubboContextInitializer</param-value>
</context-param>

总结

这篇文章简单地介绍了 spring 常用的扩展点,如有错误的地方还请各位大佬加以斧正哦。在接下来的这篇文章,将会总结本人在项目中的实战经验,《spring源码扩展点与实战(二)》,期待与您再会!

猜你喜欢

转载自blog.csdn.net/dwade_mia/article/details/79372768