@Autowired依赖注入

  • 本文源码基于spring-framework-5.3.10。
  • 源码位置:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(PropertyValues, Object, String)
  • 他在Bean的生命周期的实例化后之后,初始化前(或者说各种Aware回调之前)之前调用。

@Autowired依赖注入的大致流程

  • 寻找注入点:被@Autowired注解的方法和字段
  • 遍历每个注入点
  • 根据注入点类型去寻找bean:属性的类型、方法参数的类型
  • 找到一个Bean进行注入。没有找到Bean,如果是必填的,抛异常。找到多个Bean,去取出唯一的Bean
  • 判断是不是isAutowireCandidate
  • 判断是不是数组、Map、列表…
  • 这时候如果还有多个:取@Primary标注了的Bean
  • 这时候如果还有多个:取优先级最高的Bean
  • 这时候如果还有多个:根据名字(属性就是属性的名字,方法是参数的名字)筛选出唯一一个

@Autowired依赖注入源码分析

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {// 寻找注入点(所有被@Autowired注解了的Field或Method)InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);try {// 依赖注入。Field与Method的实现方式不同metadata.inject(bean, beanName, pvs);}catch (BeanCreationException ex) {throw ex;}catch (Throwable ex) {throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);}// 返回全部的属性值对象return pvs;
}/*** 依赖注入:这个方法主要作用是循环每个注入点进行注入。区分字段和方法进行注入*/
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {// 得到全部的注入点Collection<InjectedElement> checkedElements = this.checkedElements;Collection<InjectedElement> elementsToIterate =(checkedElements != null ? checkedElements : this.injectedElements);if (!elementsToIterate.isEmpty()) {// 遍历每个注入点进行依赖注入for (InjectedElement element : elementsToIterate) {element.inject(target, beanName, pvs);}}
}

@Autowired针对字段的注入方式

protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {// 得到当前注入点的字段Field field = (Field) this.member;// 定义一个具体注入的值Object value;if (this.cached) {// 对于原型Bean,第一次创建的时候,也找注入点,然后进行注入,此时cached为false,注入完了之后cached为true// 第二次创建的时候,先找注入点(此时会拿到缓存好的注入点),也就是AutowiredFieldElement对象,此时cache为true,也就进到此处了// 注入点内并没有缓存被注入的具体Bean对象,而是beanName,这样就能保证注入到不同的原型Bean对象try {value = resolvedCachedArgument(beanName, this.cachedFieldValue);}catch (NoSuchBeanDefinitionException ex) {// Unexpected removal of target bean for cached argument -> re-resolvevalue = resolveFieldValue(field, bean, beanName);}}else {// 根据filed从BeanFactory中获取的匹配的Bean对象value = resolveFieldValue(field, bean, beanName);}// 反射给filed赋值if (value != null) {// 主要设置私有私有可以访问,标记在反射的时候跳过校验!ReflectionUtils.makeAccessible(field);// 给字段设置值!field.set(bean, value);}
}/*** 根据filed从BeanFactory中获取的匹配的Bean对象*/
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {// 根据当前字段以及@autowired(required = true)构建一个属性描述器DependencyDescriptor desc = new DependencyDescriptor(field, this.required);// 设置属性描述器对应的class为当前Bean的classdesc.setContainingClass(bean.getClass());// 得到需要注入的BeanName几个Set<String> autowiredBeanNames = new LinkedHashSet<>(1);Assert.state(beanFactory != null, "No BeanFactory available");// 得到当前Bean工厂的类型转化器TypeConverter typeConverter = beanFactory.getTypeConverter();// 属性实际注入的值Object value;try {// 获取要注入的值value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);}catch (BeansException ex) {throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);}synchronized (this) {// 缓存不存在的时候(走过一遍的时候会有缓存),会走下面的逻辑if (!this.cached) {// 缓存的属性描述器Object cachedFieldValue = null;// 要注入的值不为null或者这个属性是必填的!if (value != null || this.required) {// 属性描述器赋值cachedFieldValue = desc;// 注册一下beanName依赖了autowiredBeanNames,registerDependentBeans(beanName, autowiredBeanNames);// 只有一个,找到了唯一的Bean。上面的定义,这里肯定是一个if (autowiredBeanNames.size() == 1) {// 得到要注入的BeanNameString autowiredBeanName = autowiredBeanNames.iterator().next();// Bean工厂中可以获取当前Bean,并且类型与要注入的类型一致if (beanFactory.containsBean(autowiredBeanName) &&beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {// 构造一个ShortcutDependencyDescriptor作为缓存,保存了当前filed所匹配的autowiredBeanName,而不是对应的bean对象(考虑原型bean)cachedFieldValue = new ShortcutDependencyDescriptor(desc, autowiredBeanName, field.getType());}}}// 全局缓存的属性描述器赋值this.cachedFieldValue = cachedFieldValue;// 下次缓存判断就不会进来了this.cached = true;}}// 返回具体注入的值return value;
}

@Autowired针对方法的注入方式

protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {// 如果pvs中已经有当前注入点的值了,则跳过注入。程序员自己设置了值if (checkPropertySkipping(pvs)) {return;}// 得到当前要注入的的方法Method method = (Method) this.member;// 得到当前方法的参数Object[] arguments;if (this.cached) {try {// 使用缓存的方式进行注入arguments = resolveCachedArguments(beanName);}catch (NoSuchBeanDefinitionException ex) {// Unexpected removal of target bean for cached argument -> re-resolvearguments = resolveMethodArguments(method, bean, beanName);}}else {// 根据method从BeanFactory中获取的参数匹配的Bean对象arguments = resolveMethodArguments(method, bean, beanName);}// 参数不为空的时候(没有抛异常的时候),进行方法调用if (arguments != null) {try {// 主要设置私有方法可以访问,标记在反射的时候跳过校验!ReflectionUtils.makeAccessible(method);// 执行具体的方法method.invoke(bean, arguments);}catch (InvocationTargetException ex) {throw ex.getTargetException();}}
}/*** 根据method从BeanFactory中获取的参数匹配的Bean对象*/
private Object[] resolveMethodArguments(Method method, Object bean, @Nullable String beanName) {// 得到方法参数的个数int argumentCount = method.getParameterCount();// 定义一个参数的数组Object[] arguments = new Object[argumentCount];// 定义一个依赖描述器DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount];// 定义要注入的每个Bean的集合Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);Assert.state(beanFactory != null, "No BeanFactory available");// 得到当前Bean工厂的类型转化器TypeConverter typeConverter = beanFactory.getTypeConverter();// 遍历每个方法参数,找到匹配的bean对象for (int i = 0; i < arguments.length; i++) {// 构建具体的参数对象MethodParameter methodParam = new MethodParameter(method, i);// 构建具体的依赖描述器对象DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);// 设置属性描述器对应的class为当前Bean的classcurrDesc.setContainingClass(bean.getClass());// 依赖描述器集合数据设置descriptors[i] = currDesc;try {// 获取要注入的值Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);// 有任意一个参数没找到,并且当前的方法不是必须的要注入的。直接放弃继续找参数if (arg == null && !this.required) {arguments = null;break;}// 参数赋值arguments[i] = arg;}catch (BeansException ex) {throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);}}synchronized (this) {// 缓存不存在的时候(走过一遍的时候会有缓存),会走下面的逻辑if (!this.cached) {// 存在参数(得到了参数值)if (arguments != null) {// 得到参数值的数组DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length);// 注册一下beanName依赖了autowiredBeanNames。registerDependentBeans(beanName, autowiredBeans);// 得到的参数和需要的参数数量相同的时候if (autowiredBeans.size() == argumentCount) {// 得到要注入参数的迭代器Iterator<String> it = autowiredBeans.iterator();// 得到当前方法各个参数的类型Class<?>[] paramTypes = method.getParameterTypes();// 遍历每一个参数的类型for (int i = 0; i < paramTypes.length; i++) {// 得到类型对应的Bean名称String autowiredBeanName = it.next();// 能在Bean工厂找到当前参数的Bean,并且类型能匹配上,参数值赋值if (beanFactory.containsBean(autowiredBeanName) &&beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {cachedMethodArguments[i] = new ShortcutDependencyDescriptor(descriptors[i], autowiredBeanName, paramTypes[i]);}}}// 设置全局的参数对应的BeanNamethis.cachedMethodArguments = cachedMethodArguments;}else {// 参数值不全的时候,或者没有参数的时候,这里设置为nullthis.cachedMethodArguments = null;}// 下次缓存判断就不会进来了this.cached = true;}}// 返回具体注入需要的参数值的集合return arguments;
}

获取要注入的值外部方法:resolveDependency源码分析

/*** DependencyDescriptor descriptor:依赖描述器,可能是字段,方法* requestingBeanName:请求的BeanName* autowiredBeanNames:需要注入的BeanName集合* typeConverter:类型转换器*/
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {// 用来获取方法入参名字的descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());// 所需要的类型是Optional,包装成Optional对象,核心调用的也是doResolveDependencyif (Optional.class == descriptor.getDependencyType()) {return createOptionalDependency(descriptor, requestingBeanName);}// 所需要的的类型是ObjectFactory,或ObjectProviderelse if (ObjectFactory.class == descriptor.getDependencyType() ||ObjectProvider.class == descriptor.getDependencyType()) {return new DependencyObjectProvider(descriptor, requestingBeanName);}else if (javaxInjectProviderClass == descriptor.getDependencyType()) {return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);}else {// 在属性或set方法上使用了@Lazy注解,那么则构造一个代理对象并返回,真正使用该代理对象时才进行类型筛选BeanObject result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName);// 没有@Lazy注解的时候if (result == null) {// descriptor表示某个属性或某个set方法// requestingBeanName表示正在进行依赖注入的Beanresult = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);}return result;}
}

获取要注入的值内部方法:doResolveDependency源码分析

/*** 最核心的方法,如何寻找Bean* descriptor:表示某个属性或某个set方法* beanName:表示正在进行依赖注入的Bean* autowiredBeanNames:需要注入的BeanName列表* typeConverter:类型转换器*/
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {// 设置当前的descriptor(存储了方法字段参数等信息)为当前注入点InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);try {// 如果当前descriptor之前做过依赖注入了,则可以直接取shortcut了,相当于缓存Object shortcut = descriptor.resolveShortcut(this);if (shortcut != null) {return shortcut;}// 找到要注入的类型,可能是字段的类型,也可能是方法参数的类型Class<?> type = descriptor.getDependencyType();// 获取@Value所指定的值Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);// 字段上或者方法参数存在@Value注解if (value != null) {// 占位符或者spring表达式的解析if (value instanceof String) {// 占位符填充(${})String strVal = resolveEmbeddedValue((String) value);BeanDefinition bd = (beanName != null && containsBean(beanName) ?getMergedBeanDefinition(beanName) : null);// 解析Spring表达式(#{})value = evaluateBeanDefinitionString(strVal, bd);}// 将value转化为descriptor所对应的类型TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());try {// 需要转码后的值return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());}catch (UnsupportedOperationException ex) {// A custom TypeConverter which does not support TypeDescriptor resolution...// 不是string转Bean,变为使用自定义的转化器return (descriptor.getField() != null ?converter.convertIfNecessary(value, type, descriptor.getField()) :converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));}}// 如果descriptor所对应的类型是数组、Map这些,就将descriptor对应的类型所匹配的所有bean方法,不用进一步做筛选了Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);if (multipleBeans != null) {return multipleBeans;}// 找到所有Bean,key是beanName, value有可能是bean对象,有可能是beanClass(接口的方式注入,优先级较低,类没有加载)Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);if (matchingBeans.isEmpty()) {// required为true,抛异常if (isRequired(descriptor)) {raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);}return null;}// 定义要注入的Bean名称String autowiredBeanName;// 定义要注入的Bean值Object instanceCandidate;if (matchingBeans.size() > 1) {// 根据类型找到了多个Bean,进一步筛选出某一个, @Primary-->优先级最高--->name// 这里实现的是ByNameautowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);// 注入的Bean的名称为nullif (autowiredBeanName == null) {// 他是必填的if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {// 抛异常return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);}else {// In case of an optional Collection/Map, silently ignore a non-unique case:// possibly it was meant to be an empty collection of multiple regular beans// (before 4.3 in particular when we didn't even look for collection beans).// 非必填返回nullreturn null;}}// 给当前要注入的具体指赋值instanceCandidate = matchingBeans.get(autowiredBeanName);}else {// We have exactly one match.// 只找到了一个BeanMap.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();// 给当前要注入的BeanName赋值autowiredBeanName = entry.getKey();// 给当前要注入的具体指赋值instanceCandidate = entry.getValue();}// 记录匹配过的beanNameif (autowiredBeanNames != null) {autowiredBeanNames.add(autowiredBeanName);}// 有可能筛选出来的是某个bean的类型,此处就进行实例化,调用getBeanif (instanceCandidate instanceof Class) {instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);}// 空的抛异常Object result = instanceCandidate;if (result instanceof NullBean) {if (isRequired(descriptor)) {raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);}result = null;}// 类型不匹配抛异常if (!ClassUtils.isAssignableValue(type, result)) {throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());}// 返回注入的值return result;}finally {//ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);}
}

针对数组、Map的特殊处理:resolveMultipleBeans源码分析

private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {// 得到当前要注入的类型Class<?> type = descriptor.getDependencyType();// 属于Stream相关的if (descriptor instanceof StreamDependencyDescriptor) {// 找到type所匹配的所有beanMap<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);if (autowiredBeanNames != null) {autowiredBeanNames.addAll(matchingBeans.keySet());}// 构造成一个streamStream<Object> stream = matchingBeans.keySet().stream().map(name -> descriptor.resolveCandidate(name, type, this)).filter(bean -> !(bean instanceof NullBean));// 排序if (((StreamDependencyDescriptor) descriptor).isOrdered()) {stream = stream.sorted(adaptOrderComparator(matchingBeans));}return stream;}// 属于数组相关的else if (type.isArray()) {// 得到数组元素的类型Class<?> componentType = type.getComponentType();ResolvableType resolvableType = descriptor.getResolvableType();Class<?> resolvedArrayType = resolvableType.resolve(type);if (resolvedArrayType != type) {componentType = resolvableType.getComponentType().resolve();}if (componentType == null) {return null;}// 根据数组元素类型找到所匹配的所有BeanMap<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType,new MultiElementDescriptor(descriptor));// 没有获取到bean,返回Nullif (matchingBeans.isEmpty()) {return null;}// 把需要注入的BeanName传入的数组中if (autowiredBeanNames != null) {autowiredBeanNames.addAll(matchingBeans.keySet());}// 进行类型转化TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());// Map的Values转为数组Object result = converter.convertIfNecessary(matchingBeans.values(), resolvedArrayType);// 排序if (result instanceof Object[]) {Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);if (comparator != null) {Arrays.sort((Object[]) result, comparator);}}return result;}// Collection处理else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {// 找到这个列表的具体类型Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric();// 类似是null,直接返回if (elementType == null) {return null;}// 找到type所匹配的所有beanMap<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType,new MultiElementDescriptor(descriptor));// 没有找到Bean,直接返回if (matchingBeans.isEmpty()) {return null;}// 把需要注入的BeanName传入的数组中if (autowiredBeanNames != null) {autowiredBeanNames.addAll(matchingBeans.keySet());}// 类型转换TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());// 把Map的Values拿出来Object result = converter.convertIfNecessary(matchingBeans.values(), type);// 遍历拿到的Valuesif (result instanceof List) {// 排序if (((List<?>) result).size() > 1) {Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);if (comparator != null) {((List<?>) result).sort(comparator);}}}// 返回链表的结果return result;}// Map类型处理else if (Map.class == type) {// 找到Map的Key的类型ResolvableType mapType = descriptor.getResolvableType().asMap();Class<?> keyType = mapType.resolveGeneric(0);// 如果Map的key不是Stringif (String.class != keyType) {return null;}Class<?> valueType = mapType.resolveGeneric(1);// Map的Balue不能是nullif (valueType == null) {return null;}// 找到type所匹配的所有beanMap<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType,new MultiElementDescriptor(descriptor));// 找到的Bean为空,直接返回nullif (matchingBeans.isEmpty()) {return null;}// 把需要注入的BeanName传入的数组中if (autowiredBeanNames != null) {autowiredBeanNames.addAll(matchingBeans.keySet());}// 返回找到的所有Beanreturn matchingBeans;}else {// 不是上面的几种类型,直接返回nullreturn null;}
}

@Autowire自己的ByName源码分析

protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {Class<?> requiredType = descriptor.getDependencyType();// candidates表示根据类型所找到的多个Bean,判断这些Bean中是否有一个是@Primary的,存在多个@Primary注解,抛异常!String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);if (primaryCandidate != null) {return primaryCandidate;}// 取优先级最高的Bean。这里的优先级指的是@Priority注解配置的优先级,数字越小,优先级越高!String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);if (priorityCandidate != null) {return priorityCandidate;}// Fallback// 匹配descriptor的名字,要么是字段的名字,要么是set方法入参的名字for (Map.Entry<String, Object> entry : candidates.entrySet()) {String candidateName = entry.getKey();Object beanInstance = entry.getValue();// resolvableDependencies记录了某个类型对应某个Bean,启动Spring时会进行设置,比如BeanFactory.class对应BeanFactory实例// 注意:如果是Spring自己的byType,descriptor.getDependencyName()将返回空,只有是@Autowired才会方法属性名或方法参数名if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) ||matchesBeanName(candidateName, descriptor.getDependencyName())) {return candidateName;}}return null;
}

获取方法入参名称:DefaultParameterNameDiscoverer源码分析

public DefaultParameterNameDiscoverer() {// TODO Remove this conditional inclusion when upgrading to Kotlin 1.5, see https://youtrack.jetbrains.com/issue/KT-44594if (KotlinDetector.isKotlinReflectPresent() && !NativeDetector.inNativeImage()) {addDiscoverer(new KotlinReflectionParameterNameDiscoverer());}// 反射(1.8+):getParameterNamesaddDiscoverer(new StandardReflectionParameterNameDiscoverer());// ASM分析.class文件addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
}

结束语

  • 获取更多本文的前置知识文章,以及新的有价值的文章,让我们一起成为架构师!
  • 关注公众号,可以让你对MySQL、并发编程、spring源码有深入的了解!
  • 关注公众号,后续持续高效的学习JVM!
  • 这个公众号,无广告!!!每日更新!!!

【spring】依赖注入之@Autowired依赖注入相关推荐

  1. 万字长文带你吃透Spring是怎样解决循环依赖的

    在Spring框架中,处理循环依赖一直是一个备受关注的话题.这是因为Spring源代码中为了解决循环依赖问题,进行了大量的处理和优化.同时,循环依赖也是Spring高级面试中的必考问题,回答得好可以成 ...

  2. Spring源码分析(十二)autowire和@Autowired 依赖注入源码解析总结

    XML的autowire自动注入 在XML中,我们可以在定义一个Bean时去指定这个Bean的自动注入模式: byType byName constructor default no 比如: < ...

  3. Spring IoC是如何进行依赖注入的

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 依赖注入(DI) DI(Dependency Injection) ...

  4. spring常用的三种依赖注入方式

    平常的java开发中,程序员在某个类中需要依赖其它类的方法,则通常是new一个依赖类再调用类实例的方法,这种开发存在的问题是new的类实例不好统一管理,spring提出了依赖注入的思想,即依赖类不由程 ...

  5. SpringMVC:学习笔记(11)——依赖注入与@Autowired

    SpringMVC:学习笔记(11)--依赖注入与@Autowired 使用@Autowired 从Spring2.5开始,它引入了一种全新的依赖注入方式,即通过@Autowired注解.这个注解允许 ...

  6. spring依赖注入_Spring的依赖注入陷阱

    spring依赖注入 Spring框架中有三种注入变量: 基于二传手的注射 基于构造函数的注入 基于现场的注入 这些机制中的每一种都有优点和缺点,并且不仅只有一种正确的方法. 例如现场注入: @Aut ...

  7. spring bean依赖_Spring @Configuration并将bean依赖项作为方法参数注入

    spring bean依赖 一个春天建议注射豆从Spring的参考指南复制下面的示例中显示之间的相互依存关系的方式在这里 : @Configuration public class AppConfig ...

  8. 来谈谈Spring构造函数注入的循环依赖问题

    作者:服务端开发 blog.csdn.net/u010013573/article/details/90573901 一.循环依赖 spring的循环依赖主要是指两个类相互之间通过@Autowired ...

  9. Spring第七弹—依赖注入之注解方式注入及编码解析@Resource原理

        注入依赖对象可以采用手工装配或自动装配,在实际应用中建议使用手工装配,因为自动装配会产生未知情况,开发人员无法预见最终的装配结果. 手工装配依赖对象  手工装配依赖对象,在这种方式中又有两种编 ...

最新文章

  1. SVO中 Inverse Compositional Image Alignment方法的学习笔记
  2. 没错,接单就是特简单!
  3. 【python图像处理】tiff文件的保存与解析
  4. 端口如何支持非localhost访问_新特性解读 | MySQL 8.0.19 支持 DNS SRV
  5. 软件项目可行性分析定义_如何定义最低可行产品
  6. linux可以不用grub吗,既然不用Win了,那么GrubDOS也不用了。linux grub求指导
  7. LeetCode 102. 二叉树的层次遍历(BFS)
  8. c语言闰年的判断条件DS1302,DS1302驱动程序(平年和闰年天数自动调整)
  9. 文件md5码怎么生成_Linux 系统文件校验方法--MD5,SHA1,PGP,SHA256,SHA512
  10. vs2005 pro 在浏览器查看下的一个问题!
  11. 金秋该有的样子,平面设计师秋季海报值得借鉴的PSD分层模板
  12. Tomcat环境开发技巧
  13. 手把手教你最好用的数据分析方法,会用的没几个
  14. [PyTorch] torchvision库及其常用的函数
  15. 虚拟机备份克隆导致SQL SERVER 出现IO错误案例
  16. DR模式 + keepalived
  17. apollo重要服务之metaService
  18. 计算机辅助绘图包括,计算机辅助绘图实用教程
  19. linux打开7z文件_什么是7Z文件(以及如何打开一个文件)?
  20. Minecraft mod制作简易教程(三)——创建一个物品

热门文章

  1. Multi-Label Image Classification(多标签图像分类)
  2. c语言屏幕输出函数相关题,C语言上机考试题目
  3. WinForm的控件
  4. 朴素贝叶斯、贝叶斯网络分类器
  5. Socket网络编程详解
  6. Balsamiq Mockups简单介绍(UI草图绘制工具)
  7. netty 工控网关_开源软件分享-基于.NET的工控网关和组态软件
  8. linux shell logout,.bash_pfofile、.bash_logout和.bashrc区别
  9. this.$router.push方法,父子如何传值和接收值
  10. 华为ensp模拟器及各设备镜像