问题抛出:
1、假如spring想在一个单例对象中引用原型对象如何实现呢?
我们都知道spring默认的对象都是单例对象、默认放到一级缓存中,假如一个单例Bean想要引用原型Bean, 就需要使用lookup-method 、replace-method.
注:源码跟踪示例使用配置文件方式 毕竟使用的方式千篇一律 有趣的原理才是万里挑一
源码跟踪:
提示:源码跟踪禁忌抠每一行代码 弄清源码功能整体的脉络思路就好
- doCreateBean方法里面调用一个方法prepareMethodOverrides.
public void prepareMethodOverrides() throws BeanDefinitionValidationException { // Check that lookup methods exist and determine their overloaded status. if (hasMethodOverrides()) { getMethodOverrides().getOverrides().forEach(this::prepareMethodOverride); } }
①通过跟踪代码逻辑:当前Bean的定义信息是否有methodOverrides,其实就是指的是Bean是否有lookup-method标签和reoplace-method标签。进入到prepareMethodOverride这个方法我当时都懵了,也没有对这两个标签进行处理逻辑啊,这里就是做了一个标记。官方翻译[将重写标记为未重载,以避免arg类型检查的开销。] 说实话我没看懂什么意思 不影响这里脉络. 这里我的理解标记MethodOverride的overloaded的属性为false 方便Cglib方法拦截器MethodInterceptor去处理MethodOverride 。
②这里为什么又提到Cglib呢 因为如果一个Bean有lookup-method 或replace-method,spring首先将这个Bean生成代理对象 放到一级缓存,这个代理对象设置了三个方法拦截器分别是NoOp、LookupOverrideMethodInterceptor、LookupOverrideMethodInterceptor
③这里还要说明 比如这个代理对象 具体会执行那个方法拦截器呢? 这里就要用到Cglib的过滤器(setCallbackFilter)了 这里是MethodOverrideCallbackFilter,通过accept方法返回的int下标,用于指定调用哪个拦截器进行拦截处理。这里下标取值的方法 就是用到①里面的标记MethodOverride的overloaded的属性为false逻辑 - 实例化Bean的方法 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) { // 通过bd,即bean定义,拿到bean的class final Class<?> clazz = bd.getBeanClass(); if (clazz.isInterface()) { // 很显然,接口类型的bean是无法实例化的,不要问为什么,问了会显得你很无知 throw new BeanInstantiationException(clazz, "Specified class is an interface"); } try { if (System.getSecurityManager() != null) { constructorToUse = AccessController.doPrivileged( (PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor); } else { // 通过bean的class,拿到其public的无参构造器 constructorToUse = clazz.getDeclaredConstructor(); } bd.resolvedConstructorOrFactoryMethod = constructorToUse; } catch (Throwable ex) { // 如果没有指定默认public构造方法,那就jdk自动加上 // 如果指定了很多public构造方法,但是却没有无参的,就会报下面这个错 throw new BeanInstantiationException(clazz, "No default constructor found", ex); } } } // 下面就有意思了,通过bean工具类,将bean的默认共有无参构造器作为参数,去实例化bean return BeanUtils.instantiateClass(constructorToUse); } else { // Must generate CGLIB subclass. return instantiateWithMethodInjection(bd, beanName, owner); } }
判断Bean的定义信息是否有lookup-method 或replace-method标签 如果有 要生成代理对象放到一级缓存中
- 如何生成代理CglibSubclassingInstantiationStrategy.instantiate方法逻辑
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); } } // SPR-10785: set callbacks directly on the instance instead of in the // enhanced class (via the Enhancer) in order to avoid memory leaks. Factory factory = (Factory) instance; factory.setCallbacks(new Callback[] {NoOp.INSTANCE, new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner), new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)}); return instance; }
①首先创建EnhancedClass对象 这个对象设置CallbackFilter过滤器 主要作用 代理对象调用的方法,用具体哪个方法拦截器处理
②生成代理对象 设置方法拦截器 NoOp、LookupOverrideMethodInterceptor、LookupOverrideMethodIntercepto
③ 代理对象放入一级缓存
- 方法拦截器具体做了什么呢?LookupOverrideMethodInterceptor举例
@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())); } }
①根据调用的方法对象 获取LookupOverride对象
②根据配置lookup-method标签获取Bean的名称 到Spring容器中获取Bean你对象
③我们知道如果一个Bean是一个原型类型 那么spring是不向缓存放的 ,每次根据Bean的定义信息重新获取、从而达到单例对象引用原型
案例:
- 配置文件
<bean id="apple" class="com.zhangfuyin.methodOverride.Apple" scope="prototype"/> <bean id="fruitPlant" class="com.zhangfuyin.methodOverride.FruitPlant"> <lookup-method name="getFruit" bean="apple"/> </bean>
- java对象
public class Apple extends Fruit{ public Apple(){ System.out.println("I got fresh apple"); } } ========================================================== public class Fruit { public Fruit(){ System.out.println("I got fruit"); } } =========================================================== public abstract class FruitPlant { public abstract Fruit getFruit(); }
- 测试代码输出
public static void main(String agrs[]){ ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:methodoverride.xml"); FruitPlant fruitPlant1 = (FruitPlant)ac.getBean("fruitPlant"); System.out.println(fruitPlant1.getFruit().hashCode()); FruitPlant fruitPlant2 = (FruitPlant)ac.getBean("fruitPlant"); System.out.println(fruitPlant2.getFruit().hashCode());
I got fruit
I got fresh apple
1087072654
I got fruit
I got fresh apple
864221358
可以看出 两次打印对象hashcode值不一样 从而单例对象引用原型模式