由前文可得知, Spring Framework的自动装配有两种方式:xml配置和注解配置;

  自动装配的类型有:

  (1)xml配置中的byType根据类型查找(@Autowired注解是默认根据类型查找,类型查找不到会使用名称查找);

  (2)xml配置中的byName根据名称查找,它是xml配置中根据setter方法来查找(@Resource注解也是根据名称查找,但它是根据属性名称来查找,跟setter方法无关,所以setter方法可以不用写,可以使用type指定类);

  如果没有上面的在xml中进行配置和使用了@Autowired、@Resource等注解,那如何判断一个类中的属性要不要自动装配和会不会自动装配?

  如果不会自动装配,除了使用上面的xml配置和注解方式外,还有其他方式可以导致属性自动装配吗?

  自动装配有类型限制吗?比如哪些类型无法自动装配?

  以下面的案例进行探讨:

@Component
public class Userclazz {
} @Component
public class UserService { private Userclazz userclazz; public Userclazz getUserclazz() {
return userclazz;
} public void setUserclazz(Userclazz userclazz) {
this.userclazz = userclazz;
}
}

  测试:

BeanFactory beanFactory  = new AnnotationConfigApplicationContext(Config.class);
System.out.println(beanFactory.getBean(UserService.class).getUserclazz());
//=====结果======
null

   从上面可以看到,上面的案例中无法进行自动装配,如果我们利用 ImportBeanDefinitionRegistrar 接口的特性来修改BeanDefition,如下所示,则可以解决自动装配的问题:

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
GenericBeanDefinition definition= (GenericBeanDefinition) registry.getBeanDefinition("userService");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
registry.registerBeanDefinition("userService",definition);
}
}
@Configuration
@ComponentScan("com.hrh")
@Import(MyImportBeanDefinitionRegistrar.class)
public class MyBatisConfig {}

  从上面可以看到,使用了 definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); 这行代码可以导入变量自动装配,根据类型的自动注入 setAutowireMode 有3种模型:

  1)AUTOWIRE_NO:Spring Framework 默认,不自动装配,但会对加了@Autowired 注解的属性进行自动装配;

  2)AUTOWIRE_BY_TYPE:不需要加任何注解,会根据类型进行自动装配,它是根据当前属性的 setter 方法来进行自动装配,如果当前属性有 setter 方法,会进行自动装配,如果没有则不会进行自动装配;从下图可以看出,Spring 容器在初始化时发现 UserService 有 setter 方法会调用,所以如果有属性会自动装配;【AUTOWIRE_BY_TYPE跟setter方法有关,跟属性没关

  3)AUTOWIRE_BY_NAME:根据名称进行自动装配,也是根据当前属性的 setter 方法来进行自动装配;

  前文讲到的 @MapperScan 注解进行扫描包解析将每个 Mapper 接口转换成对应的 MapperFactoryBean,那它是使用上面的哪种类型呢?从下面的代码可以看出它也是使用 AUTOWIRE_BY_TYPE 的:

//下面是扫描解析包路径的主要逻辑:
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner { /**
* Calls the parent search that will search and register all the candidates.
* Then the registered objects are post processed to set them as
* MapperFactoryBeans
*/
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//将每个Mapper解析成BeanDefinitionHolder存放到set集合中
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
//对BeanDefinitionHolder集合进行处理
processBeanDefinitions(beanDefinitions);
} return beanDefinitions;
} private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition(); if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
} // the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
//给每个BeanDefition提供一个有参构造方法,在后面Mapper接口进行实例化的JDK代理时可以根据这个class返回对应的代理对象;
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
//Mapper接口转换成MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass()); definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
} if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
} if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
}

  对于“自动装配有类型限制吗?比如哪些类型无法自动装配?”的问题,以 MapperFactoryBean 为例来进行探讨:

  从上面可以看到 MapperFactoryBean 的属性有 mapperInterface、addToConfig,对应的 setter 方法有 setAddToConfig,所以属性 addToConfig 可能会进行自动装配【为什么说可能呢?因为自动装配可能存在类型限制,有些类型可能无法进行自动装配】; 它的父类 SqlSessionDaoSupport 有属性 sqlSession 和 externalSqlSession,对应的 setter 方法有 setSqlSessionFactory、setSqlSessionTemplate,所以 sqlSession 和 externalSqlSession 可能会进行自动装配,结合前文 Mybatis一级缓存和结合Spring Framework后失效的源码探究 就可以知道,sqlSession 一定会进行自动装配,由此才会进行 new SqlSessionTemplate,后面才会有 SqlSessionTemplate 对象的调用,至于 SqlSessionFactory 如何来的,其实我们在配置类中就已经实例化了,所以会直接从配置类中拿,SqlSessionFactoryBean 是一个 FactoryBean,所以获取时从 getObject 方法获取,而该方法返回的就是一个 SqlSessionFactory;

  那么为什么不直接使用 @Autowired 注解呢,其实是为了解耦,如果加了该注解,就永远需要依赖 Spring Framework 来进行编译,如果不用注解,可以利用 ImportBeanDefinitionRegistrar 接口特性和 setAutowireMode 的3种模型 来实现自动装配。

  从前文Mybatis的初始化和结合Spring Framework后初始化的源码探究可以得知当UserService在实例化过程中发现有属性变量,会对属性变量进行实例化,所以需要在 AbstractAutowireCapableBeanFactory#populateBean 方法处打断点查看上面的哪些属性会进行自动装配:

  从上图可以看出最开始它有一个 addToConfig 变量,但这个变量不是上面提到的 MapperFactoryBean 的 addToConfig 属性变量,而是 @MapperScan 扫描时注入的:

  当上面的 AbstractAutowireCapableBeanFactory#populateBean 方法继续执行后发现 pvs 发生了改变,属性变成了两个,从而可以判断 autowireByType 方法就是获取类的属性:

  接下来查看 autowireByType 方法,从下面可以看出,它拿出了两个属性 sqlSessionFactory  和 sqlSessionTemplate【因为 SqlSessionDaoSupport 的 setter 方法有 setSqlSessionFactory、setSqlSessionTemplate】,为什么没有拿出 addToConfig 属性变量呢?因为前面已经拿出包含了:

  所以接下来查看AbstractAutowireCapableBeanFactory#unsatisfiedNonSimpleProperties 方法是如何筛选过滤只剩下两个的:从下面看出 pds(属性描述器)拿出了所有的属性,有9个,第一个是 addToConfig,是MapperFactoryBean 的属性 addToConfig,第二个是 class ,是因为每个类的父类都是 Object 类,而 Object 类中有一个 getClass 方法,由此可以得知 pds(属性描述器)会对类中所有的 readMethod(getXXX方法)和 writeMethod(setXXX方法)都拿出来,其中对 singleton 做了优化成 isSingleton(对Boolean类型的属性)

  下面是展开每个属性所获取的对应位置:

  查看属性过滤的详细代码:pd.getWriteMethod() != null 从中可以看到如果 writeMethod 是空的会过滤掉,所以上面的属性 class、object、objectType、singleton 都过滤掉了【所以对应了前面的 AUTOWIRE_BY_TYPE 模型只关心 setter方法】;isExcludedFromDependencyCheck 设置一些接口不进行自动装配(前面没有进行设置,所以可以忽略);pvs.contains 由于前面 pvs 已经包含了 addToConfig ,所以 MapperFactoryBean 的属性 addToConfig 不会自动装配;BeanUtils.isSimpleProperty 是对一些简单类型进行过滤

    protected String[] unsatisfiedNonSimpleProperties(AbstractBeanDefinition mbd, BeanWrapper bw) {
Set<String> result = new TreeSet<String>();
PropertyValues pvs = mbd.getPropertyValues();
//获取所有属性
PropertyDescriptor[] pds = bw.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
//进行属性过滤
if (pd.getWriteMethod() != null && !isExcludedFromDependencyCheck(pd) && !pvs.contains(pd.getName()) &&
!BeanUtils.isSimpleProperty(pd.getPropertyType())) {
result.add(pd.getName());
}
}
return StringUtils.toStringArray(result);
}

  下面是 BeanUtils.isSimpleProperty 的过滤规则:从下面的 isSimpleValueType 可以看出对于 char字符串、number、date、Class 类型等的进行过滤,由此可以得出 MapperFactoryBean 的属性 mapperInterface 被过滤掉不会自动装配,所以最后只剩下 sqlSessionFactory  和 sqlSessionTemplate 两个会自动装配,即 SqlSessionDaoSupport 的属性 sqlSession 和 externalSqlSession 会自动装配;

    public static boolean isSimpleProperty(Class<?> clazz) {
Assert.notNull(clazz, "Class must not be null");
return isSimpleValueType(clazz) || (clazz.isArray() && isSimpleValueType(clazz.getComponentType()));
}
    public static boolean isSimpleValueType(Class<?> clazz) {
return (ClassUtils.isPrimitiveOrWrapper(clazz) ||
Enum.class.isAssignableFrom(clazz) ||
CharSequence.class.isAssignableFrom(clazz) ||
Number.class.isAssignableFrom(clazz) ||
Date.class.isAssignableFrom(clazz) ||
URI.class == clazz || URL.class == clazz ||
Locale.class == clazz || Class.class == clazz);
}

Spring Framework自动装配setAutowireMode和Mybatis案例的源码探究的更多相关文章

  1. Spring的自动装配Bean

    spring的自动装配功能的定义:无须在Spring配置文件中描述javaBean之间的依赖关系(如配置<property>.<constructor-arg>).IOC容器会 ...

  2. Spring Framework 条件装配 之 @Conditional

    Spring Framework 条件装配 之 @Conditional 前言 了解SpringBoot的小伙伴对Conditional注解一定不会陌生,在SpringBoot项目中,Conditio ...

  3. Spring Boot 自动装配(二)

    目录 目录 前言 1.起源 2.Spring Boot 自动装配实现 2.1.@EnableAutoConfiguration 实现 2.1.1. 获取默认包扫描路径 2.1.2.获取自动装配的组件 ...

  4. Spring Boot 自动装配流程

    Spring Boot 自动装配流程 本文以 mybatis-spring-boot-starter 为例简单分析 Spring Boot 的自动装配流程. Spring Boot 发现自动配置类 这 ...

  5. Spring Boot系列(二):Spring Boot自动装配原理解析

    一.Spring Boot整合第三方组件(Redis为例) 1.加依赖 <!--redis--> <dependency> <groupId>org.springf ...

  6. Spring的自动装配与依赖注入

    Spring的自动装配与依赖注入 装配 = 创建Bean + 注入Bean 创建Bean 自动发现 显式注册Bean 注入Bean 基于配置的注入 自动注入 Spring的装配分为显式装配和隐式装配, ...

  7. spring完成自动装配

    让spring完成自动装配 Autowiring 解决标签为javaBean注入时难以维护而实现的 下面是几种autowire type的说明: 1,byname:试图在容器中寻找和需要自动装配的属性 ...

  8. spring中自动装配bean

    首先用@Component注解类: package soundsystem: import org.springframework.stereotype.Component; @Component p ...

  9. spring的自动装配,骚话@Autowired的底层工作原理

    前言 开心一刻 十年前,我:我交女票了,比我大两岁.妈:不行!赶紧分! 八年前,我:我交女票了,比我小两岁,外地的.妈:你就不能让我省点心? 五年前,我:我交女票了,市长的女儿.妈:别人还能看上你?分 ...

随机推荐

  1. 关于,java-webservice接口,根据服务端,自动生成客户端调用时,响应时间慢

    我这边遇到的问题,是在和对方进行webservice接口交互的时候,用工具,调用对方的webservice接口,对方响应很快.但是用java生成的客户端调用就会很慢才得到响应.大概有5分钟左右. 这里 ...

  2. CloudQuery v1.3.4 版本更新

    Hello,大家好久不见! 上一个版本(v1.3.3)发布已是春节前的事情了,此次 v1.3.4 是 CloudQuery 社区版在辛丑牛年的第一个版本发布.本次更新增加了新功能,优化了原有功能点.同 ...

  3. pandas函数高级

    一.处理丢失数据 有两种丢失数据: None np.nan(NaN) 1. None None是Python自带的,其类型为python object.因此,None不能参与到任何计算中. #查看No ...

  4. Kotlin/Java Base64编码和解码(图片、文件)

    原文: Kotlin/Java Base64编码和解码(图片.文件) | Stars-One的杂货小窝 最近在项目中使用到了Base64编码和解码,便是稍微写篇文章记录一下 PS:本文代码都是使用Ko ...

  5. Cloudam云端,探索高性能计算在药物研究领域的解决方案

    近日,Cloudam云端与国内某知名药企与合作,通过接入Cloudam云端自主研发的云E云超算服务,计算效率提高的数百倍.这也是云算力在生命科学领域的又一次成功应用.Cloudam云端云E云超算服务是 ...

  6. k8s 日志收集之 EFK

    如今越来越多的应用部署在容器之中,如何收集日志也是一个很重要的问题.服务出问题了,排查问题需要给开发看日志.服务一般会在多个不同的 pod 中,一个一个的登进去看也的确不方便.业务数据统计也需要日志. ...

  7. java面试记很多次还是记不住的问题

    1.java底层如何实现多态 https://blog.csdn.net/fan2012huan/article/details/51007517 (1)在常量池中找到方法调用的符号引用 (2)查看P ...

  8. 2019 GDUT Rating Contest III : Problem E. Family Tree

    题面: E. Family Tree Input file: standard input Output file: standard output Time limit: 1 second Memory ...

  9. ImportError: No module named site

    cmd中执行python提示:ImportError: No module named site 运行python.exe Fatal Python error: initfsencoding: un ...

  10. 深度学习---1cycle策略:实践中的学习率设定应该是先增再降

    深度学习---1cycle策略:实践中的学习率设定应该是先增再降 本文转载自机器之心Pro,以作为该段时间的学习记录 深度模型中的学习率及其相关参数是最重要也是最难控制的超参数,本文将介绍 Lesli ...