@Scope(“prototype“)的正确⽤法——解决Bean的多例问题1. 问题,Spring管理的某个Bean需要使⽤多例
在使⽤了Spring的web⼯程中,除⾮特殊情况,我们都会选择使⽤Spring的IOC功能来管理Bean,⽽不是⽤到时去new⼀个。Spring 管理的Bean默认是单例的(即Spring创建好Bean,需要时就拿来⽤,⽽不是每次⽤到时都去new,⼜快性能⼜好),但有时候单例并不满⾜要求(⽐如Bean中不全是⽅法,有成员,使⽤单例会有线程安全问题,可以搜索线程安全与线程不安全的相关⽂章),你上⽹可以很容易到解决办法,即使⽤@Scope("prototype")注解,可以通知Spring把被注解的Bean变成多例,如下所⽰:
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/testScope")
public class TestScope {
private String name;
@RequestMapping(value = "/{username}",method = RequestMethod.GET)
public void userProfile(@PathVariable("username") String username) {
name = username;
try {
for(int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getId() + "name:" + name);
Thread.sleep(2000);
}
} catch (Exception e) {
e.printStackTrace();
}
return;
}
}
34name:aaa
34name:aaa
35name:bbb
34name:bbb
(34和35是两个线程的ID,每次运⾏都可能不同,但是两个请求使⽤的线程的ID肯定不⼀样,可以⽤来区分两个请求。)可以看到第⼆个请求bbb开始后,将name的内容改为了“bbb”,第⼀个请求的name也从“aaa”改为了“bbb”。要想避免这种情况,可以使
⽤@Scope("prototype"),注解加在TestScope这个类上。加完注解后重复上⾯的请求,发现第⼀个请求⼀直输出“aaa”,第⼆个请求⼀直输出“bbb”,成功。
2. 问题升级,多个Bean的依赖链中,有⼀个需要多例
第⼀节中是⼀个很简单的情况,真实的Spring Web⼯程起码有Controller、Service、Dao三层,假如Controller层是单例,Service 层需要多例,这时候应该怎么办呢?
2.1 ⼀次失败的尝试
⾸先我们想到的是在Service层加注解@Scope("prototype"),如下所⽰:
controller类代码
st.service.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/testScope")
public class TestScope {
@Autowired
private Order order;
private String name;
@RequestMapping(value = "/{username}", method = RequestMethod.GET) public void userProfile(@PathVariable("username") String username) {
name = username;
order.setOrderNum(name);
try {
for (int i = 0; i < 100; i++) {
System.out.println(
Thread.currentThread().getId()
+ "name:" + name
+ "--order:"
+ OrderNum());
Thread.sleep(2000);
}
} catch (Exception e) {
e.printStackTrace();
}
return;
}
}
Service类代码
import t.annotation.Scope;
import org.springframework.stereotype.Service;
@Service
@Scope("prototype")
public class Order {
private String orderNum;
public String getOrderNum() {
return orderNum;
}
public void setOrderNum(String orderNum) {
}
@Override
public String toString() {
return "Order{" +
"orderNum='" + orderNum + '\'' +
'}';
}
}
32name:aaa--order:aaa
32name:aaa--order:aaa
34name:bbb--order:bbb
32name:bbb--order:bbb
可以看到Controller的name和Service的orderNum都被第⼆个请求从“aaa”改成了“bbb”,Service并不是多例,失败。
2.2 ⼀次成功的尝试
我们再次尝试,在Controller和Service都加上@Scope("prototype"),结果成功,这⾥不重复贴代码,读者可以⾃⼰试试。
2.3 成功的原因(对2.1、2.2的理解)
Spring定义了多种作⽤域,可以基于这些作⽤域创建bean,包括:
单例( Singleton):在整个应⽤中,只创建bean的⼀个实例。
原型( Prototype):每次注⼊或者通过Spring应⽤上下⽂获取的时候,都会创建⼀个新的bean实例。
对于以上说明,我们可以这样理解:虽然Service是多例的,但是Controller是单例的。如果给⼀个组件加上@Scope("prototype")注解,每次请求它的实例,spring的确会给返回⼀个新的。问题是这个多例对象Service是被单例对象Controller依赖的。⽽单例服务Controller初始化的时候,多例对象Service就已经注⼊了;当你去使⽤Controller的时候,Service也不会被再次创建了(注⼊时创建,⽽注⼊只有⼀次)。
2.4 另⼀种成功的尝试(基于2.3的猜想)
为了验证2.3的猜想,我们在Controller钟每次去请求获取Service实例,⽽不是使⽤@Autowired注⼊,代码如下:
Controller类
st.service.Order;
st.utils.SpringBeanUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/testScope")
public class TestScope {
private String name;
@RequestMapping(value = "/{username}", method = RequestMethod.GET) public void userProfile(@PathVariable("username") String username) {
name = username;
Order order = Bean(Order.class);
order.setOrderNum(name);
try {
for (int i = 0; i < 100; i++) {
System.out.println(
Thread.currentThread().getId()
+ "name:" + name
+ "--order:"
+ OrderNum());
Thread.sleep(2000);
}
} catch (Exception e) {
e.printStackTrace();
}
return;
}
}
⽤于获取Spring管理的Bean的类
st.utils;
import org.springframework.beans.BeansException;
import t.ApplicationContext;
import t.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringBeanUtil implements ApplicationContextAware {
/**
* 上下⽂对象实例
*/
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext;
}
/**
* 获取applicationContext
*
* @return
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 通过name获取 Bean.spring ioc注解
*
* @param name
* @return
*/
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
/**
* 通过class获取Bean.
*
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* 通过name,以及Clazz返回指定的Bean
*
* @param name
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论