spring擴展點之BeanFactoryPostProcessor和BeanPostProcessor

 

先來看下這兩個接口的定義:

/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.beans.factory.config;

import org.springframework.beans.BeansException;
import org.springframework.lang.Nullable;

/**
 * Factory hook that allows for custom modification of new bean instances,
 * e.g. checking for marker interfaces or wrapping them with proxies.
 *
 * <p>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.
 *
 * <p>Typically, post-processors that populate beans via marker interfaces
 * or the like will implement {@link #postProcessBeforeInitialization},
 * while post-processors that wrap beans with proxies will normally
 * implement {@link #postProcessAfterInitialization}.
 *
 * @author Juergen Hoeller
 * @since 10.10.2003
 * @see InstantiationAwareBeanPostProcessor
 * @see DestructionAwareBeanPostProcessor
 * @see ConfigurableBeanFactory#addBeanPostProcessor
 * @see BeanFactoryPostProcessor
 */
public interface BeanPostProcessor {

	/**
	 * Apply this BeanPostProcessor to the given new bean instance <i>before</i> any bean
	 * initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
	 * or a custom init-method). The bean will already be populated with property values.
	 * The returned bean instance may be a wrapper around the original.
	 * <p>The default implementation returns the given {@code bean} as-is.
	 * @param bean the new bean instance
	 * @param beanName the name of the bean
	 * @return the bean instance to use, either the original or a wrapped one;
	 * if {@code null}, no subsequent BeanPostProcessors will be invoked
	 * @throws org.springframework.beans.BeansException in case of errors
	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
	 */
	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	/**
	 * Apply this BeanPostProcessor to the given new bean instance <i>after</i> any bean
	 * initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
	 * or a custom init-method). The bean will already be populated with property values.
	 * The returned bean instance may be a wrapper around the original.
	 * <p>In case of a FactoryBean, this callback will be invoked for both the FactoryBean
	 * instance and the objects created by the FactoryBean (as of Spring 2.0). The
	 * post-processor can decide whether to apply to either the FactoryBean or created
	 * objects or both through corresponding {@code bean instanceof FactoryBean} checks.
	 * <p>This callback will also be invoked after a short-circuiting triggered by a
	 * {@link InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation} method,
	 * in contrast to all other BeanPostProcessor callbacks.
	 * <p>The default implementation returns the given {@code bean} as-is.
	 * @param bean the new bean instance
	 * @param beanName the name of the bean
	 * @return the bean instance to use, either the original or a wrapped one;
	 * if {@code null}, no subsequent BeanPostProcessors will be invoked
	 * @throws org.springframework.beans.BeansException in case of errors
	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
	 * @see org.springframework.beans.factory.FactoryBean
	 */
	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

}
/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.beans.factory.config;

import org.springframework.beans.BeansException;

/**
 * Allows for custom modification of an application context's bean definitions,
 * adapting the bean property values of the context's underlying bean factory.
 *
 * <p>Application contexts can auto-detect BeanFactoryPostProcessor beans in
 * their bean definitions and apply them before any other beans get created.
 *
 * <p>Useful for custom config files targeted at system administrators that
 * override bean properties configured in the application context.
 *
 * <p>See PropertyResourceConfigurer and its concrete implementations
 * for out-of-the-box solutions that address such configuration needs.
 *
 * <p>A BeanFactoryPostProcessor may interact with and modify bean
 * definitions, but never bean instances. Doing so may cause premature bean
 * instantiation, violating the container and causing unintended side-effects.
 * If bean instance interaction is required, consider implementing
 * {@link BeanPostProcessor} instead.
 *
 * @author Juergen Hoeller
 * @since 06.07.2003
 * @see BeanPostProcessor
 * @see PropertyResourceConfigurer
 */
@FunctionalInterface
public interface BeanFactoryPostProcessor {

	/**
	 * Modify the application context's internal bean factory after its standard
	 * initialization. All bean definitions will have been loaded, but no beans
	 * will have been instantiated yet. This allows for overriding or adding
	 * properties even to eager-initializing beans.
	 * @param beanFactory the bean factory used by the application context
	 * @throws org.springframework.beans.BeansException in case of errors
	 */
	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

一、BeanFactoryPostProcessor和BeanPostProcessor的區別

BeanFactoryPostProcessor和BeanPostProcessor都是spring初始化bean的擴展點。兩個接口非常相似。

BeanFactoryPostProcessor可以對bean的定義(配置元數據)進行處理。也就是說,Spring IoC容器允許BeanFactoryPostProcessor在容器實際實例化任何其它的bean之前讀取配置元數據,並有可能修改它。如果你願意,你可以配置多個BeanFactoryPostProcessor。你還能通過設置'order'屬性來控制BeanFactoryPostProcessor的執行次序。


註冊BeanFactoryPostProcessor的實例,需要重載void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

通過beanFactory可以獲取bean的示例或定義等。同時可以修改bean的屬性,這是和BeanPostProcessor最大的區別。

例如:

BeanDefinition bd = beanFactory.getBeanDefinition("xxBean");  
MutablePropertyValues mpv =  bd.getPropertyValues();  
if(pv.contains("xxName")) {  
    pv.addPropertyValue("xxName", "icoder");  
}

BeanPostProcessor的實例,需要重載下面兩個方法

	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

二、BeanPostProcessors

1、bean生成過程

首先回顧下bean的生命週期如下圖:

如果這個接口的某個實現類被註冊到某個容器,那麼該容器的每個受管Bean在調用初始化方法的前後,都會獲得該接口實現類的一個回調。容器調用接口定義的方法時會將該受管Bean的實例和名字通過參數傳入方法,進過處理後通過方法的返回值返回給容器。

要使用BeanPostProcessor回調,就必須先在容器中註冊實現該接口的類,那麼如何註冊呢?BeanFactory和ApplicationContext容器的註冊方式不大一樣:

  • 若使用BeanFactory,則必須要顯示的調用其addBeanPostProcessor()方法進行註冊,參數爲BeanPostProcessor實現類的實例;
  • 如果是使用ApplicationContext,那麼容器會在配置文件在中自動尋找實現了BeanPostProcessor接口的Bean,然後自動註冊,我們要做的只是配置一個BeanPostProcessor實現類的Bean就可以了。

假如我們使用了多個的BeanPostProcessor的實現類,那麼如何確定處理順序呢?其實只要實現Ordered接口,設置order屬性就可以很輕鬆的確定不同實現類的處理順序了。

3、示例

3.1 applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>  
  
<beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xmlns:aop="http://www.springframework.org/schema/aop"  
       xmlns:context="http://www.springframework.org/schema/context"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans  
       http://www.springframework.org/schema/beans/spring-beans-3.1.xsd  
       http://www.springframework.org/schema/aop  
       http://www.springframework.org/schema/aop/spring-aop-2.5.xsd  
       http://www.springframework.org/schema/context  
       http://www.springframework.org/schema/context/spring-context-3.0.xsd">  
  
  
    <!-- 聲明註解方式加載bean-->  
    <context:annotation-config/>  
  
    <!-- 要加載的bean的包路徑-->  
    <context:component-scan base-package="com.meituan.hyt.test1"/>  
  
    <bean id="userPostProcessor" class="com.meituan.hyt.test1.UserPostProcessor"/>  
</beans>

3.2 自己的業務bean

package com.meituan.hyt.test1;  
  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.stereotype.Component;  
  
@Component  
public class User {  
    @Value("老名字")  
    private String name;  
    @Value("50")  
    private Integer id;  
  
    public Integer getId() {  
        return id;  
    }  
  
    public void setId(Integer id) {  
        this.id = id;  
    }  
  
    public String getName() {  
        return name;  
    }  
  
    public void setName(String name) {  
        this.name = name;  
    }  
  
    @Override  
    public String toString() {  
        return "User{" +  
                "name='" + name + '\'' +  
                ", id=" + id +  
                '}';  
    }  
}

 

 

3.3 postProcessor bean

package com.meituan.hyt.test1;  
  
import org.springframework.beans.BeansException;  
import org.springframework.beans.factory.config.BeanPostProcessor;  
  
public class UserPostProcessor implements BeanPostProcessor {  
    @Override  
    public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {  
        if(o instanceof User){  
            User user = (User)o;  
            user.setName("新名字");  
            user.setId(100);  
            return user;  
        }  
        return o;  
    }  
  
    @Override  
    public Object postProcessAfterInitialization(Object o, String s) throws BeansException {  
        return o;  
    }  
}
package com.meituan.hyt.test1;  
  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
  
public class Main2 {  
    public static void main(String[] args) {  
        ApplicationContext cxt = new ClassPathXmlApplicationContext("applicationContext.xml");  
        User user = (User) cxt.getBean("user");  
        System.out.println(user.toString());  
    }  
}

3.5 執行結果

如果沒有<bean id="userPostProcessor" class="com.meituan.hyt.test1.UserPostProcessor"/>
User{name='老名字', id=50}

添加<bean id="userPostProcessor" class="com.meituan.hyt.test1.UserPostProcessor"/>
User{name='新名字', id=100}

4、InstantiationAwareBeanPostProcessor是BeanPostProcessor的子接口

InstantiationAwareBeanPostProcessor是BeanPostProcessor的子接口,可以在Bean生命週期的另外兩個時期提供擴展的回調接口,即實例化Bean之前(調用postProcessBeforeInstantiation方法)和實例化Bean之後(調用postProcessAfterInstantiation方法)。 其使用方法與上面介紹的BeanPostProcessor接口類似,只時回調時機不同。該接口是在bean被實例化之後,相關屬性被設置之前之後回調

三、與BeanFactoryPostProcessor接口的區別

1、實現BeanFactoryPostProcessor 接口,會被Application contexts自動發現 
2、BeanFactoryPostProcessor 僅僅對 bean definitions 發生關係,不能對bean instances 交互,對bean instances 的交互,由BeanPostProcessor的實現來處理 
3、PropertyResourceConfigurer 是一個典型的實現
 (PropertyResourceConfigurer是BeanFactoryPostProcessor的一個實現)

  BeanFactoryPostProcessor接口實現類可以在當前BeanFactory初始化後,bean實例化之前對BeanFactory做一些處理。BeanFactoryPostProcessor是針對於bean容器的,在調用它時,BeanFactory只加載了bean的定義,還沒有對它們進行實例化,所以我們可以通過對BeanFactory的處理來達到影響之後實例化bean的效果。跟BeanPostProcessor一樣,ApplicationContext也能自動檢測和調用容器中的BeanFactoryPostProcessor。

示例1:

package com.meituan.hyt.test1;  
  
import org.springframework.beans.BeansException;  
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;  
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;  
  
public class UserBeanFactoryPostProcessor implements BeanFactoryPostProcessor {  
    @Override  
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {  
        System.out.println("BeanFactoryPostProcessor doing");  
    }  
}

applicationContext.xml中添加bean配置

<bean id="userBeanFactoryPostProcessor" class="com.meituan.hyt.test1.UserBeanFactoryPostProcessor"/> 

重新運行,結果

BeanFactoryPostProcessor doing
User{name='新名字', id=100}

示例2:

有這樣的也個業務場景: 

<bean id="user" class="com.gym.UserServiceImpl" >  
      <property name="username" value="${username_}"/>  
      <property name="password" value="${password_}"/>  
</bean> 

spring支持系統對username_進行佔位符的配置爲properties文件配置,試想如果我們有個配置中心,我們希望spring啓動的時候,從遠程配置中心取數據,而非本地文件,這裏就需要我們自定義一個實現BeanFactoryPostProcessor的PropertyResourceConfigurer 實例。 
看下面的例子: 

bean.xml配置:

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans   
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
    http://www.springframework.org/schema/context   
    http://www.springframework.org/schema/context/spring-context-3.0.xsd"  
    default-autowire="byName">  
  
     <bean id="user" class="com.gym.UserServiceImpl" >  
       <property name="username" value="${username_}"/>  
       <property name="password" value="${password_}"/>  
     </bean>  
       
     <bean id="myFactoryPostProcessor" class="com.gym.MyFilePlaceHolderBeanFactoryPostProcessor"/>  
</beans>

模擬從遠程取文件:

import org.springframework.beans.factory.InitializingBean;  
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;  
import org.springframework.core.io.support.PropertiesLoaderUtils;  
  
/** 
 * @author xinchun.wang 
 */  
public class MyFilePlaceHolderBeanFactoryPostProcessor   
    extends PropertyPlaceholderConfigurer implements InitializingBean{  
      
    public void afterPropertiesSet() throws Exception {  
        List<Properties> list = new ArrayList<Properties>();  
        Properties p = PropertiesLoaderUtils.loadAllProperties("config.properties");  
        list.add(p);  
        //這裏是關鍵,這就設置了我們遠程取得的List<Properties>列表  
        setPropertiesArray(list.toArray(new Properties[list.size()]));  
    }  
      
}

java bean配置

public class UserServiceImpl implements IUserService{  
    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);  
  
    public UserServiceImpl(){  
        logger.info("UserServiceImpl 構造函數 ");  
    }  
      
    private String username;  
    private String password;  
  
    public String getUsername() {  
        return username;  
    }  
  
    public String getPassword() {  
        return password;  
    }  
  
    public void setUsername(String username) {  
        logger.info("UserServiceImpl setUsername {}",username);  
        this.username = username;  
    }  
  
    public void setPassword(String password) {  
        logger.info("UserServiceImpl setPassword {}",password);  
        this.password = password;  
    }  
  
    @Override  
    public String toString() {  
        return "UserServiceImpl [username=" + username + ", password="  
                + password + "]";  
    }  
      
}

測試

public class TestApplicationContext {  
    public static void main(String[] args) {  
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(  
                "classpath:spring/applicationContext.xml");  
        IUserService userService = applicationContext.getBean(IUserService.class);  
        String password = userService.getPassword();  
        applicationContext.destroy();  
        System.out.println(password);  
    }  
  
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章