前言
最近看到个面试题: Spring中注入的bean,如何每次获取新的实例?
相信稍微了解点Spring
的小伙伴都能够马上回答出: spring bean
默认scope
是singleton
,将其修改为prototype
就可以了!
但事实真的是这样吗?我们来看下面这个案例~
案例
普通案例
java
public interface UserService {
}
@Service
public class UserServiceImpl implements UserService {
}
public interface OrderService {
/**
* 测试 bean
*/
void testBean();
}
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private UserService userService;
@Override
public void testBean() {
System.out.println(userService);
System.out.println(userService.hashCode());
}
}
再写个测试类
java
@SpringBootTest
public class TestBean {
@Autowired
private OrderService orderService;
@Test
public void testBean() {
for (int i = 0; i < 5; i++) {
orderService.testBean();
}
}
}
运行并查看执行结果
我们可以发现,打印五次的userService bean
的地址和hashCode
都是一样的。
也就是说,我们在5次中获取的注入的userService bean
都是同一个对象。
这个是很符合预期的,因为spring bean
默认的scope
就是singleton
,接下来我们将userService bean
的scope
修改为prototype
再看看情况~
使用prototype案例
在spring
中,我们如果想要修改bean
的scope
,可以使用@Scope
注解,将value
设置为你想要的scope
即可~
java
@Scope(value = "prototype")
@Service
public class UserServiceImpl implements UserService {
}
如上,我已经们将userService bean
的scope
修改为prototype,接下来再运行下测试类查看执行结果~
通过执行结果,我们可以惊奇的发现,即使我们已经将bean
的scope
修改为了prototype
,但是我们仍然还是获取的同一个对象~
这时候不要着急,让我们回顾下案例代码~
虽然我们已经将userService bean
的scope
修改为了prototype
,但是我们本身是在OrderService
中去获取的userService
,
而OrderService bean
仍然是singleton
,其只会被创建一次,创建时注入的userService bean
已经固定
嘿嘿,这里就呼应文章标题了!Spring注入prototype bean被固定
破局
通过以上两个案例,现状我们已经了解了,那么该怎么破局呢?
两个方法
- 提供
bean
的get
方法,get
时,利用ApplicationContext
重新从容器中获取 - 提供
bean
的get
方法,再利用@Lookup
注解
方法一:ApplicationContext
第一步: 先简单封装一个ApplicationContextUtil
工具类,方便从Spring
容器中获取bean
java
@Component
public class ApplicationContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextUtil.applicationContext = applicationContext;
}
public static <T> T getBeanByClass(Class<T> targetClass) {
return applicationContext.getBean(targetClass);
}
}
第二步: 提供UserService
的get
方法,在get
方法中利用封装好的ApplicationContextUtil
重新从容器中获取UserService bean
(前提还是要把scope
设置为prototype
)
java
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private UserService userService;
public UserService getUserService() {
return ApplicationContextUtil.getBeanByClass(userService.getClass());
}
@Override
public void testBean() {
System.out.println(getUserService());
System.out.println(getUserService().hashCode());
}
}
第三步: 做完前两步,可再运行测试类查看执行结果~
此时我们通过执行结果就可发现,每次获取到的userService bean
是不同的对象~
原理
跟踪代码流程太长了,我就直接贴出核心代码了,在ApplicationContext getBean
时,会检查bean
的scope
结合下面源码我们可见
- 如果是
singleton
,那么会先从容器里获取,获取不到再createBean
- 而
prptotype
则是直接createBean
这样一来,在外层,我们每次获取到的就是新的一个bean
了,所以地址和hashCode
都不同
java
// org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// .....
// Create bean instance.
if (mbd.isSingleton()) {
// todo scope为singleton
// 先从容器中获取单例bean,拿不到再createBean
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
else if (mbd.isPrototype()) {
// todo scope为prototype
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
// todo 直接createBean
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
// .....
}
方法二:@Lookup注解
我们仍然需要提供UserService bean
的get
方法,并在get
方法上加上@Lookup
即可~
java
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private UserService userService;
@Lookup
public UserService getUserService() {
return userService;
}
@Override
public void testBean() {
System.out.println(getUserService());
System.out.println(getUserService().hashCode());
}
}
再次运行测试类查看执行结果~
结果符合预期,每次拿到的都是不同的userService bean
~
原理
实例化bean
时,如果bean
存在lookup-method
和 replaced-method
,则通过cglib
生成代理类
java
// SimpleInstantiationStrategy#instantiate
@Override
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
// Don't override the class with CGLIB if no overrides.
if (!bd.hasMethodOverrides()) {
Constructor<?> constructorToUse;
synchronized (bd.constructorArgumentLock) {
constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
if (constructorToUse == null) {
final Class<?> clazz = bd.getBeanClass();
if (clazz.isInterface()) {
throw new BeanInstantiationException(clazz, "Specified class is an interface");
}
try {
if (System.getSecurityManager() != null) {
constructorToUse = AccessController.doPrivileged(
(PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);
}
else {
constructorToUse = clazz.getDeclaredConstructor();
}
bd.resolvedConstructorOrFactoryMethod = constructorToUse;
}
catch (Throwable ex) {
throw new BeanInstantiationException(clazz, "No default constructor found", ex);
}
}
}
return BeanUtils.instantiateClass(constructorToUse);
}
else {
// todo 如果存在 lookup-method 和 replaced-method,则通过cglib生成代理类
return instantiateWithMethodInjection(bd, beanName, owner);
}
}
java
// CglibSubclassingInstantiationStrategy#instantiateWithMethodInjection
@Override
protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
return instantiateWithMethodInjection(bd, beanName, owner, null);
}
@Override
protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
@Nullable Constructor<?> ctor, Object... args) {
// Must generate CGLIB subclass...
return new CglibSubclassCreator(bd, owner).instantiate(ctor, args);
}
生成代理类后,会设置方法拦截器,拦截lookup-method
和 replaced-method
java
public Object instantiate(@Nullable Constructor<?> ctor, Object... args) {
Class<?> subclass = createEnhancedSubclass(this.beanDefinition);
Object instance;
if (ctor == null) {
instance = BeanUtils.instantiateClass(subclass);
}
else {
try {
Constructor<?> enhancedSubclassConstructor = subclass.getConstructor(ctor.getParameterTypes());
instance = enhancedSubclassConstructor.newInstance(args);
}
catch (Exception ex) {
throw new BeanInstantiationException(this.beanDefinition.getBeanClass(),
"Failed to invoke constructor for CGLIB enhanced subclass [" + subclass.getName() + "]", ex);
}
}
Factory factory = (Factory) instance;
// todo 设置拦截器
factory.setCallbacks(new Callback[] {NoOp.INSTANCE,
new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner),
new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)});
return instance;
}
LookupOverrideMethodInterceptor: 拦截被@LookUp
修饰的方法
java
private static class LookupOverrideMethodInterceptor extends CglibIdentitySupport implements MethodInterceptor {
private final BeanFactory owner;
public LookupOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) {
super(beanDefinition);
this.owner = owner;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy mp) throws Throwable {
// Cast is safe, as CallbackFilter filters are used selectively.
LookupOverride lo = (LookupOverride) getBeanDefinition().getMethodOverrides().getOverride(method);
Assert.state(lo != null, "LookupOverride not found");
Object[] argsToUse = (args.length > 0 ? args : null); // if no-arg, don't insist on args at all
if (StringUtils.hasText(lo.getBeanName())) {
Object bean = (argsToUse != null ? this.owner.getBean(lo.getBeanName(), argsToUse) :
// todo 重新从spring容器里获取bean
this.owner.getBean(lo.getBeanName()));
// Detect package-protected NullBean instance through equals(null) check
return (bean.equals(null) ? null : bean);
}
else {
// Find target bean matching the (potentially generic) method return type
ResolvableType genericReturnType = ResolvableType.forMethodReturnType(method);
return (argsToUse != null ? this.owner.getBeanProvider(genericReturnType).getObject(argsToUse) :
this.owner.getBeanProvider(genericReturnType).getObject());
}
}
}
可见,@Lookup
最终本质上也是通过重新从容器中获取bean
,如果bean
是prototype
则重新创建并返回。
总结
之所以注入的prototype bean
会被固定,是因为其所属的bean
属于singleton
,只会实例化一次,所以prototype bean
只会被注入一次,是同一个对象。
破局的方法也很简单,提供prototype bean
的get
方法,每次从容器中重新获取即可,容器检测到bean
的scope
是prototype
时,会重新创建一个bean
。
我是 Code皮皮虾 ,会在以后的日子里跟大家一起学习,一起进步! 觉得文章不错的话,可以在 掘金 关注我,这样就不会错过很多技术干货啦~