一、Bean作用域
用来声明IOC容器中的对象应该处的限定场景或者说该对象的存活空间称作Bean的作用域(Scope)。
IOC容器在对象进入相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁这些对象。
默认是单例模式,@Scope("singleton")。
- singleton:全局有且仅有一个实例
- prototype:每次获取Bean的时候会有一个新的实例
- request:表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效。
- session:表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效。
- application:将单个bean定义限定为ServletContext的生命周期。仅在支持web的Spring应用程序上下文中有效。
- websocket:将单个bean定义限定为WebSocket的生命周期。仅在支持web的Spring应用程序上下文中有效。
Singleton Bean 和 prototype Bean 的区别
当一个bean被声明为单例bean(singleton)的时候,在处理多次请求的时候Spring容器中只会实例化出一个bean,且后续的请求都会共用这个对象,这个对象将被保存在一个Map集合中。当有请求来时则先从缓存中查看之前有无生成该对象,有的话直接使用这个对象,没有则实例化一个新的对象。而对于原型bean(Prototype)来说,每次请求来时都直接实例化新的bean,没有从缓存中查询获取的过程。
单例Bean的优势
- 减少了新生成实例的消耗。这个消耗主要体现在 ①Spring在创建实例时会造成性能的消耗。②给对象分配内存也会涉及到一些复杂的算法。
- 可以快速的获取到bean。在单例下的bean除了第一次生成时需要创建bean外,其余时间都是从缓存(Map)中获取,所以速度快。
- 减少jvm垃圾回收。对于所有请求只生成一个bean实例,所以垃圾回收自然就少了。
单例Bean的劣势
单例bean的一个很大劣势是是线程不安全的。由于所有请求都共享一个bean实例,且当这个bean有状态的时候在并发的场景下容易出现问题,相比之下原型bean则不会有这样的问题(也有例外,比如这个原型bean被单例bean所依赖),因为原型bean会给每个请求都新创建实例。
什么是有状态Bean和无状态Bean
有状态会话bean :每个用户有自己特有的一个实例,在用户的生存期内,bean保持了用户的信息,即“有状态”;一旦用户灭亡(调用结束或实例结束),bean的生命期也告结束。即每个用户最初都会得到一个初始的bean。简单来说,有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象 ,可以保存数据,是非线程安全的。
无状态会话bean :bean一旦实例化就被加进会话池中,各个用户都可以共用。即使用户已经消亡,bean的生命期也不一定结束,它可能依然存在于会话池中,供其他用户调用。由于没有特定的用户,那么也就不能保持某一用户的状态,所以叫无状态bean。但无状态会话bean 并非没有状态,如果它有自己的属性(变量),那么这些变量就会受到所有调用它的用户的影响,这是在实际应用中必须注意的。简单来说,无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象 .不能保存数据,是不变类,是线程安全的。
不变模式
不变模式只涉及到一个类。一个类的内部状态创建后,在整个生命期间都不会发生变化时,这样的类称为不变类。这种使用不变类的做法叫做不变模式。
不变模式有两种形:一种是弱不变模式,另一种是强不变模式。
弱不变模式
一个类的实例的状态是不可变化的;但是这个类的子类的实例具有可能会变化的状态。这样的类符合弱不变模式的定义。要实现弱不变模式,一个类必须满足下列条件:
- 所考虑的对象没有任何方法会修改对象的状态;当对象的构造方法将对象的状态初始化之后,对象的状态将不再改变。
- 所有的属性都应当是私有的。不要声明任何公开的属性,以防客户端对象直接修改任何内部状态。
- 这个对象所引用的其他对象如果是可变对象的话,必须设法限制外界对这些可变对象的访问,以防止外界修改这些对象。
弱不变模式的缺点:
- 一个弱不变对象的子对象是可以改变的;换言之,一个弱不变对象的子对象可能是可变的。
- 这个可变的子对象可能可以修改父对象的状态,从而可能会允许外界修改父对象的状态。
强不变模式
一个类的实例的状态不会改变, 同时它的子类的实例也具有不可变化的状态。这样的类符合强不变模式,一个类必须首先满足弱不变模式所要求的所有条件,并且还要满足下面条件之一:
- 所考虑的类所有的方法都应该是final的;这个类的子类不能够重写此类的方法;
- 类本身是final的,那么这个类也就不可能有子类。
不变模式在Java中的应用
- String类
- 其他包装类,如Integer、Float、Double、Byte、Long、Short和Character等。
不变模式的优缺点
优点:
- 不能修改一个不变对象的状态,所以可以避免由此引起的不必要的程序错误,易于维护。
- 不变对象本身是线程安全的,这样可以省掉处理同步化的开销。
缺点:
一旦需要修改一个对象的状态,就只好创建一个新的同类对象。在需要频繁修改不变对象的环境里,会有大量的不变对象作为中间结果被创建出来再被GC收集,这是一种资源上的浪费。
二、Bean定制
Spring框架提供了许多接口,可以用来定制bean。
生命周期回调
为了与容器对bean生命周期的管理进行交互,可以实现Spring的InitializationBean 和 DisposableBean接口。容器为前者调用afterPropertiesSet(),为后者调用destroy(),让bean在初始化和销毁bean时执行某些操作。
JSR-250提供的@PostConstruct和@PreDestroy注解通常被认为是Spring应用程序中接收生命周期回调的最佳实践。使用这些注释意味着bean不耦合到Spring特定的接口。
如果您不想使用JSR-250注解,但仍然希望消除耦合,请考虑init method和destroy method bean定义元数据。
ApplicationContextAware 和 BeanNameAware
当ApplicationContext创建实现org.springframework.context.ApplicationContextAware接口,则为实例提供对该ApplicationContext的引用。
public interface ApplicationContextAware { void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }
通过该种方式,bean可以通过ApplicationContext接口或通过将引用强制转换为该接口的已知子类(例如ConfigurableApplicationContext,它公开了附加功能),以编程方式操作创建它们的ApplicationContext。一个用途是对其他bean进行编程式检索。有时这种能力很有用。但是,一般来说,应该避免它,因为它将代码与Spring耦合。ApplicationContext的其他方法提供对文件资源的访问、发布应用程序事件和访问MessageSource。这些附加功能在ApplicationContext的附加功能中进行了描述。
import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public final class SpringBeanHelper implements ApplicationContextAware { private SpringBeanHelper(){} private static ApplicationContext applicationContext = null; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { if(SpringBeanHelper.applicationContext == null){ SpringBeanHelper.applicationContext = applicationContext; } } //通过name获取 Bean. public static Object getBean(String name){ return SpringBeanHelper.applicationContext.getBean(name); } //通过class获取Bean. public static <T> T getBean(Class<T> clazz){ return SpringBeanHelper.applicationContext.getBean(clazz); } //通过name,以及Clazz返回指定的Bean public static <T> T getBean(String name,Class<T> clazz){ return SpringBeanHelper.applicationContext.getBean(name, clazz); } }
当ApplicationContext创建实现org.springframework.beans.工厂.BeanNameAware接口,则向类提供对其关联对象定义中定义的名称的引用。
public interface BeanNameAware { void setBeanName(String name) throws BeansException; }
在填充普通bean属性之后,但在初始化回调(如initializegbean、afterPropertiesSet或自定义init方法)之前调用回调。
ApplicationContextAware 和 BeanNameAware在执行顺序上,先执行BeanNameAware接口的setBeanName方法,再执行ApplicationContextAware接口的setApplicationContext方法。
Aware接口列表
- ApplicationContextAware
- ApplicationEventPublisherAware
- BeanClassLoaderAware
- BeanFactoryAware
- BeanNameAware
- BootstrapContextAware
- LoadTimeWeaverAware
- MessageSourceAware
- NotificationPublisherAware
- ResourceLoaderAware
- ServletConfigAware
- ServletContextAware
三、容器扩展点
BeanPostProcessor
BeanPostProcessor接口定义回调方法,你可以实现这些方法来提供自己的(或覆盖容器的默认)实例化逻辑、依赖关系解析逻辑等。如果你想在Spring容器完成对bean的实例化、配置和初始化之后实现一些自定义逻辑,那么可以插入一个或多个自定义beanoptprocessor实现。
public interface BeanPostProcessor { //bean初始化方法调用前被调用 Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; //bean初始化方法调用后被调用 Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; }
你可以配置多个BeanPostProcessor实例,并且可以通过设置order属性来控制这些BeanPostProcessor实例的执行顺序。仅当BeanPostProcessor实现有序接口时,才能设置此属性。如果你编写自己的BeanPostProcessor,你也应该考虑实现有序接口。
BeanPostProcessor实例的作用域为容器(ApplicationContext),只对该容器中的bean进行后期处理。
要更改实际的bean定义,您需要使用BeanFactoryPostProcessor。
BeanFactoryPostProcessor
BeanFactoryPostProcessor操作bean配置元数据。
可以配置多个BeanFactoryPostProcessor实例,并且可以通过设置order属性来控制这些BeanFactoryPostProcessor实例的运行顺序。但是,只有在BeanFactoryPostProcessor实现有序接口时才能设置此属性。如果你编写自己的BeanFactoryPostProcessor,也应该考虑实现有序接口。
如果要更改实际的bean实例(即从配置元数据创建的对象),则需要使用BeanPostProcessor(前面在使用BeanPostProcessor定制bean中进行了描述)。而在技术上可以在BeanFactoryPostProcessor中使用bean实例(例如,使用BeanFactory.getBean()),这样做会导致过早的bean实例化,违反标准容器生命周期。这可能会产生负面的副作用,比如绕过bean的后期处理。
ApplicationContext会自动检测部署到其中实现BeanFactoryPostProcessor接口的任何bean。它在适当的时候使用这些bean作为bean工厂的后处理程序。您可以像部署任何其他bean一样部署这些后处理器bean。
FactoryBean
FactoryBean接口是springioc容器实例化逻辑的一个可插拔点。如果您的复杂初始化代码可以更好地用Java表示,而不是(可能)冗长的XML,那么您可以创建自己的FactoryBean,在该类中编写复杂的初始化,然后将自定义的FactoryBean插入容器中。
FactoryBean接口提供了三种方法:
- Object getObject():返回此工厂创建的对象的实例。实例可以共享,这取决于这个工厂返回的是单例实例还是原型。
- boolean isSingleton():如果此FactoryBean是singleton,则返回true,否则返回false
- Class getObjectType():返回由getObject()方法返回的对象类型,如果类型事先未知,则返回null。
FactoryBean的概念和接口在Spring框架中的许多地方都有使用。超过50个FactoryBean接口的实现随Spring一起提供。
当您需要向容器请求一个实际的FactoryBean实例本身,而不是它生成的bean时,在调用ApplicationContext的getBean()方法时,在bean的id前面加上(&)。因此,对于一个id为myBean的给定FactoryBean,在容器上调用getBean(“myBean”)将返回FactoryBean的产品,而调用getBean(“&myBean”)则返回FactoryBean实例本身。