Spring @Lookup深入分析实现原理
程序员小潘 人气:01. 前言
在使用Spring的时候,往单例bean注入原型bean时,原型bean可能会失效,如下:
@Component public class Person { @Autowired Car car; public Car getCar() { return car; } } @Component @Scope("prototype") public class Car { }
调用Person#getCar()
方法返回的总是同一个Car对象,这也很好理解,因为Person是单例的,Spring在创建Person时只会注入一次Car对象,以后Car都不会再改变了。
怎么解决这个问题呢?Spring提供了多种方式来获取原型bean。
2. 解决方案
解决方案有很多,本文重点分析@Lookup
注解的方式。
1、每次从ApplicationContext重新获取bean。
@Component public class Person implements ApplicationContextAware { private ApplicationContext applicationContext; @Lookup public Car getCar() { return applicationContext.getBean(Car.class); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
2、通过CGLIB生成Car子类代理对象,每次都从容器内获取bean执行。
@Component @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS) public class Car { }
3、通过**@Lookup**
注解的方式,方法体已经不重要了,代理对象每次都会从容器中重新获取bean。
@Component public class Person { @Lookup public Car getCar() { return null; } }
3. 源码分析
为什么方法上加了@Lookup
注解,调用该方法就能拿到原型bean了呢?其实纵观上述三种方式,要想拿到原型bean,底层原理都是一样的,那就是每次都通过ApplicationContext#getBean()
方法从容器中重新获取,只要Car本身是原型的,Spring就会保证每次拿到的都是新创建的Car实例。
**@Lookup**
注解也是通过生成代理类的方式,重写被标记的方法,每次都从ApplicationContext获取bean。
1、Spring加载Person的时候,容器内不存在该bean,那首先就是要实例化Person对象。Spring会通过SimpleInstantiationStrategy#instantiate()
方法去实例化Person。
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) { /** * 如果bean没有要重写的方法,直接反射调用构造函数创建对象 * 反之,需要通过CGLIB创建增强子类代理对象 */ 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 { /** * 存在 lookup-method 和 replaced-method * 通过CGLIB生成代理类 */ return instantiateWithMethodInjection(bd, beanName, owner); } }
BeanDefinition有一个属性**methodOverrides**
,它里面存放的是当前bean里面是否有需要被重写的方法,这些需要被重写的方法可能是**lookup-method**
或**replaced-method**
。如果没有需要重写的方法,则直接通过反射调用构造函数来实例化对象;如果有需要重写的方法,这个时候就不能直接实例化对象了,需要通过CGLIB来创建增强子类,把父类的方法给重写掉。
于是会调用instantiateWithMethodInjection()
方法来实例化bean,最终是通过CglibSubclassCreator
来实例化。
@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); }
2、CglibSubclassCreator创建CGLIB子类,重写父类方法。Spring会通过Enhancer来创建增强子类,被@Lookup
标记的方法会被LookupOverrideMethodInterceptor拦截。
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; factory.setCallbacks(new Callback[] {NoOp.INSTANCE, new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner), new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)}); return instance; }
3、LookupOverrideMethodInterceptor要拦截被@Lookup
标记的方法,必然要实现intercept()
方法。逻辑很简单,就是每次都调用BeanFactory#getBean()
从容器中获取bean。
@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())) { return (argsToUse != null ? this.owner.getBean(lo.getBeanName(), argsToUse) : this.owner.getBean(lo.getBeanName())); } else { return (argsToUse != null ? this.owner.getBean(method.getReturnType(), argsToUse) : this.owner.getBean(method.getReturnType())); } }
4. 总结
一个bean一旦拥有被@Lookup
注解标记的方法,就意味着该方法需要被重写掉,Spring在实例化bean的时候会自动基于CGLIB生成增强子类对象重写掉父类方法。此时父类被@Lookup
注解标记的方法体已经不重要了,不会被执行了,CGLIB子类会通过LookupOverrideMethodInterceptor拦截掉被@Lookup
注解标记的方法。方法体重写的逻辑也很简单,就是每次都通过BeanFactory获取bean,只要bean本身是原型的,每次拿到的都将是不同的实例。
加载全部内容