Spring原理学习(六)Scope

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

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

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

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

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

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

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

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

主程序类

@SpringBootApplication
public class A08Application_1 {
    public static void main(String[] args) {
        SpringApplication.run(A08Application_1.class, args);
    }
}

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");
    }
}

 BeanForApplication类

@Scope("application")
@Component
public class BeanForApplication {
    private static final Logger log = LoggerFactory.getLogger(BeanForApplication.class);

    @PreDestroy
    public void destroy() {
        log.debug("destroy");
    }
}

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

@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;
    }

}

打开不同的浏览器,刷新 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

@Component
public class E {
    @Autowired
    @Lazy
    private F1 f1;

    public F1 getF1() {
        return f1;
    }
}

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

@Scope("prototype")
@Component
public class F1 {
}

主程序类

@ComponentScan("com.itheima.a08.sub")
public class A08Application_2 {

    private static final Logger log = LoggerFactory.getLogger(A08Application_2.class);

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(A08Application_2.class);

        E e = context.getBean(E.class);

        log.debug("{}", e.getF1());
        log.debug("{}", e.getF1());
        log.debug("{}", e.getF1());

        context.close();
    }
}

结果:

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

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

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

3、4种解决方法

1)使用 @Lazy 生成代理

@Component
public class E {

    private static final Logger log = LoggerFactory.getLogger(E.class);

    private F1 f1;

    @Autowired
    @Lazy
    public void setF(F1 f1) {
        this.f1 = f1;
        log.info("setF(F f) {}", f1.getClass());
    }

    public F1 getF1() {
        return f1;
    }
}
  • @Lazy 也可以加在成员变量上,但加在 set 方法上的目的是可以观察输出,加在成员变量上就不行了

  • @Autowired 加在 set 方法的目的类似

结果:

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

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

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

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

结果:

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

3)ObjectFactory

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

@Component
public class E {

    private static final Logger log = LoggerFactory.getLogger(E.class);

    @Autowired
    private ObjectFactory<F3> f3ObjectFactory;

    public F3 getF3(){
        return f3ObjectFactory.getObject();
    }

}

结果:

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

 4)ApplicationContext

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

@Component
public class E {

    private static final Logger log = LoggerFactory.getLogger(E.class);

    @Autowired
    private ApplicationContext context;

    public F4 getF4(){
        return context.getBean(F4.class);
    }
}

总结:

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

    • @Lazy

    • @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)

    • ObjectFactory

    • ApplicationContext

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小鲁蛋儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值