第八讲-Scope作用域分析

第八讲-Scope作用域

1. Scope类型有哪些

在目前的Spring版本(Spring5)中,关于Scope的类型一共有5种:

  1. singleton:每次从容器中返回的bean都是同一个对象
  2. prototype:每次从容器中返回的都会产生新的对象
  3. request:该bean存在于request域中,生命周期和request域是一致的,每次请求来会创建一个bean放入到request域中,请求结束,该bean生命周期也就是结束了
  4. session:该bean存在于session域中,生命周期和session域是一致的,每次请求来会创建一个bean放入到session域中,请求结束,该bean生命周期也就是结束了
  5. application:应用程序域,表示应用程序启动时,该bean会被创建,应用程序销毁时,该bean会被销毁,不过此处的bean,指的是WebServletContext

注意,在演示3,4,5中的示例时如果JDK版本>8,则在运行时加上 --add-opens java.base/java.lang=ALL-UNNAMED 参数

1.1 演示 singleton scope

如下面的代码:

@SpringBootApplication
public class A08Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(A08Application.class);
        // 获取Bean1示例
        System.out.println(context.getBean(Bean1.class));
        System.out.println(context.getBean(Bean1.class));
        System.out.println(context.getBean(Bean1.class));
        context.close();
    }
}



@Component
@Scope("singleton")
public class Bean1 {
}

运行如下:

com.cherry.chapter1.a08.Bean1@3e546734
com.cherry.chapter1.a08.Bean1@3e546734
com.cherry.chapter1.a08.Bean1@3e546734

1.1 演示 prototype scope

如下面的代码:

@SpringBootApplication
public class A08Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(A08Application.class);
        System.out.println(context.getBean(Bean2.class));
        System.out.println(context.getBean(Bean2.class));
        System.out.println(context.getBean(Bean2.class));
        context.close();
    }
}

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

运行如下:

com.cherry.chapter1.a08.Bean2@20801cbb
com.cherry.chapter1.a08.Bean2@581b1c08
com.cherry.chapter1.a08.Bean2@1c240cf2

1.1 演示 request scope,session scope,application scope

如下面的代码:

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

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

编写一个controller:

@RestController // 单例使用其它域的bean必须要加上@Lazy注解,避免scope失效
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, HttpServletResponse response){
        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;
    }
}

编写主方法测试:

@SpringBootApplication
public class A08Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(A08Application.class);
    }
}

接下来我们观察三种不同域的变化,首先浏览器访问/test

image-20240725101031705

再刷新一次:

image-20240725101050089

我们发现,每当新的请求到来,就会创建一个新的request bean对象放到request中!每当请求结束后该bean就会被销毁。

接下来我们打开新的浏览器继续测试:

image-20240725101311465

我们发现,每当新的会话建立时,就会创建一个新的session bean对象放到会话中!会话结束后该bean就会被销毁。

而对于application scope,只要是同一个WEB应用程序(WebServletContext),该域就不会发生变化!

同样的,我们观察控制台也可以看出上面的结论

2024-07-25 10:10:22.410 DEBUG 13528 --- [nio-8080-exec-1] com.cherry.chapter1.a08.BeanForRequest   : request scope destroy
2024-07-25 10:10:44.979 DEBUG 13528 --- [nio-8080-exec-5] com.cherry.chapter1.a08.BeanForRequest   : request scope destroy
2024-07-25 10:12:57.958 DEBUG 13528 --- [nio-8080-exec-8] com.cherry.chapter1.a08.BeanForRequest   : request scope destroy

由于session默认的销毁时间是30min,太长了,这里只手动再配置文件调整一下SpringBoot的session失效时间,来观察session scope的销毁状态

server.servlet.session.timeout=10s

重新测试session scope销毁(10s多后关闭浏览器或者直接看控制台):

2024-07-25 10:22:42.280 DEBUG 9648 --- [alina-utility-1] com.cherry.chapter1.a08.BeanForSession   : session scope destroy

但是,当我们停止程序的执行,好像并没有看到Application scope的bean的销毁,不知道是Spring Boot并没有去实现这个销毁方法,还是其它问题?

2. scope失效分析

刚才我们讲了,一个单例的Bean要想使用其它域的Bean就会出现是Bean失效的问题,解决方法是在单例Bean中注入其它域的Bean时加上@Lazy注解,这里我们分析一些scope失效的问题,首先观察下面的代码:

@Component
@Scope("singleton")
public class Bean1 {
    @Autowired
    private Bean2 bean2;

    public Bean2 getBean2() {
        return bean2;
    }
}


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

编写主方法测试运行:

@SpringBootApplication
public class A08Application {
    private static final Logger log = LoggerFactory.getLogger(A08Application.class);
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(A08Application.class);
        Bean1 bean1 = context.getBean(Bean1.class);
        log.debug("bean1:{}",bean1);
        log.debug("bean2:{}",bean1.getBean2());
        log.debug("bean2:{}",bean1.getBean2());
        log.debug("bean2:{}",bean1.getBean2());
    }
}
2024-07-25 10:44:10.278 DEBUG 10488 --- [           main] com.cherry.chapter1.a08.A08Application   : bean1:com.cherry.chapter1.a08.Bean1@ebd06a9
2024-07-25 10:44:10.280 DEBUG 10488 --- [           main] com.cherry.chapter1.a08.A08Application   : bean2:com.cherry.chapter1.a08.Bean2@76db540e
2024-07-25 10:44:10.280 DEBUG 10488 --- [           main] com.cherry.chapter1.a08.A08Application   : bean2:com.cherry.chapter1.a08.Bean2@76db540e
2024-07-25 10:44:10.281 DEBUG 10488 --- [           main] com.cherry.chapter1.a08.A08Application   : bean2:com.cherry.chapter1.a08.Bean2@76db540e

我们发现,我们每次获取Bean2都是同一个Bean,这并不是期望的多例Bean对象。这个我们分析一下:

对于单例对象,依赖注入仅发生一次,后续再没有用到多例的Bean2,因此Bean1始终用到的就是第一次注入的Bean2

image-20240725105213050

解决方案也很简单:

  1. 仍然使用@Lazy生成代理(不注入对应的代理对象)
  2. 代理对象虽然只有一个,但当每次调用使用代理对象的任意方法,由代理对象创建新的Bean2对象:

image-20240725105201680

接下里我们测试一下:

@Component
@Scope("singleton")
public class Bean1 {

    @Autowired
    @Lazy
    private Bean2 bean2;

    public Bean2 getBean2() {
        return bean2;
    }
}
2024-07-25 10:54:33.082 DEBUG 5600 --- [           main] com.cherry.chapter1.a08.A08Application   : bean1:com.cherry.chapter1.a08.Bean1@16c587de
2024-07-25 10:54:33.084 DEBUG 5600 --- [           main] com.cherry.chapter1.a08.A08Application   : proxy class:class com.cherry.chapter1.a08.Bean2$$EnhancerBySpringCGLIB$$ea2e46c2
2024-07-25 10:54:33.084 DEBUG 5600 --- [           main] com.cherry.chapter1.a08.A08Application   : bean2:com.cherry.chapter1.a08.Bean2@28737371
2024-07-25 10:54:33.093 DEBUG 5600 --- [           main] com.cherry.chapter1.a08.A08Application   : bean2:com.cherry.chapter1.a08.Bean2@760245e1
2024-07-25 10:54:33.094 DEBUG 5600 --- [           main] com.cherry.chapter1.a08.A08Application   : bean2:com.cherry.chapter1.a08.Bean2@295bf2a

此外还有一种解决办法:这里以F3(多例)为例,这里就不使用代理来解决了,而是使用Spring提供的ObejctFactory解决

@Component
@Scope("singleton")
public class Bean1 {

    @Autowired
    @Lazy
    private Bean2 bean2;

    @Autowired
    private ObjectFactory<F3> f3;


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

    public Bean2 getBean2() {
        return bean2;
    }
}
posted @   LilyFlower  阅读(14)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示