问题产生
以下无论多次 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();
}
}
实现原理
- Spring会在某一时机调用getBean方法, 若bean不存在则会创建所有单例非懒加载的bean, 而对于加了@Bean注解的方法, 则会调用该方法创建bean, 若已经存在或者处于正在创建中, 则不调用, 会从容器直接返回bean, 从而保证了bean的单例, 所以走不走创建bean流程是getBean方法里面判断的, 保证了单例
- 对于加了@Bean注解的方法, 除了getBean方法外, 还可以在加了@Bean的方法之间调用, 这点要保证单例的话就要进行代理, 保证方法调用一次, 所以加了@Configuration的类, Spring会进行cglib代理, 贴了@Bean的方法会作为产生bean的factoryMethod
- 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()));
}
总结
- getBean会判断容器中是否存在bean, 不存在才创建, 存在直接返回, 保证多次getBean返回同一对象
- 对@Configuration注解类Spring进行了cglib代理, 对加了@Bean的方法之间调用, 保证了只会在bean不存在的时候才会调用factoryMethod进行生产