說明:咱們知道Spring有一個<context:component-scan base-package="" />組件用於實現包搜索並加載bean到Spring容器中(參見:對受管組件的Classpath掃描)。可是這樣一來仍是要爲每一個bean對象標註相應的註解,如@Resource 和@Autowired等(參見:基於註解(Annotation-based)的配置)。html
如今的問題是,已經有了一整套的程序,使用Spring-XML的方式配置全部bean,因爲bean數量過多,致使配置文件的數量一樣不少(超過50個,並在持續增長中),因而想改用component-scan的方式,來自動註冊某個包下符合命名規則條件的全部bean,固然,重點是不想對原有代碼進行任何修改。不想使用註解去對每個bean進行標註,從而單純的組件掃描方式是不可行的。java
分析:因而想到了Struts2的Spring插件。spring
咱們知道這個插件有一個奇妙的能力,對於Struts2 Action中引用的bean,只須要有對應的setter方法便可實現對該bean對象的自動注入(若是你使用@Autowired,你甚至無需寫setter方法,只需一個私有變量便可),Spring容器透明的完成了這一點。固然,一切都是在下面一個攔截器中完成的。express
Struts2採用Spring生成對象時,默認的對象工廠變成了StrutsSpringObjectFactory,這是一個對SpringObjectFactory進行了簡單包裝的對象工廠,主要實現仍是基於SpringObjectFactory。對象的自動注入依靠的是ActionAutowiringInterceptor這個攔截器,Struts2-Spring-plugin配置文件中首次聲明並引用了該攔截器。ide
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; }
說了許多,尚未進入正題~post
上面說到了Struts2的處理方式,但實際上這裏用不上。在參考了「瞭解bean的一輩子」系列文章後明白了Spring初始、以及實例化bean的過程(流程圖以下),推薦查看原文。this
所以,基本得出了本文的一個解決方案。spa
實現:因爲項目中良好的命名習慣,全部服務接口的名稱均是以「Service」結尾,實現類則是「*ServiceImpl」,而全部對實現類的引用均是以服務名首字母小寫的非限定類名的形式,即AbcService - AbcServiceImpl - abcService的對應關係。因而要把全部實現類註冊爲bean,要作的就很明確了。.net
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