Spring揭秘-读书笔记
Spring的IoC容器之BeanFactory
Spring提供了两种容器类型: BeanFactory 和 ApplicationContext
BeanFactory:基础类型IoC容器,提供完整的IoC服务支持,如果没有特殊指定,默认采用延迟初始化策略(lazy-load)
只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入操作。
ApplicationContext: ApplicationContext 在 BeanFactory 的基础上构建,是相对比较高级的容器实现,
除了拥有 BeanFactory 的所有支持, ApplicationContext 还提供了其他高级特性,比如事件发布、国际化信息支持等
ApplicationContext所管理的对象,在该类型容器启动之后,默认全部初始化并绑定完成。
所以,相对于 BeanFactory 来说, ApplicationContext 要求更多的系统资源,同时,因为在启动时就完成所有初始化,
容器启动时间较之 BeanFactory 也会长一些。在那些系统资源充足,并且要求更多功能的场景中,ApplicationContext 类型的容器是比较合适的选择
DefaultListableBeanFactory: BeanFactory只是一个接口,我们最终需要一个该接口的实现来进行实际的Bean的管理,
DefaultListableBeanFactory 就是这么一个比较通用的 BeanFactory 实现类
DefaultListableBeanFactory 除了间接地实现了 BeanFactory 接口,还实现了 BeanDefinitionRegistry 接口,
该接口才是在 BeanFactory 的实现中担当Bean注册管理的角色。
基本上, BeanFactory 接口只定义如何访问容器内管理的Bean的方法,各个 BeanFactory 的具体实现类负责具体Bean的注册以及管理工作。
BeanDefinitionRegistry 接口定义抽象了Bean的注册逻辑。
打个比方说, BeanDefinitionRegistry 就像图书馆的书架,所有的书是放在书架上的。虽然你
还书或者借书都是跟图书馆(也就是 BeanFactory ,或许BookFactory可能更好些)打交道,但书架才
是图书馆存放各类图书的地方。所以,书架相对于图书馆来说,就是它的“BookDefinitionRegistry”。
每一个受管的对象,在容器中都会有一个 BeanDefinition 的实例(instance)与之相对应,该
BeanDefinition 的实例负责保存对象的所有必要信息,包括其对应的对象的class类型、是否是抽象
类、构造方法参数以及其他属性等。当客户端向 BeanFactory 请求相应对象的时候, BeanFactory 会
通过这些信息为客户端返回一个完备可用的对象实例。 RootBeanDefinition 和 ChildBean-
Definition 是 BeanDefinition 的两个主要实现类。
外部配置文件
Spring的IoC容器支持两种配置文件格式:Properties文件格式和XML文件格式
Spring容器最初提供了两种bean的scope类型:singleton和prototype,但发布2.0之后,又引入了另外三种scope类型,即request、session和global session类型
如果你不指定bean的scope,singleton便是容器默认的scope
- request: Spring容器,即 XmlWebApplicationContext 会为每个HTTP请求创建一个全新的 RequestProcessor 对象供当前请求使用,当请求结束后,该对象实例的生命周期即告结束。
- session: Spring容器会为每个独立的session创建属于它们自己的全新的对象实例。
与request相比,除了拥有session scope的bean的实例具有比request scope的bean可能更长的存活时间,其他方面真是没什么差别。 - global session:global session只有应用在基于portlet的Web应用程序中才有意义,它映射到portlet的global范围的session。
如果在普通的基于servlet的Web应用中使用了这个类型的scope,容器会将其作为普通的session类型的scope对待。 - 自定义scope类型:
实现接口:
public interface Scope {
Object get(String name, ObjectFactory objectFactory);
Object remove(String name);
void registerDestructionCallback(String name, Runnable callback);
String getConversationId();
}
实现时get和remove方法是必须要实现
eg:
public class ThreadScope implements Scope {
private final ThreadLocal threadScope = new ThreadLocal() {
protected Object initialValue() {
return new HashMap();
}
};
public Object get(String name, ObjectFactory objectFactory) {
Map scope = (Map) threadScope.get();
Object object = scope.get(name);
if(object==null) {
object = objectFactory.getObject();
scope.put(name, object);
}
return object;
}
public Object remove(String name) {
Map scope = (Map) threadScope.get();
return scope.remove(name);
}
}
FactoryBean(不是BeanFactory)
FactoryBean 是Spring容器提供的一种可以扩展容器对象实例化逻辑的接口
当某些对象的实例化过程过于烦琐,通过XML配置过于复杂,使我们宁愿使用Java代码来完成这个实例化过程的时候
或者,某些第三方库不能直接注册到Spring容器的时候,就可以实现 org.springframework.beans.factory.FactoryBean 接口,给出自己的对象实例化逻辑代码。
public interface FactoryBean {
Object getObject() throws Exception;
Class getObjectType();
boolean isSingleton();
}
getObject() 方法会返回该 FactoryBean “生产”的对象实例,我们需要实现该方法以给出自己的对象实例化逻辑;
getObjectType() 方法仅返回 getObject() 方法所返回的对象的类型,如果预先无法确定,则返回 null ;
isSingleton() 方法返回结果用于表明,工厂方法( getObject() )所生产的对象是否要以singleton形式存在于容器中。如果以singleton形式存在,则返回 true ,否则返回 false
eg: 获取下一天的bean
public class NextDayDateFactoryBean implements FactoryBean {
public Object getObject() throws Exception {
return new DateTime().plusDays(1);
}
public Class getObjectType() {
return DateTime.class;
}
public boolean isSingleton() {
return false;
}
}
注册到容器
<bean id="nextDayDateDisplayer" class="...NextDayDateDisplayer">
<property name="dateOfNextDay">
<ref bean="nextDayDate"/>
</property>
</bean>
<bean id="nextDayDate" class="...NextDayDateFactoryBean">
</bean>
使用
public class NextDayDateDisplayer
{
private DateTime dateOfNextDay;
// 相应的setter方法
// ...
}
- 常见的 FactoryBean 实现
JndiObjectFactoryBean
LocalSessionFactoryBean
SqlMapClientFactoryBean
ProxyFactoryBean
TransactionProxyFactoryBean
springioc容器分为两个阶段:容器启动阶段和Bean实例化阶段
Spring的IoC容器在实现的时候,充分运用了这两个实现阶段的不同特点,在每个阶段都加入了相应的容器扩展点,以便我们可以根据具体场景的需要加入自定义的扩展逻辑
1.启动阶段:
容器启动伊始,首先会通过某种途径加载Configuration MetaData。除了代码方式比较直接,在大部分情况下,容器需要依赖某些工具类( BeanDefinitionReader )对加载的Configuration MetaData
进行解析和分析,并将分析后的信息编组为相应的 BeanDefinition ,最后把这些保存了bean定义必要信息的 BeanDefinition ,注册到相应的 BeanDefinitionRegistry ,这样容器启动工作就完成了。
2.bean实例化阶段:
经过第一阶段,现在所有的bean定义信息都通过BeanDefinition的方式注册到了 BeanDefinitionRegistry中。当某个请求方通过容器的getBean方法明确地请求某个对象,或者因依赖关系容器需要隐式地调用getBean方法时,就会触发第二阶段的活动。
该阶段,容器会首先检查所请求的对象之前是否已经初始化。如果没有,则会根据注册的BeanDefinition所提供的信息实例化被请求对象,并为其注入依赖。如果该对象实现了某些回调接口,也会根据回调接口的要求来装配它。当该对象装配完毕之后,容器会立即将其返回请求方使用。
如果说第一阶段只是根据图纸装配生产线的话,那么第二阶段就是使用装配好的生产线来生产具体的产品了。
Spring提供了一种叫做BeanFactoryPostProcessor的容器扩展机制。该机制允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做相应的修改
因为Spring已经提供了几个现成的BeanFactoryPostProcessor 现类,已经实现了BeanFactoryPostProcessor接口
两个比较常用的: PropertyPlaceholderConfigurer和OverrideConfigurer
1.PropertyPlaceholderConfigurer
通常情况下,我们不想将类似于系统管理相关的信息同业务对象相关的配置信息混杂到XML配置文件中,以免部署或者维护期间因为改动繁杂的XML配置文件而出现问题。我们会将一些数据库连接信息、邮件服务器等相关信息单独配置到一个properties文件中,这样,如果因系统资源变动的话,只需要关注这些简单properties配置文件即可。
eg:
<property name="username">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
然后在属性文件配置
jdbc.driver=com.mysql.jdbc.Driver
jdbc.username=your username
当BeanFactory在第一阶段加载完成所有配置信息时,BeanFactory中保存的对象的属性信息还只是以占位符的形式存在,如${jdbc.url}、${jdbc.driver}。
当PropertyPlaceholderConfigurer作为BeanFactoryPostProcessor被应用时,它会使用properties配置文件中的配置信息来替换相应BeanDefinition中占位符所表示的属性值。这样,当进入容器实现的第二阶段实例化bean时,bean定义中的属性值就是最终替换完成的了
PropertyPlaceholderConfigurer不单会从其配置的properties文件中加载配置项,同时还会检查Java的System类中的Properties,可以通过setSystemPropertiesMode()或者 setSystemPropertiesModeName()来控制是否加载或者覆盖System相应Properties的行为。
2.PropertyOverrideConfigurer
eg:配置数据库配置信息
dataSource.minEvictableIdleTimeMillis=1000
dataSource.maxActive=50
当按照如下代码,将PropertyOverrideConfigurer加载到容器之后,dataSource原来定义的默认值就会被pool-adjustment.properties文件中的信息所覆盖:
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="location" value="pool-adjustment.properties"/>
</bean>
3.CustomEditorConfigurer
CustomEditorConfigurer是另一种类型的BeanFactoryPostProcessor实现,它只是辅助性地将后期会用到的信息注册到容器,对BeanDefinition没有做任何变动
不管对象是什么类型,也不管这些对象所声明的依赖对象是什么类型,通常都是通过XML(或者properties甚至其他媒介)文件格式来配置这些对象类型。
但XML所记载的,都是String类型,即容器从XML格式的文件中读取的都是字符串形式,最终应用程序却是由各种类型的对象所构成。
要想完成这种由字符串到具体对象的转换(不管这个转换工作最终由谁来做),都需要这种转换规则相关的信息,而 CustomEditorConfigurer 就是帮助我们传达类似信息的。
Spring内部通过JavaBean的 PropertyEditor 来帮助进行 String 类型到其他类型的转换工作。只要为每种对象类型提供一个PropertyEditor
就可以根据该对象类型取得与其相对应的PropertyEditor 来做具体的类型转换。
-
自定义 PropertyEditor
-
通过CustomEditorConfigurer的propertyEditorRegistrars注册自定义PropertyEditor
baen实例化过程
1.Bean的实例化与BeanWrapper
容器在内部实现的时候,采用策略模式来决定采用何种方式初始化bean实例。
通常,可以通过反射或者CGLIB动态字节码生成来初始化相应的bean实例或者动态生成其子类。
org.springframework.beans.factory.support.InstantiationStrategy
定义是实例化策略的抽象接口,其直接子类SimpleInstantiationStrategy实现了简单的对象实例化功能,
可以通过反射来实例化对象实例,但不支持方法注入方式的对象实例化。 CglibSubclassingInstantiationStrategy继承了SimpleInstantiationStrategy的以反射方式实例化对象的功能,并且通过CGLIB的动态字节码生成功能,该策略实现类可以动态生成某个类的子类,进而满足了方法注入所需的对象实例化需求。默认情况下,容器内部采用的是CglibSubclassingInstantiationStrategy。
容器只要根据相应bean定义的BeanDefintion取得实例化信息,结合CglibSubclassingInstantiationStrategy以及不同的bean定义类型,就可以返回实例化完成的对象实例。
这个返回的对象不是实际的对象,而是BeanWrapper实例
有了BeanWrapper就不用使用反射了,代替了反射的功能
2.各种aware接口
当对象实例化完成并且相关属性以及依赖设置完成之后,Spring容器会检查当前对象实例是否实现了一系列的以Aware命名结尾的接口定义
如果是,则将这些Aware接口定义中规定的依赖注入给当前对象实例。
对于ApplicationContext类型容器,容器在这一步还会检查以下几个Aware接口并根据接口定义设置相关依赖。
-
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 接口,则会将自身注入当前对象实例。
3.BeanPostProcessor
BeanPostProcessor会处理容器内所有符合条件的实例化后的对象实例。该接口声明了两个方法,分别在两个不同的时机执行
public interface BeanPostProcessor
{
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
4.InitializingBean和init-method
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
该接口定义很简单,其作用在于,在对象实例化过程调用过“ BeanPostProcessor 的前置处理”之后,会接着检测当前对象是否实现了 InitializingBean 接口,
如果是,则会调用其 afterPropertiesSet() 方法进一步调整对象实例的状态。
5.DisposableBean与destroy-method
当所有的一切,该设置的设置,该注入的注入,该调用的调用完成之后,容器将检查singleton类型的bean实例,看其是否实现了 org.springframework.beans.factory.DisposableBean接口。
或者其对应的bean定义是否通过
以便在这些singleton类型的对象实例销毁之前,执行销毁逻辑。
对于 BeanFactory 容器来说。我们需要在独立应用程序的主程序退出之前,或者其他被认为是合适的情况下(依照应用场景而定)
调用ConfigurableBeanFactory提供的destroySingletons()方法销毁容器中管理的所有singleton类型的对象实例
SpringIoC容器ApplicationContext
Spring中的Resource
Spring框架内部使用org.springframework.core.io.Resource接口作为所有资源的抽象和访问接口
Resource接口可以根据资源的不同类型,或者资源所处的不同场合,给出相应的具体实现。Spring框架在这个理念的基础上,提供了一些实现类
(ByteArrayResource ClassPathResource FileSystemResource UrlResource InputStreamResource)
ResourceLoader:资源查找定位策略的统一抽象
默认实现类:DefaultResourceLoader
(1) 首先检查资源路径是否以 classpath:前缀打头,如果是,则尝试构造 ClassPathResource 类型资源并返回。
(2) 否则:
(a) 尝试通过URL,根据资源路径来定位资源,如果没有抛出 MalformedURLException ,有则会构造 UrlResource 类型的资源并返回
(b)如果还是无法根据资源路径定位指定的资源,则委派getResourceByPath(String) 方 法 来 定 位 ,
DefaultResourceLoader 的getResourceByPath(String) 方法默认实现逻辑是,构造 ClassPathResource 类型的资源并返回。
Spring AOP实现机制
springaop实现机制:动态代理机制和字节码生成都是在运行期间为目标对象生成一个代理对象,将横切的逻辑放入这个代理对象中,系统最终用的是代理对象,而不是真正的目标对象
Spring中 以接口定义PointCut作为所有PointCut顶层抽象,接口定义了两个方法帮助捕获系统中相应的JoinPoint,并提供一个TruePointCut类型实例
如果Pointcut类型为TruePointcut,默认为系统中所有对象
isRuntime() 如果是false,则表示不忽略方法的参数,这种类型的MethodMatcher称之为StaticMethodMatcher,只会执行不带参数的matches方法
如果返回true,则每次调用都会对方法调用的参数检查,无法对匹配结果进行缓存,匹配效率差一些,这种类型的MethodMatcher称之为DynamicStaticMethodMatcher
如果为ture则会对方法的参数
常见pointcut
ThrowsAdvice
- Order:指定Advice的顺序
- Advice:单一横切关注点逻辑的载体
- Aspect:对系统中横切关注点逻辑进行模块化封装的aop概念体
- SpringAOP中的Advice
eg:资源初始化:
- ThrowAdvice
eg:如果出现异常则立马发邮件
- AroudAdvice