Scope

在当前版本的 Spring 和 Spring Boot 程序中,支持五种 Scope
  • singleton,容器启动时创建(未设置延迟),容器关闭时销毁
  • prototype,每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory.destroyBean(bean) 销毁
  • request,每次请求用到此 bean 时创建,请求结束时销毁
  • session,每个会话用到此 bean 时创建,会话结束时销毁
  • application,web 容器用到此 bean 时创建,容器停止时销毁
有些文章提到有 globalSession 这一 Scope,也是陈旧的说法,目前 Spring 中已废弃
但要注意,如果在 singleton 注入其它 scope 都会有问题,解决方法有
  • @Lazy
  • @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
  • ObjectFactory
  • ApplicationContext.getBean

request,session,application案例

@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 = "<ul>" + "<li>" + "request scope:" + beanForRequest + "</li>" + "<li>" + "session scope:" + beanForSession + "</li>" + "<li>" + "application scope:" + beanForApplication + "</li>" + "</ul>"; return sb; } }
 
@Scope("application") @Component public class BeanForApplication { private static final Logger log = LoggerFactory.getLogger(BeanForApplication.class); @PreDestroy public void destroy() { log.debug("destroy"); } }
 
@Scope("request") @Component public class BeanForRequest { private static final Logger log = LoggerFactory.getLogger(BeanForRequest.class); @PreDestroy public void destroy() { log.debug("destroy"); } }
 
@Scope("session") @Component public class BeanForSession { private static final Logger log = LoggerFactory.getLogger(BeanForSession.class); @PreDestroy public void destroy() { log.debug("destroy"); } }
  • 如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED
 
第一次请求
notion image
[TRACE] 11:35:58.541 [http-nio-8080-exec-8] o.s.web.servlet.DispatcherServlet - GET "/test", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' [TRACE] 11:35:58.544 [http-nio-8080-exec-8] o.s.web.servlet.DispatcherServlet - No view rendering, null ModelAndView returned. [DEBUG] 11:35:58.544 [http-nio-8080-exec-8] com.onethink.a08.BeanForRequest - destroy [DEBUG] 11:35:58.544 [http-nio-8080-exec-8] o.s.web.servlet.DispatcherServlet - Completed 200 OK, headers={masked}
第二次请求
notion image
[TRACE] 11:35:58.541 [http-nio-8080-exec-8] o.s.web.servlet.DispatcherServlet - GET "/test", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' [TRACE] 11:35:58.544 [http-nio-8080-exec-8] o.s.web.servlet.DispatcherServlet - No view rendering, null ModelAndView returned. [DEBUG] 11:35:58.544 [http-nio-8080-exec-8] com.onethink.a08.BeanForRequest - destroy [DEBUG] 11:35:58.544 [http-nio-8080-exec-8] o.s.web.servlet.DispatcherServlet - Completed 200 OK, headers={masked} [TRACE] 11:36:55.788 [http-nio-8080-exec-9] o.s.web.servlet.DispatcherServlet - GET "/test", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' [TRACE] 11:36:55.789 [http-nio-8080-exec-9] o.s.web.servlet.DispatcherServlet - No view rendering, null ModelAndView returned. [DEBUG] 11:36:55.789 [http-nio-8080-exec-9] com.onethink.a08.BeanForRequest - destroy [DEBUG] 11:36:55.790 [http-nio-8080-exec-9] o.s.web.servlet.DispatcherServlet - Completed 200 OK, headers={masked}
 
换浏览器访问
notion image
产生新的session对象
新的session对象在一段时间后销毁
[DEBUG] 11:36:55.789 [http-nio-8080-exec-9] com.onethink.a08.BeanForRequest - destroy [DEBUG] 11:36:55.790 [http-nio-8080-exec-9] o.s.web.servlet.DispatcherServlet - Completed 200 OK, headers={masked} [TRACE] 11:37:48.564 [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet - GET "/test", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' [TRACE] 11:37:48.567 [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet - No view rendering, null ModelAndView returned. [DEBUG] 11:37:48.567 [http-nio-8080-exec-1] com.onethink.a08.BeanForRequest - destroy [DEBUG] 11:37:48.567 [http-nio-8080-exec-1] o.s.web.servlet.DispatcherServlet - Completed 200 OK, headers={masked} [TRACE] 11:37:49.008 [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet - GET "/favicon.ico", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet' [TRACE] 11:37:49.013 [http-nio-8080-exec-2] o.s.w.s.r.ResourceHttpRequestHandler - Applying default cacheSeconds=-1 [TRACE] 11:37:49.024 [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet - No view rendering, null ModelAndView returned. [DEBUG] 11:37:49.024 [http-nio-8080-exec-2] o.s.web.servlet.DispatcherServlet - Completed 200 OK, headers={masked} [DEBUG] 11:38:01.587 [Catalina-utility-1] com.onethink.a08.BeanForSession - destroy [DEBUG] 11:39:01.633 [Catalina-utility-1] com.onethink.a08.BeanForSession - destroy

Scope失效原因

 

singleton 注入其它 scope 失效

@Scope("prototype") @Component public class F1 { }
@Component public class E { @Autowired private F1 f1; public F1 getF1() { return f1; } }
 
@ComponentScan("com.onethink.a08.sub") public class A08_1 { private static final Logger log = LoggerFactory.getLogger(A08_1.class); public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A08_1.class); E e = context.getBean(E.class); log.debug("{}", e.getF1().getClass()); log.debug("{}", e.getF1()); log.debug("{}", e.getF1()); log.debug("{}", e.getF1()); context.close(); } }
输出
notion image
发现它们是同一个对象,而不是期望的多例对象
对于单例对象来讲,依赖注入仅发生了一次,后续再没有用到多例的 F,因此 E 用的始终是第一次依赖注入的 F
graph LR e1(e 创建) e2(e set 注入 f) f1(f 创建) e1-->f1-->e2
解决

@Lazy

  • 仍然使用 @Lazy 生成代理
  • 代理对象虽然还是同一个,但当每次使用代理对象的任意方法时,由代理创建新的 f 对象
graph LR e1(e 创建) e2(e set 注入 f代理) f1(f 创建) f2(f 创建) f3(f 创建) e1-->e2 e2--使用f方法-->f1 e2--使用f方法-->f2 e2--使用f方法-->f3
@Component public class E { @Lazy @Autowired private F1 f1; public F1 getF1() { return f1; } }
notion image
从输出日志可以看到调用 setF 方法时,f 对象的类型是代理类型

proxyMode = ScopedProxyMode.TARGET_CLASS

@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS) @Component public class F2 { }
@Component public class E { @Lazy @Autowired private F1 f1; @Autowired private F2 f2; public F1 getF1() { return f1; } public F2 getF2() { return f2; } }
@ComponentScan("com.onethink.a08.sub") public class A08_1 { private static final Logger log = LoggerFactory.getLogger(A08_1.class); public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A08_1.class); E e = context.getBean(E.class); log.debug("{}", e.getF2().getClass()); log.debug("{}", e.getF2()); log.debug("{}", e.getF2()); log.debug("{}", e.getF2()); context.close(); /* 学到了什么 a. 单例注入其它 scope 的四种解决方法 b. 解决方法虽然不同, 但理念上殊途同归: 都是推迟其它 scope bean 的获取 */ } }
notion image

ObjectFactory

@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(); } }
@ComponentScan("com.onethink.a08.sub") public class A08_1 { private static final Logger log = LoggerFactory.getLogger(A08_1.class); public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A08_1.class); E e = context.getBean(E.class); log.debug("{}", e.getF3()); log.debug("{}", e.getF3()); // // log.debug("{}", e.getF4()); // log.debug("{}", e.getF4()); context.close(); /* 学到了什么 a. 单例注入其它 scope 的四种解决方法 b. 解决方法虽然不同, 但理念上殊途同归: 都是推迟其它 scope bean 的获取 */ } }
notion image

ApplicationContext

使用spring容器获取多例
@Component public class E { @Lazy @Autowired private F1 f1; @Autowired private F2 f2; @Autowired private ObjectFactory<F3> f3; @Autowired private ApplicationContext context; public F1 getF1() { return f1; } public F2 getF2() { return f2; } public F3 getF3() { return f3.getObject(); } public F4 getF4() { return context.getBean(F4.class); } }
 
@ComponentScan("com.onethink.a08.sub") public class A08_1 { private static final Logger log = LoggerFactory.getLogger(A08_1.class); public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A08_1.class); E e = context.getBean(E.class); // log.debug("{}", e.getF4()); log.debug("{}", e.getF4()); context.close(); /* 学到了什么 a. 单例注入其它 scope 的四种解决方法 b. 解决方法虽然不同, 但理念上殊途同归: 都是推迟其它 scope bean 的获取 */ } }
notion image
解决方法虽然不同,但理念上殊途同归: 都是推迟其它 scope bean 的获取