Spring学习(四):Scope的介绍及其失效解决方案

目录

一、spring当中有哪些scope

二、scope初始化与销毁演示

2.1 scope的初始化 

2.2 scope的销毁

三、scope失效及其解决方案 

3.1 scope失效演示

3.2 scope失效解决方案一:@Lazy 

3.3 scope失效解决方案二:设置proxyMode属性

3.4 scope失效解决方案三:ObjectFactory

3.5  scope失效解决方案四:ApplicationContext容器

3.6 总结


一、spring当中有哪些scope

        我使用的spring版本是5.3.10,其中scope有五种:

  • singleton:从spring中获取bean时,每次获取的是同一个bean

  • prototype:从spring中获取bean时,每次获取的是新的bean

  • request:生成的bean存活于request域中,生命周期也与request相同

  • session:生成的bean存活于session域中,生命周期也与session相同

  • application:生成的bean存活于application域中,生命周期也与application相同(spring中的application指的是servletContext)

二、scope初始化与销毁演示

2.1 scope的初始化 

        主启动类:

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

        主启动类启动后,就会扫描到下面的三个bean:

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

    @PreDestroy
    public void destroy() {
        log.debug("destroy");
    }
}
@Scope("session")
@Component
public class BeanForSession {
    private static final Logger log = LoggerFactory.getLogger(BeanForSession.class);

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

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

        这三个bean在controller中被用到:

@RestController
public class MyController {

    //MyController是单例,单例去使用其他的域,都要加@Lazy,否则会失效
    @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

        打开一个浏览器(例如:Chrome)可以发现,每次刷新都可以看到request域的bean不一样,说明每次创建的是新的bean,request的bean会在请求结束后销毁。

        再打开一个浏览器(例如:edge)可以发现,会话域的bean也不同,是因为每个浏览器都会创建自己的会话。

         打开不同的浏览器也能看到,request和session的bean不相同,是因为每个浏览器都有自己的请求和会话域;而application的bean时相同的,因为他们对应的是同一个web应用程序

2.2 scope的销毁

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

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

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

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

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

        在浏览器中每次刷新我们都可以看到控制台打印出了beanForRequest的销毁方法,这是因为每次刷新都代表着一个请求结束了;session销毁时间默认为30min,为了方便演示,我们在application.properties文件中设置

server.servlet.session.timeout=10s

         这样session就可以在1min后自动销毁了(spring内部设置session销毁时间低于1min就视为1min,这篇文章剖析了这部分源码:SpringBoot设置Session失效时间 - 简书),我们可以在控制台上看到session销毁的日志

image-20220402144856449 

三、scope失效及其解决方案 

3.1 scope失效演示

        以单例注入多例为例(单例注入其他类型的scope也是同理),先看看主启动类:

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

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

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

        E e = context.getBean(E.class);
        log.debug("{}", e.getF1().getClass());
        log.debug("{}", e.getF1());
        log.debug("{}", e.getF1());
        log.debug("{}", e.getF1());

        context.close();
    }
}

        在主启动类中,我们先通过getBean方法拿到E的对象,再通过get方法拿到e中注入的f1对象。E并没有加@prototype注解,所以默认是个单例,而f1是个多例。

@Component
public class E {

    @Autowired
    private F1 f1;

    public F1 getF1() {
        return f1;
    }
}
@Scope("prototype")
@Component
public class F1 {
}

        那么问题来了,我们在主启动类中多调用几次,每次返回的f1对象是同一个对象还是不同对象?我们既然将F1标注为多例,自然是期望返回不同对象的。

        然而运行可得,每次返回的都是同一对象 :

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

3.2 scope失效解决方案一:@Lazy 

        我们在成员变量或者使用的方法上加上@Lazy注解,@Lazy能够帮助我们生成代理对象,这样,代理对象虽然还是同一个,但当每次使用代理对象的任意方法时,由代理创建新的f1对象。 

@Component
public class E {

    @Lazy
    @Autowired
    private F1 f1;

    public F1 getF1() {
        return f1;
    }
}

        运行发现每次生成的bean已经不一样了。我们也打印一下e.getF1().getClass,看看生成的F1的真实类型,可以看到并不是F1,而是代理增强的类。

3.3 scope失效解决方案二:设置proxyMode属性

        我们还可以在被调用的多例类上设置 proxyMode 属性,我们用F2来演示这个解决办法:

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

        这个方法的本质也是生成代理。运行后可以看到,生成的bean也不一样了,获取到的F2的真实类型也是代理增强的类。 

3.4 scope失效解决方案三:ObjectFactory

        我们可以在E中注入的时候将F声明为 ObjectFactory 类型,这样每次获取的时候都由工厂生成不同的F。我们用F3来演示这个解决办法:

@Component
public class E {

    @Autowired
    private ObjectFactory<F3> f3;

    public F3 getF3() {
        return f3.getObject();
    }
}
@Scope("prototype")
@Component
public class F3 {
}

3.5  scope失效解决方案四:ApplicationContext容器

        直接实现容器,利用容器的getBean方法来获取F多例bean。我们用F4来演示:

@Component
public class E {
    @Autowired
    private ApplicationContext context;

    public F4 getF4() {
        return context.getBean(F4.class);
    }
}
@Scope("prototype")
@Component
public class F4 {
}

3.6 总结

        虽然上述四种方法各不相同,但理念基本相同,都是延迟其他scope bean的获取。他们都是不直接获取多例,而是在中间加一个对象,通过这个对象等到运行时再去获取多例bean 

        更推荐使用BeanFactory 或 ApplicationContext,因为更加简洁,且不像代理一样会造成一定的性能损耗。

猜你喜欢

转载自blog.csdn.net/m0_49499183/article/details/130029023