Spring Framework自动装配setAutowireMode和Mybatis案例的源码探究

发布时间:2026/10/21 10:35:02
Spring Framework自动装配setAutowireMode和Mybatis案例的源码探究 源码仓库Gitee·GitHub由前文可得知 Spring Framework的自动装配有两种方式xml配置和注解配置自动装配的类型有1xml配置中的byType根据类型查找Autowired注解是默认根据类型查找类型查找不到会使用名称查找2xml配置中的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种模型1AUTOWIRE_NOSpring Framework 默认不自动装配但会对加了Autowired 注解的属性进行自动装配2AUTOWIRE_BY_TYPE不需要加任何注解会根据类型进行自动装配它是根据当前属性的 setter 方法来进行自动装配如果当前属性有 setter 方法会进行自动装配如果没有则不会进行自动装配从下图可以看出Spring 容器在初始化时发现 UserService 有 setter 方法会调用所以如果有属性会自动装配【AUTOWIRE_BY_TYPE跟setter方法有关跟属性没关】3AUTOWIRE_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 SetBeanDefinitionHolder doScan(String... basePackages) { //将每个Mapper解析成BeanDefinitionHolder存放到set集合中 SetBeanDefinitionHolder 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(SetBeanDefinitionHolder 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属性描述器会对类中所有的 readMethodgetXXX方法和 writeMethodsetXXX方法都拿出来其中对 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) { SetString result new TreeSetString(); 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); }