@Bean实现内幕

问题产生

以下无论多次 getBean(A.class)还是factoryMethodA多次调用factoryMethodB, 然而factoryMethodB只会被调用一次, Spring如何做到的?

@Configuration
public class SpringTest {
   public static void main(String[] args) {
      AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringTest.class);
      System.out.println(context.getBean(A.class).hashCode()); // 2101842856
      System.out.println(context.getBean(A.class).hashCode()); // 2101842856
      System.out.println(context.getBean(B.class));
   }

   @Bean
   public A factoryMethodA() {
      System.out.println("Create A");
      // 只会打印一次Create B
      factoryMethodB(); // 第一次调用
      factoryMethodB(); // 第二次调用
      return new A();
   }
   
   @Bean
   public B factoryMethodB() {
      System.out.println("Create B");
      return new B();
   }
}

实现原理

  1. Spring会在某一时机调用getBean方法, 若bean不存在则会创建所有单例非懒加载的bean, 而对于加了@Bean注解的方法, 则会调用该方法创建bean, 若已经存在或者处于正在创建中, 则不调用, 会从容器直接返回bean, 从而保证了bean的单例, 所以走不走创建bean流程是getBean方法里面判断的, 保证了单例
  2. 对于加了@Bean注解的方法, 除了getBean方法外, 还可以在加了@Bean的方法之间调用, 这点要保证单例的话就要进行代理, 保证方法调用一次, 所以加了@Configuration的类, Spring会进行cglib代理, 贴了@Bean的方法会作为产生bean的factoryMethod
  3. Spring要判断出factoryMethod是被谁调用的, 有可能是getBean中创建bean调过来的, 也可能是其他factoryMethod调过来的, 这两者有区别!!!
    ① 如果是从创建bean的方法直接调过来的, 就给当前线程设置变量为factoryMethod, 走到拦截器拦截方法时, 拿到cglib代理的method, 比较两个方法的名字和参数, 这时都是factoryMethod, 所以会一致, 然后创建对象
    ② 如果创建A对象的factoryMethodA调用创建B对象的factoryMethodB, 在创建A对象时, 此时当前线程的变量存入factoryMethodA, cglib拦截到factoryMethodA时, 发现两个方法名字和参数是一致的, 然后调用父类方法创建A对象, 在调用父类方法创建A时, 里面调用了factoryMethodB, 此时又会被cglib方法拦截器拦截到, 而此时当前线程保存的仍然是factoryMethodA, 但是cglib拦截的方法是factoryMethodB, 两个方法名字不一致, 则Spring认为是方法中调用方法, 此时Spring就去容器里面拿B对象, 能拿到就直接返回, 不能拿到再走创建B的bean的流程, 此时当前线程变量会先设置为factoryMethodB, 然后会手动再调用一次factoryMethodB, 然后cglib的方法拦截器又会拦截到factoryMethodB后, 此时两个方法的名字和参数都是factoryMethodB的, 一致, 则就调用factoryMethodB创建出B对象, 然后当前线程的变量又会重置回factoryMethodA(流程是递归的方式处理的), 其实相当于拦截器会走两次, 第一次重置了线程变量值, 第二次真正调用方法创建bean

cglib使用

Object:由CGLib动态生成的代理类实例
Method:上文中实体类所调用的被代理的方法引用
Object[]:参数值列表
MethodProxy:生成的代理类对方法的代理引用
methodProxy.invokeSuper(obj, args)这行代码表示执行父类的方法

public class CglibTest {
   public static void main(String[] args) {
      test();
   }
   public static void test() {
      Enhancer enhancer = new Enhancer();
      enhancer.setSuperclass(A.class);
      // obj为生产的代理对象, method为原生目标方法, args为方法参数, methodProxy为代理方法
      enhancer.setCallback((MethodInterceptor) (obj, method, args, methodProxy) -> {
         System.out.println("代理的逻辑");
         // 调用父类方法, 即目标方法
         return methodProxy.invokeSuper(obj, args);
      });
      // 返回的就是代理对象
      A a = (A) enhancer.create();
      // 调用a对象的doSth方法就会调用到拦截器的拦截方法
      a.doSth();
   }
}

源码分析

① 在使用@Bean方式创建bean的流程中, 会回调以下方法, 把factoryMethod方法对象存到当前线程
org.springframework.beans.factory.support.SimpleInstantiationStrategy#instantiate

@Override
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
      @Nullable Object factoryBean, final Method factoryMethod, Object... args) {

   try {
      // 省略代码...
      // 先拿到上一次存的factoryMethod
      Method priorInvokedFactoryMethod = currentlyInvokedFactoryMethod.get();
      try {
          // 这里会把方法对象放到ThreadLocal中
          // private static final ThreadLocal<Method> currentlyInvokedFactoryMethod = new ThreadLocal<>();
         currentlyInvokedFactoryMethod.set(factoryMethod);
         Object result = factoryMethod.invoke(factoryBean, args);
         if (result == null) {
            result = new NullBean();
         }
         return result;
      }
      finally {
          // 重置回上一次存的factoryMethod
         if (priorInvokedFactoryMethod != null) {
            currentlyInvokedFactoryMethod.set(priorInvokedFactoryMethod);
         }
         else {
             // 上一次要是没有的话, 当前的factoryMethod执行完就要删除
            currentlyInvokedFactoryMethod.remove();
         }
      }
   }
   // 省略代码...
}

② 在cglib的拦截器BeanMethodInterceptor中的intercept方法中, 判断线程中存取的方法对象和当前的方法对象方法名, 参数是否一致
org.springframework.context.annotation.ConfigurationClassEnhancer.BeanMethodInterceptor#intercept

public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
         MethodProxy cglibMethodProxy) throws Throwable {
    // 省略...
    // 本方法调用, 创建bean, 调用父类的方法
    // 判断线程中存取的方法对象和当前的方法对象方法名, 参数是否一致
   if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
      // The factory is calling the bean method in order to instantiate and register the bean
      // (i.e. via a getBean() call) -> invoke the super implementation of the method to actually
      // create the bean instance.
      if (logger.isInfoEnabled() &&
            BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
         logger.info(String.format("@Bean method %s.%s is non-static and returns an object " +
                     "assignable to Spring's BeanFactoryPostProcessor interface. This will " +
                     "result in a failure to process annotations such as @Autowired, " +
                     "@Resource and @PostConstruct within the method's declaring " +
                     "@Configuration class. Add the 'static' modifier to this method to avoid " +
                     "these container lifecycle issues; see @Bean javadoc for complete details.",
               beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
      }
      // 这行代码表示执行父类的方法, 即factoryMethod
      return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
   }
   // 走这里, 代表方法中调用方法
   return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}

// 方法中调用方法核心判断
private boolean isCurrentlyInvokedFactoryMethod(Method method) {
    // 获取当前线程中存取的方法对象
   Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
   // 判断当前代理的方法对象和线程存的方法对象名字/参数是否一致, 一致则不是方法调用, 不一致则为方法调用
   return (currentlyInvoked != null && method.getName().equals(currentlyInvoked.getName()) &&
         Arrays.equals(method.getParameterTypes(), currentlyInvoked.getParameterTypes()));
}

总结

  1. getBean会判断容器中是否存在bean, 不存在才创建, 存在直接返回, 保证多次getBean返回同一对象
  2. 对@Configuration注解类Spring进行了cglib代理, 对加了@Bean的方法之间调用, 保证了只会在bean不存在的时候才会调用factoryMethod进行生产
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值