目录

一、概述

二、@Autowired注解的用法

三、@Autowired自动装配原理

四、源码调试环境

五、AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition()源码

六、AutowiredAnnotationBeanPostProcessor#postProcessProperties()源码

七、总结


一、概述

@Autowired注解大家再熟悉不过了,在项目中使用 @Autowired注解的比例非常高,可以说基本用过 Spring 的同学都接触过这个注解,因此,我们有必要详细了解一下其工作原理。

@Autowired注解的作用是帮我们注入我们需要的对象,Spring会自动按照类型注入,只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。

在分析这个注解的实现原理之前,我们不妨先来回顾一下@Autowired注解的用法。

二、@Autowired注解的用法

先定义几个接口:

@Repository
public class UserDao {public void insert() {// 执行数据库操作}}public interface UserService {void insert();}

@Autowired注解的主要使用方式有下面四种:

  • (一)、@Autowired注解应用于构造函数
@Service
public class UserServiceImpl implements UserService {private final UserDao userDao;@Autowiredpublic UserServiceImpl(UserDao userDao) {this.userDao = userDao;}@Overridepublic void insert() {userDao.insert();}}
  • (二)、@Autowired注释应用于setter方法
@Service
public class UserServiceImpl implements UserService {private UserDao userDao;@Autowiredpublic void setUserDao(UserDao userDao) {this.userDao = userDao;}@Overridepublic void insert() {userDao.insert();}}
  • (三)、@Autowired应用于字段
@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Overridepublic void insert() {userDao.insert();}}
  • (四)、@Autowired应用于类型数组/集合对象的字段或方法
public interface ICalculateStrategy {void calculate();
}@Component
public class StategyA implements ICalculateStrategy {@Overridepublic void calculate() {}
}@Component
public class StategyB implements ICalculateStrategy {@Overridepublic void calculate() {}
}@Service
public class UserServiceImpl implements UserService {@Autowiredprivate ICalculateStrategy[] calculateStrategies;@Autowiredprivate List<ICalculateStrategy> calculateStrategyList;@Overridepublic void insert() {//[com.wsh.autowired.StategyA@490d6c15, com.wsh.autowired.StategyB@7d4793a8]System.out.println(Arrays.toString(calculateStrategies));//[com.wsh.autowired.StategyA@490d6c15, com.wsh.autowired.StategyB@7d4793a8]System.out.println(calculateStrategyList);}}@Configuration
@ComponentScan("com.wsh.autowired")
public class AppConfig {}public class Client {public static void main(String[] args) {AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);UserService userService = (UserService) annotationConfigApplicationContext.getBean("userServiceImpl");userService.insert();}
}

三、@Autowired自动装配原理

首先看一下@Autowired这个注解在Spring中的定义:

// 可作用于: 构造方法、普通方法、参数、字段、注解上
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
// 保留策略是运行时
@Retention(RetentionPolicy.RUNTIME)
// @Documented: 表明是否在java doc中添加Annotation
@Documented
public @interface Autowired {/*** 声明这个依赖是否必须,默认为true*/boolean required() default true;}

我们可以看到,@Autowired注解可以应用在构造方法,普通方法,参数,字段,以及注解这五种类型的地方,它的保留策略是在运行时。在Spring中,@Autowired注解位于org.springframework.beans.factory.annotation包下面,我们发现在这个包下面还有一个关键的类:AutowiredAnnotationBeanPostProcessor。

看名字盲猜AutowiredAnnotationBeanPostProcessor可能与@Autowired自动注入功能有关,确实,Spring对@Autowired注解的实现逻辑就在AutowiredAnnotationBeanPostProcessor实现的。

先看一下AutowiredAnnotationBeanPostProcessor类的继承关系图:

可以看到,AutowiredAnnotationBeanPostProcessor间接实现了InstantiationAwareBeanPostProcessor和

MergedBeanDefinitionPostProcessor两个BeanPostProcessor后置处理器接口,通过之前的文章,大家对BeanPostProcessor应该不陌生了,Spring很多功能都是利用BeanPostProcessor后置处理器来实现的。

  • MergedBeanDefinitionPostProcessor类:合并Bean的定义信息
// 合并Bean的定义信息的后处理方法,该方法是在Bean的实例化之后设置值之前调用。
void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);

MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition方法的调用时机是在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors方法,而applyMergedBeanDefinitionPostProcessors()方法是在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean通过反射创建bean的实例之后调用的。

  • InstantiationAwareBeanPostProcessor类:在 Bean 实例化前后和Bean设置属性值时执行的后置处理器
default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)throws BeansException {return null;
}

在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean()给bean的属性赋值方法中,会执行InstantiationAwareBeanPostProcessor的postProcessProperties()方法回调,如下图:

postProcessProperties()方法的作用: 允许对填充前的属性进行处理(如对属性的验证)

回顾一下Spring的创建流程:doCreateBean()通过反射创建bean的实例 -> applyMergedBeanDefinitionPostProcessors()执行MergedBeanDefinitionPostProcessor后置处理器增强方法postProcessMergedBeanDefinition() -> populateBean()属性填充 -> 执行InstantiationAwareBeanPostProcessor的postProcessProperties()方法回调 -> 初始化bean。

可以看到,在实例化bean之后,就会调用AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition()方法,然后在属性填充的时候,会调用AutowiredAnnotationBeanPostProcessor#postProcessProperties方法,我们按照这个先后顺序进行分析。

所以,我们将重点关注在AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition()方法以及AutowiredAnnotationBeanPostProcessor#postProcessProperties方法即可,下面挨个进行分析。

四、源码调试环境

下面以一个示例来跟踪@Autowired的整体执行流程。代码比较简单,这里就不过多说明,如下所示:

@Repository
public class UserDao {public void insert() {// 执行数据库操作}}public interface UserService {void insert();}@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Overridepublic void insert() {}}@Configuration
@ComponentScan("com.wsh.autowired")
public class AppConfig {}public class Client {public static void main(String[] args) {AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);UserService userService = (UserService) annotationConfigApplicationContext.getBean("userServiceImpl");userService.insert();}
}

Spring 是如何实现自动装配的呢? 我们慢慢往下看(注意:主要以 @Autowired 为例来讲解)

五、AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition()源码

AutowiredAnnotationBeanPostProcessor后置处理器主要负责对添加了@Autowired和@Value注解的元素实现自动装配。所以找到需要自动装配的元素,其实就是对@Autowired和@Value注解的解析,找出需要自动装配的元素是在MergedBeanDefinitionPostProcessor.postProcessMergedBeanDefinition()方法中实现的,而AutowiredAnnotationBeanPostProcessor间接实现了MergedBeanDefinitionPostProcessor,所以我们从AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition()方法开始分析。

我们以Debug方式启动Client,当我们执行new AnnotationConfigApplicationContext(AppConfig.class)时,会执行Spring IOC容器的初始化,熟悉IOC的小伙伴都知道,在刷新容器方法refresh()中有一个非常关键的步骤:finishBeanFactoryInitialization(beanFactory),那就是实例化所有剩下的非懒加载的单例Bean。

本例中我们主要分析userServiceImpl这个bean的实例化过程。

执行getBean("userServiceImpl") -> doCreateBean("userServiceImpl"),在doCreateBean()方法中,有一个方法:applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);它会执行MergedBeanDefinitionPostProcessor后置处理器增强方法postProcessMergedBeanDefinition(),允许修改 MergedBeanDefinition。

由于AutowiredAnnotationBeanPostProcessor间接实现了MergedBeanDefinitionPostProcessor,所以会执行到

AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition()方法,如下图。

接着我们看下AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition()方法的源码:

// org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);metadata.checkConfigMembers(beanDefinition);
}

postProcessMergedBeanDefinition()方法主要逻辑是在findAutowiringMetadata()方法。这个方法主要是找到需要自动装配的元素,该方法会去调用buildAutowiringMetadata()方法构建元数据信息。

private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {// Fall back to class name as cache key, for backwards compatibility with custom callers.// 缓存key, 在本例中就是bean的名称:userServiceImplString cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());// Quick check on the concurrent map first, with minimal locking.// 快速检查缓存中是否存在InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);if (InjectionMetadata.needsRefresh(metadata, clazz)) {synchronized (this.injectionMetadataCache) {// 检查缓存中是否存在metadata = this.injectionMetadataCache.get(cacheKey);if (InjectionMetadata.needsRefresh(metadata, clazz)) {if (metadata != null) {metadata.clear(pvs);}// 获取目标类对应的自动注入相关的元数据信息metadata = buildAutowiringMetadata(clazz);// 存入缓存this.injectionMetadataCache.put(cacheKey, metadata);}}}return metadata;
}

核心逻辑在buildAutowiringMetadata()方法,源码如下:

private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();Class<?> targetClass = clazz;do {// 存放找到的元数据信息final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();// 通过反射获取目标类中所有的字段,并遍历每一个字段,然后通过findAutowiredAnnotation()方法判断字段是否使用@Autowired和@Value修饰,// 如果字段被@Autowired和@Value修饰,则返回注解的相关属性信息ReflectionUtils.doWithLocalFields(targetClass, field -> {// findAutowiredAnnotation(): 判断字段是否使用@Autowired和@Value修饰,并返回相关属性AnnotationAttributes ann = findAutowiredAnnotation(field);if (ann != null) {// 校验@Autowired和@Value修饰注解是否应用在static方法上if (Modifier.isStatic(field.getModifiers())) {if (logger.isInfoEnabled()) {logger.info("Autowired annotation is not supported on static fields: " + field);}return;}// 获取到@Autowired注解的required()的值boolean required = determineRequiredStatus(ann);// 将该字段封成AutowiredFieldElement对象currElements.add(new AutowiredFieldElement(field, required));}});// 前面是通过反射获取目标类中所有的字段,这里是通过反射获取目标类中所有的方法ReflectionUtils.doWithLocalMethods(targetClass, method -> {Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {return;}AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {// 判断是否应用在静态方法上if (Modifier.isStatic(method.getModifiers())) {if (logger.isInfoEnabled()) {logger.info("Autowired annotation is not supported on static methods: " + method);}return;}// 判断方法的参数个数是否为0if (method.getParameterCount() == 0) {if (logger.isInfoEnabled()) {logger.info("Autowired annotation should only be used on methods with parameters: " +method);}}boolean required = determineRequiredStatus(ann);PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);// 将该字段封成AutowiredMethodElement对象currElements.add(new AutowiredMethodElement(method, required, pd));}});elements.addAll(0, currElements);targetClass = targetClass.getSuperclass();}// 循环处理父类需要自动装配的元素while (targetClass != null && targetClass != Object.class);// 将目标类对应的所有自动注入相关的元信息封装成InjectionMetadata,然后合并到Bean定义中// 包含所有带有@Autowired注解修饰的一个InjectionMetadata集合. 由两部分组成: 一是我们处理的目标类,二就是上述方法获取到的所以elements集合。return new InjectionMetadata(clazz, elements);
}

buildAutowiringMetadata()方法的处理流程:

  • (1)、通过反射获取目标类中所有的字段,并遍历每一个字段,然后通过findAutowiredAnnotation()方法判断字段是否使用@Autowired和@Value修饰,如果字段被@Autowired和@Value修饰,则返回注解的相关属性信息;

在本例中,目标类是com.wsh.autowired.UserServiceImpl,此时目标类中有一个字段userDao,如下图:

然后通过findAutowiredAnnotation(field)方法查找此属性是否被@Autowired注解修饰,很显然,我们的userDao使用了@Autowired注解修饰,所以能够获取到注解的属性信息:

  • (2)、通过反射获取目标类中所有的方法,跟前面处理字段的过程类似;
  • (3)、将每个字段或者方法解析到的元信息保存到List<InjectionMetadata.InjectedElement> elements集合中,字段对应的是AutowiredFieldElement类型,方法对应的则是AutowiredMethodElement类型,等待下一步的自动装配;
  • (4)、将目标类对应的所有自动注入相关的元信息封装成InjectionMetadata,然后返回;

buildAutowiringMetadata()方法执行完成后,会将解析得到的自动注入相关信息保存到缓存injectionMetadataCache中,如下图:

总结:AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition()方法的作用其实是,找到目标bean对象中的属性或者方法是否使用了@Autowired注解修饰,如果有@Autowired注解修饰,将会解析得到注解相关信息,将需要依赖注入的属性信息封装到InjectionMetadata类中,InjectionMetadata类中包含了哪些需要注入的元素及元素要注入到哪个目标类中。并将其存入到缓存injectionMetadataCache中,方便后面使用。说简单点,AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition()方法其实就是找到那些需要自动装配的元素。

六、AutowiredAnnotationBeanPostProcessor#postProcessProperties()源码

前面分析到,在doCreateBean()方法中,会执行applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName)方法,然后会调用到AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition()方法,此方法主要完成解析@Autowired和@Value注解相关自动注入信息,并将解析之后的结果存入缓存中,接下来就需要自动注入属性了。

在doCreateBean()方法执行后,接着就是执行populateBean("userServiceImpl")完成userServiceImpl这个bean的属性填充。

如上图,在populateBean("userServiceImpl")方法中,会执行InstantiationAwareBeanPostProcessor的postProcessProperties()方法回调。

AutowiredAnnotationBeanPostProcessor间接实现了InstantiationAwareBeanPostProcessor接口,所以会执行到

AutowiredAnnotationBeanPostProcessor#postProcessProperties()方法,如下图:

AutowiredAnnotationBeanPostProcessor后置处理器注入属性值是在postProcessPropertyValues()方法中实现的。一起看下AutowiredAnnotationBeanPostProcessor#postProcessProperties()方法的实现:

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {// 首先尝试从缓存injectionMetadataCache中获取对应的注入元信息,如果缓存不存在,将会执行buildAutowiringMetadata()获取InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);try {// 循环InjectionMetadata的injectedElements属性,挨个调用InjectionMetadata.InjectedElement.inject方法,通过反射方式设置属性的值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;
}

postProcessProperties()方法的处理流程:

  1. 调用findAutowiringMetadata()方法,尝试从缓存injectionMetadataCache中获取对应的注入元信息,如果缓存不存在,将会执行buildAutowiringMetadata()获取;
  2. 循环InjectionMetadata的injectedElements属性,挨个调用InjectionMetadata.InjectedElement.inject方法,通过反射方式设置属性的值;

首先会调用findAutowiringMetadata()方法,在前面已经分析过了,findAutowiringMetadata()方法将目标类对应的@Autowired注解元信息都已经解析好了,存入到缓存injectionMetadataCache中,所以 这里我们直接从缓存中获取即可。

这里主要分析metadata.inject(bean, beanName, 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()) {// 循环elementsToIterate, 挨个调用InjectionMetadata.InjectedElement.inject方法,通过反射方式设置属性的值;for (InjectedElement element : elementsToIterate) {if (logger.isTraceEnabled()) {logger.trace("Processing injected element of bean '" + beanName + "': " + element);}element.inject(target, beanName, pvs);}}
}

通过AutowiredAnnotationBeanPostProcessor的源码,我们可以发现,在AutowiredAnnotationBeanPostProcessor类中,定义了AutowiredFieldElement以及AutowiredMethodElement:

private class AutowiredFieldElement extends InjectionMetadata.InjectedElement {}private class AutowiredMethodElement extends InjectionMetadata.InjectedElement {}

所以,当执行element.inject(target, beanName, pvs)方法的时候,也是调用到AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject()方法。

protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {// 获取到对应的字段Field field = (Field) this.member;Object value;if (this.cached) {value = resolvedCachedArgument(beanName, this.cachedFieldValue);}else {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();try {// 解析依赖对象,实际上是从bean工厂中获取目标对象所依赖的bean对象value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);} catch (BeansException ex) {throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);}synchronized (this) {if (!this.cached) {if (value != null || this.required) {this.cachedFieldValue = desc;registerDependentBeans(beanName, autowiredBeanNames);if (autowiredBeanNames.size() == 1) {String autowiredBeanName = autowiredBeanNames.iterator().next();if (beanFactory.containsBean(autowiredBeanName) &&beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {this.cachedFieldValue = new ShortcutDependencyDescriptor(desc, autowiredBeanName, field.getType());}}} else {this.cachedFieldValue = null;}this.cached = true;}}}if (value != null) {// 设置可访问权限ReflectionUtils.makeAccessible(field);// 通过反射方式设置字段的值// 在本例中,就是注入userServiceImpl类中的userDao属性// bean - userServiceImpl// value - userDao对象// field - UserServiceImpl中的userDao属性field.set(bean, value);}
}

从源码中我们看到,核心逻辑就是通过beanFactory.resolveDependency()方法从bean工厂中获取依赖的对象。跟踪一下resolveDependency()方法可以发现,底层会调用到org.springframework.beans.factory.config.DependencyDescriptor#resolveCandidate()方法,而resolveCandidate()方法内部其实是通过beanFactory.getBean("userDao")从bean容器中获取到userDao这个bean对象。如下图:

依赖的userDao对象获取成功后,将会通过反射方式field.set(bean, value)设置userServiceImpl这个bean对象中的userDao属性的值。如下图:

到这里,@Autowired就完成了依赖对象的注入工作,接着将会执行populateBean()属性填充后续的一系列处理流程。

七、总结

通过前面的分析,我们已经知道了@Autowired完成自动装配主要是在AutowiredAnnotationBeanPostProcessor后置处理器中实现的,主要分为两个步骤:

  • 找出需要自动装配的元素:具体实现在AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition()方法;
  • 注入属性:具体实现在AutowiredAnnotationBeanPostProcessor#postProcessProperties()方法;

通过一张图来回顾一下@Autowired自动注入的流程:

下面我们总结一下Spring Bean的自动装配过程:

  1. 根据Class目标类类型,通过反射获取其所有的Field和Method信息,然后判断字段或者方法上面是否添加了@Autowired和@Value注解,以此来判断是否需要自动装配;
  2. 如果标注有@Autowired和@Value注解,表示需要自动装配,则会将需要自动装配的元素,封装成AutowiredFieldElement(针对字段)或AutowiredMethodElement(针对方法)对象;
  3. 调用AutowiredFieldElement或AutowiredMethodElement的inject方法,通过反射,调用容器的getBean()方法找到需要注入的Bean对象,然后注入到目标Bean中;

Spring中@Autowired注解的工作原理相关推荐

  1. Spring中Autowired注解到底怎么实现的

    前言 使用Spring开发时,进行配置主要有两种方式,一是XML的方式,二是Java Config的方式. Spring技术自身也在不断的发展和改变,从当前springboot的火热程度来看,java ...

  2. Spring中@Autowired注解、@Resource注解的区别

    Spring不但支持自己定义的@Autowired注解,还支持几个由JSR-250规范定义的注解,它们分别是@Resource.@PostConstruct以及@PreDestroy. @Resour ...

  3. Spring中 @Autowired注解与@Resource注解的区别

    相同点: @Resource的作用相当于@Autowired,均可标注在字段或属性的setter方法上. 不同点: (1)提供方:@Autowired是由org.springframework.bea ...

  4. Spring的@Enable*注解的工作原理

    一 列举几个@Enable*注解的功能 @EnableAspectJAutoProxy:开启对AspectJ自动代理的支持. @EnableAsync:开启异步方法支持. @EnableSchedul ...

  5. Spring框架你敢写精通,面试官就敢问@Autowired注解的实现原理

    面试官:Spring框架中的@Autowired注解可以标注在哪些地方? 小小白:@Autowired注解可以被标注在构造函数.属性.setter方法或配置方法上,用于实现依赖自动注入. 面试官:有没 ...

  6. @Autowire注解的工作原理

    @Autowired注解的工作原理 @Autowired注解用法 将@Autowired注解应用于构造函数,如以下示例所示 public class MovieRecommender {private ...

  7. Spring中异步注解@Async的使用、原理及使用时可能导致的问题

    前言 最近,很多同学碰到了下面这个问题,添加了Spring提供的一个异步注解@Async循环依赖无法被解决了,下面是一些读者的留言跟群里同学碰到的问题: 本着讲一个知识点就要讲明白.讲透彻的原则,我决 ...

  8. Spring中@AliasFor注解的作用及原理

    本文基于Springboot 2.1.6.RELEASE 版本分析. 关于@AliasFor注解,曾提过的一个issue Explicit attribute overrides configured ...

  9. Spring中常用注解的介绍

    spring中使用注解时配置文件的写法: <?xml version="1.0" encoding="UTF-8"?> <span style ...

  10. Spring中@Autowired、@Qualifier、@Resource的区别

    转自: Spring中@Autowired.@Qualifier.@Resource的区别_老周聊架构的博客-CSDN博客_qualifier和resource区别1.@Autowired@Autow ...

最新文章

  1. Linux上隐藏进程名(初级版)
  2. 企消互动广告:网络时代广告活动的创新形式——兼谈杜丽反败为胜对企业的启示...
  3. 如何在24行JavaScript中实现Redux
  4. 【转】学习Entity Framework 中的Code First
  5. [转]windows系统激活
  6. 灰色关联分析_灰色关联分析模型研究综述
  7. linux下(ubuntu)反删除(误删恢复)与回收站制作
  8. Ubuntu Linux服务器安装JDK8
  9. 【交往智慧】002.和优秀的人接触
  10. docker网络配置和4G网卡关系 小白笔记
  11. 关于ios的ipa包的分析之link map 文件的分析
  12. 2016-2017-1(实变函数56, 点集拓扑56)
  13. Linux错误码汇总
  14. java list获取某个字段
  15. python基础语法25_Python基础语法习题参考(0-9关)
  16. 网络安全培训小白入门课,从buu到红帽的图片隐写
  17. Html入门学习总结
  18. img图片不失真,进行比例缩放
  19. Linux专家必读的几本书
  20. 树莓派基础实验14:PS2操纵杆实验

热门文章

  1. TensorFlow进阶:CNN对CIFAR10图像分类
  2. 在UITextView显示HTML,以及NSAttributedString乱码问题解决 swift
  3. 图像分类数据集(Fashion-MNIST)
  4. 计算机博弈军旗程序,军棋机器人UCT算法及计算机博弈行为研究
  5. linux挂载曙光存储,曙光I1620G30获取设备的cpu、内存、存储等参数信息。
  6. php mysql 1040_php – 如何修复消息:SQLSTATE [08004] [1040]连接太多
  7. 2021-09-08173. 二叉搜索树迭代器 栈
  8. 第二章随机过程的基本知识part I
  9. ServletContext的用法
  10. 凸优化第五章对偶 5.6扰动及灵敏度分析