前言

上篇博客【@Autowired 源码为什么是ByType注入】跟着源码详细的说明了@Autowired在Spring源码里面是如何设计为byType注入的。本篇博客的主要内容就是源码追踪探究@Resource是为什么被称为byName注入的。更多Spring内容进入【Spring解读系列目录】。

构建Sample

为了说明这个流程,我们还是要做一个例子进行追踪。既然要读源码,就必须有一个相应的例子去跟随源码的调试过程查看调用链是怎么走的。首先要有一个接口Demo,然后有两个实现类DemoImpl1DemoImpl2,以及一个DemoService去依赖Demo

@Service
public class DemoService {@ResourceDemo demoImpl1;
}

Spring默认命名规则

为了解读源码更方便一些,简单说下Spring默认的命名规则。Spring默认命名规则很简单,就是把原类名的第一个大写字母替换为小写字母作为这个类的默认实例对象名字,我们在Spring容器的单例对象池中的默认名字就是这个。简单来说就是DemoService会被Spring默认创建一个demoService作为名字的对象。而这个名字也会被当作一个默认的参数做check。

Spring启动调用链

因为这样的追踪过程必须通过调试进行,所以首先还是要把调用流程展示出来。

postProcessProperties:318, CommonAnnotationBeanPostProcessor (org.springframework.context.annotation)
populateBean:1415, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:608, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:531, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:335, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, 232307208 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$34)
getSingleton:234, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:333, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:208, AbstractBeanFactory (org.springframework.beans.factory.support)
preInstantiateSingletons:944, DefaultListableBeanFactory (org.springframework.beans.factory.support)
finishBeanFactoryInitialization:925, AbstractApplicationContext (org.springframework.context.support)
refresh:588, AbstractApplicationContext (org.springframework.context.support)
<init>:93, AnnotationConfigApplicationContext (org.springframework.context.annotation)
main:9, Test (com.example.test)

通过追踪@Resource的流程,可以明显的看出@Resource使用的后置处理器是CommonAnnotationBeanPostProcessor,而不是@Autowired使用的AutowiredAnnotationBeanPostProcessor。但是前面的流程还是一致的,都是从赋值用的populateBean()方法进入相应的后置处理器的postProcessProperties()方法。

源码流程

既然是走的后置处理方法,那么就直接走到postProcessProperties()中。同样的这里的bean就是DemoService对象,此时DemoService刚刚被实例化出来,正要被Spring容器进行依赖注入。其中的beanName就是根据Spring默认的生成规则生成的"demoService"

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);try { //进入这里metadata.inject(bean, beanName, pvs);}catch (Throwable ex) {throw new BeanCreationException(beanName, "Injection of resource 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);  //进入这个inject()方法}}
}

到目前为止可以看到虽然@Reource@Autowired使用的后置处理器是不一样的,但是其代码是没有什么变化的。真正变化的地方就是在element.inject(target, beanName, pvs)这里。回想一下@Autowired使用的是AutowiredAnnotationBeanPostProcessor内部类的方法AutowiredFieldElement#inject()。但是@Resource使用的则是InjectionMetadata内部类的方法InjectedElement#inject()

protected 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 (this.isField)传入进来的是否是一个字段,拿到字段以后直接对target(DemoService)对象进行字段设置。于是我们需要进入getResourceToInject()方法去查看里面是如何赋值的。

protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :getResource(this, requestingBeanName));
}

进入以后,由于不需要设置懒加载,到getResource(this, requestingBeanName)方法里面。

protected Object getResource(LookupElement element, @Nullable String requestingBeanName)throws NoSuchBeanDefinitionException {if (StringUtils.hasLength(element.mappedName)) {return this.jndiFactory.getBean(element.mappedName, element.lookupType);}if (this.alwaysUseJndiLookup) {return this.jndiFactory.getBean(element.name, element.lookupType);}if (this.resourceFactory == null) {throw new NoSuchBeanDefinitionException(element.lookupType,"No resource factory configured - specify the 'resourceFactory' property");}return autowireResource(this.resourceFactory, element, requestingBeanName);
}

碰到几个if语句,看条件的内容,显然是Spring容器自己的类,和Sample都不沾边,直接到return语句的方法autowireResource()方法里。

protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName)throws NoSuchBeanDefinitionException {Object resource;Set<String> autowiredBeanNames;String name = element.name;if (factory instanceof AutowireCapableBeanFactory) {AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;DependencyDescriptor descriptor = element.getDependencyDescriptor();if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {autowiredBeanNames = new LinkedHashSet<>();resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);if (resource == null) {throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");}}else {//会直接到这里给resource赋值resource = beanFactory.resolveBeanByName(name, descriptor);autowiredBeanNames = Collections.singleton(name);}}else {resource = factory.getBean(name, element.lookupType);autowiredBeanNames = Collections.singleton(name);}/**判断factory是否是ConfigurableBeanFactory,显然不是略过**/return resource;
}

方法进来以后,首先构造返回值对象,下一步自然就是找赋值的地方。往下走可以看到if (factory instanceof AutowireCapableBeanFactory)条件,判断是否是自动注入工厂的类型。很明显这里的factory属于这个类型,进入条件块。碰见下一个if条件,第一个条件没有特别设置默认就是true;第二个条件是否符合默认的命名,显然也是ture;第三个条件就不会成立了,factory里面有这个对象是在Spring容器初始化就完成的,加了非因此就是false。其实这里没有什么关系,因为根据源码来看,如果满足if逻辑,就直接从工厂里拿出来赋值给resource,然后直接返回出去。但是我们当前的逻辑会到else里面,因此还是要走到resolveBeanByName()方法里面。

public Object resolveBeanByName(String name, DependencyDescriptor descriptor) {InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);try { //老熟人儿,getBean()return getBean(name, descriptor.getDependencyType());}finally {ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);}
}

到这个方法里面以后,就能看到一个很熟悉的方法getBean(),之前看过笔者【Spring源码解析AOP(二)】博客的读者,肯定很熟悉这个方法,进入看。

public <T> T getBean(String name, Class<T> requiredType) throws BeansException {return doGetBean(name, requiredType, null, false);
}

接着走到doGetBean()

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)throws BeansException {String beanName = transformedBeanName(name);Object bean;Object sharedInstance = getSingleton(beanName); //根据name去单例对象池拿出对象if (sharedInstance != null && args == null) {if (logger.isTraceEnabled()) {if (isSingletonCurrentlyInCreation(beanName)) {logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +"' that is not fully initialized yet - a consequence of a circular reference");}else {logger.trace("Returning cached instance of singleton bean '" + beanName + "'");}}bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);}/**无关很多,略。包括忽略了对象类型检测的部分,会在下面单独说**/return (T) bean;
}

最终这个方法返回的还是bean,也就是方法最前面创建的变量。其实到这里已经很明显了,通过sharedInstance = getSingleton(beanName)从单例对象池里取出已经实例化好的对象,然后到bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);赋值,最终返回出去,一层层往上返回直到field.set(target, getResourceToInject(target, requestingBeanName));给字段赋值。

单例对象池没有对象怎么办

到这里肯定有同学会有疑问:如果单例对象池找不到对应的实例对象怎么办?其实这个问题基本上不会遇到,因为Spring容器在初始化的时候会在@ComponentScan的时候把所有注册到容器里的类实例化出来,只要变量名符合Spring的命名规则就一定可以找到。

异常is expected to be of type ‘xxx’ but was actually of type ‘xxx’

【源码解析异常is expected to be of type ‘xxx’ but was actually of type ‘xxx’ 是如何发生的】

异常expected single matching bean but found 2

【源码解析异常expected single matching bean but found 2是如何发生的】
这篇文章里有分析,@Resource是如何进行byType注入的。

总结

通过上面的源码流程以及报错源码流程的分析,可以得出这样的一个结论:无论是@Autowired或者@Resource都不能单纯的说是byType或者byName去自动注入的。二者其实是交叉使用,作为双保险。只能说@Autowired默认byType进行的注入,而@Resource默认是byName进行的注入。但是依然不可避免的出现异常,因此也可以说规范命名十分的重要。由于篇幅的原因,其实还有两个异常问题没有说,那就是is expected to be of type 'xxx' but was actually of type 'xxx'expected single matching bean but found 2产生的原因,笔者会单独作为两个篇章进行讲解。这两篇文章已经作为本文的两个标题补充到博客里。

Spring @Resource 源码解析 – 为什么是ByName注入相关推荐

  1. spring boot 源码解析23-actuate使用及EndPoint解析

    前言 spring boot 中有个很诱人的组件–actuator,可以对spring boot应用做监控,只需在pom文件中加入如下配置即可: <dependency><group ...

  2. spring事务源码解析

    前言 在spring jdbcTemplate 事务,各种诡异,包你醍醐灌顶!最后遗留了一个问题:spring是怎么样保证事务一致性的? 当然,spring事务内容挺多的,如果都要讲的话要花很长时间, ...

  3. Spring AOP源码解析-拦截器链的执行过程

    一.简介 在前面的两篇文章中,分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在得到了 bean 的代理对象,且通知也以合适的方式插在了目标方 ...

  4. Spring Session源码解析

    AbstractHttpSessionApplicationInitializer,很明显它是一个初始化的类,它是一个抽象类,可以理解为一个公用的基类,然后看一下onStartup这个方法,最主要的方 ...

  5. Spring @Import源码解析

    在Spring boot中常用到@Import,允许通过它引入 @Configuration 注解的类 (java config), 引入ImportSelector接口(这个比较重要, 因为要通过它 ...

  6. Spring AOP源码解析(一)——核心概念

    目录 Pointcut ClassFilter MethodMatcher Advisor IntroductionAdvisor PointcutAdvisor AbstractPointcutAd ...

  7. Spring AOP源码解析——AOP动态代理原理和实现方式

    2019独角兽企业重金招聘Python工程师标准>>> Spring介绍 Spring(http://spring.io/)是一个轻量级的Java 开发框架,同时也是轻量级的IoC和 ...

  8. Spring MVC源码解析——HandlerMapping(处理器映射器)

    Sping MVC 源码解析--HandlerMapping处理器映射器 1. 什么是HandlerMapping 2. HandlerMapping 2.1 HandlerMapping初始化 2. ...

  9. Spring IOC源码解析笔记

    小伙伴们,你们好,我是老寇 Spring最重要的概念就算IOC和AOP,本篇主要记录Spring IOC容器的相关知识,本文适合有spring相关基础并且想了解spring ioc的相关原理的人看 本 ...

最新文章

  1. 接口自动化框架(java)--2.接口用例POST请求,参数配置
  2. POJ 1170 Shoping Offers(IOI 95)
  3. css3 自定义滚动条样式
  4. STM32 进阶教程 4 - 软件实现高精度延时 2
  5. 版本变迁_冰枪?卢登?大天使?——从发条魔灵的装备变迁看版本变动
  6. mysql数据库(1):连接与断开服务器
  7. python中链表是什么_python 单链表的实现
  8. angular动态绑定样式以及改变UI框架样式的方法
  9. 真是恍然大悟啊!java从入门到精通pdf百度云
  10. MFC 鼠标画线总结
  11. 360产品无法安装,此程序被组策略阻止
  12. sql 连续两个月活跃的用户
  13. react全局状态管理_Recoil - Facebook 官方 React 状态管理器
  14. Anyka云平台调用api
  15. 西部学刊杂志西部学刊杂志社西部学刊编辑部2022年第22期目录
  16. git命令出现fatal: Unable to create 'xxx/.git/index:File exists.问题
  17. 解决rdlc报错 An error occurred during local report processing
  18. Erlang词法分析器、语法分析器(lexer-leex,yac-yecc)
  19. 网站带不带www真的不一样,很多新手不知道区别会被坑死的
  20. 癌症有哪些数据集_癌症

热门文章

  1. eNSP内部网络访问外部网络实验
  2. 好不容易弄好了google app engine ,想不到不能用了。只好去安装sina app engine
  3. 零拷贝之splice( )函数和tee( )函数
  4. 模拟设计的100条圣经(汉化版)
  5. [笔记的明子] - C语言笔记
  6. Python|让python帮忙做鸡兔同笼
  7. 微步星辰的逆袭,专访微步星辰合伙人齐成岳
  8. VM (虚拟机)下载及安装详细步骤
  9. 计算机二战一个双非学校怎样,双非二战学长成功调剂985:看他的调剂院校选择!...
  10. 使用深度优先搜索算法解决迷宫问题