Spring之Scope注解使用详解
作者:tanglin_030907031026
在当前版本的 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 作用域
/* singleton, prototype, request, session, application jdk >= 9 如果反射调用 jdk 中方法,会报非法访问异常 jdk <= 8 不会有问题 演示 request, session, application 作用域 打开不同的浏览器, 刷新 http://localhost:8080/test 即可查看效果 如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED */ @SpringBootApplication public class A08 { public static void main(String[] args) { SpringApplication.run(A08.class, args); } }
使用不同作用域的Bean如下:
BeanForApplication
@Scope("application") @Component public class BeanForApplication { private static final Logger log = LoggerFactory.getLogger(BeanForApplication.class); @PreDestroy public void destroy() { log.debug("destroy"); } }
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"); } }
使用的Bean使用代码如下:
@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; } }
单例使用其他的例,都得使用@azy注解,否则会失效
分析 - singleton 注入其它 scope 失效
以单例注入多例为例
有一个单例对象 E
@Component public class E { private static final Logger log = LoggerFactory.getLogger(E.class); private F f; public E() { log.info("E()"); } @Autowired public void setF(F f) { this.f = f; log.info("setF(F f) {}", f.getClass()); } public F getF() { return f; } }
要注入的对象 F 期望是多例
@Component @Scope("prototype") public class F { private static final Logger log = LoggerFactory.getLogger(F.class); public F() { log.info("F()"); } }
测试
E e = context.getBean(E.class); F f1 = e.getF(); F f2 = e.getF(); System.out.println(f1); System.out.println(f2);
输出
com.tangyuan.demo.cycle.F@6622fc65
com.tangyuan.demo.cycle.F@6622fc65
发现它们是同一个对象,而不是期望的多例对象
对于单例对象来讲,依赖注入仅发生了一次,后续再没有用到多例的 F,因此 E 用的始终是第一次依赖注入的 F
解决
- 仍然使用 @Lazy 生成代理
- 代理对象虽然还是同一个,但当每次使用代理对象的任意方法时,由代理创建新的 f 对象
@Component public class E { @Autowired @Lazy public void setF(F f) { this.f = f; log.info("setF(F f) {}", f.getClass()); } // ... }
注意
- @Lazy 加在也可以加在成员变量上,但加在 set 方法上的目的是可以观察输出,加在成员变量上就不行了
- @Autowired 加在 set 方法的目的类似
输出
E: setF(F f) class com.itheima.demo.cycle.F$$EnhancerBySpringCGLIB$$8b54f2bc
F: F()
com.tangyuan.demo.cycle.F@3a6f2de3
F: F()
com.tangyuan.demo.cycle.F@56303b57
从输出日志可以看到调用 setF 方法时,f 对象的类型是代理类型
4种解决方法
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS) @Component public class F2 { }
@Autowired private ObjectFactory<F3> f3; public F3 getF3() { return f3.getObject(); }
@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 的获取
到此这篇关于Spring之Scope注解使用详解的文章就介绍到这了,更多相关Spring Scope注解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!