Scope的类型有哪些
- singleton:在Spring的IoC容器中只存在一个对象实例,所有该对象的引用都共享这个实例。Spring 容器只会创建该bean定义的唯一实例,这个实例会被保存到缓存中,并且对该bean的所有后续请求和引用都将返回该缓存中的对象实例,一般情况下,无状态的bean使用该scope。
- prototype:每次对该bean的请求都会创建一个新的实例,一般情况下,有状态的bean使用该scope。
- request:每次http请求将会有各自的bean实例,类似于prototype。
- session:在一个http session中,一个bean定义对应一个bean实例。
- global session【application】:在一个全局的http session中,一个bean定义对应一个bean实例。典型情况下,仅在使用portlet context的时候有效。
singleton和prototype我们接触得比较多,今天主要聊一聊其它三个作用域
创建三个不同作用域的请求对象
@Scope("request")
@Component
public class BeanForRequest {
@PreDestroy
public void destroy() {
System.out.println("request destroy...");
}
}
@Scope("session")
@Component
public class BeanForSession {
@PreDestroy
public void destroy() {
System.out.println("session destroy...");
}
}
@Scope("application")
@Component
public class BeanForApplication {
@PreDestroy
public void destroy() {
System.out.println("application destroy...");
}
}
@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 str = "<ul>" +
"<li>" + "request scope:" + beanForRequest + "</li>" +
"<li>" + "session scope:" + beanForSession + "</li>" +
"<li>" + "application scope:" + beanForApplication + "</li>" +
"</ul>";
return str;
}
}
@SpringBootApplication
public class A08Application {
public static void main(String[] args) {
SpringApplication.run(A08Application.class);
}
}
自定义Controller后启动程序
在同一浏览器连续请求两次结果如下:
可以发现,只有request域请求对象有变化
在不同浏览器查看:
同样可以发现,request和session域都有变化,而application始终不变。
特殊说明:老师使用的是JDK17,所以会有访问权限的异常,博主使用的是JDK8所以没出现异常。
异常原因:JDK >= 9 反射调用 jdk 中的方法会有权限检查
解决方案:运行时添加参数 --add-opens java.base/java.lang=ALL-UNNAMED
Scope失效情况分析
单例对象注入其它作用域的对象会有问题,也就是为什么在Controller中要添加@Lazy注解,下面一起来看看原因究竟是什么?
创建Scope为prototype的F1类,在Scope为Singleton的E类中注入F1
@Scope("prototype")
@Component
public class F1 {
}
@Component
public class E {
@Autowired
private F1 f1;
public F1 getF1() {
return f1;
}
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(SubApplication.class);
E e = context.getBean(E.class);
System.out.println(e.getF1().getClass());
System.out.println(e.getF1());
System.out.println(e.getF1());
System.out.println(e.getF1());
}
连续获取F1对象,结果如下:
class com.shuttle.spring5.sub.F1
com.shuttle.spring5.sub.F1@3febb011
com.shuttle.spring5.sub.F1@3febb011
com.shuttle.spring5.sub.F1@3febb011
发现F1对象并不是按照我们预期的那样,每次获取都生成一个新的对象。
原因如下【截取自老师课件】
添加@Lazy后程序运行结果如下:
class com.shuttle.spring5.sub.F1$$EnhancerBySpringCGLIB$$9953ecf4
com.shuttle.spring5.sub.F1@293a5bf6
com.shuttle.spring5.sub.F1@1283bb96
com.shuttle.spring5.sub.F1@f6efaab
可以发现,F1的Class类型变为了代理类,并且每次调用它生成了新对象 。
第二种解决方式:
创建Scope为prototype的F2类
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class F2 {
}
E类中注入F2,不添加@Lazy注解
@Autowired
private F2 f2;
public F2 getF2() {
return f2;
}
class com.shuttle.spring5.sub.F2$$EnhancerBySpringCGLIB$$fba119f1
com.shuttle.spring5.sub.F2@2a4fb17b
com.shuttle.spring5.sub.F2@5c6648b0
com.shuttle.spring5.sub.F2@6f1de4c7
也同样达到了上述的效果。
第三种解决方式:
使用ObjectFactory
E类中注入F3对象工厂
@Autowired
private ObjectFactory<F3> f3;
public F3 getF3() {
return f3.getObject();
}
class com.shuttle.spring5.sub.F3
com.shuttle.spring5.sub.F3@47eaca72
com.shuttle.spring5.sub.F3@55182842
com.shuttle.spring5.sub.F3@235834f2
第四种解决方式:
注入ApplicationContext
E类中添加如下代码:
@Autowired
private ApplicationContext context;
public F4 getF4() {
return context.getBean(F4.class);
}
程序运行结果如下:
class com.shuttle.spring5.sub.F4
com.shuttle.spring5.sub.F4@71d44a3
com.shuttle.spring5.sub.F4@7b98f307
com.shuttle.spring5.sub.F4@4802796d
与前两种方式不同,注入对象工厂和ApplicationContext方式并不是使用代理来实现。 解决方式虽然不同,但理论上殊途同归,都是推迟其它scope bean 的获取。