说明:我们知道Spring有一个<context:component-scan base-package="" />组件用于实现包搜索并加载bean到Spring容器中(参见:对受管组件的Classpath扫描)。但是这样一来还是要为每个bean对象标注相应的注解,如@Resource 和@Autowired等(参见:基于注解(Annotation-based)的配置)。
现在的问题是,已经有了一整套的程序,使用Spring-XML的方式配置所有bean,由于bean数量过多,导致配置文件的数量同样很多(超过50个,并在持续增加中),于是想改用component-scan的方式,来自动注册某个包下符合命名规则条件的所有bean,当然,重点是不想对原有代码进行任何修改。不想使用注解去对每一个bean进行标注,从而单纯的组件扫描方式是不可行的。
分析:于是想到了Struts2的Spring插件。
我们知道这个插件有一个奇妙的能力,对于Struts2 Action中引用的bean,只需要有对应的setter方法即可实现对该bean对象的自动注入(如果你使用@Autowired,你甚至无需写setter方法,只需一个私有变量即可),Spring容器透明的完成了这一点。当然,一切都是在下面一个拦截器中完成的。
Struts2采用Spring生成对象时,默认的对象工厂变成了StrutsSpringObjectFactory,这是一个对SpringObjectFactory进行了简单包装的对象工厂,主要实现还是基于SpringObjectFactory。对象的自动注入依靠的是ActionAutowiringInterceptor这个拦截器,Struts2-Spring-plugin配置文件中首次声明并引用了该拦截器。
Object bean = invocation.getAction(); factory.autoWireBean(bean);拦截器的 before(ActionInvocation invocation)方法中有上面这两行代码,回调 SpringObjectFactory中的 autoWireBean(Object bean)方法,实现对bean对象的自动注入:(代码见StrutsSpringObjectFactory类)
public Object autoWireBean(Object bean, AutowireCapableBeanFactory autoWiringFactory) { if (autoWiringFactory != null) { autoWiringFactory.autowireBeanProperties(bean, autowireStrategy, false); } injectApplicationContext(bean); injectInternalBeans(bean); return bean; }
说了许多,还没有进入正题~
上面说到了Struts2的处理方式,但实际上这里用不上。在参考了“了解bean的一生”系列文章后明白了Spring初始、以及实例化bean的过程(流程图如下),推荐查看原文。
因此,基本得出了本文的一个解决方案。
实现:由于项目中良好的命名习惯,所有服务接口的名称均是以“Service”结尾,实现类则是“*ServiceImpl”,而所有对实现类的引用均是以服务名首字母小写的非限定类名的形式,即AbcService - AbcServiceImpl - abcService的对应关系。于是要把所有实现类注册为bean,要做的就很明确了。
1.首先需要一个BeanNameGenerator,并注册到组件扫描器中,以为bean类重命名。代码如下:
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; public class MyBeanNameGenerator implements BeanNameGenerator { @Override public String generateBeanName(BeanDefinition bd, BeanDefinitionRegistry bdr) { String classFullName = bd.getBeanClassName(); String beanName = classFullName.substring(classFullName.lastIndexOf(".") + 1); beanName = String.valueOf(beanName.charAt(0)).toLowerCase() + beanName.substring(1); int end = beanName.lastIndexOf("Impl"); if (end > 0) beanName = beanName.substring(0, end); return beanName; } }
2.按照上图的理解,你可以知道一个BeanPostProcessor的实现类在bean对象的实例化过程中有何作用。实际上就是一层接口,用于在Spring实例化bean的前后执行一些附加的自定义动作。简单到输出一行debug信息,复杂可以重定义整个bean,至于是前置方法还是后置方法区别不大。具体栗子如下:
public class MyBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware { private ApplicationContext ac; @Override public Object postProcessAfterInitialization(Object bean, String arg1) throws BeansException { if (null != bean && bean.getClass().getName().endsWith("Impl")) { try { BeanInfo bi = Introspector.getBeanInfo(bean.getClass()); for (PropertyDescriptor pd : bi.getPropertyDescriptors()) { String beanName = pd.getName(); Method m = pd.getWriteMethod(); if ((!"class".equals(beanName)) && this.ac.containsBean(beanName) && null != m && Modifier.isPublic(m.getModifiers()) && m.getParameterTypes().length == 1) { try { Object param = this.ac.getBean(beanName); if (m.getParameterTypes()[0].isInstance(param)) m.invoke(bean, param); } catch (Exception e) { e.printStackTrace(); } } } } catch (IntrospectionException e1) { e1.printStackTrace(); } ReflectionUtils.doWithFields(bean.getClass(), new FieldCallback() { @Override public void doWith(Field f) throws IllegalArgumentException, IllegalAccessException { if (!f.isAccessible()) f.setAccessible(true); Object param = MyBeanPostProcessor.this.ac.getBean(f.getName()); if (f.get(bean) == null && f.getType().isInstance(param)) f.set(bean, param); } }, new FieldFilter() { @Override public boolean matches(Field f) { // 只处理private(not static or final)参数不要求为interface if (Modifier.isPrivate(f.getModifiers()) && !Modifier.isFinal(f.getModifiers()) && !Modifier.isStatic(f.getModifiers()) && ac.containsBean(f.getName())) return true; return false; } }); } return bean; } @Override public Object postProcessBeforeInitialization(final Object arg0, String arg1) throws BeansException { return arg0; } @Override public void setApplicationContext(ApplicationContext ac) throws BeansException { this.ac = ac; } }
上面的代码实现了对bean中引用的其他bean对象的自动注入,根据项目中已经使用的规则,凡是有setter方法、且属性名可以在Spring容器中找到对应名字的bean的属性均会被自动注入(私有、非static及final的属性,属性类型可以是接口亦可以是普通类)。此外,更支持对私有字段(没有公共setter方法)的赋值注入(利用Java反射特性)。
3.最后,只需在Spring配置文件中加上简单的几行:
<bean class="test.MyBeanPostProcessor" /> <context:component-scan base-package="test.beans" name-generator="test.MyBeanNameGenerator" use-default-filters="false" annotation-config="false"> <context:include-filter type="regex" expression=".*Impl" /> </context:component-scan>
然后就可以把之前在配置文件中的绝大部分bean定义给删除了(除了部分需要特殊定义的、或是DataSource等bean对象)。上面的配置中,其中name-generator就是指定命名器的选项,另外,关于annotation-config这个参数,我们知道还有一个类似的配置项:<context:annotation-config />,这个参数则是指定可被扫描到的bean都可以使用annotation配置(即文首所说的autowire注解等),默认是为true,即默认启用。
4.what's more,设置annotation-config会同时引用进其他几个BeanPostProcessor,参见:Spring配置项<context:annotation-config/>解释说明。这就造成一个 BeanPostProcessor的执行顺序和默认覆盖问题。参考AbstractApplicationContext类的invokeBeanFactoryPostProcessors和registerBeanPostProcessors方法,其中有关于order顺序的特殊处理。因为系统定义的BeanPostProcessor实现类都有同时实现了order接口,因此会以一定的顺序执行(具体顺序以OrderComparator类的sort方法执行结果为准),对于没有实现order接口或自定义的BeanPostProcessor实现类,都将在最后执行,此外,也受到Spring配置文件中的配置顺序的影响。关于默认覆盖问题,则同样以配置文件为准。
至此,本文方休。
EOF ? 最后再总结一下本文讲了些什么,好吧,我不想把开头重复一遍,所以我讲一个小插曲:让我们回到MyBeanPostProcessor类中,有一行这样的代码使用JDK的标准bean定义解析器来解析bean对象的get/set方法,
BeanInfo bi = Introspector.getBeanInfo(bean.getClass()); for (PropertyDescriptor pd : bi.getPropertyDescriptors()) {...
然而属性包装器(PropertyDescriptor)似乎是有一个bug(找了很多bean定义都没有关于此的特殊说明)。譬如,对于首字母是小写而次字母是大写的属性(如aBc),则其setter方法是setABc,对这个setter方法进行反向解析时得到的属性名确是ABc,即对应关系成了aBc - setAbc - ABc
-_- ~很奇妙吧~这样注入对象时就会在Spring容器中找不到对应名字的bean。
Finaly.本文还参考了以下链接中的内容:
Spring BeanPostProcessor类 (在Spring实例化bean的前后执行一些附加操作),
Spring开闭原则的表现-BeanPostProcessor扩展点系列文章,
Spring英文档中关于BeanPostProcessor的部分:http://sina.lt/d5b