Spring为定制其行为提供了哪些机会?

大家好。联系弗拉迪斯拉夫·罗丹(Vladislav Rodin)。我目前是OTUS高负载架构师课程的负责人,并且还教授软件架构课程。

除了教学之外,我还在Haber上为OTUS博客编写版权材料,我想与今天的文章相吻合,以开设“ Spring Framework开发人员 ”课程,该课程现已开放供招募。




介绍


从读者的角度来看,使用Spring的应用程序的代码看起来非常简单:声明了一些bean,用注释标记了类,然后在必要时注入了bean,一切正常。但是好奇的读者有一个问题:“它是如何工作的?发生了什么?”。在本文中,我们将尝试回答这个问题,但并不是为了满足闲置的好奇心。

Spring框架以其足够的灵活性而著称,并提供了用于自定义框架行为的选项。对于应用某些注释(例如,Transactional),Spring也有很多相当有趣的规则。为了理解这些规则的含义,能够导出它们,以及理解在Spring中配置什么以及如何配置它们,您需要了解Spring在幕后的原理。如您所知,对若干原理的了解不包括对许多事实的了解。如果您当然还不了解这些原理,我建议您在下面熟悉这些原理。

阅读配置


从一开始,您就需要解析应用程序中的配置。由于存在多种类型的配置(xml,groovy,java配置,基于注释的配置),因此使用不同的方法来读取它们。一种或另一种方式是,收集形式为Map <String,BeanDefinition>的映射,其中将Bean名称分配给其Bean定义。BeanDefinition类的对象是关于bean的元信息,并且包含bean的id-id,其名称,其类,destroy-和init-methods。

此过程涉及的类示例:GroovyBeanDefinitionReader,XmlBeanDefinitionReader,AnnotatedBeanDefinitionReader,实现了BeanDefinitionReader接口

设置Beandefinitions


因此,我们有关于bean的描述,但是没有bean本身,还没有创建它们。在创建bean之前,Spring可以自定义生成的BeanDefinition。为此,使用了BeanFactoryPostProcessor接口看起来像这样:

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

此接口的method参数允许使用其自己的getBeanDefinitionNames方法来获取名称,通过这些名称,您可以从地图中获取BeanDefinitions并进行编辑。

为什么可能需要这样做?假设某些bean需要详细信息才能连接到任何外部系统,例如数据库。我们希望已经使用细节创建了bean,但是细节本身存储在属性文件中。我们可以应用标准BeanFactoryPostProcessors-PropertySourcesPlaceholderConfigurer之一,它将使用存储在属性文件中的实际值替换BeanDefinition中的name属性。也就是说,它实际上将BeanDefinion中的Value(“用户”)替换为Value(“ root”)。为此,PropertySourcesPlaceholderConfigurer当然需要连接。但这不限于此,您可以注册BeanFactoryPostProcessor,在其中实现处理BeanDefinitions所需的任何逻辑。

创建豆


在这个阶段,我们有一个映射,通过键具有bean的名称,并通过值来配置BeanDefinitions。现在,您需要创建这些bean。这就是BeanFactory所做的。但是在这里,您可以通过编写和注册FactoryBean来添加自定义。 FactoryBean是以下形式的接口:

public interface FactoryBean {
    T getObject() throws Exception;
    Class<?> getObjectType();
    boolean isSingleton();
}

因此,如果没有与该bean类相对应的FactoryBean,则BeanFactory会自己创建一个bean,或者要求FactoryBean创建此bean。有一个细微的差别:如果bean的范围是单调的,则在此阶段创建bean,如果是原型,则每次需要此bean时,都会从BeanFactory中请求它。

结果,我们再次获得了一张地图,但是已经有些不同了:键包含了bean的名称以及bean本身的值。但这仅适用于单调。

配置Bean


现在到了最有趣的阶段。我们有一个包含创建的bean的映射,但是尚未配置这些bean。也就是说,我们没有处理设置bean状态的注释:自动装配,值。我们也没有处理改变bean行为的注释:Transactional,Async。BeanPostProcessors再次是相应接口的实现,使我们能够解决此问题

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

我们看到两种带有可怕但全面名称的方法。两种方法都将bean作为输入,您可以从中询问它是哪个类,然后使用Reflection API来处理注释。返回bean方法,可能被代理替换。

对于将每个bean放置在上下文中之前,将发生以下情况:为所有BeanPostProcessor触发postProcessBeforeInitialization方法,然后触发init方法,然后也为所有BeanPostProcessor触发postProcessAfterInitialization方法。

这两种方法具有不同的语义:postProcessBeforeInitialization处理状态注释,postProcessAfterInitialization处理行为,因为使用代理来处理行为,这可能导致注释丢失。这就是为什么行为最后改变的原因。

定制在哪里?我们可以为其编写注释BeanPostProcessor,然后Spring将对其进行处理。但是,要使BeanPostProcessor正常工作,还必须将其注册为Bean。

例如,要在字段中嵌入随机数,我们创建一个InjectRandomInt批注(挂在字段上),创建并注册一个InjectRandomIntBeanPostProcessor,在第一种方法中我们处理创建的批注,在第二种方法中,我们简单地返回传入的bean。

要对bean进行概要分析,请创建一个广播到方法的Profile批注,创建并注册一个ProfileBeanPostProcessor,在第一个方法中我们返回传入的bean,在第二个方法中,我们返回一个代理,该代理将对调用的原始包装并包含裁剪和执行时间日志记录。



了解有关该课程的更多信息



All Articles