likes
comments
collection
share

Mybatis核心原理分析

作者站长头像
站长
· 阅读数 23

mybatis核心原理分析

Mybatis核心原理分析

一:Spring+Mybatis的demo

在正式的讲Spring之前要先讲mybatis是如何扩展到spring中的,这样更有利于我们理解spring的一些机制。

首先我们在数据库中创建一个表:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for member
-- ----------------------------
DROP TABLE IF EXISTS `member`;
CREATE TABLE `member`  (
  `id` int(11) NOT NULL,
  `desc` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

然后在自己创建的模块引入对应的资源:

dependencies {
  testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
  testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'

  //添加Spring-context核心依赖包
  compile(project(":spring-context"))
  //添加数据源依赖jdbc
  compile(project(":spring-jdbc"))
  //添加mybatis依赖
  implementation group: 'org.mybatis', name: 'mybatis', version: '3.5.5'
  //日志
  implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.21'
  implementation group: 'ch.qos.logback', name: 'logback-core', version: '1.1.7'
  testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.7'
  //lombok
  compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.12'
  //mybatis和spring的插件
  implementation group: 'org.mybatis', name: 'mybatis-spring', version: '2.0.5'
}

添加相对应的mybatisConfig配置类,当然我们也可以使用xml的形式来进行配置,但是基于现在大部分的配置都是使用配置类来做我们也是使用配置类来做。

@Configuration
public class mybatisConfig {

	@Bean
	public SqlSessionFactory sqlSessionFactory() throws Exception {
		SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
		sqlSessionFactoryBean.setDataSource(dataSource());
		return sqlSessionFactoryBean.getObject();
	}

	@Bean
	public DataSource dataSource() {
		DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
		driverManagerDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
		driverManagerDataSource.setUsername("root");
		driverManagerDataSource.setPassword("LKP");
		driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/spring_boot?useSSL=true&useUnicode=true&characterEncoding=UTF-8");
		return driverManagerDataSource;
	}

}

配置类添加完成后添加dao

public interface IndexDao {
  //测试是否能连接数据库
	@Select("select * from member")
	public List<Map<String, Object>> list();
	//错误数据
	@Select("select * from aaa")
	public List<Map<String, Object>> list2();
}

ok下面我们来看mybatis是如何获取数据源并且进行对数据库的操作的。

  1. 我们来介绍一下参与到的类:application、service、dao、config
public class TestIndex {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext();
		ioc.register(Application.class);
		ioc.refresh();
		MemberService bean = ioc.getBean(MemberService.class);
		bean.list().forEach(System.out::println);
	}
}
@Component
@Slf4j(topic = "springStudent:")
public class MemberService {

	//从spring容器当中拿出来自动注入(对象不是接口)
	@Autowired
	MemberDao memberDao;

	public List<Map<String, Object>> list() {
		List<Map<String, Object>> list = memberDao.list();
		return list;
	}
}
public interface MemberDao {
	@Select("select * from member")
	public List<Map<String, Object>> list();
}
@Configuration
@MapperScan("com.lukp.test.dao")
public class mybatisConfig {
	@Bean
	public SqlSessionFactory sqlSessionFactory() throws Exception {
		SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
		sqlSessionFactoryBean.setDataSource(dataSource());
		return sqlSessionFactoryBean.getObject();
	}
	@Bean
	public DataSource dataSource() {
		DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
		driverManagerDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
		driverManagerDataSource.setUsername("root");
		driverManagerDataSource.setPassword("LKP");
		driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/spring_student?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai");
		return driverManagerDataSource;
	}
}
  1. 我们现在以debug的形式启动,然后观察在service中是如何调用list()方法时,是如何拿到memberDao对象的。

Mybatis核心原理分析

  1. 由上图我们看到mapper的生成是靠mapperinterface来完成的,并且获取到的对象是MemberDao,接口->实现类,这是不是就是JDK的动态代理实现的!
  2. 好的我们现在再思考一个问题:mapperFactroyBean使用JDK提供的动态代理来完成对于代理的对象的生成这毋庸置疑,但是这个生成代理对象的工作是spring做的嘛?显然不是为什么:想想看市面的中间件多的数都数不清,如果spring要一个一个去实现这些中间件的代理对象生成,那这是不可能的!所以得出第一个结论:mapper对象是mapperFactroyBean使用JDK的动态代理生成的。
  3. 那么现在问题由来了,mapperFactroyBean是如何生成可以被Spring使用的Bean呢,其实也就是生成可以被放入Spring容器中的代理对象呢?
  4. 我们可以注意一下我的mapperFactroyBean中间的单词:Factroy,没错就是我们的工厂模式!
  5. 其实只要你是一个中间件来实现我Spring提供某些生成对应代理对象的一些规则即可,但是我们的生成规则如何呢?我们可以debug一下源码。

二:通过 debug Mybatis是如何工作的?

在说这个话题之前我们现在想一下子,如果不适用spring来生成对应的mapper接口的代理对象,或者说咱们不借助spring来生成代理对象,再通过代理对象去调用接口中的方法呢?ok,我们来看这块代码:

public class TestIndex {
	public static void main(String[] args) {
		/*AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext();
		ioc.register(Application.class);
		ioc.refresh();
		MemberService bean = ioc.getBean(MemberService.class);
		bean.list().forEach(System.out::println);*/

		/**
		 * 不使用spring来获取mybatis的代理对象,我们试试光用mybatis来完成代理对象的生成
		 */
		DataSource dataSource = null;
		TransactionFactory factory = null;
		Environment environment = null;
		Configuration configuration = new Configuration(environment);
		configuration.addMapper(MemberDao.class);
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
		SqlSession sqlSession = sqlSessionFactory.openSession();
    ==========================================分隔符================================================
		MemberDao mapper = sqlSession.getMapper(MemberDao.class);
		mapper.list();
	}
}
  1. 注意我们这里是按照mybatis光网上的方式来获取Dao接口并获取接口实现类的方法,完成功能使用的目的,可以看上面我们的代码中就是示例。
  2. 问题来了,这里的MemberDao.class接口的代理对象的获取是分隔符下面还是上面,可以通过查看源码来解释:
  3. 我们可以点开分隔符下的getMapper()方法,进去看看到底是咋获取代理对象的。
  4. SqlSession接口:
/**
* Retrieves a mapper.
* @param <T> the mapper type
* @param type Mapper interface class
* @return a mapper bound to this SqlSession
*/
<T> T getMapper(Class<T> type);
  1. 可以看到它下面有三个子实现类:DefaultSqlSession、SqlSessionManager、SqlSessionTemplate这里我们就进到DefaultSqlSession中
@Override
public <T> T getMapper(Class<T> type) {
  return configuration.getMapper(type, this);
}
  1. 我们再进去到Configuration.java中
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
	return mapperRegistry.getMapper(type, sqlSession);
}
  1. 我们再进去到MapperRegistry.java中,在当前的getMapper()方法中我们可以看到一行很熟悉的代码,
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //1.获取mapper工厂的代理类型
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        //2.判断如果获取不到当前代理工厂的类型报错
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        //使用newInstance()方法来获取到当前类型代理工厂生成出来的代理mapper
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
  1. 我们进到代理方法newInstance(sqlSession)看看,它到底是如何代理的。
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
	return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
	final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
	return newInstance(mapperProxy);
}
  1. 我们可以看到首先是调用了下面的newInstance(SqlSession sqlSession)方法,然后再将sqlSession、mapperInterface、methodCache三个参数new了一个MapperProxy对象出来,含义就是我将当前的mapper接口生成了一个MapperProxy对象,然后再调用上面的那个netInstance(MapperProxy mapperProxy)方法,这段代码:
(T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  1. 我们很熟悉就是调用了jdk提供Proxy类下的动态代理方法。这时我们再简化一下子这个方法,回归本真!
(T) Proxy.newProxyInstance(类加载器, 接口数组, invocationHandler);
  1. 牛不牛逼就是这样子没错,总结一下:mybatis也是实现了jdk的动态代理来实现的mapper接口的代理对象的生成!

三:没了Spring的mybatis有咋样的问题?

从上面我们观察到mybatis生成mapperProxyObject实现,但是我们要思考一下,为什么mybatis要依赖于spring来生成自己的代理对象,难道仅仅只是因为要被mapperProxyObjcet放入到Spring的容器当中这么简单嘛?

@Slf4j
public class CustomSqlSession {

	/**
	 * getMapper方法的功能:
	 * 1、代理对象:符合要求的对象
	 * 2、功能:能根据不同的子实现类来完成对应的功能
	 *
	 * @param clazz
	 * @return
	 */
	public Object getMapper(Class clazz) {
		return Proxy.newProxyInstance(
				CustomSqlSession.class.getClassLoader(),
				new Class[]{clazz},
				new CustomInvocationHandler()
		);
	}

	class CustomInvocationHandler implements InvocationHandler {

		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			if (method.isAnnotationPresent(Select.class)) {
				Select select = method.getAnnotation(Select.class);
				String[] value = select.value();
				log.info("sqlRes:{}", value[0]);
				log.info("jdbc connect execute...");
				log.info("execute query...");
			}
			return null;
		}

	}
}
@Component
@Slf4j(topic = "springStudent:")
public class MemberService {

	//从spring容器当中拿出来自动注入(对象不是接口)
	@Autowired
	MemberDao memberDao;

	public List<Map<String, Object>> list() {
		List<Map<String, Object>> list = memberDao.list();
		return list;
	}
}
public interface MemberDao {
	@Select("select * from member;")
	public List<Map<String, Object>> list();

	@Select("select * from member where id = 3;")
	public List<Map<String, Object>> listByIdIs3();
}
public class TestIndex {
	public static void main(String[] args) {
		CustomSqlSession customSqlSession = new CustomSqlSession();
		MemberDao memberDao = (MemberDao) customSqlSession.getMapper(MemberDao.class);
		memberDao.list();
		memberDao.listByIdIs3();
	}
}

可以看到我们上面是没有使用mybatis提供的代理生成,是我们自己创建了一个sqlSessionFactory来充当创建代理对象的角色。那么按照CustomSqlSession中invoke()方法的执行逻辑,我们是不会真的去执行到 @Select注解中的sql语句到数据库中进行查询的,但是我们可以看到的是,日志确实被打印出来了说明我们自己创建的SessionFactory来扫扫描接口并执行接口上的注解中的方法是生效的,其实就是没有真的去连接数据库完成查询动作嘛,这个也很简单我们可以自己做的,就是加个数据库连接即可。

四:研究mybatis是如何把代理对象注入到spring的容器中的?

首先我们在上面说过了,类似于mybatis等中间件,都是自己实现spring提供的一些规范,然后中间件们实现这些个规范,自己生成代理对象。但是最后一个问题,那生成的代理对象是如何放入到spring容器中的呢?

  1. 在这里我们先列举一下有哪些手段可以将代理对象注入到spring容器中。
  • xml配置:其实就是在xml文件中配置一个mapperBean啦。
    • 不可用:原因是因为,你使用xml来完成mapperInterface对象的注入,本质上来说是将整个类对象交给spring,让spring来生成mapperInterface代理对象,那么其实这里就不是mybatis自己来生成代理对象了,而且由于是mybatis的bean所以要做一些自定义化的操作(比如:连接数据库,解析@Select注解生成sql语句去数据库里查询),必须由于mybatis自己来做,完成给spring,那spring又不知道你mybatis要干啥子,我spring只是一个放bean的容器罢了,所以不行。
  • 注解扫描:在一个mapperInterface的实现类上打上一个注解标签,通过spring的注解扫描器来将符合条件类作为bean注入到容器中。
    • 不可用:这个问题的原因和xml配置的形式很像,就像我上面举的例子,其实就是一个很好的案例,mybatis中有一个注解@Select,那它的注解实现,spring肯定是不知道的,这个注解只能是交给mybatis自己来解析,所以也是不可行的。
  • @Bean:将一个mapperInterface打上一个@Bean的注解,其实也是通过spring的注解扫描来完成,将指定的mapperInterface实现类注入到spring容器中。
    • 可用但是不推荐。原因:因为你要一个一个的将mapper的去注入到spring容器中,相当于你每个mapper接口都要在配置类中加上一个@Bean注解然后改名字,那不得累死你。
  1. ok,在上面我们介绍了三种方式来实现将mybatis生成的代理对象放入到spring容器中的方法,但其实都是不太可行的,要么根本不行,要么是有不少的缺陷。那有没有一种较好的办法来实现我们的需求呢?就是又能满足mybatis的自定义化和兼容spring并且注入到spring容器中。
  2. 工厂模式!
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  public MapperFactoryBean() {
    // intentionally empty
  }

  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  /**
   * {@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 (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

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

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

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

  // ------------- mutators --------------

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

  /**
   * Return the mapper interface of the MyBatis mapper
   *
   * @return class of the interface
   */
  public Class<T> getMapperInterface() {
    return 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 addToConfig is true.
   *
   * @param addToConfig
   *          a flag that whether add mapper to MyBatis or not
   */
  public void setAddToConfig(boolean addToConfig) {
    this.addToConfig = addToConfig;
  }

  /**
   * Return the flag for addition into MyBatis config.
   *
   * @return true if the mapper will be added to MyBatis in the case it is not already registered.
   */
  public boolean isAddToConfig() {
    return addToConfig;
  }
}
  1. 上述代码是按照mybatis实现spring提供的FactoryBean接口来实现工厂代理对象生成的逻辑。那么我们简化一下:
public class CustomFactoryBean implements FactoryBean {
	Class mapperInterface;

	public void setMapperInterface(Class mapperInterface) {
		this.mapperInterface = mapperInterface;
	}

	@Override
	public Object getObject() throws Exception {
		CustomSqlSession customSqlSession = new CustomSqlSession();
		//返回mybatis生成出来的代理对象
		return customSqlSession.getMapper(mapperInterface);
	}

	@Override
	public Class<?> getObjectType() {
		return mapperInterface;
	}

}
  1. 上述代码是我们自己实现FactoryBean来完成mybatis代理对象生成的逻辑,虽然很简化,但是也能看的出来满足了我们需要的两点:自定义化,加入Spring容器规范。
  2. 那在spring当中有没有什么一个已经写好的东西来完成对实现不同的自定义操作的手段呢?有,beanDefinition。

五:关于beanDefinition

关于beanDefinition首先介绍三篇文章:

  1. blog.csdn.net/java_lyvee/…
  2. blog.csdn.net/java_lyvee/…
  3. blog.csdn.net/java_lyvee/…

我们简单的介绍一下什么beanDefinition,一句话他是一个spring bean的建模对象(bean不一定是对象,但是对象一定是bean)。

Mybatis核心原理分析

  1. 我们说一个class对象是一个基础的bean,他只是包含了该对象最基础的信息,但是该对象是存在于spring容器当中的,所以它要满足很多spring容器中默认的规范,比如:是否是单例的。对于所有bean这个肯定是要有的。而beanDefinition就是做这个事情的。
  2. 我们来看普通的java对象的生成:

Mybatis核心原理分析

  1. 再看Bean的生成:

Mybatis核心原理分析

  1. 上述描述了spring bean的简易生成的图,下面我们再用文字来描述一下整体的过程:
  • 举个例子:我们在spring项目中会有一个主启动类,该类是在注解上有一个@compoentScan()注解,在该注解中会填入spring需要扫描的包。
  • 那当spring去扫描该包下,有spring原生注解的类,比如说@Compent注解,扫描到后,会将该类变成beanDefinition。
  • beanDefinition是一个接口,在他下面有很多子实现类。
  • 我们先来看一下beanDefinition的结构:

Mybatis核心原理分析

  • 其实我们可以从中看到这些就是你想要将bean注入到spring的"基本模板"。
  • 再解释一下,我们将一个类变成为一个bean,需要给他打上一些spring原生的注解,当spring扫描的时候,就会将该类变为beanDefinition对象(当然他是一个接口具体的实现不是他),因为我们可以看到beanDefinition中有很多我们耳熟能详的方法,像什么isLazy(),描述一个bean是否是懒加载的bean,那么这个是不是一个spring的基本信息,任何的bean是不是都有这个属性,那现在再看别的一些方法,其实就是对照着bean的其实固有属性。
  • 最后只有符合要求的bean,才能被成为spring的bean,因为这样spring才能管理这些bean。
  1. ok上面我们说的是spring先是把项目中需要被注入到spring容器的bean的bean信息获取到,过程为 java->class->beanDefinition,到最后变为一个spring规范的bean信息,spring会将这个bean信息放入到一个叫做beanDefinitionMap的集合中。
  2. 最后再去读取beanDefinitionMap中的信息,完成bean的加载工作,或者说"bean的实例化"。
  3. 但是这里还有一个点就是我们常说的bean的扩展点,各种的postProcessor,在看懂上述的机制之后,我们明白其实所有的bean信息是存放在beanDefinitionMap中,那么我们想要对一个bean进行扩展是不是只需要把这个map中对应的bean拿出来,然后"搞一搞"再把它放回到Map中,最后通过spring去加载或者说是去示例这个beanDefinitonMap中的bean信息就完成对了对一个或者多个或者是你指定的bean的扩展话操作。

Mybatis核心原理分析

  1. 下面我们来证明一下这个过程:

六:证明spring bean的可扩展机制是通过beanDefinition+beanDefinitionMap来实现的

@Component
@ComponentScan("com.lukp.test.beanPostProcessor")
/*@ImportResource("classpath:spring.xml")*/
public class Application {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(Application.class);
		Y bean = annotationConfigApplicationContext.getBean(Y.class);
		String simpleName = bean.getClass().getSimpleName();
		if (StringUtils.hasText(simpleName)) {
			System.out.println("类名为:"+simpleName);
		}
		System.out.println("没有获取到类信息");
	}
}
public class X {
}


@Component
public class Y {
}

public class CustomerBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		//1.从beanDefinitionMap中获取到Y
		GenericBeanDefinition y = (GenericBeanDefinition) beanFactory.getBeanDefinition("y");
		//2.中途改变Y的bean信息
		y.setBeanClass(X.class);
	}
}
  1. 在beanPostProcessor类中我们实现了BeanFactoryPostProceesor接口,在目前阶段我们先理解为beanFactory就是Map或者说是Bean的信息。
  2. 再看到下面postProcessorBeanFactory()方法中的逻辑。
  3. 到最后,我们会发现启动spring的时候y是拿不到的。因为y被修改为了X.class。
  4. 然后我们再提一点,扩展到mybatis中,其实他是实现了ImportBeanDefinitionRegister接口,该接口提供了注册bean到spring容器的能力。
public class CustomerBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		ImportBeanDefinitionRegistrar.super.registerBeanDefinitions(importingClassMetadata, registry);
	}
}
  1. 但是在实现了BeanFactoryPostProcessor接口中是做不到的,原因是该接口下根本没有registrar的方法。

Mybatis核心原理分析

  1. 咦?这是为什么呢?为什么在BeanFactoryProcessor接口没有这个能力?
  • 原因是beanFactoryPostProcessor只提供拿Map中的bean的功能,但是他不具备放入bean定义信息的功能。
  • spring的想法是,加入一个bean应该在更早的地方进行,于是就提供了ImportBeanDefinitionRegistrar接口。
  • 如上述的代码所示,registerBeanDefinitions()该方法就是哪来注入bean信息,或者叫bean定义的。
@Override
	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {

		Assert.hasText(beanName, "Bean name must not be empty");
		Assert.notNull(beanDefinition, "BeanDefinition must not be null");

		if (beanDefinition instanceof AbstractBeanDefinition) {
			try {
				((AbstractBeanDefinition) beanDefinition).validate();
			}
			catch (BeanDefinitionValidationException ex) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Validation of bean definition failed", ex);
			}
		}

		BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
		if (existingDefinition != null) {
			if (!isAllowBeanDefinitionOverriding()) {
				throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
			}
			else if (existingDefinition.getRole() < beanDefinition.getRole()) {
				// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
				if (logger.isInfoEnabled()) {
					logger.info("Overriding user-defined bean definition for bean '" + beanName +
							"' with a framework-generated bean definition: replacing [" +
							existingDefinition + "] with [" + beanDefinition + "]");
				}
			}
			else if (!beanDefinition.equals(existingDefinition)) {
				if (logger.isDebugEnabled()) {
					logger.debug("Overriding bean definition for bean '" + beanName +
							"' with a different definition: replacing [" + existingDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			else {
				if (logger.isTraceEnabled()) {
					logger.trace("Overriding bean definition for bean '" + beanName +
							"' with an equivalent definition: replacing [" + existingDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		else {
			if (hasBeanCreationStarted()) {
				// Cannot modify startup-time collection elements anymore (for stable iteration)
				synchronized (this.beanDefinitionMap) {
					this.beanDefinitionMap.put(beanName, beanDefinition);
					List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
					updatedDefinitions.addAll(this.beanDefinitionNames);
					updatedDefinitions.add(beanName);
					this.beanDefinitionNames = updatedDefinitions;
					removeManualSingletonName(beanName);
				}
			}
			else {
				// Still in startup registration phase
				this.beanDefinitionMap.put(beanName, beanDefinition);
				this.beanDefinitionNames.add(beanName);
				removeManualSingletonName(beanName);
			}
			this.frozenBeanDefinitionNames = null;
		}

		if (existingDefinition != null || containsSingleton(beanName)) {
			resetBeanDefinition(beanName);
		}
		else if (isConfigurationFrozen()) {
			clearByTypeCache();
		}
	}
  1. 下面来完整的描述一遍程序员是如何通过spring提供的ImportBeanDefinitionRegistrar接口来完成自定义话的beanDefinition信息的注入。
public class CustomerBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		//构建X.class
		BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(CustomFactoryBean.class);
		//获取X.class变为beanDefinition的对象
		AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
		//获取beanDefinition中的信息
		MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
		//将我们需要DI给beanDefinition的注入类放进到当前的beanDefinition中
		propertyValues.add("mapperInterface", MemberDao.class);

		registry.registerBeanDefinition("x", beanDefinition);
	}
}
  • 首先要使用BeanDefinitionBuilder下的genericBeanDefinition()方法来构建对应程序员想要构建的类。也就是生成beanDefinition实例。
  • 然后再获取到该beanDefinition实例,并且调用getPropertyValues()方法获取到beanDefinition中的信息。
  • 这个getPropertyValues()方法的返回类型是MutablePropertyValues,对于他我们可以简单理解为一个set结构的beanDefinition信息的集合。
  • 将我们想要放入到当前BeanDefinition的DI对象,通过add()方法加入到当前的beanDefinition中。
  • 最后spring在获取BeanDefinitionMap中的beanDefinition中的我们自定义话的beanDefinition信息后会将他实例化,并且放入到Spring容器中。
  1. 其实这里的话还一个问题就是spring不是还会扫描你指定包下的类,并将他们注入到spring容器中吗?是的,确实是这样,但是这里我们就稍微模拟一下就行了。
public class CustomerBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		ArrayList<Class<?>> classes = new ArrayList<>();
		classes.add(MemberDao.class);
		classes.add(MemberDao02.class);
		for (Class<?> aClass : classes) {
			BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(CustomFactoryBean.class);
			AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
			MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
			propertyValues.add("mapperInterface", aClass);
			//这里再模拟一下如果当前的bean你没有传入name的话spring是有一个默认生成beanName的规则的,其实就是我们常说的类名首字母小写
			String lastName = aClass.getSimpleName().substring(1);
			String firstName = aClass.getSimpleName().substring(0, 1).toLowerCase();
			String beanName = firstName + lastName;
			registry.registerBeanDefinition(beanName, beanDefinition);
		}
	}
}
  1. 然后我们现在模拟一下扫描指定包下信息。
@Import(CustomerBeanDefinitionRegister.class)//在这里写这个东西相当于是继承关系,所以application类中只需要写当前注解即可
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomScan {
	String value() default "";
}
//该注解直接导入了我们要实现的CustomerBeanDefinitionRegister类,完成bean的自定义注入
@Component
@CustomScan("snadioasnoi")
@Slf4j
public class Application {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(Application.class);
		Y bean = annotationConfigApplicationContext.getBean(Y.class);
		String simpleName = bean.getClass().getSimpleName();
		if (StringUtils.hasText(simpleName)) {
			System.out.println("类名为:" + simpleName);
		}
		System.out.println("没有获取到类信息");
	}
}
public class CustomerBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		MergedAnnotations annotations = importingClassMetadata.getAnnotations();
		System.out.println("注解中的内容:" + annotations);

		ArrayList<Class<?>> classes = new ArrayList<>();
		classes.add(MemberDao.class);
		classes.add(MemberDao02.class);
		for (Class<?> aClass : classes) {
			BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(CustomFactoryBean.class);
			AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
			MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
			propertyValues.add("mapperInterface", aClass);
			//这里再模拟一下如果当前的bean你没有传入name的话spring是有一个默认生成beanName的规则的,其实就是我们常说的类名首字母小写
			String lastName = aClass.getSimpleName().substring(1);
			String firstName = aClass.getSimpleName().substring(0, 1).toLowerCase();
			String beanName = firstName + lastName;
			registry.registerBeanDefinition(beanName, beanDefinition);
		}
	}
}

在第4行获取到了我们自定义注解中的value信息。

Mybatis核心原理分析

这样spring就可以通过我们指定的包下的资源去给自定义生成的beanDefinition完成自定义注入了,因为给他指定了注入时需要的类。

  1. 其实你看mybatis中也是这么做的:MapperScan注解

Mybatis核心原理分析

  1. 再看看Mybatis中对于自定义BeanDefinition的设置:在MapperSacannerConfigurer中
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    scanner.registerFilters();
    //这段方法开始扫描
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
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);
	}
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}
  1. 下面我们来解析一下spring是如何把myabtis生成的代理对象放入到spring容器中的完整流程的:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    scanner.registerFilters();
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

这里需要注意一点,上述的扫描器是ClassPathMapperScanner对象,但是这里我们直接点scanner.scan()方法的话进去的是ClassPathMapperScanner对象的父类下的scan()方法,其实这个方法的主要目的就是扫描Dao包的。下面是代码:

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);
	}
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}

到这里包的扫描完成了,下面要开始调用子类的doScan()方法来完成beanPostProcessor()工作。

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 {
      //该方法完成对应mybatisBean的beanDefinition生成工作
      processBeanDefinitions(beanDefinitions);
    }
    return beanDefinitions;
  }
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
          + "' mapperInterface");

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      definition.setBeanClass(this.mapperFactoryBeanClass);

      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) {
        LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
      definition.setLazyInit(lazyInitialization);
    }
  }
  1. ok,上述spring是如何把myabtis生成的代理对象放入到spring容器中的完整流程介绍完毕,下面我们思考一个问题,在我们的CustomFactoryBean中是提供了一个setMapperInterface()方法来放入Interface的。
public class CustomFactoryBean implements FactoryBean<Class<?>> {
	Class<?> mapperInterface;

	public void setMapperInterface(Class<?> mapperInterface) {
		this.mapperInterface = mapperInterface;
	}

	@Override
	public Class<?> getObject() {
		CustomSqlSession customSqlSession = new CustomSqlSession();
		//返回mybatis生成出来的代理对象
		return (Class<?>) customSqlSession.getMapper(mapperInterface);
	}

	@Override
	public Class<?> getObjectType() {
		return mapperInterface;
	}

}

对应的在CustomerBeanDefinitionRegister中获取到beanDefinition中的PropertyValues信息的时候,其实是靠set方法来填充这个PropertyValues值的,如果你没有set方法也就意味着你没有办法把你想要的Interface放入到Factroy中。

public class CustomerBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		MergedAnnotations annotations = importingClassMetadata.getAnnotations();
		System.out.println("注解中的内容:" + annotations);

		ArrayList<Class<?>> classes = new ArrayList<>();
		classes.add(MemberDao.class);
		classes.add(MemberDao02.class);
		for (Class<?> aClass : classes) {
			BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(CustomFactoryBean.class);
			AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();

            
            //就是在这一行,在PropertyValues时,你没有set方法如何将interface注入呢???
			MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();

            
			propertyValues.add("mapperInterface", aClass);
			//这里再模拟一下如果当前的bean你没有传入name的话spring是有一个默认生成beanName的规则的,其实就是我们常说的类名首字母小写
			String lastName = aClass.getSimpleName().substring(1);
			String firstName = aClass.getSimpleName().substring(0, 1).toLowerCase();
			String beanName = firstName + lastName;
			registry.registerBeanDefinition(beanName, beanDefinition);
		}
	}
}
  1. 那么这个问题是显而易见的,那产生疑问?除了set方法还有别的办法可以搬到set方法的作用吗?有的就是构造器!

针对这个事情我们可以看到在spring官方的对于DI和IOC的描述:

This chapter covers the Spring Framework implementation of the Inversion of Control (IoC) principle. IoC is also known as dependency injection (DI). It is a process whereby objects define their dependencies (that is, the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes or a mechanism such as the Service Locator pattern.

译文:

本章涵盖了Spring框架对控制反转(Inversion of Control, IoC)原则的实现。IoC也称为依赖注入(dependency injection, DI) 。在这个过程中,对象仅通过构造函数参数、工厂方法的参数或在对象实例被构造或从工厂方法返回后设置的属性来定义它们的依赖项(也就是它们处理的其他对象) 。然后容器在创建bean时注入这些依赖项。这个过程基本上是bean本身的逆过程(因此得名,控制反转),通过使用类的直接构造或类似于服务定位器模式的机制来控制其依赖项的实例化或位置。

public class CustomFactoryBean implements FactoryBean<Class<?>> {
	Class<?> mapperInterface;

	/*public void setMapperInterface(Class<?> mapperInterface) {
		this.mapperInterface = mapperInterface;
	}*/

	public CustomFactoryBean() {
		System.out.println("调用了Factory的无参构造器...");
	}

	public CustomFactoryBean(Class<?> mapperInterface) {
		System.out.println("调用了Factory的有参构造器...");
		this.mapperInterface = mapperInterface;
	}

	@Override
	public Class<?> getObject() {
		CustomSqlSession customSqlSession = new CustomSqlSession();
		//返回mybatis生成出来的代理对象
		return (Class<?>) customSqlSession.getMapper(mapperInterface);
	}

	@Override
	public Class<?> getObjectType() {
		return mapperInterface;
	}

}
public class CustomerBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		MergedAnnotations annotations = importingClassMetadata.getAnnotations();
		System.out.println("注解中的内容:" + annotations);

		ArrayList<Class<?>> classes = new ArrayList<>();
		classes.add(MemberDao.class);
		classes.add(MemberDao02.class);
		for (Class<?> aClass : classes) {
			BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(CustomFactoryBean.class);
			AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
			/*
			这个是针对于使用的set方法来获取beanfinition信息
			MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
			propertyValues.add("mapperInterface", aClass);
			*/
			//使用构造器的方法
			ConstructorArgumentValues constructorArgumentValues = beanDefinition.getConstructorArgumentValues();
			constructorArgumentValues.addGenericArgumentValue(aClass);

			//这里再模拟一下如果当前的bean你没有传入name的话spring是有一个默认生成beanName的规则的,其实就是我们常说的类名首字母小写
			String lastName = aClass.getSimpleName().substring(1);
			String firstName = aClass.getSimpleName().substring(0, 1).toLowerCase();
			String beanName = firstName + lastName;
			registry.registerBeanDefinition(beanName, beanDefinition);
		}
	}
}
转载自:https://juejin.cn/post/7383029376383090725
评论
请登录