在讲解之前,这里要说一下InitializingBean和BeanPostProcessor的区别,必免混淆。两个接口都可以执行bean初始化前置处理的功能。区别在于InitializingBean是目标Bean主动实现的接口(常用业务逻辑可以写在里面),而BeanPostProcessor是由BeanFatotry在Bean初始化时把Bean作为参数传入BeanPostProcessor执行的(具体BeanPostProcessor是被存储在AbstractBeanFactory中的一个List中)。暂且说成被动执行。
BeanPostProcessor的实现类主要是Spring自带的一些辅助类,比如注解扫描类(CommonAnnotationBeanPostProcessor,xml中配置为<context:component-scan> ),配置以后会自动扫描标记了@Controller,@Service等注解的类,提供给其它类进行注入。
下面介绍另一个配合hibernate使用BeanPostProcessor的另一个场景。我们有这样一种场景,如何让容器在启动的时候加载各模块之间配置的mapping文件呢。
首先看一下常规的做法,在sessionFactory中显示的配置。
<bean id="routeSessionFactory" name="routeSessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource" ref="routeDataSourceLocal" /> <property name="mappingResources"> <list> <value>com/sf/module/routeshare/META-INF/config/mapping.xml</value> .....//添加其它mapping.xml文件 </list> </property>
此种做法不好的地方在于多个模块同时开发时,需要修改同一个文件,不利于管理。
其实BeanPostProcessor提供了一种便捷的方式,在容器启动时,先收集好mapping.xml文件的位置,在实例化LocalSessionFactoryBean时把mapping数组注入到mappingResources属性中。
具体步骤如下:
1.web.xml中配置容器启动后的监听器。
<listener> <listener-class>com.xxx.framework.server.core.deploy.FrameworkListener</listener-class> </listener>
2.加载所有mapping文件的路径。
//始始化容器时读取mapping文件路径。 private Application initApplication() { //加载指定路径下各模块的mapping.xml文件。 List mappingConfigs = initConfigs("/META-INF/config/mapping.xml", moduleNames); ...} //使用spring工具类进行解析 private List initConfigs(String configName, List configs) { try { Resource resources[] = new PathMatchingResourcePatternResolver().getResources((new StringBuilder("classpath*:com/xxx/**")).append(configName).toString()); for(int i = 0; i < resources.length; i++) { Resource resource = resources[i]; try { String file = resource.getURL().getPath(); String config = file.substring(file.lastIndexOf("com/sf/")); if(!configs.contains(config)) { configs.add(config); } ..... }
3.编写BeanPostProcessor。
public class MappingAutowiring implements BeanPostProcessor { public MappingAutowiring() { } public String[] getMappingResources() { return mappingResources; } //此方法通过MappingFactoryBean进行注入,里面会获取到之前Application的mapping的路径 public void setMappingResources(String mappingResources[]) { this.mappingResources = mappingResources; } public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if(bean instanceof LocalSessionFactoryBean) { log.debug((new StringBuilder("============ MappingAutowiring: ")).append(mappingResources).toString()); ((LocalSessionFactoryBean)bean).setMappingResources(mappingResources); } return bean; }
<!--配置MappingAutowiring --> <bean id="mappingAutowiring" class="com.xxx.framework.server.core.deploy.MappingAutowiring"> <property name="mappingResources" ref="mappingResources" /> </bean> <bean id="mappingResources" class="com.xxx.framework.server.core.deploy.MappingFactoryBean" />
public class MappingFactoryBean implements FactoryBean { public MappingFactoryBean(){} public Object getObject() throws Exception { List configs = ApplicationContext.getContext().getApplication().getMappingConfigs(); return ((Object) (configs.toArray(new String[configs.size()]))); } .... }
从上述代码看出,通过BeanPostProcessor的postProcessBeforeInitialization方法,可以给LocalSessionFactoryBean注入mapping文件的路径