关于Mybatis扫描所有mapper包并生成dao实例不需要显示注入SqlSessionFactory的原因探查

大家都知道,一般spring配合mybatis会引入 mybatis-spring-1.2.2  这个jar包,会用到以下的类,在配置文件中

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.xxxx.dao.mapper" />
        <!--<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />-->
        <!--<property name="sqlSessionFactory" value="se"></property>-->
    </bean>

 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--dataource-->
        <property name="dataSource" ref="dataSource"/>
        <!--Mapper files-->
        <property name="mapperLocations" value="classpath*:config/mybatis/local/*.xml" />
        <!--Entity package-->
        <!--<property name="typeAliasesPackage" value="com.xxxx.dao.entity" />-->
        <property name="typeAliasesPackage" value="com.xxxx.dao.entity"/>
        <property name="configLocation" value="classpath:config/mybatis/mybatis-configuration.xml" />
    </bean>

首先这个版本中,不推荐使用sqlSessionFactory ,通过type进行引入,源码中可以发现已经被Deprecated了

@Deprecated
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
  }

推荐使用 通过 bean name 进行注入 sqlsessionfactory

  /**
   * Specifies which {@code SqlSessionFactory} to use in the case that there is
   * more than one in the spring context. Usually this is only needed when you
   * have more than one datasource.
   * <p>
   * Note bean names are used, not bean references. This is because the scanner
   * loads early during the start process and it is too early to build mybatis
   * object instances.
   *
   * @since 1.1.0
   *
   * @param sqlSessionFactoryName Bean name of the {@code SqlSessionFactory}
   */
  public void setSqlSessionFactoryBeanName(String sqlSessionFactoryName) {
    this.sqlSessionFactoryBeanName = sqlSessionFactoryName;
  }

当然,这样做没有问题,可以正常运行,但经过测试你会发现,在MapperScannerConfigurer的申明中,把上述两种SqlSessionFactory的注入方式注释掉,在程序启动后,mapper还是可以正常访问数据库和进行查询操作,这是为什么呢?

一开始的答案是想当然的,在xml中没有配置的情况下,肯定是在扫描生成mapper的时刻注入进去了,随后进入源码进行查看

也可以参考https://www.cnblogs.com/jpfss/p/7799806.htmlhttps://blog.csdn.net/z69183787/article/details/104545466

1、首先可以发现MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor和InitializingBean,那么肯定是直接去找对应的override方法了。

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

2、其实最后使用的是ClassPathMapperScanner对basePackage 进行了扫描,这次先看一下ClassPathMapperScanner类的继承体系

发现,它继承了ClassPathBeanDefinitionScanner,如果经常看源码,应该知道这个类可以根据包名进行扫描并得到符合条件的Bean定义-BeanDefinition,这里进入它的scan方法

/**
	 * Perform a scan within the specified base packages.
	 * @param basePackages the packages to check for annotated classes
	 * @return number of beans registered
	 */
	public int scan(String... basePackages) {
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

		doScan(basePackages);

		// Register annotation config processors, if necessary.
		if (this.includeAnnotationConfig) {
			AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
		}

		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
	}

3、发现它在方法内部调用了doScan,由于子类ClassPathMapperScanner实现了这个方法,所以进入子类查看

@Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    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 {
      for (BeanDefinitionHolder holder : beanDefinitions) {
        GenericBeanDefinition 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
        definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
        definition.setBeanClass(MapperFactoryBean.class);

        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);
        }
      }
    }

    return beanDefinitions;
  }

4、首先它调用了父类的scan方法,获取了basePackage下的所有BeanDefinition,即我们自己的mapper或dao代码。
接下来就是关键的地方了,MapperFactoryBean是一个实现了FactoryBean,并且继承了SqlSessionDaoSupport的类,FactoryBean是Spring中提供的Bean工厂类,可以根据override T getObject() 和 Class<?> getObjectType();  生成bean实例;
而SqlSessionDaoSupport中有sqlSession这个属性,
而且还有一个public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) 方法,这个方法是一切的关键,上一下源码:

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  /**
   * Sets the mapper interface of the MyBatis mapper
   *
   * @param mapperInterface class of the interface
   */
  public void setMapperInterface(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  /**
   * If addToConfig is false the mapper will not be added to MyBatis. This means
   * it must have been included in mybatis-config.xml.
   * <p>
   * If it is true, the mapper will be added to MyBatis in the case it is not already
   * registered.
   * <p>
   * By default addToCofig is true.
   *
   * @param addToConfig
   */
  public void setAddToConfig(boolean addToConfig) {
    this.addToConfig = addToConfig;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Throwable t) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", t);
        throw new IllegalArgumentException(t);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

  /**
   * {@inheritDoc}
   */
  public Class<T> getObjectType() {
    return this.mapperInterface;
  }

  /**
   * {@inheritDoc}
   */
  public boolean isSingleton() {
    return true;
  }

}
public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSession sqlSession;

  private boolean externalSqlSession;

  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }

  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSession = sqlSessionTemplate;
    this.externalSqlSession = true;
  }

  /**
   * Users should use this method to get a SqlSession to call its statement methods
   * This is SqlSession is managed by spring. Users should not commit/rollback/close it
   * because it will be automatically done.
   *
   * @return Spring managed thread safe SqlSession
   */
  public SqlSession getSqlSession() {
    return this.sqlSession;
  }

  /**
   * {@inheritDoc}
   */
  protected void checkDaoConfig() {
    notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
  }

}

5、细节方面不再赘述,网上看见过一些说法,在mybatis-spring的某个版本前,setSqlSessionTemplate()方法上是有@Autowired注解的,那显而易见会在Bean初始化的时候注入对应的SqlSessionFactory实例,但1.2.2版本的源码中没有,那究竟是怎么回事呢?

重新审视了一边doScan的方法,发现了这段代码:

if (!explicitFactoryUsed) {
          if (logger.isDebugEnabled()) {
            logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
          }
          definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        }

可以结合上面的源码发现,当注入了SqlSessionFactoryBeanName或SqlSessionFactory的时候,explicitFactoryUsed变量会被设置为true;在没有设置的情况下,会执行

definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

原来关键原因在这里,之前一直没搞懂AutowireMode的作用,经过一番搜索才搞懂缘由。

AutowireMode一共有下面几个枚举值:

/**
	 * Constant that indicates no autowiring at all.
	 * @see #setAutowireMode
	 */
	public static final int AUTOWIRE_NO = AutowireCapableBeanFactory.AUTOWIRE_NO;

	/**
	 * Constant that indicates autowiring bean properties by name.
	 * @see #setAutowireMode
	 */
	public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;

	/**
	 * Constant that indicates autowiring bean properties by type.
	 * @see #setAutowireMode
	 */
	public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;

	/**
	 * Constant that indicates autowiring a constructor.
	 * @see #setAutowireMode
	 */
	public static final int AUTOWIRE_CONSTRUCTOR = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR;

同时在 AbstractBeanDefinition中 autowireMode的默认值是:

private int autowireMode = AUTOWIRE_NO;

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

先介绍一下这几种模式,在Bean注入时候的区别,援引:https://blog.csdn.net/qq_27409289/article/details/100753656

spring注入方式有两种: 1 通过set方法  2 通过构造函数(如果有多个构造函数会选择参数多的构造方法)

自动装配技术(手动装配):

@Resource: 默认是通过name来查找注入值,如果不存在就报错

@Autowired 通过类型查找(类型),然后再通过name

以上两种通过反射,然后设置值

AutowireMode(自动装配模型):在spring中有四种模式分别是: 

1 autowire_no(0): 默认装配模式, 目前非xml配置都是使用这种方式,然后程序员使用注解手动注入

2 autowire_name: 通过set方法,并且 set方法的名称需要和bean的name一致     byName

3 autowire_type: 通过set方法,并且再根据bean的类型,注入属性,是通过类型配置  byType

4 autowire_construcor: 通过构造器注入

下面详细讲解

/**

* Constant that indicates no autowiring at all.

* @see #setAutowireMode

* 默认的装配模式: 这种方式bean无法注入 只有个通过其他方式才能注入,比如人为set,加@Resource或@Autoried

*/

1 public static final int AUTOWIRE_NO = AutowireCapableBeanFactory.AUTOWIRE_NO;

代码如下:

1 首先自定义一个bean注册器,用来设置bean属性的注入方式
public class MyImportBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		GenericBeanDefinition son = 
        (GenericBeanDefinition)registry.getBeanDefinition("son");
         //设置装配模型
		//设置auto类型: AUTO_NO 默认是程序要经常加的 @Autowried或者@Resource
        //设置Son这个类的属性注入方式为默认类型
		son.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_NO);
		registry.registerBeanDefinition("son",son);
 
	}
}
 
2 定义Son这个类
public class Son {
 
	//不做任何处理,这个时候很明显Son对象里面student是空的
	private  Student student;
 
	public Student getStudent() {
		return student;
	}
 
	public void setStudent(Student student) {
		this.student = student;
	}
}
 
3 测试类:
public class Test {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext applicationContext = new 
        AnnotationConfigApplicationContext();
		applicationContext.register(MyBean.class);
		applicationContext.register(Appconfig.class);
		applicationContext.refresh();
 
		/*  1 不加 @Autowired或者@Resource 不做特殊处理
		 *   下面这对象会为空
		 *   2 写个类实现ImportBeanDenitionRegester, 设置Son属性的注入方式 
            MyImportBeanDefinitionRegister
		 *
		 *
		 * */
		System.out.println("==="+applicationContext.getBean(Son.class).getStudent());
 
	}
}
 

这个时候很显然结果是null,如果你想student的值不为空,那么你可以在 student 上面加注解@Autowired或者@Resource

所以说默认类型是spring是不会帮你做属性值注入的,目前程序员编写的bean都是这个模式,所以我们都是加@Autowired或者@Resource

下面的例子测试可以使用上面的代码测试,这里就不重复写了

/**
 * Constant that indicates autowiring bean properties by name.
 * @see #setAutowireMode
 * 这种是通过属性的名字来注入,不过需要提供set方法,如果没有提供set方法也是空
 * 在student 上面可以不加注解 @Autowried或者@Resource
 * setAAA(Student student) ,set方法名 AAA只能是和类Student名相同,否则无法注入,

* 原理: spring 根据AAA区spring容器中查找是否有名称为AAA的类,如果有就通过setAAA方法注入进入,否则就为空

* 和属性名无关

*  通过set方法注入

*  byName 通过名字
 */
2 public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;

/**
 * Constant that indicates autowiring bean properties by type.
 * @see #setAutowireMode
 *  通过属性类型来注入值: 同时也是通过set方法来赋值的,如果没有提供set方法也是空
 * 在student 上面可以不加注解 @Autowried或者@Resource
 * setAAA(Student student) 只需要是Student类型即可,set方法名可以随便取

*  byType 通过类型注入
 */
3 public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;

/**
 * Constant that indicates autowiring a constructor.
 * @see #setAutowireMode
 * 通过构造器注入:如果没有通过@Autowried或者@Resource注解的话,Spring只能通过在Son中的构造器中才能给student赋      上值
 */
4 public static final int AUTOWIRE_CONSTRUCTOR = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR;

所以说呢spring中的AutowireMode(自动装配模型)并不是我们说的自动状态技术,平时说的自动转配技术要么是

byType和byName,。

综合上面讲的自动状态模型只能通过set方法和构造方法来注入值,但是@Resource,@Autowired的不是通过set方法来注入值的

这也是他们之间的区别。

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

看到这里,就应该知道一切的一切,SqlSessionFactory的注入就是因为设置了
AbstractBeanDefinition.AUTOWIRE_BY_TYPE
在Bean初始化的过程中,捕获到了 MapperFactoryBean的父类SqlSessionDaoSupport中的public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) 方法,从而进行了SqlSessionFactory的注入。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值