IoC使用进阶
本文讨论Spring IoC高级一点的用法,主要是一些深入定制化操作。
FactoryBean
我们之前的例子都是一个Bean对应一个对象,像下面这样
<beans> <bean id="wheel" class="cn.lu.spring.ioc.Wheel" /> </beans>
|
但是像Connection这样的对象,我们肯定希望是连接池模式,可是默认scope是singleton 只能使用一个连接,如果修改scop为 prototype 每次都创建一个新的连接,两者不能满足要求,那么如何创建对象池呢?
这个时候可以使用FactoryBean,通过FactoryBean的getOjbect()方法返回对象,进行对象池管理。
package org.springframework.beans.factory; public interface FactoryBean<T> { T getObject() throws Exception; Class<?> getObjectType(); boolean isSingleton(); }
|
定义自己的Factory类
public class WheelFactory implements FactoryBean<Wheel> { @Override public Wheel getObject() throws Exception { } }
|
然后修改xml配置
<beans> <bean id="wheel" class="cn.lu.spring.ioc.WheelFactory" /> </beans>
|
因为WheelFactory实现了FactoryBean接口,所以IoC容器不会返回WheelFactory类的实例对象,而是调用其getObject()方法返回真正的对象。
上面是通过XML配置FactoryBean,如何通过Java配置呢?
@Configuration public class Config { @Bean public WheelFactory wheelFactory() { return new WheelFactory(); } }
|
定义@Bean时返回WheelFactory,使用是正常@Autowired即可,实际返回Wheel对象。
@Autowired private Wheel wheel;
|
ApplicationContextAware
通常都是Spring自动帮我注入Bean对象,但有时我们也需要主动从IoC容器获取Bean对象,例如:查找声明了某个注解的Bean对象,这个时候就需要用到ApplicationContextAware了。实现ApplicationContextAware接口的setApplicationContext()方法就可以获得Spring的IoC容器(对应ApplicationContext类),我们看一下ApplicationContext类都提供了哪些方法:
package org.springframework.context;
public interface ApplicationContext extends ListableBeanFactory { String getId(); String getApplicationName(); }
|
ApplicationContext类没啥干货,再看看基类ListableBeanFactory提供了哪些方法
package org.springframework.beans.factory; public interface ListableBeanFactory extends BeanFactory { boolean containsBeanDefinition(String beanName); int getBeanDefinitionCount(); String[] getBeanDefinitionNames(); <T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException; Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) throws BeansException; }
|
最常用的应该是getBeansOfType()和getBeansWithAnnotation(),根据类型或者注解获取Bean对象。
下面以解析自定义注解@RocketMQProduer为例说明ApplicationContextAware的用法:
- 得到applicationContext后可以调用getBeansWithAnnotation()方法获取所有声明了RocketMQProducer注解的Bean对象。
public class MQProducerAutoConfiguration implements ApplicationContextAware { protected ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } @PostConstruct public void init() throws Exception { Map<String, Object> beans = null; beans = applicationContext.getBeansWithAnnotation(RocketMQProducer.class); } }
|
BeanPostProcessor
所有Bean对象初始化前后都会回调BeanPostProcessor接口,实现BeanPostProcessor接口可以做很多定制化工作,例如:解析自定义注解。 注意:BeanPostProcessor和InitializingBean不同,InitializingBean只会在实现类Bean创建以后回调,BeanPostProcessor则是在所有Bean创建以后都会回调,所以实现BeanPostProcessor接口相当于可以遍历所有创建的Bean对象。
package org.springframework.beans.factory.config; public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; }
|
特别注意:BeanPostProcessor接口的两个方法不能返回null,否则后面就走不下去了,千万注意。
执行顺序
Constructor @PostConstruct afterPropertiesSet postProcessBeforeInitialization postProcessAfterInitialization
|
下面仍然以解析自定义注解@RocketMQProduer为例说明BeanPostProcessor的用法:
@Configuration public class MyBeanPostProcessor implements BeanPostProcessor{ @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Class clazz = bean.getClass(); if(!clazz.isAnnotationPresent(RocketMQProducer.class)) { return bean; } RocketMQProducer annotation = (RocketMQProducer)clazz.getAnnotation(RocketMQProducer.class); System.out.println(annotationTable.value()); return bean; } }
|
BeanFactoryPostProcessor
BeanFactoryPostProcessor允许容器实例化Bean之前读取Bean的定义(配置元数据),并可以修改它。典型的,<context:property-placeholder> 标签就是使用BeanFactoryPostProcessor的例子,读取Bean对象value中的${db.username} ,替换为从properties文件中读取到的真实值。<context:property-placeholder> 标签使用PropertyResourceConfigurer类实现。
package org.springframework.beans.factory.config; public abstract class PropertyResourceConfigurer implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { try { Properties mergedProps = mergeProperties(); convertProperties(mergedProps); processProperties(beanFactory, mergedProps); } catch (IOException ex) { throw new BeanInitializationException("Could not load properties", ex); } } }
|
post()方法最重要是传入了ConfigurableListableBeanFactory参数,通过它可以读取到Bean的定义
package org.springframework.beans.factory.config; public interface ConfigurableListableBeanFactory extends ListableBeanFactory, AutowireCapableBeanFactory, ConfigurableBeanFactory { BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException; }
|
BeanDefinitionRegistryPostProcessor
这个接口给了我们在Spring容器构造完所有Bean对象后对其进行修改的可能,Mybatis的动态代理就是基于BeanDefinitionRegistryPostProcessor实现的。
下面通过一个例子说明它的用法。
我们定义@Car注解
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Car { String value() default ""; }
|
我们定义接口ICar和它的实现类Benz、Audi
@Car("Benz") public interface ICar { String getBrand(); } public class Benz implements ICar { protected String brand = "Benz"; @Override public String getBrand() { return brand; } } public class Audi implements ICar { protected String brand = "Audi"; @Override public String getBrand() { return brand; } }
|
我们希望注入ICar时,通过@Car注解的value属性来确定创建那个实体类对象
由于@Car("Benz") ,所以这里car变量注入Benz类实例对象。
我们先来分析一下如何实现,然后再来看代码:
- 我们在Spring容器完成Bean创建后,扫描@Car注解,手动将正确的Bean对象加入到容器中;
- 加入到容器中的Bean对象的类型是ICar,实体类对象是Benz或者Audi;
- 这里我们用FactoryBean来返回ICar对象。
先看如何扫描注解
public class MyBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor { private static final String RESOURCE_PATTERN = "/**/*.class"; private ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); CarFactoryBean carFactoryBean = new CarFactoryBean(); @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { String basePackageName = "cn.lu.spring.ioc.post.registry"; String basePackage = ClassUtils.convertClassNameToResourcePath(basePackageName); try { String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + basePackage + RESOURCE_PATTERN; Resource[] resources = resolver.getResources(pattern); MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resolver); for (Resource resource : resources) { if (resource.isReadable()) { MetadataReader reader = readerFactory.getMetadataReader(resource); for (String annotationType : reader.getAnnotationMetadata().getAnnotationTypes()) { if (!annotationType.equalsIgnoreCase(Car.class.getName())) { continue; } ScannedGenericBeanDefinition definition = new ScannedGenericBeanDefinition(reader); String shortClassName = ClassUtils.getShortName(definition.getBeanClassName()); String beanName = Introspector.decapitalize(shortClassName); definition.setBeanClass(carFactoryBean.getClass()); Map<String, Object> properties = reader.getAnnotationMetadata() .getAnnotationAttributes(annotationType); definition.getPropertyValues().add("brand", properties.get("value")); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); BeanDefinitionHolder holder = new BeanDefinitionHolder(definition,beanName); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); } } } } catch (IOException e) { e.printStackTrace(); } } }
|
几个要点:
- postProcessBeanDefinitionRegistry()是入口
- 使用PathMatchingResourcePatternResolver读取.class文件
- 使用MetadataReader解析.class文件
- 创建BeanDefinition就是手动定义一个Bean的信息
- setBeanClass()为FactoryBean把接口改为了实体类,如果不修改Spring会尝试创建ICar对象,会报错
- 最后registerBeanDefinition()把手工定义的Bean加入到容器中
FactoryBean就比较简单了
public class CarFactoryBean implements FactoryBean<Car> { protected String brand; @Override public Car getObject() throws Exception { if (brand.equalsIgnoreCase("Benz")) { return new Benz(); } else if (brand.equalsIgnoreCase("Audi")) { return new Audi(); } else { return new Car("Unknown"); } } }
|
PostProcessor对比
- InitializingBean,每个对象实例化完成后执行该对象的afterPropertiesSet()方法,每个对象执行自己的方法,互不影响,无参数传入;一般我们可以在这个时间点做初始化操作。
- BeanPostProcessor,所有对象初始化前后都会执行同一个postProcessAfterInitialization()方法,每个对象执行的都是同一个对象的方法,出入参数为Bean对象本身;一般我们可以在这个时间点对Bean的特殊属性做特殊处理,例如自定义注解。通过修改返回值,实际上可以返回另外一个Bean对象。
- BeanFactoryPostProcessor,每个对象实例化前执行,可以读取到bean的配置元数据,并进行修改;
- BeanDefinitionRegistryPostProcessor,在IoC容器完成Bean初始化之后执行,我们可以在此时加入其他Bean对象到IoC容器中。
参考
Spring探秘|妙用BeanPostProcessor