• Spring原理学习(六)Scope


    在当前版本的 Spring 和 Spring Boot 程序中,支持五种 Scope

    • singleton,容器启动时创建(未设置延迟),容器关闭时销毁

    • prototype,每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory.destroyBean(bean) 销毁

    • request,每次请求用到此 bean 时创建,请求结束时销毁

    • session,每个会话用到此 bean 时创建,会话结束时销毁

    • application,web 容器用到此 bean 时创建,容器停止时销毁

    但要注意,如果在 singleton 注入其它 scope 都会有问题。

    1、演示request, session, application 作用域

    主程序类

    1. @SpringBootApplication
    2. public class A08Application_1 {
    3. public static void main(String[] args) {
    4. SpringApplication.run(A08Application_1.class, args);
    5. }
    6. }

    BeanForRequest类

    1. @Scope("request")
    2. @Component
    3. public class BeanForRequest {
    4. private static final Logger log = LoggerFactory.getLogger(BeanForRequest.class);
    5. @PreDestroy
    6. public void destroy() {
    7. log.debug("destroy");
    8. }
    9. }

    BeanForSession类 

    1. @Scope("session")
    2. @Component
    3. public class BeanForSession {
    4. private static final Logger log = LoggerFactory.getLogger(BeanForSession.class);
    5. @PreDestroy
    6. public void destroy() {
    7. log.debug("destroy");
    8. }
    9. }

     BeanForApplication类

    1. @Scope("application")
    2. @Component
    3. public class BeanForApplication {
    4. private static final Logger log = LoggerFactory.getLogger(BeanForApplication.class);
    5. @PreDestroy
    6. public void destroy() {
    7. log.debug("destroy");
    8. }
    9. }

    MyController类,使用@Lazy的原因后面会说

    1. @RestController
    2. public class MyController {
    3. @Lazy
    4. @Autowired
    5. private BeanForRequest beanForRequest;
    6. @Lazy
    7. @Autowired
    8. private BeanForSession beanForSession;
    9. @Lazy
    10. @Autowired
    11. private BeanForApplication beanForApplication;
    12. @GetMapping(value = "/test", produces = "text/html")
    13. public String test(HttpServletRequest request, HttpSession session) {
    14. ServletContext sc = request.getServletContext();
    15. String sb = "
        " +
    16. "
    17. " + "request scope:" + beanForRequest + "
    18. " +
  • "
  • " + "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 会变。

    2、分析 singleton 注入其它 scope 失效

    以单例注入多例为例

    有一个单例对象 E

    1. @Component
    2. public class E {
    3. @Autowired
    4. @Lazy
    5. private F1 f1;
    6. public F1 getF1() {
    7. return f1;
    8. }
    9. }

    要注入的对象 F 期望是多例

    1. @Scope("prototype")
    2. @Component
    3. public class F1 {
    4. }

    主程序类

    1. @ComponentScan("com.itheima.a08.sub")
    2. public class A08Application_2 {
    3. private static final Logger log = LoggerFactory.getLogger(A08Application_2.class);
    4. public static void main(String[] args) {
    5. AnnotationConfigApplicationContext context =
    6. new AnnotationConfigApplicationContext(A08Application_2.class);
    7. E e = context.getBean(E.class);
    8. log.debug("{}", e.getF1());
    9. log.debug("{}", e.getF1());
    10. log.debug("{}", e.getF1());
    11. context.close();
    12. }
    13. }

    结果:

    1. [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F1@6bf0219d
    2. [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F1@6bf0219d
    3. [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F1@6bf0219d

    发现它们是同一个对象,而不是期望的多例对象。

    对于单例对象来讲,依赖注入仅发生了一次,后续再没有用到多例的 F,因此 E 用的始终是第一次依赖注入的 F。

    3、4种解决方法

    1)使用 @Lazy 生成代理

    1. @Component
    2. public class E {
    3. private static final Logger log = LoggerFactory.getLogger(E.class);
    4. private F1 f1;
    5. @Autowired
    6. @Lazy
    7. public void setF(F1 f1) {
    8. this.f1 = f1;
    9. log.info("setF(F f) {}", f1.getClass());
    10. }
    11. public F1 getF1() {
    12. return f1;
    13. }
    14. }

    结果:

    1. com.itheima.a08.sub.E - setF(F f) class com.itheima.a08.sub.F1$$EnhancerBySpringCGLIB$$21b324fd
    2. [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F1@306cf3ea
    3. [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F1@5136d012
    4. [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F1@e1de817

    从输出日志可以看到调用 setF 方法时,f 对象的类型是代理类型

    2)设置@Scope 属性 proxyMode 为代理模式

    1. @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
    2. @Component
    3. public class F2 {
    4. }

    结果:

    1. [main] com.itheima.a08.A08Application_2 - class com.itheima.a08.sub.F2$$EnhancerBySpringCGLIB$$b84bf307
    2. [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F2@6a6afff2
    3. [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F2@1649b0e6
    4. [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F2@865dd6

    3)ObjectFactory

    使用的是工厂方式,并不是代理模式

    1. @Component
    2. public class E {
    3. private static final Logger log = LoggerFactory.getLogger(E.class);
    4. @Autowired
    5. private ObjectFactory f3ObjectFactory;
    6. public F3 getF3(){
    7. return f3ObjectFactory.getObject();
    8. }
    9. }

    结果:

    1. [main] com.itheima.a08.A08Application_2 - class com.itheima.a08.sub.F3
    2. [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F3@62e7f11d
    3. [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F3@6a370f4
    4. [main] com.itheima.a08.A08Application_2 - com.itheima.a08.sub.F3@770d3326

     4)ApplicationContext

    通过容器拿到 F4,容器可以读到F4的多例定义,从而会产生多个不同bean。

    1. @Component
    2. public class E {
    3. private static final Logger log = LoggerFactory.getLogger(E.class);
    4. @Autowired
    5. private ApplicationContext context;
    6. public F4 getF4(){
    7. return context.getBean(F4.class);
    8. }
    9. }

    总结:

    1. 单例注入其它 scope 的四种解决方法

    2. 解决方法虽然不同,但理念上殊途同归: 都是推迟其它 scope bean 的获取

  • 相关阅读:
    nextjs引入tailwindcss 、antd、sass
    MySQL注入绕安全狗脚本 -- MySQLByPassForSafeDog,以及端口爆破工具 -- PortBrute配置使用
    “遥感新纪元:GPT技术引领地球观测的智慧革新“
    ID(码值Value)拼接串转换成Name(码值Label)拼接串
    【IMX6ULL学习笔记之Linux系统移植02】——Uboot移植
    Linux中scp命令复制文件
    MAUI 框架安卓入门开发04 内容模板加载
    hadoop2.2.0开机启动的后台服务脚本(请结合上一篇学习)
    SpringBoot整合RabbitMQ实战附加死信交换机
    rust的排序
  • 原文地址:https://blog.csdn.net/qq_51409098/article/details/127677214