19、Spring5.x源码之旅十九getBean详解五

  • 只有一个无参工厂方法
    • instantiate工厂方法实例化
  • SimpleInstantiationStrategy的instantiate
  • 有多个有参工厂方法(重点)
    • 分段1-排序
    • AutowireUtils的sortFactoryMethods排序
    • EXECUTABLE_COMPARATOR比较器
  • 分段2-准备工作
  • 分段3-核心工作

只有一个无参工厂方法

如果只有一个构造函数的话,看是否有参数,没有的话直接就设置bean定义的属性,然后实例化设置进包装对象。

//如果只获取到一个方法,且传入的参数为空,且没有设置构造方法参数值
			if (candidateList.size() == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {
   
     
				Method uniqueCandidate = candidateList.get(0);//获取出来
				if (uniqueCandidate.getParameterCount() == 0) {
   
     //没参数的话
					mbd.factoryMethodToIntrospect = uniqueCandidate;//设置工厂方法
					synchronized (mbd.constructorArgumentLock) {
   
     
						mbd.resolvedConstructorOrFactoryMethod = uniqueCandidate;//设置解析出来的方法
						mbd.constructorArgumentsResolved = true;//参数也已经解析了
						mbd.resolvedConstructorArguments = EMPTY_ARGS;//方法参数为空
					}
					bw.setBeanInstance(instantiate(beanName, mbd, factoryBean, uniqueCandidate, EMPTY_ARGS));//创建实例并设置到持有器里
					return bw;
				}
			}

instantiate工厂方法实例化

这个就是最简单的无参工厂方法实例化。

private Object instantiate(String beanName, RootBeanDefinition mbd,
			@Nullable Object factoryBean, Method factoryMethod, Object[] args) {
   
     

		try {
   
     
			if (System.getSecurityManager() != null) {
   
     
				return AccessController.doPrivileged((PrivilegedAction<Object>) () ->
						this.beanFactory.getInstantiationStrategy().instantiate(
								mbd, beanName, this.beanFactory, factoryBean, factoryMethod, args),
						this.beanFactory.getAccessControlContext());
			}
			else {
   
     //获取实例化策略进行实例化
				return this.beanFactory.getInstantiationStrategy().instantiate(
						mbd, beanName, this.beanFactory, factoryBean, factoryMethod, args);
			}
		}
		catch (Throwable ex) {
   
     
			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
					"Bean instantiation via factory method failed", ex);
		}
	}

SimpleInstantiationStrategy的instantiate

其实默认的实例化策略是CglibSubclassingInstantiationStrategy,就是可以用CGLIB做动态代理,但是仅限于方法注入的形式,所以这里是无参工厂方法还是调用父类SimpleInstantiationStrategy的实现。其实就是调用工厂实例的工厂方法,传入参数,只是参数是个空数组EMPTY_ARGS,返回对象。

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

		try {
   
     
			if (System.getSecurityManager() != null) {
   
     
				AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
   
     
					ReflectionUtils.makeAccessible(factoryMethod);
					return null;
				});
			}
			else {
   
     //设置factoryMethod可访问
				ReflectionUtils.makeAccessible(factoryMethod);
			}
			//获取前面存在的线程本地的FactoryMethod
			Method priorInvokedFactoryMethod = currentlyInvokedFactoryMethod.get();
			try {
   
     
				currentlyInvokedFactoryMethod.set(factoryMethod);//设置新的
				Object result = factoryMethod.invoke(factoryBean, args);//调用工厂方法
				if (result == null) {
   
     
					result = new NullBean();
				}
				return result;
			}
			finally {
   
     
				if (priorInvokedFactoryMethod != null) {
   
     //如果线程本地存在,就设置回老的
					currentlyInvokedFactoryMethod.set(priorInvokedFactoryMethod);
				}
				else {
   
     //否则就删除,等于没设置
					currentlyInvokedFactoryMethod.remove();
				}
			}
		}
		...
	}

有多个有参工厂方法(重点)

接下去又是很经典的地方了,怎么处理有多个参数的工厂方法呢,到底哪一个才是应该调用呢。

分段1-排序

首先会根据一定的规则进行工厂方法的排序,规则就是先按修饰符public优先排,然后同修饰符的按参数多的排

Method[] candidates = candidateList.toArray(new Method[0]);
AutowireUtils.sortFactoryMethods(candidates);//排序,根据public优先,参数多的优先

比如我有6个同名的,其实也就是重载的,有不同修饰符的,有不同参数个数的,没排序前:
*
排序后:
*

AutowireUtils的sortFactoryMethods排序

	public static void sortFactoryMethods(Method[] factoryMethods) {
   
     
		Arrays.sort(factoryMethods, EXECUTABLE_COMPARATOR);
	}

EXECUTABLE_COMPARATOR比较器

	//先比较修饰符,再比较参数个数 从Public到非Public,参数从多到少
	private static final Comparator<Executable> EXECUTABLE_COMPARATOR = (e1, e2) -> {
   
     
		int result = Boolean.compare(Modifier.isPublic(e2.getModifiers()), Modifier.isPublic(e1.getModifiers()));
		return result != 0 ? result : Integer.compare(e2.getParameterCount(), e1.getParameterCount());
	};

分段2-准备工作

因为接下去解析的工厂方法是有参数的,所以要进行一些处理,比如获取构造器参数值(如果有传的话),然后获取自动装配模式,工厂方法一般默认是AUTOWIRE_CONSTRUCTOR,还会定义一个minTypeDiffWeight 差异值,这个就是用在如果有多个工厂方法的时候,看工厂方法的参数和具体装配的bean的类型的差异,取最小的。还定义了有个ambiguousFactoryMethods ,用来存放差异值一样的方法,说明是一样的类型,无法判断要用哪个工厂方法实例化。比如protected UserDao userDao(TestBean2 t2)public UserDao userDao(TestBean t1)两个构造方法类型差异是一样的,所以不知道要用哪个,就会报异常啦。还有minNrOfArgs也很关键,用来做优化的,如果有最小参数个数,那么一些参数个数少于最小个数的就不需要去判断了,直接跳过,否则就算获取到了也不匹配,反而浪费资源了。如果minNrOfArgs0,那就说明没有参数个数限制,那后面就需要一个个去判断哪个差异最小,最符合了。

			ConstructorArgumentValues resolvedValues = null;//构造器参数值
			boolean autowiring = (mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
			int minTypeDiffWeight = Integer.MAX_VALUE;//最小的类型差距
			Set<Method> ambiguousFactoryMethods = null;//模棱两可的工厂方法集合

			int minNrOfArgs;//最小参数个数
			if (explicitArgs != null) {
   
     
				minNrOfArgs = explicitArgs.length;//如果存在显示参数,就是显示参数的个数
			}
			else {
   
     
				if (mbd.hasConstructorArgumentValues()) {
   
     //如果存在构造器参数值,就解析出最小参数个数
					ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
					resolvedValues = new ConstructorArgumentValues();
					minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
				}
				else {
   
     
					minNrOfArgs = 0;//没有就为0
				}
			}

分段3-核心工作

这段最核心的,自动装配就在里面,首先前面已经过滤出candidates工厂方法了,但是我们这不知道要选哪个去实例化,所以得有个选取规则,首先参数个数不能小于最少的,如果有显示的参数存在,那个数不一致的也不要,然后就获取参数名字探测器去进行每一个工厂方法的参数名字探测,然后创建一个参数持有器,这里面就会涉及自定装配,依赖的参数会实例化。最后根据参数持有器的参数和工厂方法的参数类型作比较,保存最小的差异值的那个,把模棱两可的集合设置空。如果有相同差异值的就放入一个集合里,如果集合有数据,说明不知道用哪个工厂方法来实例化,会报异常。大致就这么个过程,但是其中涉及了很多东西,只能慢慢来分析了。

//遍历每个后选的方法,查看可以获取实例的匹配度
			for (Method candidate : candidates) {
   
     
				Class<?>[] paramTypes = candidate.getParameterTypes();
				if (paramTypes.length >= minNrOfArgs) {
   
     
					ArgumentsHolder argsHolder;

					if (explicitArgs != null) {
   
     //显示参数存在,如果长度不对就继续下一个,否则就创建参数持有其持有

						if (paramTypes.length != explicitArgs.length) {
   
     
							continue;
						}
						argsHolder = new ArgumentsHolder(explicitArgs);
					}
					else {
   
     

						try {
   
     
							String[] paramNames = null;
							ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();//获取参数名字探测器
							if (pnd != null) {
   
     //存在的话进行探测
								paramNames = pnd.getParameterNames(candidate);
							}
							argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw,
									paramTypes, paramNames, candidate, autowiring, candidates.length == 1);
						}
						catch (UnsatisfiedDependencyException ex) {
   
     
							...
							continue;
						}
					}
					//根据参数类型匹配,获取类型的差异值
					int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
							argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
					// Choose this factory method if it represents the closest match.
					if (typeDiffWeight < minTypeDiffWeight) {
   
     //保存最小的,说明参数类型相近
						factoryMethodToUse = candidate;
						argsHolderToUse = argsHolder;
						argsToUse = argsHolder.arguments;
						minTypeDiffWeight = typeDiffWeight;
						ambiguousFactoryMethods = null;//没有模棱两可的方法
					}//如果出现类型差异相同,参数个数也相同的,而且需要严格判断,参数长度也一样,常数类型也一样,就可能会无法判定要实例化哪个,就会报异常

					else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight &&
							!mbd.isLenientConstructorResolution() &&
							paramTypes.length == factoryMethodToUse.getParameterCount() &&
							!Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes())) {
   
     
						if (ambiguousFactoryMethods == null) {
   
     
							ambiguousFactoryMethods = new LinkedHashSet<>();
							ambiguousFactoryMethods.add(factoryMethodToUse);
						}
						ambiguousFactoryMethods.add(candidate);
					}
				}
			}

后面一篇用一个简单的例子来分析下这个过程。

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。