• Spring5学习笔记08--Scope


    第八讲:Scope

    内容概要:

    • Scope 类型有哪些
    • 在 singleton 中使用其他几种 scope 的注意事项
    • scope 的销毁
    • 实战: scope 失效分析
    • Scope 类型
      • singleton
        • 在Spring的IoC容器中只存在一个对象实例,所有该对象的引用都共享这个实例
        • Spring 容器只会创建该bean定义的唯一实例,这个实例会被保存到缓存中,并且对该bean的所有后续请求和引用都将返回该缓存中的对象实例
        • 一般情况下,无状态的bean使用该scope
        • 默认scope
      • prototype
        • 每次对该bean的请求都会创建一个新的实例
        • 一般情况下,有状态的bean使用该scope
      • request
        • 每次http请求将会有各自的bean实例,类似于prototype
      • session
        • 在一个 http session 中,一个bean定义对应一个bean实例
      • application
        • Web工程生命周期
    • 分别定义Scope类型为 request session application 的Component
    @Slf4j
    @Scope("request")
    @Component
    public class BeanForRequest {
    
        @PreDestroy
        public void destroy() {
            log.debug("destroy");
        }
    }
    
    @Slf4j
    @Scope("session")
    @Component
    public class BeanForSession {
    
        @PreDestroy
        public void destroy() {
            log.debug("destroy");
        }
    }
    
    @Slf4j
    @Scope("application")
    @Component
    public class BeanForApplication {
    
        @PreDestroy
        public void destroy() {
            log.debug("destroy");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 定义 Controller 验证上面的三种Scope类型
    @RestController
    public class ScopeController {
    
        // controller 的 scope 是 singleton, 自动注入不同scope的bean时,需要添加 @Lazy 注解
        @Lazy
        @Autowired
        public BeanForRequest beanForRequest;
    
        @Lazy
        @Autowired
        public BeanForSession beanForSession;
    
        @Lazy
        @Autowired
        public BeanForApplication beanForApplication;
    
        /*
        (1)每刷新一次,beanForRequest 都改变了,每次http请求重新创建beanForRequest
        (2)打开新的浏览器请求,beanForSession 改变了, 不同session会重新创建beanForRequest
        (3)设置 server.servlet.session.timeout = 10s 可以修改 session 的过期时间,session 过期后,也会重新创建beanForRequest
         */
        @RequestMapping(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; } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    Singleton 注入其他 scope 失效分析

    • scope 是 singleton, 自动注入不同scope的bean时, scope会失效

    • 对于单例对象来说,依赖注入仅发生一次,后续再没有用到注入的多例,因此单例对象始终用的都是第一次依赖注入的 Bean

      • Bean1 创建
      • Bean2 创建
      • Bean1 set()方式 注入 Bean2
    /**
     * Bean1 scope 默认是 singleton
     */
    @Component
    public class Bean1 {
    
        /**
         * Bean2 scope是 prototype, 每次获取都会返回新的实例
         * @Autowired 注入不同的scope时,scope会失效,此时每次获取的都是同一个bean2
         */
        @Autowired
        private Bean2 bean2;
      
        public Bean2 getBean2() {
            return bean2;
        }
    }
    
    
    @ComponentScan("com.huat.lisa.studyspring.s08.scopeinvalid")
    public class ScopeInvalidApplication {
    
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScopeInvalidApplication.class);
            Bean1 bean1 = context.getBean(Bean1.class);
    
            // bean2注入后,scope失效,每次获取的都是同一个实例
            System.out.println("bean2: " + bean1.getBean2());
            System.out.println("bean2: " + bean1.getBean2());
            System.out.println("bean2: " + bean1.getBean2());
          
            context.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 解决方案一:
      • 在 @Autowired 时,使用 @Lazy 注入一个SpringCGLIB代理对象
      • 代理对象虽然还是同一个,但当每次使用代理对象的任意方法时,由代理对象创建新的 Bean 对象
        • Bean1 创建
        • Bean1 set()方式注入 Bean3的代理对象
        • 每次使用bean3时,Bean3的代理创建新的Bean3对象
      • 在BeanFactory中注册的 Bean3 仍然是原始Bean3
    /**
     * Bean3 scope是 prototype, 每次获取都会返回新的实例
     * 第一种解决方案:
     * 使用 @Autowired 注入时,添加@Lazy注解,在注入时,会创建一个代理对象注入进去
     */
    @Scope("prototype")
    @Component
    public class Bean3 {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    /**
     * Bean1 scope 默认是 singleton
     */
    @Component
    public class Bean1 {
    
        /**
         * Bean3 scope是 prototype, 每次获取都会返回新的实例
         * 第一种解决方案:
         * 添加了 @Lazy 注解
         * 使用 @Autowired 注入不同的scope时,注入的是SpringCGLIB代理对象,scope有效
         */
        @Lazy
        @Autowired
        private Bean3 bean3;
      
        public Bean3 getBean3() {
            return bean3;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    // bean3加了@Lazy注入后,scope生效,每次获取的都是新的实例
    System.out.println("bean3: " + bean1.getBean3());
    System.out.println("bean3: " + bean1.getBean3());
    System.out.println("bean3: " + bean1.getBean3());
    // Bean1中注入的Bean3是个SpringCGLIB代理对象
    // bean1.bean3.class: class com.studyspring.scopeinvalid.Bean3$$EnhancerBySpringCGLIB$$2e870000
    System.out.println("bean1.bean3.class: " + bean1.getBean3().getClass());
    // BeanFactory中的Bean3是原始Bean3
    System.out.println("beanFactory.bean3.class" + context.getBean(Bean3.class).getClass());
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 解决方案二:
      • 在 @Scope 注解中设置 proxyMode = ScopeProxyMode.TARGET_CLASS 属性
      • 这种方式Bean在注册到BeanFactory时注册的就是一个scope为singleton 的 SpringCGLIB代理类
        • 此时再 @Autowired 注入, scope 是相同的,因此不会失效
        • 注入后,当每次使用代理对象的任意方法时,也是由代理对象创建新的 Bean 对象
    /**
     * Bean3 scope是 prototype, 每次获取都会返回新的实例
     * 第二种解决方案:
     * 注解 @Scope 添加 proxyMode 属性,属性值填写 ScopedProxyMode.TARGET_CLASS,这种方式Bean在注册到BeanFactory时注册的就是一个代理类
     *
     */
    @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
    @Component
    public class Bean4 {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    /**
     * Bean1 scope 默认是 singleton
     */
    @Component
    public class Bean1 {
    
        /**
         * Bean4 scope是 prototype, 每次获取都会返回新的实例
         * 第二种解决方案:
         * 注解 @Scope 添加 proxyMode=ScopedProxyMode.TARGET_CLASS 属性
         * 这种方式Bean在注册到BeanFactory时注册的就是一个SpringCGLIB代理类
         */
        @Autowired
        private Bean4 bean4;
      
        public Bean4 getBean4() {
            return bean4;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    // bean4 加了@Lazy注入后,scope生效,每次获取的都是新的实例;Bean3的class是个SpringCGLIB代理对象
    System.out.println("bean4: " + bean1.getBean4());
    System.out.println("bean4: " + bean1.getBean4());
    System.out.println("bean4: " + bean1.getBean4());
    // Bean1中注入的Bean4是个SpringCGLIB代理对象
    // bean1.bean4.class: class com.studyspring.scopeinvalid.Bean4$$EnhancerBySpringCGLIB$$49128911
    System.out.println("bean1.bean4.class: " + bean1.getBean4().getClass());
    
    // BeanFactory中的Bean4和Bean1中注入的是同一个SpringCGLIB代理对象实例
    // beanFactory.bean4.class: class com.studyspring.scopeinvalid.Bean4$$EnhancerBySpringCGLIB$$49128911
    System.out.println("beanFactory.bean4.class: " + context.getBean(Bean4.class).getClass());
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 解决方案三:
      • 使用ObjectFactory, 这种方式不再是代理模式注入了,注入的是个对象工厂,对象工厂可以识别对象的scope
      • 当每次使用 Bean 对象时,对象工厂会根据scope自动创建新的 Bean 对象
    /**
     * Bean3 scope是 prototype, 每次获取都会返回新的实例
     * 第三种解决方案:
     * 注入一个 ObjectFactory 对象工厂,对象工厂可以识别 Bean 的scope, 每次使用时由 ObjectFactory 来创建 Bean 对象,ObjectFactory 会根据scope,每次都返回新的对象
     */
    @Scope("prototype")
    @Component
    public class Bean5 {
    }
    
    /**
     * Bean6 scope是 singlton, ObjectFactory 会根据scope,每次都返回同一个对象
     */
    @Component
    public class Bean6 {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    /**
     * Bean1 scope 默认是 singleton
     */
    @Component
    public class Bean1 {
        /**
         * 第三种解决方案:
         * 注入 ObjectFactory 对象工厂,对象工厂会根据scope创建bean对象
         */
        @Autowired
        private ObjectFactory<Bean5> bean5;
    
        @Autowired
        private ObjectFactory<Bean6> bean6;
      
        public Bean5 getBean5() {
            return bean5.getObject();
        }
    
        public Bean6 getBean6() {
            return bean6.getObject();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    // 第三种解决方案,注入都是ObjectFactory而不是代理对象,ObjectFactory每次获取都会根据scope获取Bean对象
    System.out.println("bean5: " + bean1.getBean5());
    System.out.println("bean5: " + bean1.getBean5());
    System.out.println("bean5: " + bean1.getBean5());
    // 单例时每次获取的都是同一个
    System.out.println("bean6: " + bean1.getBean6());
    System.out.println("bean6: " + bean1.getBean6());
    System.out.println("bean6: " + bean1.getBean6());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 解决方案四:
      • 注入 Spring 容器 ApplicationContext,直接使用BeanFactory 获取 Bean 对象
      • BeanFactory 也可以根据scope创建Bean对象
    /**
     * Bean3 scope是 prototype, 每次获取都会返回新的实例
     * 第四种解决方案:
     * 注入Spring容器 ApplicationContext,BeanFactory 也可以识别 Bean 的scope, 每次使用时由 BeanFactory 来创建 Bean 对象
     */
    @Scope("prototype")
    @Component
    public class Bean7 {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    /**
     * Bean1 scope 默认是 singleton
     */
    @Component
    public class Bean1 {
        /**
         * 第三种解决方案:
         * 注入Spring容器 ApplicationContext, BeanFactory 会根据scope创建bean对象
         */
        @Autowired
        private ApplicationContext context;
    
        public Bean7 getBean7() {
            return context.getBean(Bean7.class);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    // 第四种解决方案: 注入Spring 容器,BeanFactory也可以根据scope获取Bean对象
    System.out.println("bean7: " + bean1.getBean7());
    System.out.println("bean7: " + bean1.getBean7());
    System.out.println("bean7: " + bean1.getBean7());
    
    • 1
    • 2
    • 3
    • 4
    • 四种解决方法虽然不同,但理念上都是推迟其他 scope bean 的获取 (运行时,真正需要调用bean方法时才获取bean)
  • 相关阅读:
    【打卡】【sysfs相关API详解】21天学习挑战赛—RK3399平台开发入门到精通-Day20
    Linux 常用运维使用指令
    makefile之静态库的生成
    python转xml为json
    SpringBoot-37-RPC概述
    将VMware平台虚拟机瞬时恢复并在线迁移至深信服Sangfor平台
    学习黑马程序员JavaScript总结
    提高Qt开发软件运算性能提升
    zkPoT:基于机器学习模型训练的ZKP
    【Python】【OpenCV】关于cv2.findContours()轮廓索引(编号)解析(RETR_TREE)
  • 原文地址:https://blog.csdn.net/qingqingxiangyang/article/details/126751735