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");
    }
}
  • 定义 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 = "<ul>" +
                "<li>" + "request scope:" + beanForRequest + "</li>" +
                "<li>" + "session scope:" + beanForSession + "</li>" +
                "<li>" + "application scope:" + beanForApplication + "</li>" +
                "</ul>";
        return sb;
    }
}

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();
    }
}
  • 解决方案一:
    • 在 @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 {
}
/**
 * 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;
    }
}
// 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());

  • 解决方案二:
    • 在 @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 {
}
/**
 * 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;
    }
}
// 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());

  • 解决方案三:
    • 使用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 {
}
/**
 * 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();
    }
}
// 第三种解决方案,注入都是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());
  • 解决方案四:
    • 注入 Spring 容器 ApplicationContext,直接使用BeanFactory 获取 Bean 对象
    • BeanFactory 也可以根据scope创建Bean对象
/**
 * Bean3 scope是 prototype, 每次获取都会返回新的实例
 * 第四种解决方案:
 * 注入Spring容器 ApplicationContext,BeanFactory 也可以识别 Bean 的scope, 每次使用时由 BeanFactory 来创建 Bean 对象
 */
@Scope("prototype")
@Component
public class Bean7 {
}
/**
 * Bean1 scope 默认是 singleton
 */
@Component
public class Bean1 {
    /**
     * 第三种解决方案:
     * 注入Spring容器 ApplicationContext, BeanFactory 会根据scope创建bean对象
     */
    @Autowired
    private ApplicationContext context;

    public Bean7 getBean7() {
        return context.getBean(Bean7.class);
    }
}
// 第四种解决方案: 注入Spring 容器,BeanFactory也可以根据scope获取Bean对象
System.out.println("bean7: " + bean1.getBean7());
System.out.println("bean7: " + bean1.getBean7());
System.out.println("bean7: " + bean1.getBean7());
  • 四种解决方法虽然不同,但理念上都是推迟其他 scope bean 的获取 (运行时,真正需要调用bean方法时才获取bean)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值