写点什么

spring 的 IOC 使用以及原理

用户头像
邱学喆
关注
发布于: 2021 年 04 月 10 日
spring的IOC使用以及原理

一. 概述

IOC 是控制反转,即是将对象的创建以及属性的注入交给 spring 来操作。

在创建对象,通过通过类构造方法来创建对象,也可以通过 Bean 工厂类来创建对象;还有一种方式,在 1.8 以后提供的 Supplier 进行获取该对象。

对对象中的属性注入,通过 set 方式进行注入。

二. 创建对象

在讨论创建对象之前,不得不提的就是 BeanDefinition 结构。spring 容器是根据该结构去选择创建对象的方式。主要的入口是 AbstractAutowireCapableBeanFactory.reateBeanInstance 进行创建实例

1. BeanDefinition 结构

这里只列出关键的字段:

  • scope 区域, 用来标志是单利

  • instanceSupplier Supplier 接口的实现类,优先级比 Bean 工厂方法还要高。

  • factoryMethodName Bean 工厂方法

  • factoryBeanName Bean 工厂对象名,如果有值,说明通过 Bean 工厂创建的方式是不是用静态方法。另外说明一下,如果创建的对象是单利,且 Bean 工厂对象也是单利,则会报异常

  • beanClass 该字段存放是需要创建的类或者是 Bean 工厂类,如果是 Bean 工厂类,说明 Bean 工厂创建的方式是用静态方法。

  • constructorArgumentLock 用来对下面的四个字段进行加锁。为了避免并发情况下,访问下面的四个字段不一致,导致对应的并发问题。

  • resolvedConstructorOrFactoryMethod 缓存解析后的创建对象的方法。只有当 getBean 方法中 args 为空时,会通过解析 Bean 工厂对应的方法或者对象类对应的构造方法找到对应的方法,将该方法保存到这个字段上面来

  • constructorArgumentsResolved 是否已经解析标志

  • resolvedConstructorArguments 缓存已经解析后的入参对象列表

  • preparedConstructorArguments 缓存解析前的入参对象列表

  • autowireMode 自动注入模式,有五个选项值

  • AUTOWIRE_NO-0-无 并不做其他动作

    AUTOWIRE_BY_NAME-1-名称 会扫描对象中属性描述符,且不是基础对象,会根据该属性名从 spring 容器中查找对应的对象,并注入到当前对象对应的字段中

    AUTOWIRE_BY_TYPE-2-类型 会扫描对象中属性描述符,且不是基础对象,会根据该属性对应的类型从 spring 容器中查找对应的对象,并注入到当前对应的字段中

    AUTOWIRE_CONSTRUCTOR-3-构造 会将结合 constructorArgumentValues 进行使用,通过遍历 constructorArgumentValues 里的对象名或者类型在 spring 容器查找对应的实例。然后传递到对应的构造方法中进行创建对象

    AUTOWIRE_AUTODETECT-4-自动检测 如果构造方法中有无参的,则会使用 AUTOWIRE_BY_TYPE,否则使用 AUTOWIRE_CONSTRUCTOR

  • constructorArgumentValues 构造方法中需要传递的参数列表

  • lenientConstructorResolution 当多个创建对象的方法存在时,需要找出最优的方法来进行创建对象。这里存在两种方式,一个是宽松型计算,一个是严格型计算。默认值为 true。具体逻辑稍后列出

  • propertyValues 存放需要对对象中需要注入的属性名以及对应的值

  • methodOverrides 存放需要覆写的方法列表。主要存放 Lookup 注解的方法列表。当调用含有 Lookup 注解的方法时,并不会执行方法内的代码块,而是直接检测该方法的返回类型,从 spring 容器找出对应的对象并返回。

2. Supplier 方式

Supplier 方式比较简单,直接调用 Supplier 对象进行获取获取,即可拿到对象。逻辑比较简单,就不罗列代码;

3. Bean 工厂类方式

Bean 工厂创建对象的代码如下:

//SimpleInstantiationStrategypublic Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,			@Nullable Object factoryBean, final Method factoryMethod, @Nullable Object... args) {
try { //.... 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(); } } }catch(//.... ){ //...... } }
复制代码


  • 场景一:当入参 args 不为空时,会从这个 Bean 工厂类(factoryBeanName 对象的类或者 beanClass 类)去查找 factoryMethodName 方法名对应的方法列表。常规下,只有一个。然而也会有其他同名但入参类型不一样或者入参数量不一致的的方法列表。如果有多个方法,需要从中找出最适合的方法。找到合适的方法后,就通过反射机制调用该方法进行创建对象。

  • 场景二:当入参 args 为空,然而已经缓存有创建对象的方法以及参数,resolvedConstructorOrFactoryMethod,resolvedConstructorArgumentspreparedConstructorArguments constructorArgumentsResolved 。那么直接调用该方法进行创建对象。

if (explicitArgs != null) {  argsToUse = explicitArgs;}else {  Object[] argsToResolve = null;  synchronized (mbd.constructorArgumentLock) {    factoryMethodToUse = (Method) mbd.resolvedConstructorOrFactoryMethod;    if (factoryMethodToUse != null && mbd.constructorArgumentsResolved) {      // Found a cached factory method...      argsToUse = mbd.resolvedConstructorArguments;      if (argsToUse == null) {        argsToResolve = mbd.preparedConstructorArguments;      }    }  }  if (argsToResolve != null) {    argsToUse = resolvePreparedArguments(beanName, mbd, bw,                                          factoryMethodToUse, argsToResolve);  }}
复制代码
  • 场景三:当入参 args 不为空,且未缓存过的。那么需要从 resolvedConstructorArguments 获取入参列表。如下代码。

if (mbd.hasConstructorArgumentValues()) {  ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();  resolvedValues = new ConstructorArgumentValues();  minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);}else {  minNrOfArgs = 0;}
复制代码

接着从候选方法列表中找出最合适的方法。最合适的逻辑跟场景一的逻辑一样。等找到了,则将方法以及参数进行缓存到 BeanDefinition。代码如下:

//ArgumentsHolder类public void storeCache(RootBeanDefinition mbd, Executable constructorOrFactoryMethod) {  synchronized (mbd.constructorArgumentLock) {    mbd.resolvedConstructorOrFactoryMethod = constructorOrFactoryMethod;    mbd.constructorArgumentsResolved = true;    if (this.resolveNecessary) {      mbd.preparedConstructorArguments = this.preparedArguments;    }    else {      mbd.resolvedConstructorArguments = this.arguments;    }  }}
复制代码

然后通过反射机制调用该方法进行创建对象。


3.1 参数列表与入参的匹配逻辑

lenientConstructorResolution 的标志位来决定具体的逻辑。值越小,则说明对应的方法就越合适。

//从入参对象与入参列表进行比对,计算其值int typeDiffWeight = (mbd.isLenientConstructorResolution() ?                      argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));if (typeDiffWeight < minTypeDiffWeight) {  //值越小,说明该方法candidate方法越合适  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())) {  //如果是采用严格模式下,lenientConstructorResolution为false时,如果出现值是相等的,入参数量又是一样的。  //然而入参的类型不一致的,说明有模糊两可的方法。需要记录下,到后面直接抛出异常,停止创建对象过程。  if (ambiguousFactoryMethods == null) {    ambiguousFactoryMethods = new LinkedHashSet<>();    ambiguousFactoryMethods.add(factoryMethodToUse);  }  ambiguousFactoryMethods.add(candidate);}
复制代码


重点看一下,计算 typeDiffWeight 的逻辑

//ArgumentsHolder类public int getTypeDifferenceWeight(Class<?>[] paramTypes) {    int typeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.arguments);  int rawTypeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.rawArguments) - 1024;  return (rawTypeDiffWeight < typeDiffWeight ? rawTypeDiffWeight : typeDiffWeight);}//MethodInvoker类/** * 如果类型不匹配,则直接返回最大值 * 如果类型匹配,说明入参对象的类型是参数列表的类型的子类,层级越深,值越大。 */public static int getTypeDifferenceWeight(Class<?>[] paramTypes, Object[] args) {		int result = 0;		for (int i = 0; i < paramTypes.length; i++) {      //如果入参类型与入参对象的类型不匹配,直接返回最大			if (!ClassUtils.isAssignableValue(paramTypes[i], args[i])) {				return Integer.MAX_VALUE;			}			if (args[i] != null) {				Class<?> paramType = paramTypes[i];				Class<?> superClass = args[i].getClass().getSuperclass();				while (superClass != null) {					if (paramType.equals(superClass)) {						result = result + 2;						superClass = null;					}					else if (ClassUtils.isAssignable(paramType, superClass)) {						result = result + 2;						superClass = superClass.getSuperclass();					}					else {						superClass = null;					}				}				if (paramType.isInterface()) {					result = result + 1;				}			}		}		return result;	}
复制代码


//ArgumentsHolder类/** * 这个方法就比较粗糙的计算。 * 如果类型不匹配,则直接返回最大值。逻辑比较容易看出来,就不在简述 */public int getAssignabilityWeight(Class<?>[] paramTypes) {  for (int i = 0; i < paramTypes.length; i++) {    if (!ClassUtils.isAssignableValue(paramTypes[i], this.arguments[i])) {      return Integer.MAX_VALUE;    }  }  for (int i = 0; i < paramTypes.length; i++) {    if (!ClassUtils.isAssignableValue(paramTypes[i], this.rawArguments[i])) {      return Integer.MAX_VALUE - 512;    }  }  return Integer.MAX_VALUE - 1024;}
复制代码


这里面还有细节的逻辑,有点复杂,后续针对该逻辑输出一篇文章。

4. 通过构造方法

通过构造方法创建对象的有两种方式。

方式一:通过 cglib 方式创建

//CglibSubclassingInstantiationStrategyprotected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,			@Nullable Constructor<?> ctor, @Nullable Object... args) {		return new CglibSubclassCreator(bd, owner).instantiate(ctor, args);	}
复制代码

方式二:通过原生的 Java 的构造方式创建

//BeanUtilspublic static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {		Assert.notNull(ctor, "Constructor must not be null");		try {			ReflectionUtils.makeAccessible(ctor);			return (KotlinDetector.isKotlinType(ctor.getDeclaringClass()) ?					KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args));		}		//......	}
复制代码
  • 场景一:当入参 args 不为空时,通过前置处理其检测出构造方法。代码如下

//AbstractAutowireCapableBeanFactoryprotected Constructor<?>[] determineConstructorsFromBeanPostProcessors(@Nullable Class<?> beanClass, String beanName)			throws BeansException {
if (beanClass != null && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; Constructor<?>[] ctors = ibp.determineCandidateConstructors(beanClass, beanName); if (ctors != null) { return ctors; } } } } return null; }
复制代码

如果前置处理检测出来的构造方法列表不为空,则会从这些方法列表中找出最合适的构造方法进行创建对象。如果构造方法列表为空,则从这个 Bean 的类找出找出构造方法列表,接着再找出最合适的方法进行创建对象。具体的代码入口为:ConstructorResolver.autowireConstructor()该逻辑与 Bean 工厂的逻辑差不多,这里不再阐述。

  • 场景二:当入参 args 为空时,然而已经缓存有创建对象的方法以及参数,resolvedConstructorOrFactoryMethod,resolvedConstructorArgumentspreparedConstructorArguments constructorArgumentsResolved 。那么直接调用该方法进行创建对象。

  • 场景三:当入参 args 不为空,且未缓存过的。如果前置处理检测出有构造方法列表,或者自动注入模式 autowireMode AUTOWIRE_CONSTRUCTOR,又或者 constructorArgumentValues 不为空时,则会进入场景一的所进入的入口。具体的逻辑大同小异,不再阐述。

Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);if (ctors != null ||    mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||    mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args))  {   //其最终会调用ConstructorResolver.autowireConstructor()  return autowireConstructor(beanName, mbd, ctors, args); }
复制代码

场景四: 当上述的三种场景都不满足,则会调用默认的方法。

//SimpleInstantiationStrategy类public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {		  //....  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);  //....}
复制代码

4.1 Lookup 注解

这里有一块逻辑,比较复杂的,就是 @Lookup 注解会影响对象的创建。单独说明一下;

通过 AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors 前置处理器检测类中方法中是否有 Lookup 注解,有将封装成 Lookup 对象,存放到 methodOverrides 对象。

public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)			throws BeanCreationException {  // Let's check for lookup methods here..  if (!this.lookupMethodsChecked.contains(beanName)) {    try {      ReflectionUtils.doWithMethods(beanClass, method -> {        Lookup lookup = method.getAnnotation(Lookup.class);        if (lookup != null) {          Assert.state(beanFactory != null, "No BeanFactory available");          LookupOverride override = new LookupOverride(method, lookup.value());          try {            RootBeanDefinition mbd = (RootBeanDefinition) beanFactory.getMergedBeanDefinition(beanName);            mbd.getMethodOverrides().addOverride(override);          }          catch (NoSuchBeanDefinitionException ex) {            throw new BeanCreationException(beanName,        			"Cannot apply @Lookup to beans without corresponding bean definition");          }        }      });    }    catch (IllegalStateException ex) {      throw new BeanCreationException(beanName, "Lookup method resolution failed", ex);    }    this.lookupMethodsChecked.add(beanName);  }    //......}
复制代码

接着在创建对象时,采用 cglib 来进行创建

//SimpleInstantiationStrategy类public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {		// Don't override the class with CGLIB if no overrides.		if (!bd.hasMethodOverrides()) {			//.....		}		else {			// Must generate CGLIB subclass.			return instantiateWithMethodInjection(bd, beanName, owner);		}	}//CglibSubclassingInstantiationStrategy类protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {		return instantiateWithMethodInjection(bd, beanName, owner, null);}protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,			@Nullable Constructor<?> ctor, @Nullable Object... args) {		return new CglibSubclassCreator(bd, owner).instantiate(ctor, args);}//CglibSubclassCreatorpublic Object instantiate(@Nullable Constructor<?> ctor, @Nullable Object... args) {  Class<?> subclass = createEnhancedSubclass(this.beanDefinition);  Object instance;  //....  Factory factory = (Factory) instance;  factory.setCallbacks(new Callback[] {NoOp.INSTANCE,            new LookupOverrideMethodInterceptor(this.beanDefinition, this.owner),            new ReplaceOverrideMethodInterceptor(this.beanDefinition, this.owner)});  return instance;}
private Class<?> createEnhancedSubclass(RootBeanDefinition beanDefinition) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(beanDefinition.getBeanClass()); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); if (this.owner instanceof ConfigurableBeanFactory) { ClassLoader cl = ((ConfigurableBeanFactory) this.owner).getBeanClassLoader(); enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(cl)); } enhancer.setCallbackFilter(new MethodOverrideCallbackFilter(beanDefinition)); enhancer.setCallbackTypes(CALLBACK_TYPES); return enhancer.createClass();}
复制代码

其针对含有 Lookup 注解的方法进行拦截,具体的拦截方法如下:

//LookupOverrideMethodInterceptor类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()));  }}
复制代码

5. 汇总

创建对象有三种方式,supplier 方式逻辑是最简单的,直接调用 BeanDefinition.instanceSupplier 对象进行创建对象;Bean 工厂方式中找出与 factoryMethodName 同名的方法列表,构造方法方式是找出 Bean 类中构造方法列表,如果方法列表有多个,通过入参数量匹配以及检参数类型与入参的类型匹配,关系越近的,则该方法是最合适的。找到通过反射机制调用该方法进行创建对象;

当入参 args 为空时,会将找出最合适的方法保存到 resolvedConstructorOrFactoryMethod 这个属性中。

三. 属性注入

属性注入有两种方式,一种是通过反射机制,调用 set 方法进行属性输入。另外一种是通过 Field 字段进行属性注入。代码如下:

//InjectedElementprotected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)  throws Throwable {    if (this.isField) {    Field field = (Field) this.member;    ReflectionUtils.makeAccessible(field);    field.set(target, getResourceToInject(target, requestingBeanName));  }  else {    if (checkPropertySkipping(pvs)) {      return;    }    try {      Method method = (Method) this.member;      ReflectionUtils.makeAccessible(method);      method.invoke(target, getResourceToInject(target, requestingBeanName));    }    catch (InvocationTargetException ex) {      throw ex.getTargetException();    }  }}
复制代码

不单单只有这个 InjectedElement 类进行输入注入。还包括了 BeanPropertyHandlerFieldPropertyHandlerAutowiredFieldElementAutowiredMethodElementResourceElementBeanProperty。都是大同小异,只是执行的场景不一样。AutowiredFieldElementAutowiredMethodElementResourceElement 是用在前置处理器进行的属性注入,主要是有关 @Autowired、@Resource、@Value 的属性注入。BeanPropertyHandlerFieldPropertyHandler 是有关 BeanWrapperImpl 的属性注入,主要是针对 BeanDefinition 中的 propertyValues 字段的属性列表。BeanProperty 是针对 @ConfigurationProperties 标志的类的字段进行属性注入。代码不罗列了,具体可以去查看对应的代码。

1. PropertyHandler 属性注入

其子类有 BeanPropertyHandlerFieldPropertyHandler。可以对类型的直接注入,也可以对数组中的某个角标进行属性注入,或者 map 集合中的某个 key 进行属性注入,也可以对 Collection 集合的某个角标进行输入注入。

其关键的实现类 MutablePropertyValues,其中主要的字段为:

List<PropertyValue> propertyValueList
复制代码

其记录了需要注入到对象的属性值列表。PropertyValue 属性如下:

//字段名,其格式可以如下:fieldName[key|index][key|index].private final String name;@Nullableprivate final Object value;//值
private boolean optional = false;//是否可选,意味者如果value为空,选择忽略还是报异常。
private boolean converted = false;//是否已经进行类型转换
@Nullableprivate Object convertedValue;//记录类型转换后的值
/** Package-visible field that indicates whether conversion is necessary */@Nullablevolatile Boolean conversionNecessary;//是否有必要进行类型转换
/** Package-visible field for caching the resolved property path tokens */@Nullabletransient volatile Object resolvedTokens;//解析name后封装成一个PropertyTokenHolder
复制代码

PropertyTokenHolder 的属性如下:

public String actualName;//真实的字段名public String canonicalName;//如果是数组或者集合等,或记录完整的描述,例如,actualName[][]public String[] keys;//记录了[]包含的值
复制代码

具体例子,例如某个对象的结构如下:

public class ClassTest{	private Map<String,List<NestClass>> str;}public class NestClass{  private String name;}
复制代码

我需要注入一个 key,对应的角标下的 NestClass 对象中的 name 的值,那么需要 PropertyValue 对象中 name 等于“str[key][0].name”。这样子就可以注入进去。但前提下,需要设置 autoGrowNestedPaths 为 true,这样子才能当对象为空时,会启动创建对象。但由于存在两个 key,其不能再次创建对象,需要“key”对应的对象要存在,否则将会报异常 NullValueInNestedPathException,提示“Cannot access indexed value of property referenced in indexed property path 'str[key][0]'”

具体的代码如下:

//AbstractNestablePropertyAccessor类protected Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException {		String propertyName = tokens.canonicalName;		String actualName = tokens.actualName;		PropertyHandler ph = getLocalPropertyHandler(actualName);		//...		try {			Object value = ph.getValue();			if (tokens.keys != null) {				if (value == null) {					if (isAutoGrowNestedPaths()) {            //当autoGrowNestedPaths为true时,会自动初始化一个默认对象						value = setDefaultValue(new PropertyTokenHolder(tokens.actualName));					}					else {						throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,								"Cannot access indexed value of property referenced in indexed " +										"property path '" + propertyName + "': returned null");					}				}				StringBuilder indexedPropertyName = new StringBuilder(tokens.actualName);				// apply indexes and map keys        				for (int i = 0; i < tokens.keys.length; i++) {					String key = tokens.keys[i];					if (value == null) {            //然而有多个key,当第一个key拿到的对象为空,接着第二个key时,会直接抛异常						throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName,								"Cannot access indexed value of property referenced in indexed " +										"property path '" + propertyName + "': returned null");					}					else if (value.getClass().isArray()) {						//....					}					else if (value instanceof List) {            //...					}					else if (value instanceof Set) {            //..					}					else if (value instanceof Map) {						Map<Object, Object> map = (Map<Object, Object>) value;						Class<?> mapKeyType = ph.getResolvableType().getNested(i + 1).asMap().resolveGeneric(0);						// IMPORTANT: Do not pass full property name in here - property editors						// must not kick in for map keys but rather only for map values.						TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);						Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, typeDescriptor);						value = map.get(convertedMapKey);					}					else {						throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,								"Property referenced in indexed property path '" + propertyName +										"' is neither an array nor a List nor a Set nor a Map; returned value was [" + value + "]");					}					indexedPropertyName.append(PROPERTY_KEY_PREFIX).append(key).append(PROPERTY_KEY_SUFFIX);				}			}			return value;		}		//.....	}
复制代码

每个“."前面是一个对象封装成 BeanWrapperImpl,该对象保存到 wrappedObject 这个属性字段。如果有多个“.”,rootObject 属性字段保存的外面的对象,接着获取对应的属性字段的对象进行赋值。

针对前面的例子,我们简单梳理一下。

如果是给集合、数组等集合对象进行赋值,则是先获取该对象,接着再进行覆盖某个 Key 的值,或者某个角标的值。代码如下:

//AbstractNestablePropertyAccessor类private void processKeyedProperty(PropertyTokenHolder tokens, PropertyValue pv) {		Object propValue = getPropertyHoldingValue(tokens);		PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);		//......     	String lastKey = tokens.keys[tokens.keys.length - 1];		if (propValue.getClass().isArray()) {			int arrayIndex = Integer.parseInt(lastKey);			//...中间忽略的主要是类型转换			      Array.set(propValue, arrayIndex, convertedValue);					}		else if (propValue instanceof List) {			List<Object> list = (List<Object>) propValue;			int index = Integer.parseInt(lastKey);			//...中间忽略的主要是类型转换				if (index >= size && index < this.autoGrowCollectionLimit) {				for (int i = size; i < index; i++) {										list.add(null);				}				list.add(convertedValue);			}			else {				list.set(index, convertedValue);			}		}		else if (propValue instanceof Map) {      //...中间忽略的主要是类型转换				map.put(convertedMapKey, convertedMapValue);		}		else {			throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,					"Property referenced in indexed property path '" + tokens.canonicalName +					"' is neither an array nor a List nor a Map; returned value was [" + propValue + "]");		}	}
复制代码

2. InjectedElement 属性注入

其还有子类,AutowiredFieldElement 等。主要是在前置处理器对有关属性或者方法中添加对应的注解的字段进行注入。其原理比较简单,依赖的类型,从 spring 容器中查找对应的对象,进行注入。不再讲述

//AutowiredFieldElementprotected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {  Field field = (Field) this.member;  Object value;			  //从spring容器中找到对应的对象  DependencyDescriptor desc = new DependencyDescriptor(field, this.required);  desc.setContainingClass(bean.getClass());  Set<String> autowiredBeanNames = new LinkedHashSet<>(1);  Assert.state(beanFactory != null, "No BeanFactory available");  TypeConverter typeConverter = beanFactory.getTypeConverter();  value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);	//...中间忽略的主要是类型转换	  if (value != null) {    ReflectionUtils.makeAccessible(field);    field.set(bean, value);  }
复制代码

3. BeanProperty 属性注入

该类主要用于 @ConfigurationProperties 注解标注的对象。根据该对象的属性名从 spring 容器中 environment 对象找到对应的值,并进行注入。该查找的逻辑比较复杂,稍微会针对 @ConfigurationProperties 所对应的前置处理器 ConfigurationPropertiesBindingPostProcessor 进行解读。

四. 总结

对象在 spring 容器中过创建,以及属性的注入的大体逻辑已经说了差不多。其底层的原理很简单。主要是通过反射机制进行对象对象,属性注入等。然而我上述讲解的对象只是冰山一角,BeanDefinition 中还有很多字段属性会影响对象的创建过程,这里只列出了关键的字段属性。当然还包含前置处理器,会影响对象创建,属性注入的过程。


发布于: 2021 年 04 月 10 日阅读数: 40
用户头像

邱学喆

关注

计算机原理的深度解读,源码分析。 2018.08.26 加入

在IT领域keep Learning。要知其然,也要知其所以然。原理的爱好,源码的阅读。输出我对原理以及源码解读的理解。个人的仓库:https://gitee.com/Michael_Chan

评论

发布
暂无评论
spring的IOC使用以及原理