作用域分为两类,第一类为框架内置的作用域,第二类为自定义的作用域。
1、内置作用域
1.1 Singleton
对于同一个IOC容器,每次获取对象,都返回相同的实例。它是默认的作用域
/** * * @Title: testUserScope * @Description: 测试User对象的作用域 */ public static void testUserScope() { // 获取applicationContext对象 ApplicationContext ac = getContext(); // 获取User实例 User user1 = ac.getBean("singleton_user",User.class); User user2 = ac.getBean("singleton_user",User.class); System.out.println(user1 == user2);// 返回true }
与单例模式不同,单例模式是指在每个JVM中只存在一个实例,而这里是指每个IOC容器,即每一个applicationContext对象。
1.2 Prototype
对于同一个IOC容器,每次获取对象,都会返回不同的实例
// 获取prototype的User实例 User user3 = ac.getBean("prototype_user",User.class); User user4 = ac.getBean("prototype_user",User.class); System.out.println(user3 == user4); // 返回false
当singleton的beanA依赖singleton的beanB时,beanA和beanB都只会创建一次,并且beanB在beanA之前创建
当singleton的beanA依赖prototype的beanB时,理论上每次获取beanA都应该返回不同的beanB,但实际上每次获取得到的beanB是相同的,是因为对象beanA只会创建一次,依赖关系在创建过程中已经决定。
// 获取User实例 User user1 = ac.getBean("singleton_user",User.class); User user2 = ac.getBean("singleton_user",User.class); System.out.println(user1.getHomeAddress() == user2.getHomeAddress());
当prototype的beanA依赖prototype的beanB时,每次获取都会创建beanA和beanB,并且beanB在beanA之前创建。
当prototype的beanA依赖singleton的beanB时,每次获取都会创建beanA,如果beanB不存在,则创建beanB,在beanA之前创建。之后都会使用已创建的beanB。
1.3 方法注入
当singleton的beanA依赖prototype的beanB时,要达到每次获取beanB不同,只能通过方法注入。本示例中User类为beanA,Address类为beanB。
第一种方法注入方式,beanA实现ApplicationContextAware接口,在获取beanB时通过applicationContext对象获取
public class User implements ApplicationContextAware { // applicationContext对象, private ApplicationContext applicationContext; // 用户的家庭地址 private Address homeAddress; // 通过applicationContext获取,不再是this.homeAddress public Address getHomeAddress() { return this.applicationContext.getBean("address", Address.class); } public void setApplicationContext(ApplicationContext arg0) throws BeansException { this.applicationContext = arg0; } }
第二种方式使用lookup-method子标签,此时该方法只能是抽象方法
public abstract Address getHomeAddress();
在配置文件中,添加如下配置
<bean id="address" class="com.bean.Address" scope="prototype"/> <!-- 定义User类,它的作用域为singleton --> <bean id="singleton_user" class="com.bean.User"> <lookup-method name="getHomeAddress" bean="address"/> </bean>
其中name属性为方法的名称,方法的返回值为Address类型。Bean属性值为Address类的bean ID。
其实无论哪种方式,都存在缺点,但个人建议采用第一种方式,因为第二种必须包含抽象方法,类必须为抽象类。
1.4 request
request的作用域只有在Web application下才会有效果。在使用之前,需要将HttpRequest对象与处理请求的线程进行关联。关联的方式有三种
- 如果使用spring mvc框架,无需任何额外配置,只需要配置DispatcherServlet。在Servlet 3.0以下,在web.xml中配置DispatcherServlet,在Servlet 3.0以上,实现WebApplicationInitializer接口,具体的代码参考mvc官网。
- 添加RequestContextFilter。
- 添加RequestContextListener。
1.5 Session & Application & webSocket
Session也只有在web application下才有用,对应Session的作用域。
Application对应web application的Application作用域。HttpRequest,Session,Application这些都是Servlet的九大内置对象。
webSocket对应webSocket的作用域。
1.6 代理
当短周期的bean A依赖长周期的bean B时,此时bean B 在bean A的生命周期中是一直存在的。
当长周期的bean A依赖短周期的bean B时,此时bean B在其生命周期结束后,由于有bean A的引用,所以不会被垃圾回收,所以造成在bean A的整个生命周期中,一直引用同一个bean B,相当于延长了bean B的生命周期。这种情况是不允许出现的。例如Application应用中session对象一直不被回收,或者引用的是同一个session,后果是可想而知的。在这种情况下需要使用代理。
代理的实现方式有两种,JDK和CGLIb,JDK方式下被代理类必修实现某个接口,否则会出错,所以一般使用CGLib。它的配置如下:
<!-- 定义Address类,它的作用域为request --> <bean id="address" class="com.bean.Address" scope="request"> <aop:scoped-proxy/> </bean> <!-- 定义User类,它的作用域为session --> <bean id="session_user" class="com.bean.User" scope="session" > <property name="homeAddress" ref="address"/> </bean>
当aop:scoped-proxy的proxy-target-class属性为false时,为JDK方式。属性的默认值为true。
默认情况下,代理类会被删除,如果需要深入了解,可以查看代理究竟产生哪些class,以及它们的源码。
通过设置DEBUG_LOCATION_PROPERTY属性,代理类编译后的class都会存放在D:code目录下。
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\code");
2、自定义作用域
编写自定义作用域的步骤只有两步:
- 编写自定义XXScope,实现Scope接口
- 注册XXScope,通过ConfigurableBeanFactory的registerScope方法,它的第一个参数为作用域的名称,第二个参数为作用域对象。
Scope接口如下,Spring框架中有simpleThreadScope,但是没有注册,可以尝试注册线程作用域作为练习。
public interface Scope { /** * * @Title: get * @Description: 从当前作用域中获取bean对象 * @param name bean的标识,ID,name或alias * @param objectfactory * @return */ Object get(String name, ObjectFactory objectfactory); /** * * @Title: remove * @Description: 从当前作用域中移除bean对象 * @param name * @return */ Object remove(String name); /** * * @Title: registerDestructionCallback * @Description: 当对象被销毁时,调用runnable中的execute方法,相当于在销毁过程中,注入回调函数 * @param name * @param runnable */ void registerDestructionCallback(String name, Runnable runnable); /** * * @Title: resolveContextualObject * @Description: 获取当前上下文中的变量,它们以key-value形式存在。 * @param key 变量的key * @return */ Object resolveContextualObject(String key); /** * * @Title: getConversationId * @Description: 获取作用域对象的唯一标识,当为session对象时获取sessionId,当为Thread时,获取PID * @return */ String getConversationId(); }