Scope
在 Spring 5和 Spring Boot 程序中,支持五种 Scope
-
singleton,容器启动时创建(未设置延迟),容器关闭时销毁
-
prototype,每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory.destroyBean(bean) 销毁
-
request,每次请求用到此 bean 时创建,请求结束时销毁
-
session,每个会话用到此 bean 时创建,会话结束时销毁
-
application,web 容器用到此 bean 时创建,容器停止时销毁
1.准备bean对象
创建scope为applicatioan的bean并重写他的销毁方法
@Scope("application")
@Component
public class BeanForApplication {
private static final Logger log = LoggerFactory.getLogger(BeanForApplication.class);
@PreDestroy
public void destroy() {
log.debug("destroy");
}
}
创建scope为repuest的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的bean对象
@Scope("session")
@Component
public class BeanForSession {
private static final Logger log = LoggerFactory.getLogger(BeanForSession.class);
@PreDestroy
public void destroy() {
log.debug("destroy");
}
}
此时要准备的类就准备好了,为了演示request请求,我们要引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
有了依赖我们要创建web的控制类用来访问
我们在这个控制类注入了上面三个bean并重写了tostring方法,@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
可以看到三个bean对象都成功注入
刷新请求时scope为request的bean对象会发生改变,我们再去看控制台输出
可以发现请求一结束request的bean对象就被销毁
其它 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
解决方法
使用 @Lazy 生成代理
代理对象虽然还是同一个,但当每次使用代理对象的任意方法时,由代理创建新的 f 对象
注意
-
@Lazy 加在也可以加在成员变量上,但加在 set 方法上的目的是可以观察输出,加在成员变量上就不行了
-
@Autowired 加在 set 方法的目的类似
控制台输出可以发现创建的是一个cglib的增强代理对象,代理对象识别到了f对象上scope的标识从而代理生成不同的bean对象
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
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
在要注入的f对象上添加这个注解,也能创建出不同的bean对象
ObjectFactory
@Autowired
private ObjectFactory<F3> f3;
public F3 getF3() {
return f3.getObject();
}
利用这个工厂类生成f的bean对象
ApplicationContext
@Autowired
private ApplicationContext context;
public F4 getF4() {
return context.getBean(F4.class);
}
利用容器创建f的bean对象
总结:
-
单例注入其它 scope 的四种解决方法
-
@Lazy
-
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
-
ObjectFactory
-
ApplicationContext
-
-
解决方法虽然不同,但理念上殊途同归: 都是推迟其它 scope bean 的获取