目录
Scope种类
在 Spring 里面,可以设置创建 bean 的作用域。
目前Spring Bean的作用域或者说范围主要有五种。
作用域 | 描述 |
---|---|
singleton | 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,bean作用域范围的默认值。 |
prototype | 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()。 |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于web的Spring WebApplicationContext环境。 |
session | 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。该作用域仅适用于web的Spring WebApplicationContext环境。 |
application | 限定一个Bean的作用域为ServletContext的生命周期。该作用域仅适用于web的Spring WebApplicationContext环境。 |
Singleton
表示Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。spring中作用域默认是singleton。要在XML中将bean定义成singleton,可以这样配置:
<bean id="ServiceImpl" class="cn.example.service.ServiceImpl" scope="singleton">
Prototype
表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:
<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>
<!-- 或者 -->
<bean id="account" class="com.foo.DefaultAccount" singleton="false"/>
Request
表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。
针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。
Session
表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。
Global Session
表示在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于web的Spring ApplicationContext情形下有效。
global session作用域类似于标准的HTTP Session作用域,不过仅仅在基于portlet的web应用中才有意义。Portlet规范定义了全局Session的概念,它被所有构成某个portlet web应用的各种不同的portlet所共享。在global session作用域中定义的bean被限定于全局portlet Session的生命周期范围内。
Scope失效
为什么失效
案例演示:
创建F1,设置为多例,
package com.cys.spring.chapter07;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("prototype")
public class F1 {
}
在配置类中注入F1:
package com.cys.spring.chapter07;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class E {
@Autowired
private F1 f1;
private F1 getF1() {
return f1;
}
}
测试类
package com.cys.spring.chapter07;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan("com.cys.spring.chapter07")
public class TestScope {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestScope.class);
E e = context.getBean(E.class);
// 打印两次观察两个F1是否是同一个
System.out.println(e.getF1().getClass());
System.out.println(e.getF1());
System.out.println(e.getF1());
context.close();
}
}
运行结果两个对象是同一个
class com.cys.spring.chapter07.F1$$EnhancerBySpringCGLIB$$6816bc69
com.cys.spring.chapter07.F1@2d710f1a
com.cys.spring.chapter07.F1@2d710f1a
而我们期望的是多例,不同对象。
那是因为E是单例的,单例对象的依赖注入只会执行一次,因此E的f1属性总是第一次依赖注入的对象。
问题就是单例对象注入多例对象时,多例注入失效,注入的是同一个对象。
那么如何解决呢?
失效解决
使用@Lazy 进行代理生成对象
@Lazy
是一个Spring框架中的注解,用于实现延迟加载(懒加载)的Bean。当我们在Spring配置中标记一个Bean为@Lazy时,这个Bean的初始化会被推迟到第一次使用时才进行,而不是在容器启动时立即进行。
这个注解通常与@Component、@Service、@Repository等注解一起使用,来标记类为Spring容器管理的Bean。当这些类被标记为@Lazy时,它们的实例化会在第一次被注入或使用时才进行,而不是在容器启动时。
此外,@Lazy注解还可以用于方法上,特别是与@Bean注解一起使用时。当方法上标记了@Lazy,该方法返回的Bean对象会在第一次使用时才进行初始化。
修改E如下:
package com.cys.spring.chapter07;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@Component
public class E {
@Lazy
@Autowired
private F1 f1;
public F1 getF1() {
return f1;
}
}
再次运行结果如下:
com.cys.spring.chapter07.F1@53f3bdbd
com.cys.spring.chapter07.F1@7d61eb55
指定Scope的proxyMode属性
proxyMode
属性是 Spring 框架中 @Scope
注解的一个属性,它用于配置当前类的代理模式。这个属性主要用于非 Singleton 作用域的 Bean。因为在非 Singleton 作用域中,Spring 并不会立即创建 Bean 的实例,而是根据需要才创建。当需要注入时就产生一个代理对象,这时 proxyMode
属性就起作用了。
proxyMode
属性有几个可选值:
- DEFAULT(或 NO):这是默认值,表示不使用代理。如果需要 Bean 的实例,Spring 会立即创建它。
- INTERFACES:表示使用 JDK 提供的动态代理来实现代理。这通常适用于 Bean 实现了至少一个接口的情况。
- TARGET_CLASS:表示使用 CGLib 库来实现动态代理。这通常适用于 Bean 没有实现任何接口的情况。
使用 proxyMode
属性可以在不改变原有代码结构的情况下,通过声明开启作用域代理来实现一些高级功能,比如在单例 Bean 中使用多实例 Bean。
需要注意的是,proxyMode
属性的设置应该根据具体的业务需求和 Bean 的特点来选择。同时,还需要注意代理模式可能对性能产生的影响,因此在选择代理模式时需要权衡利弊。
这里我们指定Scope的proxyMode
属性为ScopedProxyMode.TARGET_CLASS
,
新建F2如下:
package com.cys.spring.chapter07;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class F2 {
}
修改E
package com.cys.spring.chapter07;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@Component
public class E {
@Lazy
@Autowired
private F1 f1;
@Autowired
private F2 f2;
public F1 getF1() {
return f1;
}
public F2 getF2() {
return f2;
}
}
测试类:
package com.cys.spring.chapter07;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan("com.cys.spring.chapter07")
public class TestScope {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestScope.class);
E e = context.getBean(E.class);
// 打印两次观察两个F1是否是同一个
// System.out.println(e.getF1().getClass());
// System.out.println(e.getF1());
// System.out.println(e.getF1());
System.out.println(e.getF2().getClass());
System.out.println(e.getF2());
System.out.println(e.getF2());
context.close();
}
}
运行结果如下:
class com.cys.spring.chapter07.F2$$EnhancerBySpringCGLIB$$cedb4f5a
com.cys.spring.chapter07.F2@5170bcf4
com.cys.spring.chapter07.F2@2812b107
使用工厂来创建
如下,创建一个F3,指定多例,在E中代码如下:
package com.cys.spring.chapter07;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@Component
public class E {
@Lazy
@Autowired
private F1 f1;
@Autowired
private F2 f2;
@Autowired
private ObjectFactory<F3> f3;
public F1 getF1() {
return f1;
}
public F2 getF2() {
return f2;
}
public F3 getF3() {
return f3.getObject();
}
}
测试类
package com.cys.spring.chapter07;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan("com.cys.spring.chapter07")
public class TestScope {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestScope.class);
E e = context.getBean(E.class);
System.out.println(e.getF3().getClass());
System.out.println(e.getF3());
System.out.println(e.getF3());
context.close();
}
}
运行结果如下:
class com.cys.spring.chapter07.F3
com.cys.spring.chapter07.F3@6304101a
com.cys.spring.chapter07.F3@2812b107
直接使用容器中获取
将容器注入到E,在E中直接从容器中获取
修改E如下:
package com.cys.spring.chapter07;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@Component
public class E {
@Lazy
@Autowired
private F1 f1;
@Autowired
private F2 f2;
@Autowired
private ObjectFactory<F3> f3;
@Autowired
private ApplicationContext applicationContext;
public F1 getF1() {
return f1;
}
public F2 getF2() {
return f2;
}
public F3 getF3() {
return f3.getObject();
}
public F4 getF4() {
return applicationContext.getBean(F4.class);
}
}
将applicationContext
注入到E中,然后通过applicationContext获取F4的对象。