内容概要:
- Scope 类型有哪些
- 在 singleton 中使用其他几种 scope 的注意事项
- scope 的销毁
- 实战: scope 失效分析
@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");
}
}
@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 = ""
+
"- "
+ "request scope:" + beanForRequest + "" +
"- "
+ "session scope:" + beanForSession + "" +
"- "
+ "application scope:" + beanForApplication + "" +
"";
return sb;
}
}
Singleton 注入其他 scope 失效分析
scope 是 singleton, 自动注入不同scope的bean时, scope会失效
对于单例对象来说,依赖注入仅发生一次,后续再没有用到注入的多例,因此单例对象始终用的都是第一次依赖注入的 Bean
/**
* 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();
}
}
/**
* 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());
proxyMode = ScopeProxyMode.TARGET_CLASS
属性/**
* 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());
/**
* 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());
/**
* 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());