1.构造方法注入java
public FXNewsProvider(IFXNewsListener newsListner,IFXNewsPersister newsPersister) 10{ this.newsListener = newsListner; this.newPersistener = newsPersister; }
构造方法注入。 这种注入方式的优势就是,对象在构造完成以后,即已进入就绪状态,能够立刻使用。缺点就是,当依赖对象比较多的时候,构造方法的参数列表会比较长。而经过反射构造对象的时候,对相同类型的参数的处理会比较困难,维护和使用上也比较麻烦。并且在Java中,构造方法没法被继承,没法设置默认值。对于非必须的依赖处理,可能须要引入多个构造方法,而参数数量的变更可能形成维护上的不便。mysql
2.setter 方法注入git
public class FXNewsProvider { private IFXNewsListener newsListener; private IFXNewsPersister newPersistener; public IFXNewsListener getNewsListener() { return newsListener; } public void setNewsListener(IFXNewsListener newsListener) { this.newsListener = newsListener; } public IFXNewsPersister getNewPersistener() { return newPersistener; } public void setNewPersistener(IFXNewsPersister newPersistener) { this.newPersistener = newPersistener; } }
setter方法注入。由于方法能够命名, 因此setter方法注入在描述性上要比构造方法注入好一些。 另外, setter方法能够被继承,容许设置默认值,并且有良好的IDE支持。缺点固然就是对象没法在构造完成后立刻进入就绪状态。github
接口注入。从注入方式的使用上来讲,接口注入是如今不甚提倡的一种方式,基本处于“退役状态”。由于它强制被注入对象实现没必要要的接口,带有侵入性。而构造方法注入和setter方法注入则不须要如此。spring
业务对象的构建管理:
在IoC场景中,业务对象无需关心所依赖对象如何构建如何得到,但这部分工做始终须要有人来作。因此,IoC Service Provider须要将对象的构建逻辑从客户端那里剥离出来,以避免这部分逻辑污染业务对象的实现。
业务对象间的依赖绑定:
对于IoC Service Provider来讲,这个职责是最艰巨也是最重要的,这是它的最终使命之所在。若是不能完成这个职责,那么,不管业务对象如何的“呼喊”,也不会获得依赖对象的任何反应(最多见的却是会收到一个NullPointerException)。IoC Service Provider 经过结合以前构建和管理的全部业务对象,以及各个业务对象间能够识别依赖关系,将这些对象所依赖的对象注绑定,从而保证每一个业务对象在使用的时候,能够处于就绪状态。sql
如何记录对象之间的依赖关系:apache
IoContainer container = ...; container.register(FXNewsProvider.class,new FXNewsProvider()); container.register(IFXNewsListener.class,new DowJonesNewsListener()); ... container.bind(IFXNewsListenerCallable.class, container.get(IFXNewsListener.class)); ... FXNewsProvider newsProvider = (FXNewsProvider)container.get(FXNewsProvider.class); newProvider.getAndPersistNews();
经过bind方法将“被注入对象”(由IFXNewsListenerCallable接口添加标志)所依赖的对象,绑定为容器中注册过的IFXNewsListener类型的对象实例。容器在返回FXNewsProvider对象实例以前,会根据这个绑定信息,将IFXNewsListener注册到容器中的对象实例注入到“被注入对象”——FXNewsProvider中,并最终返回已经组装完毕的FXNewsProvider对象。数组
<bean id="newsProvider" class="..FXNewsProvider"> <property name="newsListener"> <ref bean="djNewsListener"/> </property> <property name="newPersistener"> <ref bean="djNewsPersister"/> </property> </bean> <bean id="djNewsListener" class="..impl.DowJonesNewsListener"> </bean> <bean id="djNewsPersister" class="..impl.DowJonesNewsPersister"> </bean>
3.元数据方式(使用Guice)安全
public class FXNewsProvider { private IFXNewsListener newsListener; private IFXNewsPersister newPersistener; @Inject public FXNewsProvider(IFXNewsListener listener,IFXNewsPersister persister) { this.newsListener = listener; this.newPersistener = persister; } ... }
经过@Inject,咱们指明须要IoC Service Provider经过构造方法注入方式,为FXNewsProvider注入其所依赖的对象。至于余下的依赖相关信息,在Guice中是由相应的Module来提供的,代码清单3-7给出了FXNewsProvider所使用的Module实现。服务器
public class NewsBindingModule extends AbstractModule { @Override protected void configure() { bind(IFXNewsListener.class).to(DowJonesNewsListener.class).in(Scopes.SINGLETON); bind(IFXNewsPersister.class).to(DowJonesNewsPersister.class).in(Scopes.SINGLETON); } }
经过Module指定进一步的依赖注入相关信息以后,咱们就能够直接从Guice那里取得最终已经注入完毕,并直接可用的对象了。
Injector injector = Guice.createInjector(new NewsBindingModule()); FXNewsProvider newsProvider = injector.getInstance(FXNewsProvider.class); newsProvider.getAndPersistNews();
Spring的IoC容器是一个IoC Service Provider,可是,这只是它被冠以IoC之名的部分缘由,咱们不能忽略的是“容器”。 Spring的IoC容器是一个提供IoC支持的轻量级容器,除了基本的IoC支持,它做为轻量级容器还提供了IoC以外的支持。如在Spring的IoC容器之上, Spring还提供了相应的AOP框架支持、企业级服务集成等服务。
BeanFactory
ApplicationContext
做为Spring提供的基本的IoC容器,BeanFactory能够完成做为IoC Service Provider的全部职责,包括业务对象的注册和对象间依赖关系的绑定。
public interface BeanFactory { String FACTORY_BEAN_PREFIX = "&"; Object getBean(String name) throws BeansException; <T> T getBean(String name, Class<T> requiredType) throws BeansException; Object getBean(String name, Object... args) throws BeansException; <T> T getBean(Class<T> requiredType) throws BeansException; <T> T getBean(Class<T> requiredType, Object... args) throws BeansException; <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType); <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType); boolean containsBean(String name); boolean isSingleton(String name) throws NoSuchBeanDefinitionException; boolean isPrototype(String name) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException; @Nullable Class<?> getType(String name) throws NoSuchBeanDefinitionException; @Nullable Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException; String[] getAliases(String name); }
// 1-设计FXNewsProvider类用于广泛的新闻处理 public class FXNewsProvider { ... } // 2-设计IFXNewsListener接口抽象各个新闻社不一样的新闻获取方式,并给出相应实现类 public interface IFXNewsListener { ... } // 以及 public class DowJonesNewsListener implements IFXNewsListener { ... } // 3-设计IFXNewsPersister接口抽象不一样数据访问方式,并实现相应的实现类 2 public interface IFXNewsPersister { ... } // 以及 public class DowJonesNewsPersister implements IFXNewsPersister { ... }
package org.springframework.mylearntest.beanf; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.PropertyValue; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.mylearntest.before.FXNewsProvider; /** * 经过编码方式使用BeanFactory实现FX新闻相关类的注册及绑定 * 本内容来自书籍Spring揭密 * 代码搬运于此 */ public class BeanFactoryFX { public static void main(String[] args) { DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory(); BeanFactory container = bindViaCode(beanRegistry); FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider"); newsProvider.getAndPersistNews(); } // 由于传入的DefaultListableBeanFactory同 // 时实现了BeanFactory和BeanDefinitionRegistry接口,因此,这样作强制类型转换不会出 // 现问题。但须要注意的是,单纯的BeanDefinitionRegistry是没法强制转换到BeanFactory // 类型的! public static BeanFactory bindViaCode(BeanDefinitionRegistry registry) { AbstractBeanDefinition newsProvider = new RootBeanDefinition(FXNewsProvider.class, 0, true); AbstractBeanDefinition newsListener = new RootBeanDefinition(DowJonesNewsListener.class,0, true); AbstractBeanDefinition newsPersister = new RootBeanDefinition(DowJonesNewsPersister.class, 0,true); // 将bean定义注册到容器中 registry.registerBeanDefinition("djNewsProvider", newsProvider); registry.registerBeanDefinition("djListener", newsListener); registry.registerBeanDefinition("djPersister", newsPersister); // 指定依赖关系 // 1. 能够经过构造方法注入方式 /*ConstructorArgumentValues argValues = new ConstructorArgumentValues(); argValues.addIndexedArgumentValue(0, newsListener); argValues.addIndexedArgumentValue(1, newsPersister); newsProvider.setConstructorArgumentValues(argValues);*/ // 2. 或者经过setter方法注入方式 MutablePropertyValues propertyValues = new MutablePropertyValues(); propertyValues.addPropertyValue(new PropertyValue("newsListener",newsListener)); propertyValues.addPropertyValue(new PropertyValue("newPersistener",newsPersister)); newsProvider.setPropertyValues(propertyValues); // 绑定完成 2 return (BeanFactory)registry; } }
DowJonesNewsListener
package org.springframework.mylearntest.beanf; import org.springframework.mylearntest.before.FXNewsBean; import org.springframework.mylearntest.before.IFXNewsListener; public class DowJonesNewsListener implements IFXNewsListener { @Override public String[] getAvailableNewsIds() { return new String[0]; } @Override public FXNewsBean getNewsByPK(String newsId) { return null; } @Override public void postProcessIfNecessary(String newsId) { } }
DowJonesNewsPersister
package org.springframework.mylearntest.beanf; import org.springframework.mylearntest.before.FXNewsBean; import org.springframework.mylearntest.before.IFXNewsPersister; public class DowJonesNewsPersister implements IFXNewsPersister { @Override public void persistNews(FXNewsBean newsBean) { } }
IFXNewsListener
package org.springframework.mylearntest.before; public interface IFXNewsListener { String[] getAvailableNewsIds(); FXNewsBean getNewsByPK(String newsId); void postProcessIfNecessary(String newsId); }
IFXNewsPersister
package org.springframework.mylearntest.before; public interface IFXNewsPersister { void persistNews(FXNewsBean newsBean); }
FXNewsProvider
package org.springframework.mylearntest.before; import org.apache.commons.lang3.ArrayUtils; public class FXNewsProvider { private IFXNewsListener newsListener; private IFXNewsPersister newPersistener; public FXNewsProvider(IFXNewsListener newsListner,IFXNewsPersister newsPersister) { this.newsListener = newsListner; this.newPersistener = newsPersister; } public IFXNewsListener getNewsListener() { return newsListener; } public void setNewsListener(IFXNewsListener newsListener) { this.newsListener = newsListener; } public IFXNewsPersister getNewPersistener() { return newPersistener; } public void setNewPersistener(IFXNewsPersister newPersistener) { this.newPersistener = newPersistener; } public FXNewsProvider() { } public void getAndPersistNews() { String[] newsIds = newsListener.getAvailableNewsIds(); if (ArrayUtils.isEmpty(newsIds)) { return; } for (String newsId : newsIds) { FXNewsBean newsBean = newsListener.getNewsByPK(newsId); newPersistener.persistNews(newsBean); newsListener.postProcessIfNecessary(newsId); } } }
FXNewsBean
package org.springframework.mylearntest.before; public class FXNewsBean { }
djNewsProvider.(class)=..FXNewsProvider # ----------经过构造方法注入的时候------------- djNewsProvider.$0(ref)=djListener djNewsProvider.$1(ref)=djPersister # ----------经过setter方法注入的时候--------- # djNewsProvider.newsListener(ref)=djListener # djNewsProvider.newPersistener(ref)=djPersister djListener.(class)=..impl.DowJonesNewsListener djPersister.(class)=..impl.DowJonesNewsPersister
package org.springframework.mylearntest.directcode; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.PropertiesBeanDefinitionReader; public class PropConfigTest { public static void main(String[] args) { // todo Caused by: java.lang.IllegalStateException: No bean class specified on bean definition DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory(); BeanFactory container = bindViaPropertiesFile(beanRegistry); FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider"); newsProvider.getAndPersistNews(); } public static BeanFactory bindViaPropertiesFile(BeanDefinitionRegistry registry) { PropertiesBeanDefinitionReader reader = new PropertiesBeanDefinitionReader(registry); reader.loadBeanDefinitions("classpath:binding-config.properties"); return (BeanFactory)registry; } }
local、 parent和bean的区别在于:
local只能指定与当前配置的对象在同一个配置文件的对象定义的名称(能够得到XML解析器的id约束验证支持);
parent则只能指定位于当前容器的父容器中定义的对象引用;
bean则基本上通吃,因此,一般状况下,直接使用bean来指定对象引用就能够了
但这种场合下,使用idref才是最为合适的。由于使用idref,容器在解析配置的时候就能够帮 你检查这个beanName究竟是否存在,而不用等到运行时才发现这个beanName对应的对象实例不存在。毕竟,输错名字的问题很常见。如下代码演示了idref的使用:
<property name="newsListenerBeanName"> <idref bean="djNewsListener"/> </property>
autodetect
自动绑定和手动明确绑定各有利弊。自动绑定的优势有以下两点。
仅指定lazy-init-bean的lazy-init为true,并不意味着容器就必定会延迟初始化该bean的实例。若是某个非延迟初始化的bean定义依赖于lazy-init-bean,那么毫无疑问,按照依赖决计的顺序,容器仍是会首先实例化lazy-init-bean,而后再实例化后者,以下代码演示了这种相互牵连致使延迟初始化失败的状况:
<bean id="lazy-init-bean" class="..." lazy-init="true"/> <bean id="not-lazy-init-bean" class="..."> <property name="propName"> <ref bean="lazy-init-bean"/> </property> </bean>
代码清单4-27 使用模板化配置形式配置FXNewsProvider和SpecificFXNewsProvider
<bean id="newsProviderTemplate" abstract="true"> <property name="newPersistener"> <ref bean="djNewsPersister"/> </property> </bean> <bean id="superNewsProvider" parent="newsProviderTemplate" class="..FXNewsProvider"> <property name="newsListener"> </property> <ref bean="djNewsListener"/> 7 </bean> <bean id="subNewsProvider" parent="newsProviderTemplate" class="..SpecificFXNewsProvider"> <property name="newsListener"> <ref bean="specificNewsListener"/> </property> </bean>
若是你不想容器在初始化的时候实例化某些对象,那么能够将其abstract属性赋值true,以免容器将其实例化。对于ApplicationContext容器尤为如此,由于默认状况下, ApplicationContext会在容器启动的时候就对其管理的全部bean进行实例化,只有标志为abstract的bean除外。
scope用来声明容器中的对象所应该处的限定场景或者说该对象的存活时间,即容器在对象进入其相应的scope以前,生成并装配这些对象,在该对象再也不处于这些scope的限定以后,容器一般会销毁这些对象。
Spring容器最初提供了两种bean的scope类型: singleton和prototype,但发布2.0以后,又引入了另外三种scope类型,即request、 session和global session类型。不过这三种类型有所限制,只能在Web应用中使用。也就是说,只有在支持Web应用的ApplicationContext中使用这三个scope才是合理的。
global session只有应用在基于portlet的Web应用程序中才有意义,它映射到portlet的global范围的 session。若是在普通的基于servlet的Web应用中使用了这个类scope,容器会将其做为普通的session类型的scope对待。
Spring容器提出了一种叫作方法注入( Method Injection)的方式,能够帮助咱们解决上述问题。咱们所要作的很简单,只要让getNewsBean方法声明符合规定的格式,并在配置文件中通知容器,当该方法被调用的时候,每次返回指定类型的对象实例便可。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="newsBean" class="org.springframework.mylearntest.directcode.FXNewsBean" scope="prototype"> </bean> <bean id="mockPersister" class="org.springframework.mylearntest.mthdinject.MockNewsPersister"> <property name="newsBean"> <ref bean="newsBean"/> </property> </bean> </beans>
package org.springframework.mylearntest.mthdinject; import org.springframework.mylearntest.directcode.FXNewsBean; import org.springframework.mylearntest.directcode.IFXNewsPersister; public class MockNewsPersister implements IFXNewsPersister { private FXNewsBean newsBean; public void persistNews(FXNewsBean bean) { persistNews(); } public void persistNews() { System.out.println("persist bean:"+getNewsBean()); } public FXNewsBean getNewsBean() { return newsBean; } public void setNewsBean(FXNewsBean newsBean) { this.newsBean = newsBean; } }
package org.springframework.mylearntest.mthdinject; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test4MockNewsPersister { public static void main(String[] args) { BeanFactory container = new ClassPathXmlApplicationContext("mthdinject.xml"); MockNewsPersister persister = (MockNewsPersister)container.getBean("mockPersister"); persister.persistNews(); persister.persistNews(); } }
输出结果为
persist bean:org.springframework.mylearntest.directcode.FXNewsBean@5be6e01c persist bean:org.springframework.mylearntest.directcode.FXNewsBean@5be6e01c
使用方法注入后
<bean id="newsBean" class="..domain.FXNewsBean" singleton="prototype"> </bean> <bean id="mockPersister" class="..impl.MockNewsPersister"> <lookup-method name="getNewsBean" bean="newsBean"/> </bean>
经过
即便没有方法注入, 只要在实现getNewsBean()方法的时候,可以保证每次调用BeanFactory的getBean("newsBean"),就一样能够每次都取得新的FXNewsBean对象实例
package org.springframework.mylearntest.beanfactorywareinject; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.mylearntest.directcode.FXNewsBean; import org.springframework.mylearntest.directcode.IFXNewsPersister; public class MockNewsPersister1 implements IFXNewsPersister, BeanFactoryAware { private BeanFactory beanFactory; public void setBeanFactory(BeanFactory bf) throws BeansException { this.beanFactory = bf; } public void persistNews(FXNewsBean bean) { persistNews(); } public void persistNews() { System.out.println("persist bean:" + getNewsBean()); } public FXNewsBean getNewsBean() { return (FXNewsBean) beanFactory.getBean("newsBean"); } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="newsBean" class="org.springframework.mylearntest.directcode.FXNewsBean" scope="prototype"> </bean> <bean id="mockPersister1" class="org.springframework.mylearntest.beanfactorywareinject.MockNewsPersister1"> </bean> </beans>
package org.springframework.mylearntest.beanfactorywareinject; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.mylearntest.mthdinject.MockNewsPersister; public class Test4MockNewsPersister1 { public static void main(String[] args) { BeanFactory container = new ClassPathXmlApplicationContext("beanfactoryawareinject.xml"); MockNewsPersister1 persister = (MockNewsPersister1)container.getBean("mockPersister1"); persister.persistNews(); persister.persistNews(); } }
ObjectFactoryCreatingFactoryBean是 Spring 提 供 的 一 个 FactoryBean实 现 ,它 返 回 一 个ObjectFactory实例。ObjectFactoryCreatingFactoryBean返回的这个ObjectFactory实例能够为咱们返回容器管理的相关对象。实际上,ObjectFactoryCreatingFactoryBean实现BeanFactoryAware接口,它返回ObjectFactory实例只是特定于与Spring容器进行交互的一个实现而已。使用它的好处就是,隔离了客户端对象对BeanFactory的直接引用。
package org.springframework.mylearntest.objectfactoryinj; import org.springframework.beans.factory.ObjectFactory; import org.springframework.mylearntest.directcode.FXNewsBean; import org.springframework.mylearntest.directcode.IFXNewsPersister; @SuppressWarnings({"rawtypes" }) public class MockNewsPersister2 implements IFXNewsPersister { private ObjectFactory newsBeanFactory; public void persistNews(FXNewsBean bean) { persistNews(); } public void persistNews() { System.out.println("persist bean:"+getNewsBean()); } public FXNewsBean getNewsBean() { return (FXNewsBean) newsBeanFactory.getObject(); } public void setNewsBeanFactory(ObjectFactory newsBeanFactory) { this.newsBeanFactory = newsBeanFactory; } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="newsBean" class="org.springframework.mylearntest.directcode.FXNewsBean" scope="prototype"> </bean> <bean id="newsBeanFactory" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean"> <property name="targetBeanName"> <idref bean="newsBean"/> </property> </bean> <bean id="mockPersister2" class="org.springframework.mylearntest.objectfactoryinj.MockNewsPersister2"> <property name="newsBeanFactory"> <ref bean="newsBeanFactory"/> </property> </bean> </beans>
package org.springframework.mylearntest.objectfactoryinj; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test4MockNewsPersister2 { public static void main(String[] args) { BeanFactory container = new ClassPathXmlApplicationContext("objectfactoryinj.xml"); MockNewsPersister2 persister = (MockNewsPersister2)container.getBean("mockPersister2"); persister.persistNews(); persister.persistNews(); } }
使用FXNewsProviderMethodReplacer替换FXNewsProvider中的getAndPersistNews()方法
package org.springframework.mylearntest.methodreplacer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.support.MethodReplacer; import java.lang.reflect.Method; public class FXNewsProviderMethodReplacer implements MethodReplacer { private static final transient Log logger = LogFactory.getLog(FXNewsProviderMethodReplacer.class); public Object reimplement(Object target, Method method, Object[] args) throws Throwable { logger.info("before executing method["+method.getName()+ "] on Object["+target.getClass().getName()+"]."); System.out.println("sorry,We will do nothing this time."); logger.info("end of executing method["+method.getName()+ "] on Object["+target.getClass().getName()+"]."); return null; } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="FXNewsProvider" class="org.springframework.mylearntest.propconfig.FXNewsProvider"> <constructor-arg index="0"> <ref bean="djNewsListener"/> </constructor-arg> <constructor-arg index="1"> <ref bean="djNewsPersister"/> </constructor-arg> <replaced-method name="getAndPersistNews" replacer="providerReplacer"> </replaced-method> </bean> <bean id="djNewsListener" class="org.springframework.mylearntest.propconfig.DjNewsListener"/> <bean id="djNewsPersister" class="org.springframework.mylearntest.propconfig.DjNewsPersister"/> <bean id="providerReplacer" class="org.springframework.mylearntest.methodreplacer.FXNewsProviderMethodReplacer"> </bean> </beans>
package org.springframework.mylearntest.methodreplacer; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.mylearntest.propconfig.FXNewsProvider; public class Test4FXNewsProviderMethodReplacer { public static void main(String[] args) { BeanFactory container = new ClassPathXmlApplicationContext("methodreplacer.xml"); FXNewsProvider fxNewsProvider = (FXNewsProvider)container.getBean("FXNewsProvider"); fxNewsProvider.getAndPersistNews(); } }
容器启动阶段
容器启动开始,首先会经过某种途径加载Configuration MetaData。除了代码方式比较直接,在大部分状况下,容器须要依赖某些工具类(BeanDefinitionReader)对加载的Configuration MetaData进行解析和分析,并将分析后的信息编组为相应的BeanDefinition,最后把这些保存了bean定义必要信息的BeanDefinition,注册到相应的BeanDefinitionRegistry,这样容器启动工做就完成了。
Bean实例化阶段
通过第一阶段,如今全部的bean定义信息都经过BeanDefinition的方式注册到了BeanDefinitionRegistry中。当某个请求方经过容器的getBean方法明确地请求某个对象时,或者因依赖关系容器须要隐式地调用getBean方法时,就会触发第二阶段的活动。
该阶段,容器会首先检查所请求的对象以前是否已经初始化。若是没有,则会根据注册的BeanDefinition所提供的信息实例化被请求对象,并为其注入依赖。若是该对象实现了某些回调接口,也会根据回调接口的要求来装配它。当该对象装配完毕以后,容器会当即将其返回请求方使用。若是说第一阶段只是根据图纸装配生产线的话,那么第二阶段就是使用装配好的生产线来生产具体的产品了。
Spring提供了一种叫作BeanFactoryPostProcessor的容器扩展机制。该机制容许咱们在容器实例化相应对象以前,对注册到容器的BeanDefinition所保存的信息作相应的修改。这就至关于在容器实现的第一阶段最后加入一道工序,让咱们对最终BeanDefinition作一些额外的操做,好比修改其中bean定义的某些属性,为bean定义增长其余信息等。
若是要自定义实现BeanFactoryPostProcessor,一般咱们须要实现org.springframework.beans.factory.config.BeanFactoryPostProcessor接口。这个时候可能须要实现类同时实现Spring的org.springframework.core.Ordered接口,以保证各个BeanFactoryPostProcessor能够按照预先设定的顺序执行(若是顺序紧要的话)。
其中,org.springframework.beans.factory.config.PropertyPlaceholderConfigurer和org.springframework.beans.factory.config.Property OverrideConfigurer是两个比较经常使用的BeanFactoryPostProcessor。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 使用的BeanFactoryPostProcessor--> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>conf/jdbc.properties</value> <value>conf/mail.properties</value> </list> </property> </bean> <!-- 使用占位符的数据源配置--> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="url"> <value>${jdbc.url}</value> </property> <property name="driverClassName"> <value>${jdbc.driver}</value> </property> <property name="username"> <value>${jdbc.username}</value> </property> <property name="password"> <value>${jdbc.password}</value> </property> <property name="testOnBorrow"> <value>true</value> </property> <property name="testOnReturn"> <value>true</value> </property> <property name="testWhileIdle"> <value>true</value> </property> <property name="minEvictableIdleTimeMillis"> <value>180000</value> </property> <property name="timeBetweenEvictionRunsMillis"> <value>360000</value> </property> <property name="validationQuery"> <value>SELECT 1</value> </property> <property name="maxOpenPreparedStatements"> <value>100</value> </property> </bean> </beans>
jdbc.url=jdbc:mysql://server/MAIN?useUnicode=true&characterEncoding=ms932&failOverReadOnly=false&roundRobinLoadBalance=true jdbc.driver=com.mysql.jdbc.Driver jdbc.username=root jdbc.password=root
若是org.apache.commons.dbcp2.BasicDataSource报错,请加入依赖
compile(group: 'org.apache.commons', name: 'commons-dbcp2', version: '2.1.1')
基本机制就是以前所说的那样。当BeanFactory在第一阶段加载完成全部配置信息时, BeanFactory中保存的对象的属性信息还只是以占位符的形式存在,如${jdbc.url}、 ${jdbc.driver}。当PropertyPlaceholderConfigurer做为BeanFactoryPostProcessor被应用时,它会使用properties配置文件中的配置信息来替换相应BeanDefinition中占位符所表示的属性值。这样,当进入容器实现的第二阶段实例化bean时, bean定义中的属性值就是最终替换完成的了。
PropertyPlaceholderConfigurer不单会从其配置的properties文件中加载配置项,同时还会检查Java的System类中的Properties,能够经过setSystemPropertiesMode()或者setSystemPropertiesModeName()来控制是否加载或者覆盖System相应Properties的行为。
PropertyPlaceholderConfigurer提供了SYSTEM_PROPERTIES_MODE_FALLBACK、 SYSTEM_PROPERTIES_MODE_NEVER和SYSTEM_PROPERTIES_MODE_OVERRIDE三种模式。默认采用的是SYSTEM_PROPERTIES_ MODE_FALLBACK,果properties文件中找不到相应配置项,则到System的Properties中查找,咱们还能够选择不检查System的Properties或者覆盖它。
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer"> <property name="location" value="pool-adjustment.properties"/> </bean>
dataSource.minEvictableIdleTimeMillis=1000 dataSource.maxOpenPreparedStatements=50
Spring提供的部分PropertyEditor:
StringArrayPropertyEditor。该PropertyEditor会将符合CSV格式的字符串转换成String[]数组的形式,默认是以逗号(,)分隔的字符串,但能够指定自定义的字符串分隔符。ByteArrayPropertyEditor、CharArrayPropertyEditor等都属于相似功能的PropertyEditor,参照Javadoc能够取得相应的详细信息。
ClassEditor。根据String类型的class名称,直接将其转换成相应的Class对象,至关于经过Class.forName(String)完成的功效。能够经过String[]数组的形式传入需转换的值,以达到与提供ClassArrayEditor一样的目的。
FileEditor。 Spring提供的对应java.io.File类型的PropertyEditor。同属于对资源进行定位的PropertyEditor还有InputStreamEditor、 URLEditor等。
LocaleEditor。针对java.util.Locale类型的PropertyEditor,格式能够参照LocaleEditor和Locale的Javadoc说明。
PatternEditor。针对Java SE 1.4以后才引入的java.util.regex.Pattern的PropertyEditor,格式能够参照java.util.regex.Pattern类的Javadoc。
以上这些PropertyEditor,容器一般会默认加载使用,因此,即便咱们不告诉容器应该如何对这些类型进行转换,容器一样能够正确地完成工做。但当咱们须要指定的类型没有包含在以上所提到PropertyEditor之列的时候,就须要给出针对这种类型的PropertyEditor实现,并经过CustomEditorConfigurer告知容器,以便容器在适当的时机使用到适当的PropertyEditor。
自定义PropertyEditor
对于Date类型,不一样的Locale、不一样的系统在表现形式上存在不一样的需求。如系统这个部分须要以yyyy-MM-dd的形式表现日期,系统那个部分可能又须要以yyyyMMdd的形式对日期进行转换。
package org.springframework.mylearntest.beanfactorypostprocessor; import java.beans.PropertyEditorSupport; import java.time.LocalDate; import java.time.format.DateTimeFormatter; public class DatePropertyEditor extends PropertyEditorSupport { private String datePattern; @Override public void setAsText(String text) throws IllegalArgumentException { DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(getDatePattern()); LocalDate dateValue = LocalDate.parse(text,dateTimeFormatter); setValue(dateValue); } @Override public String getAsText() { return super.getAsText(); } public String getDatePattern() { return datePattern; } public void setDatePattern(String datePattern) { this.datePattern = datePattern; } }
若是仅仅是支持单向的从String到相应对象类型的转换,只要覆写方法setAsText(String)便可。若是想支持双向转换,须要同时考虑getAsText()方法的覆写。
package org.springframework.mylearntest.beanfactorypostprocessor; import org.springframework.beans.PropertyEditorRegistrar; import org.springframework.beans.PropertyEditorRegistry; import java.beans.PropertyEditor; public class DatePropertyEditorRegistrar implements PropertyEditorRegistrar { private PropertyEditor propertyEditor; public PropertyEditor getPropertyEditor() { return propertyEditor; } public void setPropertyEditor(PropertyEditor propertyEditor) { this.propertyEditor = propertyEditor; } @Override public void registerCustomEditors(PropertyEditorRegistry registry) { registry.registerCustomEditor(java.util.Date.class,getPropertyEditor()); } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" xmlns:aop="http://www.springframework.org/schema/aop"> <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="propertyEditorRegistrars"> <list> <ref bean="datePropertyEditorRegistrar"/> </list> </property> </bean> <bean id="datePropertyEditorRegistrar" class="org.springframework.mylearntest.beanfactorypostprocessor.DatePropertyEditorRegistrar"> <property name="propertyEditor"> <ref bean="datePropertyEditor"/> </property> </bean> <bean id="datePropertyEditor" class="org.springframework.mylearntest.beanfactorypostprocessor.DatePropertyEditor"> <property name="datePattern"> <value>yyyy/MM/dd</value> </property> </bean> </beans>
package org.springframework.mylearntest.beanfactorypostprocessor; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test4DateProp { public static void main(String[] args) { // applicationContext ApplicationContext context = new ClassPathXmlApplicationContext("datepropertyeditor2.xml"); DatePropertyEditor datePropertyEditor = (DatePropertyEditor) context.getBean("datePropertyEditor"); datePropertyEditor.setAsText("2020/06/21"); } }
容器启动以后,并不会立刻就实例化相应的bean定义。咱们知道,容器如今仅仅拥有全部对象的BeanDefinition来保存实例化阶段将要用的必要信息。只有当请求方经过BeanFactory的getBean()方法来请求某个对象实例的时候,才有可能触发Bean实例化阶段的活动。 BeanFactory的getBe法能够被客户端对象显式调用,也能够在容器内部隐式地被调用。隐式调用有以下两种状况。
容器在内部实现的时候,采用“策略模式(Strategy Pattern)”来决定采用何种方式初始化bean实例。一般,能够经过反射或者CGLIB动态字节码生成来初始化相应的bean实例或者动态生成其子类。org.springframework.beans.factory.support.InstantiationStrategy定义是实例化策略的抽象接口,其直接子类SimpleInstantiationStrategy实现了简单的对象实例化功能,能够经过反射来实例化对象实例,但不支持方法注入方式的对象实例化。 CglibSubclassingInstantiationStrategy继承了SimpleInstantiationStrategy的以反射方式实例化对象的功能,而且经过CGLIB的动态字节码生成功能,该策略实现类能够动态生成某个类的子类,进而知足了方法注入所需的对象实例化求。默认状况下,容器内部采用的是CglibSubclassingInstantiationStrategy。
容器只要根据相应bean定义的BeanDefintion取得实例化信息,结合CglibSubclassingInstantiationStrategy以及不一样的bean定义类型,就能够返回实例化完成的对象实例。可是,返回方式上有些“点缀”。不是直接返回构造完成的对象实例,而是以BeanWrapper对构造完成的对象实例进行包裹,返回相应的BeanWrapper实例。
第二部:设置Bean的相应属性
BeanWrapper接口一般在Spring框架内部使用,它有一个实现类org.springframework.beans.BeanWrapperImpl。其做用是对某个bean进行“包裹”,而后对这个“包裹”的bean进行操做,好比设置或者获取bean的相应属性值。而在第一步结束后返回BeanWrapper实例而不是原先的对象实例,就是为了第二步“设置对象属性”。
BeanWrapper定义继承了org.springframework.beans.PropertyAccessor接口,能够以统一的方式对对象属性进行访问; BeanWrapper定义同时又直接或者间接继承了PropertyEditorRegistry和TypeConverter接口。不知你是否还记得CustomEditorConfigurer?当把各类PropertyEditor注册给容器时,知道后面谁用到这些PropertyEditor吗?对,就是BeanWrapper!在第一步构造完成对象以后, Spring会根据对象实例构造一个BeanWrapperImpl实例,而后将以前CustomEditorConfigurer注册的PropertyEditor复制一份给BeanWrapperImpl例(这就是BeanWrapper同时又是PropertyEditorRegistry的缘由)。这样,当BeanWrapper转换类型、设置对象属性值时,就不会无从下手了。
// 使用BeanWrapper操做对象 Object provider = Class.forName("package.name.FXNewsProvider").newInstance(); Object listener = Class.forName("package.name.DowJonesNewsListener").newInstance(); Object persister = Class.forName("package.name.DowJonesNewsPersister").newInstance(); BeanWrapper newsProvider = new BeanWrapperImpl(provider); newsProvider.setPropertyValue("newsListener", listener); newsProvider.setPropertyValue("newPersistener", persister); assertTrue(newsProvider.getWrappedInstance() instanceof FXNewsProvider); assertSame(provider, newsProvider.getWrappedInstance()); assertSame(listener, newsProvider.getPropertyValue("newsListener")); assertSame(persister, newsProvider.getPropertyValue("newPersistener"));
// 使用Java反射API操做对象 Object provider = Class.forName("package.name.FXNewsProvider").newInstance(); Object listener = Class.forName("package.name.DowJonesNewsListener").newInstance(); Object persister = Class.forName("package.name.DowJonesNewsPersister").newInstance(); Class providerClazz = provider.getClass(); Field listenerField = providerClazz.getField("newsListener"); listenerField.set(provider, listener); Field persisterField = providerClazz.getField("newsListener"); persisterField.set(provider, persister); assertSame(listener, listenerField.get(provider)); assertSame(persister, persisterField.get(provider));
org.springframework.beans.factory.BeanNameAware。若是Spring容器检测到当前对象实例实现了该接口,会将该对象实例的bean定义对应的beanName设置到当前对象实例。
org.springframework.beans.factory.BeanClassLoaderAware。若是容器检测到当前对象实例实现了该接口,会将对应加载当前bean的Classloader注入当前对象实例。默认会使用加载org.springframework.util.ClassUtils类的Classloader。
org.springframework.beans.factory.BeanFactoryAware。在介绍方法注入的时候,咱们提到过使用该接口以便每次获取prototype类型bean的不一样实例。若是对象声明实现了BeanFactoryAware接口, BeanFactory容器会将自身设置到当前对象实例。这样,当前对象实例就拥有了一个BeanFactory容器的引用,而且能够对这个容器内容许访问的对象按照须要进行访问。
对于ApplicationContext类型容器,使用BeanPostProcessor处理
org.springframework.context.ResourceLoaderAware 。 ApplicationContext 实现了Spring的ResourceLoader接口(后面会说起详细信息)。当容器检测到当前对象实例实现了ResourceLoaderAware接口以后,会将当前ApplicationContext自身设置到对象实例,这样当前对象实例就拥有了其所在ApplicationContext容器的一个引用。
org.springframework.context.ApplicationEventPublisherAware。 ApplicationContext做为一个容器,同时还实现了ApplicationEventPublisher接口,这样,它就能够做为ApplicationEventPublisher来使用。因此,当前ApplicationContext容器若是检测到当前实例化的对象实例声明了ApplicationEventPublisherAware接口,则会将自身注入当前对象。
org.springframework.context.MessageSourceAware。 ApplicationContext经过MessageSource接口提供国际化的信息支持,即I18n( Internationalization)。它自身就实现了MessageSource接口,因此当检测到当前对象实例实现了MessageSourceAware接口,则会将自身注入当前对象实例。
org.springframework.context.ApplicationContextAware。 若是ApplicationContext容器检测到当前对象实现了ApplicationContextAware接口,则会将自身注入当前对象实例。
只要记住BeanPostProcessor是存在于对象实例化阶段,而BeanFactoryPostProcessor则是存在于容器启动阶段。
package org.springframework.beans.factory.config; import org.springframework.beans.BeansException; import org.springframework.lang.Nullable; public interface BeanPostProcessor { @Nullable default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Nullable default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } }
自定义BeanPostProcessor须要实现BeanPostProcessor
假设系统中全部的IFXNewsListener实现类须要从某个位置取得相应的服务器链接密码,并且系统中保存的密码是加密的,那么在IFXNewsListener发送这个密码给新闻服务器进行链接验证的时候,首先须要对系统中取得的密码进行解密,而后才能发送。
package org.springframework.mylearntest.beanpostprocessor; public interface PasswordDecodable { String getEncodedPassword(); void setDecodedPassword(String password); }
package org.springframework.mylearntest.beanpostprocessor; import org.springframework.mylearntest.directcode.FXNewsBean; import org.springframework.mylearntest.directcode.IFXNewsListener; public class DowJonesNewsListener implements IFXNewsListener,PasswordDecodable { private String password; public String[] getAvailableNewsIds() { // 省略 return new String[0]; } public FXNewsBean getNewsByPK(String newsId) { // 省略 return null; } public void postProcessIfNecessary(String newsId) { // 省略 } public String getEncodedPassword() { return this.password; } public void setDecodedPassword(String password) { this.password = password; } }
package org.springframework.mylearntest.beanpostprocessor; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; public class PasswordDecodePostProcessor implements BeanPostProcessor { public Object postProcessAfterInitialization(Object object, String beanName) throws BeansException { return object; } public Object postProcessBeforeInitialization(Object object, String beanName) throws BeansException { if(object instanceof PasswordDecodable){ String encodedPassword = ((PasswordDecodable)object).getEncodedPassword(); String decodedPassword = decodePassword(encodedPassword); ((PasswordDecodable)object).setDecodedPassword(decodedPassword); } return object; } private String decodePassword(String encodedPassword) { // 实现解码逻辑 encodedPassword = encodedPassword + "2mingwen"; return encodedPassword; } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" xmlns:aop="http://www.springframework.org/schema/aop"> <bean id="dowJonesNewsListener" class="org.springframework.mylearntest.beanpostprocessor.DowJonesNewsListener"> <property name="decodedPassword" value="123sjfg@LL"></property> </bean> <bean id="passwordDecodePostProcessor" class="org.springframework.mylearntest.beanpostprocessor.PasswordDecodePostProcessor"> </bean> </beans>
// 测试类 package org.springframework.mylearntest.beanpostprocessor; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test4BeanPostProcessor { public static void main(String[] args) { ApplicationContext beanFactory = new ClassPathXmlApplicationContext("beanpostprocessor/beanpostprocessor.xml"); DowJonesNewsListener dowJonesNewsListener = (DowJonesNewsListener) beanFactory.getBean("dowJonesNewsListener"); String encodedPassword = dowJonesNewsListener.getEncodedPassword(); System.out.println("encodedPassword = " + encodedPassword);// encodedPassword = 123sjfg@LL2mingwen } }
实际上,有一种特殊类型的BeanPostProcessor咱们没有提到,它的执行时机与一般的BeanPostProcessor不一样。org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor接口能够在对象的实例化过程当中致使某种相似于电路“短路”的效果。实际上,并不是全部注册到Spring容器内的bean定义都是按照图4-10的流程实例化的。在全部的步骤以前,也就是实例化bean对象步骤以前,容器会首先检查容器中是否注册有InstantiationAwareBeanPostProcessor类型的BeanPostProcessor。若是有,首先使用相应的InstantiationAwareBeanPostProcessor来构造对象实例。构形成功后直接返回造完成的对象实例,而不会按照“正规的流程”继续执行。这就是它可能形成“短路”的缘由。
org.springframework.beans.factory.InitializingBean是容器内部普遍使用的一个对象生命周期标识接口。
public interface InitializingBean { void afterPropertiesSet() throws Exception; }
其做用在于,在对象实例化过程调用过“BeanPostProcessor的前置处理”以后,会接着检测当前对象是否实现了InitializingBean接口,若是是,则会调用其afterPropertiesSet()方法进一步调整对象实例的状态。好比,在有些状况下,某个业务对象实例化完成后,还不能处于可使用状态。这个时候就可让该业务对象实现该接口,并在方法afterPropertiesSet()中完成对该业务对象的后续处理。
若是系统开发过程当中规定:全部业务对象的自定义初始化操做都必须以init()命名,为了省去挨个
Spring提出了一套基于org.springframework.core.io.Resource和org.springframework.core.io.ResourceLoader接口的资源抽象和加载策略。
Resource:
Resource接口能够根据资源的不一样类型,或者资源所处的不一样场合,给出相应的具体实现。能够帮助咱们查询资源状态、访问资源内容,甚至根据当前资源建立新的相对资源。咱们能够继承org.springframework.core.io.AbstractResource抽象类。
ResourceLoader:
但如何去查找和定位这些资源,则应该是ResourceLoader的职责所在了。 org.springframework.core.io.ResourceLoader接口是资源查找定位策略的统一抽象,具体的资源查找定位策略则由相应的ResourceLoader实现类给出。
DefaultResourceLoader
ResourceLoader有一个默认的实现类,即org.springframework.core.io.DefaultResourceLoader,该类默认的资源查找处理逻辑以下。
Resource和ResourceLoader类层次图
AbstractApplicationContext做为ResourceLoader和ResourcePatternResolver
四种加载方式:
使用以ResourceLoader身份登场的ApplicationContext
ResourceLoader resourceLoader = new ClassPathXmlApplicationContext("配置文件路径");
ResourceLoader类型的注入
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="resourceLoader" class="org.springframework.core.io.DefaultResourceLoader"> </bean> <bean id="fooBar" class="org.springframework.mylearntest.resourceloader.FooBar"> <property name="resourceLoader"> <ref bean="resourceLoader"/> </property> </bean> </beans>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="fooBar" class="org.springframework.mylearntest.resourceloader.FooBarImplApplicationContextAware"> </bean> </beans>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="xMailer" class="org.springframework.mylearntest.resourceloader.XMailer"> <property name="template" value="resourceloader/resources.default_template.vm"/> </bean> </beans>
Locale
不一样的Locale表明不一样的国家和地区,每一个国家和地区在Locale这里都有相应的简写代码表示,包括语言代码以及国家代码,这些代码是ISO标准代码。如,Locale.CHINA表明中国。
ResourceBundle
ResourceBundle用来保存特定于某个Locale的信息(能够是String类型信息,也能够是任何类型的对象)。一般, ResourceBundle管理一组信息序列,全部的信息序列有统一的一个basename,而后特定的Locale的信息,能够根据basename后追加的语言或者地区代码来区分。好比,咱们用一组properties文件来分别保存不一样国家地区的信息,能够像下面这样来命名相应的properties文件:
messages.properties messages_zh.properties messages_zh_CN.properties messages_en.properties messages_en_US.properties ...
其中,文件名中的messages部分称做ResourceBundle将加载的资源的basename,其余语言或地区的资源在basename的基础上追加Locale特定代码。
若是某个业务对象须要国际化的信息支持,那么最简单的办法就是让它实现MessageSourceAware接口,而后注册到ApplicationContext容器。不过这样一来,该业务对象对ApplicationContext容器的依赖性就太强了,显得容器具备较强的侵入性。而实际上, 若是真的某个业务对象须要依赖于MessageSource的话,直接经过构造方法注入或者setter方法注入的方式声明依赖就能够了。
package org.springframework.mylearntest.eventpublication.event; import java.util.EventObject; /** * 自定义事件类型 */ public class MethodExecutionEvent extends EventObject { private static final long serialVersionUID = -71960369269303337L; private String methodName; public MethodExecutionEvent(Object source) { super(source); } public MethodExecutionEvent(Object source, String methodName) { super(source); this.methodName = methodName; } public String getMethodName() { return methodName; } public void setMethodName(String methodName) { this.methodName = methodName; } }
package org.springframework.mylearntest.eventpublication.event; import java.util.EventListener; /** * 自定义事件监听器 */ public interface MethodExecutionEventListener extends EventListener { /** * 处理方法开始执行的时候发布的MethodExecutionEvent事件 */ void onMethodBegin(MethodExecutionEvent evt); /** * 处理方法执行将结束时候发布的MethodExecutionEvent事件 */ void onMethodEnd(MethodExecutionEvent evt); }
package org.springframework.mylearntest.eventpublication.event; /** * 自定义事件监听器实现 */ public class SimpleMethodExecutionEventListener implements MethodExecutionEventListener { public void onMethodBegin(MethodExecutionEvent evt) { String methodName = evt.getMethodName(); System.out.println("start to execute the method[" + methodName + "]."); } public void onMethodEnd(MethodExecutionEvent evt) { String methodName = evt.getMethodName(); System.out.println("finished to execute the method[" + methodName + "]."); } }
package org.springframework.mylearntest.eventpublication.event; public enum MethodExecutionStatus { BEGIN,END }
package org.springframework.mylearntest.eventpublication.event; import java.util.ArrayList; import java.util.List; public class MethodExeuctionEventPublisher { private List<MethodExecutionEventListener> listeners = new ArrayList<MethodExecutionEventListener>(); public void methodToMonitor() { MethodExecutionEvent event2Publish = new MethodExecutionEvent(this, "methodToMonitor"); publishEvent(MethodExecutionStatus.BEGIN, event2Publish); // 执行实际的方法逻辑 // ... publishEvent(MethodExecutionStatus.END, event2Publish); } // 为了不事件处理期间事件监听器的注册或移除操做影响处理过程,咱们对事件发布时点的监听器列表进行了一个安全复制( safe-copy) protected void publishEvent(MethodExecutionStatus status, MethodExecutionEvent methodExecutionEvent) { List<MethodExecutionEventListener> copyListeners = new ArrayList<MethodExecutionEventListener>(listeners); for (MethodExecutionEventListener listener : copyListeners) { if (MethodExecutionStatus.BEGIN.equals(status)) listener.onMethodBegin(methodExecutionEvent); else listener.onMethodEnd(methodExecutionEvent); } } public void addMethodExecutionEventListener(MethodExecutionEventListener listener) { this.listeners.add(listener); } public void removeListener(MethodExecutionEventListener listener) { if (this.listeners.contains(listener)) this.listeners.remove(listener); } public void removeAllListeners() { this.listeners.clear(); } }
package org.springframework.mylearntest.eventpublication.event; public class Test4Event { public static void main(String[] args) { MethodExeuctionEventPublisher eventPublisher = new MethodExeuctionEventPublisher(); eventPublisher.addMethodExecutionEventListener(new SimpleMethodExecutionEventListener()); eventPublisher.methodToMonitor(); eventPublisher.removeAllListeners(); } }
在实现中,须要注意到,为了不事件处理期间事件监听器的注册或移除操做影响处理过程,咱们对事件发布时点的监听器列表进行了一个安全复制( safe-copy)。另外,事件的发布是顺序执行,因此为了可以不影响处理性能,事件监听器的处理逻辑应该尽可能简短。
ApplicationEvent
Spring容器内自定义事件类型,继承自java.util.EventObject,它是一个抽象类,须要根据状况提供相应子类以区分不一样状况。默认状况下, Spring提供了三个实现。
package org.springframework.mylearntest.eventpublication.applicationevent; import org.springframework.context.ApplicationEvent; import org.springframework.mylearntest.eventpublication.event.MethodExecutionStatus; public class MethodExecutionEvent extends ApplicationEvent { private static final long serialVersionUID = -71960369269303337L; private String methodName; private MethodExecutionStatus methodExecutionStatus; public MethodExecutionEvent(Object source) { super(source); } public MethodExecutionEvent(Object source, String methodName, MethodExecutionStatus methodExecutionStatus) { super(source); this.methodName = methodName; this.methodExecutionStatus = methodExecutionStatus; } public String getMethodName() { return methodName; } public void setMethodName(String methodName) { this.methodName = methodName; } public MethodExecutionStatus getMethodExecutionStatus() { return methodExecutionStatus; } public void setMethodExecutionStatus(MethodExecutionStatus methodExecutionStatus) { this.methodExecutionStatus = methodExecutionStatus; } }
ApplicationListener
package org.springframework.mylearntest.eventpublication.applicationevent; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; @SuppressWarnings("rawtypes") public class MethodExecutionEventListener implements ApplicationListener { public void onApplicationEvent(ApplicationEvent evt) { if (evt instanceof MethodExecutionEvent) { // 执行处理逻辑 } } }
ApplicationContext
package org.springframework.mylearntest.eventpublication.applicationevent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.mylearntest.eventpublication.event.MethodExecutionStatus; public class MethodExeuctionEventPublisher implements ApplicationEventPublisherAware { private ApplicationEventPublisher eventPublisher; public void methodToMonitor() { MethodExecutionEvent beginEvt = new MethodExecutionEvent(this, "methodToMonitor", MethodExecutionStatus.BEGIN); this.eventPublisher.publishEvent(beginEvt); // 执行实际方法逻辑 // ... MethodExecutionEvent endEvt = new MethodExecutionEvent(this, "methodToMonitor", MethodExecutionStatus.END); this.eventPublisher.publishEvent(endEvt); } public void setApplicationEventPublisher(ApplicationEventPublisher appCtx) { this.eventPublisher = appCtx; } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="methodExecListener" class="org.springframework.mylearntest.eventpublication.applicationevent.MethodExecutionEventListener"> </bean> <bean id="evtPublisher" class="org.springframework.mylearntest.eventpublication.applicationevent.MethodExeuctionEventPublisher"> </bean> </beans>
package org.springframework.mylearntest.eventpublication.applicationevent; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test4AppEvent { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("eventpublication/applicationevent.xml"); MethodExeuctionEventPublisher evtPublisher = (MethodExeuctionEventPublisher) context.getBean("evtPublisher"); evtPublisher.methodToMonitor(); } }
ApplicationEventMulticaster有一抽象实现类——org.springframework.context.event.AbstractApplicationEventMulticaster,它实现了事件监听器的管理功能。事件的发布功能则委托给了其子类。 org.springframework.context.event.SimpleApplicationEventMulticaster。其默认使用了SyncTaskExecutor进行事件的发布。为了不这种方式可能存在的性能问题,咱们能够为其提供其余类型的TaskExecutor实现类。
容器启动开始,就会检查容器内是否存在名称为applicationEventMulticaster的ApplicationEventMulticaster对象实例。有的话就使用提供的实现,没有则默认初始化一个SimpleApplicationEventMulticaster做为将会使用的ApplicationEventMulticaster。
看着依赖注入相关的信息,一半分散在Java源代码中( @Autowired标注的信息),一半依然留在XML配置文件里,有不少bean标签依然存在。
当使用@Autoware注解可以同时找到两个或者多个同一类型的对象实例,可使用@Qualifier对依赖注入的条件作进一步限定,指定具体是哪一个id。
<beans> <bean class="org.springframework.beans.factory.annotation. ➥ AutowiredAnnotationBeanPostProcessor"/> <bean id="newsProvider" class="..FXNewsProvider"/> <bean id="djNewsListener" class="..DowJonesNewsListener"/> <bean id="reutersNewsListner" class="..ReutersNewsListener"/> <bean id="djNewsPersister" class="..DowJonesNewsPersister"/> </beans>
public class FXNewsProvider { @Autowired @Qualifier("reutersNewsListner")// 此时注入id=reutersNewsListner private IFXNewsListener newsListener; @Autowired private IFXNewsPersister newPersistener; ... }
// @Qualifier注解位于参数上 public class FXNewsProvider{ // ... @Autowired public void setUp(@Qualifier("reutersNewsListner") IFXNewsListener newsListener,IFXNewsPersister newPersistener) { this.newsListener = newsListener; this.newPersistener = newPersistener; } // ... }
@Resource与@Autowired不一样,它遵循的是byName自动绑定形式的行为准则,也就是说, IoC容器将根据@Resource所指定的名称,到容器中查找beanName与之对应的实例,而后将查找到的对象实例注入给@Resource所标注的对象。
@PostConstruct和@PreDestroy不是服务于依赖注入的,它们主要用于标注对象生命周期管理相关方法,这与Spring的InitializingBean和DisposableBean接口,以及配置项中的init-method和destroy-method起到相似的做用。
就像@Autowired须要AutowiredAnnotationBeanPostProcessor为 它 与 IoC 容 器 牵 线 搭 桥 一 样 , JSR250 的 这 些 注 解 也 同 样 需 要 一 个BeanPostProcessor帮助它们实现自身的价值。 这个BeanPostProcessor就是org.springframework.context.annotation.CommonAnnotationBeanPostProcessor,只有将CommonAnnotationBeanPostProcessor添加到容器, JSR250的相关注解才能发挥做用。
<beans> <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/> <bean id="newsProvider" class="..FXNewsProvider"/> <bean id="djNewsListener" class="..DowJonesNewsListener"/> <bean id="djNewsPersister" class="..DowJonesNewsPersister"/> </beans>
<context:annotation-config>
不 但 帮 我 们 把 AutowiredAnnotationBeanPostProcessor 和CommonAnnotationBeanPostProcessor注册到容器,同时还会把PersistenceAnnotationBeanPostProcessor和RequiredAnnotationBeanPostProcessor一并进行注册,可谓一举四得啊!
使用相应的注解对组成应用程序的相关类进行标注以后, classpath-scanning功能能够从某一顶层包( base package)开始扫描。当扫描到某个类标注了相应的注解以后,就会提取该类的相关信息,构建对应的BeanDefinition,而后把构建完的BeanDefinition注册到容器。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <context:component-scan base-package="org.spring21"/> </beans>
<context:component-scan>
它同时将AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor一并注册到了容器中,因此,依赖注入的需求得以知足。