spring 08 Scope

Scope

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

  • singleton,容器启动时创建(未设置延迟),容器关闭时销毁
  • prototype,每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory.destroyBean(bean) 销毁
  • request,每次请求用到此 bean 时创建,请求结束时销毁
  • session,每个会话用到此 bean 时创建,会话结束时销毁
  • application,web 容器用到此 bean 时创建,容器停止时销毁

有些文章提到有 globalSession 这一 Scope,也是陈旧的说法,目前 Spring 中已废弃

但要注意,如果在 singleton 注入其它 scope 都会有问题,解决方法有

  • @Lazy
  • @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
  • ObjectFactory
  • ApplicationContext.getBean

request, session, application 作用域

  • 打开不同的浏览器, 刷新 http://localhost:8080/test 即可查看效果
  • 如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED
  • 因为会反射调用 jdk 中 Object 类的方法,非法访问异常。
点击查看代码

小结: 1. 有几种 scope 2. 在 singleton 中使用其它几种 scope 的方法 3. 其它 scope 的销毁时机 * 可以将通过 server.servlet.session.timeout=30s 观察 session bean 的销毁 * ServletContextScope 销毁机制疑似实现有误

singleton 注入其它 scope 失效

以单例注入多例为例

有一个单例对象 E

@Component
public class E {
    private static final Logger log = LoggerFactory.getLogger(E.class);

    private F f;

    public E() {
        log.info("E()");
    }

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

    public F getF() {
        return f;
    }
}

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

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

    public F() {
        log.info("F()");
    }
}

测试

E e = context.getBean(E.class);
F f1 = e.getF();
F f2 = e.getF();
System.out.println(f1);
System.out.println(f2);

输出

com.itheima.demo.cycle.F@6622fc65
com.itheima.demo.cycle.F@6622fc65

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

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

graph LR e1(e 创建) e2(e set 注入 f) f1(f 创建) e1-->f1-->e2

解决

  • 仍然使用 @Lazy 生成代理
  • 代理对象虽然还是同一个,但当每次使用代理对象的任意方法时,由代理创建新的 f 对象
graph LR e1(e 创建) e2(e set 注入 f代理) f1(f 创建) f2(f 创建) f3(f 创建) e1-->e2 e2--使用f方法-->f1 e2--使用f方法-->f2 e2--使用f方法-->f3
@Component
public class E {

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

    // ...
}

注意

  • @Lazy 加在也可以加在成员变量上,但加在 set 方法上的目的是可以观察输出,加在成员变量上就不行了
  • @Autowired 加在 set 方法的目的类似

输出

E: setF(F f) class com.itheima.demo.cycle.F$$EnhancerBySpringCGLIB$$8b54f2bc
F: F()
com.itheima.demo.cycle.F@3a6f2de3
F: F()
com.itheima.demo.cycle.F@56303b57

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

其他解决方法 2-4种

如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED

小结:

  1. 单例注入其它 scope 的四种解决方法
    • @Lazy
    • @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
    • ObjectFactory
    • ApplicationContext
  2. 解决方法虽然不同,但理念上殊途同归: 都是推迟其它 scope bean 的获取
点击查看代码
@Component
public class S08Config {
    @Autowired
    private S08Bean2 s08Bean2;

    public S08Bean2 getS08Bean2() {
        return s08Bean2;
    }

    @Autowired
    @Lazy
    private S08Bean1 s08Bean1;

    public S08Bean1 getS08Bean1() {
        return s08Bean1;
    }

    @Autowired
    private ObjectFactory<S08Bean3> s08Bean3;

    public S08Bean3 getS08Bean3() {
        return s08Bean3.getObject();
    }

    @Autowired
    private ApplicationContext applicationContext;

    public S08Bean4 getS08Bean4() {
        return applicationContext.getBean(S08Bean4.class);
    }

}
@Component
@Scope(value = "prototype",proxyMode = ScopedProxyMode.TARGET_CLASS)
public class S08Bean2 {
}
@ComponentScan("cn.xyf.spring.s08")
public class S08 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(S08.class);
        S08Config s08Config = context.getBean(S08Config.class);
        System.out.println(s08Config.getS08Bean1());
        System.out.println(s08Config.getS08Bean1());
        System.out.println(s08Config.getS08Bean1());
        System.out.println("===============");
        System.out.println(s08Config.getS08Bean2());
        System.out.println(s08Config.getS08Bean2());
        System.out.println(s08Config.getS08Bean2());
        System.out.println("===============");
        System.out.println(s08Config.getS08Bean3());
        System.out.println(s08Config.getS08Bean3());
        System.out.println(s08Config.getS08Bean3());
        System.out.println("===============");
        System.out.println(s08Config.getS08Bean4());
        System.out.println(s08Config.getS08Bean4());
        System.out.println(s08Config.getS08Bean4());
        System.out.println("===============");
        context.close();
    }
}

posted @ 2022-06-20 16:31  xy7112  阅读(62)  评论(0)    收藏  举报