作用域有几种
首先我们先说一下Spring中bean的作用域有几种,Spring中共有5中作用域,分别是:
- singleton(单例):Spring容器中只有一个该类型的bean,每次从容器中取的时候都返回同一个。
- prototype(原型):每次从Spring容器中获取该类型的bean时都会创建一个新的bean并返回。
- request(web请求):在一个web请求中从Spring容器获取bean时会返回一个新的,请求结束bean就销毁,每个请求对应一个bean。
- session(web会话):在一个web会话中从Spring容器获取bean时会返回一个新的,会话结束bean就销毁,每个会话对应一个bean。
- application(应用):在整个应用内从Spring容器中获取bean都会返回同一个。
通常用的最多的是单例bean,其他类型的很少用到,虽然很少用到,我们还是怀着一种好奇心来一探究竟。
看看以下这几个问题吧:
第一个问题:http请求和原型Controller的关系
当HTTP请求到来时被设置成原型模式的Controller会是同一个吗?如下代码,我们把这个Controller通过@Scope注解设置为原型模式:
@RestController()@RequestMapping("prototype")@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)public class PrototypeController{ @GetMapping("index") @ResponseBody public String index() { return this.toString(); }}
然后启动服务,通过http请求访问一下这个路径会发现每次请求都会返回不同的对象,如下举四个例子:
com.controller.PrototypeController@11265a97com.controller.PrototypeController@74e6e0e8com.controller.PrototypeController@6696915ecom.controller.PrototypeController@72826ab2...
说明当HTTP请求到来时每次都是一个新的Controller来处里。其实,通过Spring源码我们发现,当每一个http请求到达时,SpringMVC都会重新从容器中去获取和这个请求对应的Controller。上述例子中我们把这个Controller设置成了原型模式,所以每个请求获得的Controller都是一个新的Controller,验证的结果也说明了这一点。
单例bean依赖单例bean
那么当单例的Controller依赖单例的service时又是会怎样呢?
单例的Controller:
@RestController()@RequestMapping("singleton")public class SingletonController{ @Autowired private SingletonService singletonService; @GetMapping("index") @ResponseBody public String index() { return singletonService.toString(); }}
我们通过http请求访问这个地址,会发现返回的Controller对象和Service对象每次都一样:
com.SingletonController@e4fff13;com.SingletonServiceImpl@36186e00com.SingletonController@e4fff13;com.SingletonServiceImpl@36186e00...
这也说明了重新从Spring容器中获取的Controller不会变,这个Controller依赖的单例bean(singletonService)也不会变,其实这也是一种最简单也是工作中最常见的一种情况,bean都是按照单例设置的。那么当这个Controller依赖的service是一个原型bean的时候,我们每次发送http请求时,这个service会不会还是同一个呢?我们一起来看一下:
单例bean依赖原型bean
如下代码说明了这种情况:
单例模式下的Controller:
@RestController()@RequestMapping("singletonWithPrototypeService")public class SingletonWithPrototypeServiceController{ @Autowired private PrototypeService prototypeService; @GetMapping("index") @ResponseBody public String index() { return prototypeService.toString(); }}
原型模式下的Service:
@Service()@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)public class PrototypeServiceImpl implements PrototypeService {...}
我们发送多次http请求得出的结果是这个原型bean还是同一个,并不会创建新的,结果如下:
com.service.PrototypeServiceImpl@69565494com.service.PrototypeServiceImpl@69565494...
现在我们说一下为什么PrototypeService已经被声明成原型模式,每次http请求调用的这个service还是同一个呢?答案就是:因为Controller是单例。Controller是单例说明这个Controller还是同一个啊并没有发生变化,同样的它的属性也没有发生变化,说得更专业一点就是当HTTP请求到来时处理这个HTTP请求的service并不是从Spring容器中重新获取的,还是在Controller被初始化时注入的那个service。所以无论有多少个HTTP请求过来,这个service都是同一个。现在应该明白了吧。
那么既然每次单例bean A调用原型bean B这个原型B还是同一个,那么在单例bean中该如何使用原型bean呢?其实也很简单,只要重新从Spring容器中获取一个就可以了,如下代码我们实现ApplicationContextAware接口来获得一个ApplicationContext对象然后从ApplicationContext对象中重新获取一下这个service就可以了:
@RestController()@RequestMapping("singletonWithPrototypeService")public class SingletonWithPrototypeServiceController implements ApplicationContextAware{ private ApplicationContext applicationContext; @GetMapping("index") @ResponseBody public String index() { return applicationContext.getBean(PrototypeService.class).toString(); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }}
单例bean依赖请求域作用下的bean
当单例bean依赖请求域作用下的bean又会是什么情况呢?先提前说一下,因为是请求域下的bean,所以在一个请求过来时都会是一个新的bean,这已经很好理解了。但是当容器刚启动时,因为没有接收到HTTP请求所以这个请求域下的bean并没有创建,那么此时注入进去的bean又是什么呢?此时我们就通过spring容器启动后的一个监听器来一探究竟。我们在一个单例的Controller里注入一个请求作用域下的Service,如下:
单例Controller代码如下:
@RestController()@RequestMapping("singletonWithRequestService")public class SingletonWithRequestServiceController{ @Autowired private RequestService requestService; public RequestService getRequestService() { return requestService; }}
请求域service如下:
@Service(BeanNameConstant.RequestService)@Scope(value = WebApplicationContext.SCOPE_REQUEST,proxyMode = ScopedProxyMode.INTERFACES)public class RequestServiceImpl implements RequestService {...}
监听器代码如下:
@Componentpublic class ApplicationOnRefreshListener implements ApplicationListener { @Override public void onApplicationEvent(ContextRefreshedEvent event) { ApplicationContext context = event.getApplicationContext(); SingletonWithRequestServiceController controller = context.getBean(SingletonWithRequestServiceController.class); String name = controller.getRequestService().getClass().getName(); System.out.println(name); }}
当启动容器时打印出来的信息:com.sun.proxy.$Proxy53
是的你没看错,是一个代理类。容器启动时Spring会先注入一个代理类,当HTTP请求到来时真实的service会被创建而且此时代理类会调用这个真实的service的方法进行处理。
Scope注解的参数有哪些值?
第一个参数value表示这个bean的范围,一共有5个值:
1. ConfigurableBeanFactory.SCOPE_SINGLETON:单例2. ConfigurableBeanFactory.SCOPE_PROTOTYPE:原型3. WebApplicationContext.SCOPE_REQUEST:请求4. WebApplicationContext.SCOPE_SESSION:会话5. WebApplicationContext.SCOPE_APPLICATION:应用
第二个参数proxyMode表示创建代理的模式,一共有4个值:
1. ScopedProxyMode.DEFAULT:默认2. ScopedProxyMode.NO:不代理3. ScopedProxyMode.INTERFACES:jdk接口的代理4. ScopedProxyMode.TARGET_CLASS:cglib子类的代理
以上就是今天的内容,喜欢的小伙伴可以关注一下哦。有问题可以在评论区留言哦。