第八讲:Scope
内容概要:
- Scope 类型有哪些
- 在 singleton 中使用其他几种 scope 的注意事项
- scope 的销毁
- 实战: scope 失效分析
- Scope 类型
- singleton
- 在Spring的IoC容器中只存在一个对象实例,所有该对象的引用都共享这个实例
- Spring 容器只会创建该bean定义的唯一实例,这个实例会被保存到缓存中,并且对该bean的所有后续请求和引用都将返回该缓存中的对象实例
- 一般情况下,无状态的bean使用该scope
- 默认scope
- prototype
- 每次对该bean的请求都会创建一个新的实例
- 一般情况下,有状态的bean使用该scope
- request
- 每次http请求将会有各自的bean实例,类似于prototype
- session
- 在一个 http session 中,一个bean定义对应一个bean实例
- application
- Web工程生命周期
- singleton
- 分别定义Scope类型为 request session application 的Component
@Slf4j
@Scope("request")
@Component
public class BeanForRequest {
@PreDestroy
public void destroy() {
log.debug("destroy");
}
}
@Slf4j
@Scope("session")
@Component
public class BeanForSession {
@PreDestroy
public void destroy() {
log.debug("destroy");
}
}
@Slf4j
@Scope("application")
@Component
public class BeanForApplication {
@PreDestroy
public void destroy() {
log.debug("destroy");
}
}
- 定义 Controller 验证上面的三种Scope类型
@RestController
public class ScopeController {
// controller 的 scope 是 singleton, 自动注入不同scope的bean时,需要添加 @Lazy 注解
@Lazy
@Autowired
public BeanForRequest beanForRequest;
@Lazy
@Autowired
public BeanForSession beanForSession;
@Lazy
@Autowired
public BeanForApplication beanForApplication;
/*
(1)每刷新一次,beanForRequest 都改变了,每次http请求重新创建beanForRequest
(2)打开新的浏览器请求,beanForSession 改变了, 不同session会重新创建beanForRequest
(3)设置 server.servlet.session.timeout = 10s 可以修改 session 的过期时间,session 过期后,也会重新创建beanForRequest
*/
@RequestMapping(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;
}
}
Singleton 注入其他 scope 失效分析
-
scope 是 singleton, 自动注入不同scope的bean时, scope会失效
-
对于单例对象来说,依赖注入仅发生一次,后续再没有用到注入的多例,因此单例对象始终用的都是第一次依赖注入的 Bean
- Bean1 创建
- Bean2 创建
- Bean1 set()方式 注入 Bean2
/**
* Bean1 scope 默认是 singleton
*/
@Component
public class Bean1 {
/**
* Bean2 scope是 prototype, 每次获取都会返回新的实例
* @Autowired 注入不同的scope时,scope会失效,此时每次获取的都是同一个bean2
*/
@Autowired
private Bean2 bean2;
public Bean2 getBean2() {
return bean2;
}
}
@ComponentScan("com.huat.lisa.studyspring.s08.scopeinvalid")
public class ScopeInvalidApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScopeInvalidApplication.class);
Bean1 bean1 = context.getBean(Bean1.class);
// bean2注入后,scope失效,每次获取的都是同一个实例
System.out.println("bean2: " + bean1.getBean2());
System.out.println("bean2: " + bean1.getBean2());
System.out.println("bean2: " + bean1.getBean2());
context.close();
}
}
- 解决方案一:
- 在 @Autowired 时,使用 @Lazy 注入一个SpringCGLIB代理对象
- 代理对象虽然还是同一个,但当每次使用代理对象的任意方法时,由代理对象创建新的 Bean 对象
- Bean1 创建
- Bean1 set()方式注入 Bean3的代理对象
- 每次使用bean3时,Bean3的代理创建新的Bean3对象
- 在BeanFactory中注册的 Bean3 仍然是原始Bean3
/**
* Bean3 scope是 prototype, 每次获取都会返回新的实例
* 第一种解决方案:
* 使用 @Autowired 注入时,添加@Lazy注解,在注入时,会创建一个代理对象注入进去
*/
@Scope("prototype")
@Component
public class Bean3 {
}
/**
* Bean1 scope 默认是 singleton
*/
@Component
public class Bean1 {
/**
* Bean3 scope是 prototype, 每次获取都会返回新的实例
* 第一种解决方案:
* 添加了 @Lazy 注解
* 使用 @Autowired 注入不同的scope时,注入的是SpringCGLIB代理对象,scope有效
*/
@Lazy
@Autowired
private Bean3 bean3;
public Bean3 getBean3() {
return bean3;
}
}
// bean3加了@Lazy注入后,scope生效,每次获取的都是新的实例
System.out.println("bean3: " + bean1.getBean3());
System.out.println("bean3: " + bean1.getBean3());
System.out.println("bean3: " + bean1.getBean3());
// Bean1中注入的Bean3是个SpringCGLIB代理对象
// bean1.bean3.class: class com.studyspring.scopeinvalid.Bean3$$EnhancerBySpringCGLIB$$2e870000
System.out.println("bean1.bean3.class: " + bean1.getBean3().getClass());
// BeanFactory中的Bean3是原始Bean3
System.out.println("beanFactory.bean3.class" + context.getBean(Bean3.class).getClass());
- 解决方案二:
- 在 @Scope 注解中设置
proxyMode = ScopeProxyMode.TARGET_CLASS
属性 - 这种方式Bean在注册到BeanFactory时注册的就是一个scope为singleton 的 SpringCGLIB代理类
- 此时再 @Autowired 注入, scope 是相同的,因此不会失效
- 注入后,当每次使用代理对象的任意方法时,也是由代理对象创建新的 Bean 对象
- 在 @Scope 注解中设置
/**
* Bean3 scope是 prototype, 每次获取都会返回新的实例
* 第二种解决方案:
* 注解 @Scope 添加 proxyMode 属性,属性值填写 ScopedProxyMode.TARGET_CLASS,这种方式Bean在注册到BeanFactory时注册的就是一个代理类
*
*/
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class Bean4 {
}
/**
* Bean1 scope 默认是 singleton
*/
@Component
public class Bean1 {
/**
* Bean4 scope是 prototype, 每次获取都会返回新的实例
* 第二种解决方案:
* 注解 @Scope 添加 proxyMode=ScopedProxyMode.TARGET_CLASS 属性
* 这种方式Bean在注册到BeanFactory时注册的就是一个SpringCGLIB代理类
*/
@Autowired
private Bean4 bean4;
public Bean4 getBean4() {
return bean4;
}
}
// bean4 加了@Lazy注入后,scope生效,每次获取的都是新的实例;Bean3的class是个SpringCGLIB代理对象
System.out.println("bean4: " + bean1.getBean4());
System.out.println("bean4: " + bean1.getBean4());
System.out.println("bean4: " + bean1.getBean4());
// Bean1中注入的Bean4是个SpringCGLIB代理对象
// bean1.bean4.class: class com.studyspring.scopeinvalid.Bean4$$EnhancerBySpringCGLIB$$49128911
System.out.println("bean1.bean4.class: " + bean1.getBean4().getClass());
// BeanFactory中的Bean4和Bean1中注入的是同一个SpringCGLIB代理对象实例
// beanFactory.bean4.class: class com.studyspring.scopeinvalid.Bean4$$EnhancerBySpringCGLIB$$49128911
System.out.println("beanFactory.bean4.class: " + context.getBean(Bean4.class).getClass());
- 解决方案三:
- 使用ObjectFactory, 这种方式不再是代理模式注入了,注入的是个对象工厂,对象工厂可以识别对象的scope
- 当每次使用 Bean 对象时,对象工厂会根据scope自动创建新的 Bean 对象
/**
* Bean3 scope是 prototype, 每次获取都会返回新的实例
* 第三种解决方案:
* 注入一个 ObjectFactory 对象工厂,对象工厂可以识别 Bean 的scope, 每次使用时由 ObjectFactory 来创建 Bean 对象,ObjectFactory 会根据scope,每次都返回新的对象
*/
@Scope("prototype")
@Component
public class Bean5 {
}
/**
* Bean6 scope是 singlton, ObjectFactory 会根据scope,每次都返回同一个对象
*/
@Component
public class Bean6 {
}
/**
* Bean1 scope 默认是 singleton
*/
@Component
public class Bean1 {
/**
* 第三种解决方案:
* 注入 ObjectFactory 对象工厂,对象工厂会根据scope创建bean对象
*/
@Autowired
private ObjectFactory<Bean5> bean5;
@Autowired
private ObjectFactory<Bean6> bean6;
public Bean5 getBean5() {
return bean5.getObject();
}
public Bean6 getBean6() {
return bean6.getObject();
}
}
// 第三种解决方案,注入都是ObjectFactory而不是代理对象,ObjectFactory每次获取都会根据scope获取Bean对象
System.out.println("bean5: " + bean1.getBean5());
System.out.println("bean5: " + bean1.getBean5());
System.out.println("bean5: " + bean1.getBean5());
// 单例时每次获取的都是同一个
System.out.println("bean6: " + bean1.getBean6());
System.out.println("bean6: " + bean1.getBean6());
System.out.println("bean6: " + bean1.getBean6());
- 解决方案四:
- 注入 Spring 容器 ApplicationContext,直接使用BeanFactory 获取 Bean 对象
- BeanFactory 也可以根据scope创建Bean对象
/**
* Bean3 scope是 prototype, 每次获取都会返回新的实例
* 第四种解决方案:
* 注入Spring容器 ApplicationContext,BeanFactory 也可以识别 Bean 的scope, 每次使用时由 BeanFactory 来创建 Bean 对象
*/
@Scope("prototype")
@Component
public class Bean7 {
}
/**
* Bean1 scope 默认是 singleton
*/
@Component
public class Bean1 {
/**
* 第三种解决方案:
* 注入Spring容器 ApplicationContext, BeanFactory 会根据scope创建bean对象
*/
@Autowired
private ApplicationContext context;
public Bean7 getBean7() {
return context.getBean(Bean7.class);
}
}
// 第四种解决方案: 注入Spring 容器,BeanFactory也可以根据scope获取Bean对象
System.out.println("bean7: " + bean1.getBean7());
System.out.println("bean7: " + bean1.getBean7());
System.out.println("bean7: " + bean1.getBean7());
- 四种解决方法虽然不同,但理念上都是推迟其他 scope bean 的获取 (运行时,真正需要调用bean方法时才获取bean)