一.Scope的类型
Spring中的scope有五种类型,分别是:
singleton,prepertype,request,session,applicati
其中前两个我们已经很熟悉了,singleton就是单例模式,每次从容器中获取的bean都是同一个,prepertype是每次获取bean都是新创建的一个bean,
下面我们来讨论后面三中情况:
二.web项目中的三种Scope
我们创建一个springboot项目,引入spirng-boot-starter-web依赖,然后我们编写以下三个类:
@Scope("request")
@Component
public class BeanForRequest {
private static final Logger log = LoggerFactory.getLogger(BeanForRequest.class);
@PreDestroy
public void destroy(){
log.info("destroy....");
}
}
@Scope("session")
@Component
public class BeanForSession {
private static final Logger log = LoggerFactory.getLogger(BeanForSession.class);
@PreDestroy
public void destroy(){
log.info("destroy...");
}
}
@Scope("application")
@Component
public class BeanForApplication {
private static final Logger log = LoggerFactory.getLogger(BeanForApplication.class);
@PreDestroy
public void destroy(){
log.info("destroy....");
}
}
我们编写Controller类
@RestController
public class TestScopeController {
@Lazy
@Autowired
private BeanForRequest beanForRequest;
@Lazy
@Autowired
private BeanForSession beanForSession;
@Lazy
@Autowired
private BeanForApplication beanForApplication;
@GetMapping("/test")
public String testScope(){
return "<ul>" +
"<li>" + beanForRequest + "</li>" +
"<li>" + beanForSession + "</li>"+
"<li>" + beanForApplication+ "</li>"+
"</ul>";
}
}
注意:这里我们在使用注入的时候要加上@Lazy注解,这是因为实际上是调用了他们的代理对象,如果不加会报错
然后我们启动项目,在浏览器中访问: localhost:8080/test
得到结果:
然后我们看一下控制台,发现:
2023-09-19 17:27:35.228 INFO 15628 --- [nio-8080-exec-4] com.jjh.component.BeanForRequest : destroy....
这是因为Scope域是一次请求,在请求完成后就会销毁bean,
我们再次访问localhost:8080/test
我们发现这个BeanForRequest对象和上一次的不一样,但是后面两个是一样的,这就说明了一次请求创建一次bean对象,每次请求都是不是不同的对象
现在让我们换另外一个浏览器:
我们发现这次BeanForSession也不一样了,这是不同的浏览器代表不同的会话,但是BeanForApplication都是一样的,因为我们的项目是同一个
三.关于在一个单例对象中注入多例失效的问题
1.验证
当在一个单例的类中注入多例对象时,返回的结果还是同一个,下面我们来验证一下:
创建三个类:
@Component
public class E {
@Autowired
private F1 f1;
public F1 getF1() {
return f1;
}
}
@Scope("prototype")
@Component
public class F1 {
}
@Scope("prototype")
@Component
public class F2 {
}
然后我们通过E对象获取F1对象,观察一下它的值:
@SpringBootApplication
public class InitAndDestroyApplication {
private static final Logger log = LoggerFactory.getLogger(InitAndDestroyApplication.class);
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(InitAndDestroyApplication.class, args);
E e = context.getBean(E.class);
log.info("{}",e.getF1());
log.info("{}",e.getF1());
log.info("{}",e.getF1());
}
}
运行查看结果:
2023-09-19 17:47:13.995 INFO 18968 --- [ main] com.jjh.InitAndDestroyApplication : com.jjh.component.F1@6232ffdb
2023-09-19 17:47:13.997 INFO 18968 --- [ main] com.jjh.InitAndDestroyApplication : com.jjh.component.F1@6232ffdb
2023-09-19 17:47:13.998 INFO 18968 --- [ main] com.jjh.InitAndDestroyApplication : com.jjh.component.F1@6232ffdb
2.原理分析
我们发现这三次获取的F1都是同一个对象,注解@Scope("prototype")失效了,这是为什么呢?
我们来看一下图:
因为单例对象的依赖注入只发生了一次,所以只能注入了一次F1,所以是同一个F1
解决方法:
我们使用代理对象,这样每次调用方法时,由代理对象来创建新的F1对象
3.解决方法:
1.使用@Lazy注解
2.@Scope(value = "prototype",proxyMode = ScopedProxyMode.TARGET_CLASS)
是在要代理的那个对象上添加
@Lazy
@Autowired
private F1 f1;
在F1上添加@Lazy,然后我们再次获取这个F1对象:
2023-09-19 17:57:38.115 INFO 2600 --- [ main] com.jjh.InitAndDestroyApplication : com.jjh.component.F1@6a12c7a8
2023-09-19 17:57:38.117 INFO 2600 --- [ main] com.jjh.InitAndDestroyApplication : com.jjh.component.F1@5740ff5e
2023-09-19 17:57:38.117 INFO 2600 --- [ main] com.jjh.InitAndDestroyApplication : com.jjh.component.F1@67f77f6e
可以看到,这次三个对象都是不一样的了,下面,我们获取F1对象的类型:
2023-09-19 17:58:57.600 INFO 2044 --- [ main] com.jjh.InitAndDestroyApplication : class com.jjh.component.F1$$EnhancerBySpringCGLIB$$bd3dda3e
我们发现这个F1的类型已经不是F1本身了,它是使用了CGLB动态代理
继续创建F3,F4类:
@Scope("prototype")
@Component
public class F3 {
}
@Scope("prototype")
@Component
public class F4 {
}
3.使用ObjectFactory<F3>
我们可以使用工厂来创建对象:
@Autowired
private ObjectFactory<F3> objectFactory;
public F3 getF3(){
return objectFactory.getObject();
}
然后我们运行一下:
2023-09-19 18:09:31.504 INFO 9672 --- [ main] com.jjh.InitAndDestroyApplication : com.jjh.component.F3@6aa3bfc
2023-09-19 18:09:31.505 INFO 9672 --- [ main] com.jjh.InitAndDestroyApplication : com.jjh.component.F3@28f4f300
2023-09-19 18:09:31.505 INFO 9672 --- [ main] com.jjh.InitAndDestroyApplication : com.jjh.component.F3@6ca8fcf3
成功了!
4.注入ApplicationContext容器通过getBean()获取
@Autowired
private ApplicationContext context;
public F4 getF4(){
return context.getBean(F4.class);
}
这是利用容器获取F4对象,这样肯定就是获取到F4本身了,
运行一下:
2023-09-19 18:09:31.505 INFO 9672 --- [ main] com.jjh.InitAndDestroyApplication : com.jjh.component.F4@69f0b0f4
2023-09-19 18:09:31.505 INFO 9672 --- [ main] com.jjh.InitAndDestroyApplication : com.jjh.component.F4@66933239
2023-09-19 18:09:31.505 INFO 9672 --- [ main] com.jjh.InitAndDestroyApplication : com.jjh.component.F4@2f7efd0b
可以看到成功了!