在当前版本的 Spring 和 Spring Boot 程序中,支持五种 Scope
singleton,容器启动时创建(未设置延迟),容器关闭时销毁
prototype,每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory.destroyBean(bean) 销毁
request,每次请求用到此 bean 时创建,请求结束时销毁
session,每个会话用到此 bean 时创建,会话结束时销毁
application,web 容器用到此 bean 时创建,容器停止时销毁
但要注意,如果在 singleton 注入其它 scope 都会有问题。
主程序类
- @SpringBootApplication
- public class A08Application_1 {
- public static void main(String[] args) {
- SpringApplication.run(A08Application_1.class, args);
- }
- }
BeanForRequest类
- @Scope("request")
- @Component
- public class BeanForRequest {
- private static final Logger log = LoggerFactory.getLogger(BeanForRequest.class);
-
- @PreDestroy
- public void destroy() {
- log.debug("destroy");
- }
-
- }
BeanForSession类
- @Scope("session")
- @Component
- public class BeanForSession {
- private static final Logger log = LoggerFactory.getLogger(BeanForSession.class);
-
- @PreDestroy
- public void destroy() {
- log.debug("destroy");
- }
- }
BeanForApplication类
- @Scope("application")
- @Component
- public class BeanForApplication {
- private static final Logger log = LoggerFactory.getLogger(BeanForApplication.class);
-
- @PreDestroy
- public void destroy() {
- log.debug("destroy");
- }
- }
MyController类,使用@Lazy的原因后面会说
- @RestController
- public class MyController {
-
- @Lazy
- @Autowired
- private BeanForRequest beanForRequest;
-
- @Lazy
- @Autowired
- private BeanForSession beanForSession;
-
- @Lazy
- @Autowired
- private BeanForApplication beanForApplication;
-
- @GetMapping(value = "/test", produces = "text/html")
- public String test(HttpServletRequest request, HttpSession session) {
- ServletContext sc = request.getServletContext();
- String sb = "
"
+ - "
- "
+ "request scope:" + beanForRequest + "" + - "
- "
+ "session scope:" + beanForSession + "" + - "
- "
+ "application scope:" + beanForApplication + "" + - "";
- return sb;
- }
-
- }
打开不同的浏览器,刷新 http://localhost:8080/test 即可查看效果,如果 jdk > 8, 运行时请添加
--add-opens java.base/java.lang=ALL-UNNAMED
原因:jdk >= 9 如果反射调用 jdk 中方法,会报IllegalAccessException
可以通过 server.servlet.session.timeout=10s 观察 session bean 的销毁,但是检查session是否过期的间隔是一分钟,所以得等一分钟才能看到session bean被销毁。
结果:刷新一次浏览器 request bean 对象会变,打开不同的浏览器 session bean 会变。
以单例注入多例为例
有一个单例对象 E
- @Component
- public class E {
- @Autowired
- @Lazy
- private F1 f1;
-
- public F1 getF1() {
- return f1;
- }
- }
要注入的对象 F 期望是多例
- @Scope("prototype")
- @Component
- public class F1 {
- }
主程序类
- @ComponentScan("com.itheima.a08.sub")
- public class A08Application_2 {
-
- private static final Logger log = LoggerFactory.getLogger(A08Application_2.class);
-
- public static void main(String[] args) {
- AnnotationConfigApplicationContext context =
- new AnnotationConfigApplicationContext(A08Application_2.class);
-
- E e = context.getBean(E.class);
-
- log.debug("{}", e.getF1());
- log.debug("{}", e.getF1());
- log.debug("{}", e.getF1());
-
- context.close();
- }
- }
结果:
- [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F1@6bf0219d
- [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F1@6bf0219d
- [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F1@6bf0219d
发现它们是同一个对象,而不是期望的多例对象。
对于单例对象来讲,依赖注入仅发生了一次,后续再没有用到多例的 F,因此 E 用的始终是第一次依赖注入的 F。

- @Component
- public class E {
-
- private static final Logger log = LoggerFactory.getLogger(E.class);
-
- private F1 f1;
-
- @Autowired
- @Lazy
- public void setF(F1 f1) {
- this.f1 = f1;
- log.info("setF(F f) {}", f1.getClass());
- }
-
- public F1 getF1() {
- return f1;
- }
- }
@Lazy 也可以加在成员变量上,但加在 set 方法上的目的是可以观察输出,加在成员变量上就不行了
@Autowired 加在 set 方法的目的类似
结果:
- com.itheima.a08.sub.E - setF(F f) class com.itheima.a08.sub.F1$$EnhancerBySpringCGLIB$$21b324fd
- [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F1@306cf3ea
- [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F1@5136d012
- [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F1@e1de817
从输出日志可以看到调用 setF 方法时,f 对象的类型是代理类型
- @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
- @Component
- public class F2 {
- }
结果:
- [main] com.itheima.a08.A08Application_2 - class com.itheima.a08.sub.F2$$EnhancerBySpringCGLIB$$b84bf307
- [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F2@6a6afff2
- [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F2@1649b0e6
- [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F2@865dd6
使用的是工厂方式,并不是代理模式
- @Component
- public class E {
-
- private static final Logger log = LoggerFactory.getLogger(E.class);
-
- @Autowired
- private ObjectFactory
f3ObjectFactory; -
- public F3 getF3(){
- return f3ObjectFactory.getObject();
- }
-
- }
结果:
- [main] com.itheima.a08.A08Application_2 - class com.itheima.a08.sub.F3
- [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F3@62e7f11d
- [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F3@6a370f4
- [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F3@770d3326
通过容器拿到 F4,容器可以读到F4的多例定义,从而会产生多个不同bean。
- @Component
- public class E {
-
- private static final Logger log = LoggerFactory.getLogger(E.class);
-
- @Autowired
- private ApplicationContext context;
-
- public F4 getF4(){
- return context.getBean(F4.class);
- }
- }
总结:
单例注入其它 scope 的四种解决方法
@Lazy
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
ObjectFactory
ApplicationContext
解决方法虽然不同,但理念上殊途同归: 都是推迟其它 scope bean 的获取