"扩展Spring的依赖注入行为"两例
public enum FixtureEnum {FIXTURE_ONE, FIXTURE_TWO;}public class Sample{private FixtureEnum fOne;...}<bean id="target" value="FIXTURE_ONE"/></bean>?
我们也可以注入以String或者复杂对象类型作为key的Map:
<bean id="target" value="anything"/><entry key="stringvalue" value-ref="..."/></map></property></bean><bean id="complexObject" name="code">public class InjectionTarget {@EnumKeyType(FixtureEnum.class)private Map<FixtureEnum,String> mapping;... // setters or getters and other things}?
如果单单简单的定义依赖注入关系如下:
<bean id="target" value="FIXTURE TWO"/></map></property></bean>?
恐怕最终得到的不是一个Map<FixtureEnum,String>类型的Map,而是一个Map<String,String>类型的Map, 小沈阳语:为什么那?
Java5的Generic是Erase-Based, 这就意味着,运行期间无法获得Map的Key相关的Generic类型信息, 那么, Spring在做注入的时候,也就没法知道应该将String形式表达的依赖对象转换成什么类型,只好保持原样啦, 所以,以通常形式表达的map注入,最终得到的就成了一个Map<String,..>类型的Map,而不是Map<Enum,..>类型的Map.那谁可能说了,那怎么其它复杂对象作为Key怎么没问题那?原因很简单嘛, 你直接指定了对象的引用嘛,不服,你把对象类型直接写上试试?
那怎么解决这个问题那? 显然我们无法在运行期间通过反射之类的途径来获得Map的Key类型了,那么,我们就明确指定呗,如何明确指定那?可以考虑几种方式...
public class EnumKeyMapFactoryBean extends MapFactoryBean {private Class<? extends Enum<?>> enumType;private EnumKeyConversionSupport conversionSupport = new EnumKeyConversionSupport();@Overrideprotected Object createInstance() {return conversionSupport.convert(super.createInstance(), enumType);}public void setEnumType(Class<? extends Enum<?>> enumType) {this.enumType = enumType;}public Class<? extends Enum<?>> getEnumType() {return enumType;}}?super.createInstance()返回的是最初的Map实例,我们通过EnumKeyConversionSupport这个类和明确指定的Enum类型进行一下转换,就可以获得最终想要的Map实例了. EnumKeyMapFactoryBean的适用看起来如下:
<bean id="ekMap" value="cn.spring21.sandbox.springext.FixtureEnum"/><property name="sourceMap"><map><entry key="FIXTURE_ONE" value="anything"/><entry key="FIXTURE_TWO" value="anything"/></map></property></bean>?
ekMap现在的Key就是我们最终想要的FixtureEnum类型.
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)public @interface EnumKeyType {Class<?> value();}?然后在遇到适用Enum作为Key的Map的情况下,就可以通过这一Annotation对这样的Map进行标注:
public class InjectionTarget {@EnumKeyType(FixtureEnum.class)private Map<FixtureEnum,String> mapping;...}?
这样,虽然我们无法在运行期间获得Map的Key的Generic类型信息,但可以通过Annotation来获得,不过,光标注一下,Spring可不会聪明到马上知道你标注这么个Annotation要干嘛,我们得写点儿东西让Spring知道遇到这个Annotation该干点儿什么事情, 所以,可以定义一个BeanPostProcessor来做这个事情,例如:
public class EnumKeyMapBeanPostProcessor implements BeanPostProcessor {protected static final transient Logger logger = LoggerFactory.getLogger(EnumKeyMapBeanPostProcessor.class);private EnumKeyConversionSupport conversionSupport = new EnumKeyConversionSupport();@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName)throws BeansException {Field[] fields = bean.getClass().getDeclaredFields();for(Field field:fields){if(field.isAnnotationPresent(EnumKeyType.class)){try {convertKeyType(field,bean);} catch (Exception e) {logger.warn("failed to do map key convert.\n{}",e);}}}return bean;}protected void convertKeyType(Field field,Object bean) throws Exception {EnumKeyType eType= field.getAnnotation(EnumKeyType.class);Class<?> clazz = eType.value();field.setAccessible(true);Object map = field.get(bean);if(Map.class.isAssignableFrom(map.getClass()) && clazz != null){Map<Object, Object> result = conversionSupport.convert(map, clazz);field.set(bean, result);}}@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName)throws BeansException {return bean;}}?
只要将这个EnumKeyMapBeanPostProcessor注册到Spring的ApplicationContext,那么,之后要注入以Enum作为Key的Map的时候,只要简单的使用EnumKeyType标注一下这些Map就可以了,一劳多得,如果应用中有多处需要这样的Map注入,使用这种方式显然要比适用自定义的FactoryBean要省事不少.
public class InjectionTarget {private List<T> collection;...}?<bean id="it" name="code">public class CollectionInjectionFactoryBean implements FactoryBean,ApplicationContextAware {private ApplicationContext applicationContext;private Class<?> componentType;@Overridepublic Object getObject() throws Exception {@SuppressWarnings("unchecked")Map<String,Object> map = this.applicationContext.getBeansOfType(getComponentType());if(map == null || map.isEmpty()){return Collections.EMPTY_LIST;}return map.values();}@SuppressWarnings("unchecked")@Overridepublic Class getObjectType() {return Collection.class;}@Overridepublic boolean isSingleton() {return false;}@Overridepublic void setApplicationContext(ApplicationContext arg0)throws BeansException {this.applicationContext = arg0;}public void setComponentType(Class<?> componentType) {this.componentType = componentType;}public Class<?> getComponentType() {return componentType;}}?
有了它之后,如果你想为某个对象注入一族A类型的依赖对象,那么就定义一个CollectionInjectionFactoryBean,并指定它的componentType为A;如果想注入一族B类型的依赖对象,就指定它的componentType为B,依此类推.例如:
<bean id="target" ..><property name=".." ref="collectionInjectionFB"/></bean><bean id="collectionInjectionFB" value="...AType"/></bean>?
如果应用里这种场景不多,那使用这种自定义FactoryBean的方式还可以将就一下,但多的话,那也依然减少不了多少配置,这个时候,可以考虑下面这种方式.
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)public @interface InjectCollectionOf {Class<?> value();}?有了该Annotation之后,我们就可以在对象中需要注入一组依赖对象的Property处标注该Annotation:
public class InjectionTarget {@InjectCollectionOf(SomeType.class)private Collection<SomeType> collection;...}?
为了让容器按照我们的旨意行事,我们最后需要提供一个自定义的BeanPostProcessor实现,如下所示:
public class CollectionInjectionBeanPostProcessor implements BeanPostProcessor,ApplicationContextAware {private static final Logger logger = LoggerFactory.getLogger(CollectionInjectionBeanPostProcessor.class);private ApplicationContext applicationContext;@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName)throws BeansException {Field[] fields = bean.getClass().getDeclaredFields();for(Field field:fields){if(field.isAnnotationPresent(InjectCollectionOf.class)){Class<?> componentType = field.getAnnotation(InjectCollectionOf.class).value();if(componentType == null){continue;}@SuppressWarnings("unchecked")Map<String,Object> componentCandidates = this.applicationContext.getBeansOfType(componentType);if(componentCandidates != null && !componentCandidates.isEmpty()){field.setAccessible(true);try {field.set(bean,componentCandidates.values());} catch (IllegalArgumentException e) {logger.warn("argument is not a collection.\n{}",ExceptionUtils.getFullStackTrace(e));} catch (IllegalAccessException e) {logger.warn(ExceptionUtils.getFullStackTrace(e));}}}}return bean;}@Overridepublic Object postProcessBeforeInitialization(Object arg0, String arg1)throws BeansException {return arg0;}@Overridepublic void setApplicationContext(ApplicationContext arg0)throws BeansException {this.applicationContext = arg0;}}?
实现原理上跟自定义的FactoryBean差不多,无非就是多了Annotation检测相关逻辑,最后,只要将这个自定义的BeanPostProcessor注册到容器,所有标注了@InjectCollectionOf的Property就可以被正确的注入了:
<bean id="target" class="title">3.?结束语无论是设计还是实现,都是在各种因素之间进行权衡, 没有普遍适用的设计方案,也没有普遍适用的实现方案, 因时因地而权衡吧! 经济学第一原则不是“People face tradeoffs”嘛, 其实哪里都一样.