乐趣区

关于spring:BeanFactoryPostProcessor-和-BeanPostProcessor-有什么区别

钻研 Spring 源码的小伙伴可能会发现,Spring 源码中有很多名称特地相近的 Bean,我就不挨个举例了,明天我是想和小伙伴们聊一聊 Spring 中 BeanFactoryPostProcessor 和 BeanPostProcessor 两个处理器的区别。

我将从以下几个方面来和小伙伴们分享。

1. 区别

这两个接口说白了都是 Spring 在初始化 Bean 时对外裸露的扩大点,因为 Spring 框架提供的性能不肯定可能满足咱们所有的需要,有的时候咱们须要对其进行扩大,那么这两个接口就是用来做扩大性能的。

其实不必看源码,单纯从字面上看,大家应该也能了解个差不多:

  1. BeanFactoryPostProcessor 是针对 BeanFactory 的处理器。
  2. BeanPostProcessor 则是针对 Bean 的处理器。

咱们先来看下 BeanFactoryPostProcessor 接口:

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

能够看到,这里的参数实际上就是一个 BeanFactory,在这个中央,咱们能够对 BeanFactory 进行批改,从新进行定制。例如能够批改一个 Bean 的作用域,能够批改属性值等。

再来看看 BeanPostProcessor 接口:

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

能够看到,这里是两个办法,这两个办法都有一个 bean 对象,阐明这里被触发的时候,Spring 曾经将 Bean 初始化了,而后才会触发这里的两个办法,咱们能够在这里对曾经到手的 Bean 进行额定的解决。其中:

  • postProcessBeforeInitialization:这个办法在 InitializingBean#afterPropertiesSetinit-method 办法之前被触发。
  • postProcessAfterInitialization:这个办法在 InitializingBean#afterPropertiesSetinit-method 办法之后被触发。

总结一下:

在 Spring 中,BeanFactoryPostProcessor 和 BeanPostProcessor 是两个不同的接口,它们在 Bean 的生命周期中表演不同的角色。

  • BeanFactoryPostProcessor 接口用于在 Bean 工厂实例化 Bean 之前对 Bean 的定义进行批改。它能够读取和批改 Bean 的定义元数据,例如批改 Bean 的属性值、增加额定的配置信息等。BeanFactoryPostProcessor 在 Bean 实例化之前执行,用于对 Bean 的定义进行预处理。
  • BeanPostProcessor 接口用于在 Bean 实例化后对 Bean 进行加强或批改。它能够在 Bean 的初始化过程中对 Bean 进行后处理,例如对 Bean 进行代理、增加额定的性能等。BeanPostProcessor 在 Bean 实例化实现后执行,用于对 Bean 实例进行后处理。

一言以蔽之,BeanFactoryPostProcessor 次要用于批改 Bean 的定义,而 BeanPostProcessor 次要用于加强或批改 Bean 的实例。

2. 代码实际

2.1 BeanFactoryPostProcessor

BeanFactoryPostProcessor 在 Spring 容器中有一个十分典型的利用。

当咱们在 Spring 容器中配置数据源的时候,个别都是依照上面这样的形式进行配置的。

首先创立 db.properties,将数据源各种信息写入进去:

db.username=root
db.password=123
db.url=jdbc:mysql:///db01?serverTimezone=Asia/Shanghai

而后在 Spring 的配置文件中,首先把这个配置文件加载进来,而后就能够在 Spring Bean 中去应用对应的值了,如下:

<context:property-placeholder location="classpath:db.properties"/>
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
    <property name="username" value="${db.username}"/>
    <property name="password" value="${db.password}"/>
    <property name="url" value="${db.url}"/>
</bean>

然而大家晓得,对于 DruidDataSource 来说,毫无疑问,它要的是具体的 username、password 以及 url,而下面的配置很显著两头还有一个转换的过程,即把 ${db.username}${db.password} 以及 ${db.url} 转为具体对应的值。那么这个转换是怎么实现的呢?

这就得剖析 <context:property-placeholder location="classpath:db.properties"/> 配置了,小伙伴们晓得,这个配置实际上是一个简化的配置,点击去能够看到真正配置的 Bean 是 PropertySourcesPlaceholderConfigurer,而 PropertySourcesPlaceholderConfigurer 恰好就是 BeanFactoryPostProcessor 的子类,咱们来看下这里是如何重写 postProcessBeanFactory 办法的:

源码比拟长,松哥这里把一些要害局部列出来和小伙伴们展现:

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {if (this.propertySources == null) {// 这里次要是如果没有加载到 properties 文件,就会尝试从环境中加载}
    // 这个就是具体的属性转换的办法了
    processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
    this.appliedPropertySources = this.propertySources;
}
/**
 * 这个属性转换方法中,对配置文件又做了一些预处理,最初调用 doProcessProperties 办法解决属性
 */
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
        final ConfigurablePropertyResolver propertyResolver) throws BeansException {propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
    propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
    propertyResolver.setValueSeparator(this.valueSeparator);
    StringValueResolver valueResolver = strVal -> {
        String resolved = (this.ignoreUnresolvablePlaceholders ?
                propertyResolver.resolvePlaceholders(strVal) :
                propertyResolver.resolveRequiredPlaceholders(strVal));
        if (this.trimValues) {resolved = resolved.trim();
        }
        return (resolved.equals(this.nullValue) ? null : resolved);
    };
    doProcessProperties(beanFactoryToProcess, valueResolver);
}
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
        StringValueResolver valueResolver) {BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
    String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
    for (String curName : beanNames) {
        // Check that we're not parsing our own bean definition,
        // to avoid failing on unresolvable placeholders in properties file locations.
        if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
            try {visitor.visitBeanDefinition(bd);
            }
            catch (Exception ex) {throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
            }
        }
    }
    // New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
    beanFactoryToProcess.resolveAliases(valueResolver);
    // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
    beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}

下面第三个 doProcessProperties 办法我要略微和小伙伴们说两句:

应用 PropertySourcesPlaceholderConfigurer 对配置中的占位符进行解决,尽管咱们只是在 DruidDataSource 中用到了相干变量,然而零碎在解决的时候,除了以后这个配置类之外,其余的 Bean 都要解决(因为你能够在任意 Bean 中注入那三个变量)。

这就是 BeanFactoryPostProcessor 一个经典实际,即在 Bean 初始化之前,把 Bean 定义时候的一些占位符给改过来。

2.2 照猫画虎

下面的源码看完了,如果小伙伴们还感觉不过瘾,咱们本人也来写一个试试。

我本人的需要是这样,假如我配置 Bean 的时候,依照上面这种形式来配置:

<bean class="org.javaboy.bean.User">
    <property name="username" value="^username"/>
</bean>

这里的 ^username 是我自定义的一个非凡的占位符,这个占位符示意 javaboy,我心愿最终从 Spring 容器中拿到的 User Bean 的 username 属性值是 javaboy。

为了实现这个需要,我能够自定义一个 BeanFactoryPostProcessor,如下:

public class MyPropBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName);
            BeanDefinitionVisitor beanDefinitionVisitor = new BeanDefinitionVisitor(strVal -> {if ("^username".equals(strVal)) {return "javaboy666";}
                return strVal;
            });
            beanDefinitionVisitor.visitBeanDefinition(beanDefinition);
        }
    }
}

这个 Bean 基本上是照着后面 2.1 大节的 Bean 来写的。我跟大家来大抵说一下我的逻辑:

  1. 首先获取到所有的 Bean 定义对象,而后进行遍历。
  2. 遍历的时候创立一个 BeanDefinitionVisitor 对象,这个对象须要一个字符解析器,也就是 lambda 表达式那一段,我这里就是说,如果传进来的字符串是 ^username,那么我就返回 javaboy,如果传进来其余值,那我一成不变,不做批改。
  3. 最初调用 visitBeanDefinition 办法去从新设置一下 Bean 的定义。

下面这几行代码基本上就是照着 2.1 大节敲的,最初,咱们把 MyPropBeanFactoryPostProcessor 注册到 Spring 容器中就行了:

<bean class="org.javaboy.bean.User">
    <property name="username" value="^username"/>
</bean>
<bean class="org.javaboy.bean.MyPropBeanFactoryPostProcessor"/>

看下执行成果:

2.3 BeanPostProcessor

BeanPostProcessor 次要是对一个曾经初始化的 Bean 做一些额定的配置,这个接口中蕴含两个办法,执行工夫如下图:

我写一个简略例子咱们来验证下:

public class UserService implements InitializingBean {public UserService() {System.out.println("UserService>Constructor");
    }

    public void init() {System.out.println("UserService>init");
    }
    @Override
    public void afterPropertiesSet() throws Exception {System.out.println("UserService>afterPropertiesSet");
    }
}

再开发一个 BeanPostProcessor:

public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("postProcessBeforeInitialization:beanName"+beanName+";beanClass:"+bean.getClass());
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("postProcessAfterInitialization:beanName"+beanName+";beanClass:"+bean.getClass());
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

而后咱们将这两个 Bean 都注册到 Spring 容器中:

<bean class="org.javaboy.bean.MyBeanPostProcessor"/>
<bean class="org.javaboy.bean.UserService" id="us" init-method="init">
</bean>

最初启动容器,来看下控制台打印的内容:

能够看到,跟咱们所料想的是一样的。在 MyBeanPostProcessor 中,第一个参数其实就是曾经初始化的 Bean 了,如果想在这里针对 Bean 做任何批改都是能够的。

2.4 典型利用

BeanPostProcessor 其实有很多经典的利用,我在写文章的时候,想到一个中央,就是咱们在 SpringMVC 中做数据验证的时候,往往只须要加几个注解就能够了(对此不相熟的小伙伴能够在公众号后盾回复 ssm 有松哥录制的 ssm 入门视频),那么这个注解是在哪里进行的校验的呢?就是 BeanPostProcessor,咱们来看一眼源码:

public class BeanValidationPostProcessor implements BeanPostProcessor, InitializingBean {

    @Nullable
    private Validator validator;

    private boolean afterInitialization = false;

    public void setAfterInitialization(boolean afterInitialization) {this.afterInitialization = afterInitialization;}

    @Override
    public void afterPropertiesSet() {if (this.validator == null) {this.validator = Validation.buildDefaultValidatorFactory().getValidator();}
    }
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if (!this.afterInitialization) {doValidate(bean);
        }
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {if (this.afterInitialization) {doValidate(bean);
        }
        return bean;
    }
    /**
     * Perform validation of the given bean.
     * @param bean the bean instance to validate
     * @see jakarta.validation.Validator#validate
     */
    protected void doValidate(Object bean) {//...}

}

这段代码其实很好懂,能够看到,咱们能够管制是在具体是在 postProcessBeforeInitialization 还是 postProcessAfterInitialization 办法中进行数据校验。

好了,当初小伙伴们应该搞懂 BeanFactoryPostProcessor 和 BeanPostProcessor 的区别了吧?

3. 小结

我再把前文的小结内容拿过去,小伙伴们当初看是不是更清晰了?

在 Spring 中,BeanFactoryPostProcessor 和 BeanPostProcessor 是两个不同的接口,它们在 Bean 的生命周期中表演不同的角色。

  • BeanFactoryPostProcessor 接口用于在 Bean 工厂实例化 Bean 之前对 Bean 的定义进行批改。它能够读取和批改 Bean 的定义元数据,例如批改 Bean 的属性值、增加额定的配置信息等。BeanFactoryPostProcessor 在 Bean 实例化之前执行,用于对 Bean 的定义进行预处理。
  • BeanPostProcessor 接口用于在 Bean 实例化后对 Bean 进行加强或批改。它能够在 Bean 的初始化过程中对 Bean 进行后处理,例如对 Bean 进行代理、增加额定的性能等。BeanPostProcessor 在 Bean 实例化实现后执行,用于对 Bean 实例进行后处理。

一言以蔽之,BeanFactoryPostProcessor 次要用于批改 Bean 的定义,而 BeanPostProcessor 次要用于加强或批改 Bean 的实例。

退出移动版