带着下面的问题进行学习:

  (1)Mybatis 框架或 Spring Framework 框架对数据层 Mapper 接口做了代理,那是做了 JDK 动态代理还是 CGLIB 代理?

  (2)Mapper 接口使用和不使用 @Mapper 注解有什么区别?

  (3)Spring Framework 框架引入 Mybatis 的 jar 包后,Spring Framework 是怎么管理的?

  (4)@MapperScan注解的作用是什么?

  在探究上面的问题前,先了解什么是 FactoryBean,FactoryBean 和 BeanFactory有什么区别?

  BeanFactory 是 Spring Framework 中的一个 Bean 工厂(AnnotationConfigApplicationContext、ClassPathXmlApplicationContext等),可以产生类,它有一个 getBean 方法可以获取到类;

  FactoryBean 是一个 Bean,受 Spring Framework 管理的一个对象,定义 Bean 有好几种方式,比如 xml 的 <bean> 标签,注解 @Bean、@Service 等;

  下面是案例:实现 FactoryBean 接口,重写方法

public class TempBean {
public void query(){
System.out.println("TempBean");
}
}
@Configuration("MyFactroyBean")
public class MyFactroyBean implements FactoryBean {
public void test() {
System.out.println("MyFactroyBean MyFactroyBeanTest");
} @Override
public Object getObject() throws Exception {
return new TempBean();
} @Override
public Class<?> getObjectType() {
return TempBean.class;
} @Override
public boolean isSingleton() {
return true;
}
}
    public static void main(String[] args) {
AnnotationConfigApplicationContext context= new AnnotationConfigApplicationContext(MyFactroyBean.class);
MyFactroyBean myFactroyBean = (MyFactroyBean) context.getBean("&MyFactroyBean");
myFactroyBean.test();
TempBean tempBean = (TempBean) context.getBean("MyFactroyBean");
tempBean.query();
}
//=========结果======

MyFactroyBean MyFactroyBeanTest
   TempBean

  如果你的类实现了 FactoryBean,那么 Spring 存在两个对象:

    一个是 getObject() 返回的对象:当前类指定的名字;
    一个是当前类:"&"+当前类指定的名字;

  当实现了 FactoryBean 后,Spring 在容器实例化过程中,会对项目中的自定义类进行实例化,当前类会被代理成一个 CGLIB 类(使用了@Configuration注解),当你获取当前类时,传递“&xx”,Spring 会进行判断,如果获取是当前类,返回代理类对象,如果是获取 getObject 返回的对象,会直接调用 getObject 方法中的 new xxx();

  下面是Spring容器初始化时,在执行一些后置处理器(ConfigurationClassPostProcessor是对项目的 ComponentScan 注解的路径、@Configuration 注解的类进行扫描解析,超级重要的一个类)过程中,对实现了 FactoryBean 接口的子类进行代理:

  下面是对实现了 FactoryBean 接口的子类的获取:先判断是不是 FactoryBean 类型,如果是加前缀“&"再进行获取;因为前面已经对子类进行了代理,并且存入了 beanDefinitionMap

  但即使上面加了前缀”&“,在后面还是会剔除掉,那怎么判断是获取当前类还是获取getObject方法中的类呢?在 Spring 容器过程中,只会对当前类(实现了 FactoryBean 的子类)进行实例化,当获取时,getSingleton 直接从缓存中获取(每个对象实例化后会放入缓存中 singletonObjects)共享对象,然后 getObjectForBeanInstance 获取实例对象返回,在获取实例对象过程中,会判断是不是 FactoryBean 类型,如果是直接将代理对象返回;

  下面判断是否是一个FactoryBean对象:

    public static boolean isFactoryDereference(@Nullable String name) {
return (name != null && name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));//FACTORY_BEAN_PREFIX=”&"
}

  如果是获取 getObject() 返回的对象,跟上面一样,但在判断是不是一个 FactoryBean 对象时,由于name没有“&”的前缀,所以走下面的流程,不会直接 return beanInstance

  FactroyBean是一个 Bean(可以当作一个业务类,比如连接数据库做一些业务操作),当一个类的依赖关系很复杂时,只需要提供一个简单的接口给外部使用时(将内部的一些关系进行封装维护好)可以使用 FactroyBean 来实现,比如 mybatis 的 SqlSessionFactoryBean 是一个 FactoryBean 实现类,里面的 getObject 方法有一个 afterPropertiesSet 方法,内部将很多依赖(Configuration 配置、TransactionFactory 事务工厂等)进行了处理封装维护到了 SqlSessionFactoryBean,外部只需要传入一个 DataSource 数据源即可,添加 MapperScan 扫描 xml 文件会自动把 xml 的信息维护到 SqlSessionFactoryBean中;

  那么 SqlSessionFactoryBean 什么时候被调用呢?可以看下idea调试的方法调用栈:

  

  从上面几张图片可以看出,当IOC容器Context进行类(Service)初始化时,发现有属性变量(Mapper),会对属性变量(Mapper)进行创建获取自动注入进去,其中会获取到Mapper封装成的一个MapperFactoryBean,执行到 SqlSessionFactoryBean的 afterPropertiesSet 方法对Mapper接口对应的xml进行解析获取;

  MapperFactoryBean 也是一个 FactoryBean,每个 Mapper 接口都会转换成一个 MapperFactoryBean,所以 doCreateBean() 创建获取时可以通过 mapperInterface(Mapper接口)获取到对应的 MapperFactoryBean,它的值是存储到 Configuration的 MapperRegistry变量中;

  那它是什么时候存储到 Configuration的 MapperRegistry变量中的呢?通过下面的图片可以看出,是在解析完xml后放入的:

  当 Mapper 接口在 MapperFactoryBean#getObject() 时,它对Mapper接口进行了代理:MapperRegistry#getMapper()

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//获得代理对象工厂:里面包含Mapper接口信息
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//代理对象实例化
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}

  下面是 MapperProxyFactory#newInstance() 的源码:从中可以看到,Mapper 接口对应的代理对象是使用了 JDK 动态代理产生的;【解决了上文的第一个问题,其实从 Mapper 是接口也可以猜出是使用 JDK 动态实现的】

  public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

  其中 MapperProxy 是一个 InvocationHandler 实现类,从前文可以得知调用 Mapper.xxx方法 会进入 MapperProxy#invoke(),最后会执行 mapperMethod.execute(sqlSession, args) 进行SQL查询返回结果;

  综上所述:每个Mapper接口转化成一个MapperFactoryBean,当调用Mapper接口方法执行JDBC操作时,Mapper 接口通过 MapperFactoryBean#getObject() 对Mapper接口进行了 JDK 动态代理。

  对于第三个问题“Spring Framework 框架引入 Mybatis 的 jar 包后,Spring Framework 是怎么管理的?”

  从前文得出每个 Mapper 接口都会转换成一个 MapperFactoryBean,而 MapperFactoryBean 继承了 SqlSessionDaoSupport(mybatis-spring-xx.jar),SqlSessionDaoSupport 继承了 DaoSupport(spring-tx.jar),DaoSupport 实现了 InitializingBean接口,所以Spring容器初始化会执行DaoSupport#afterPropertiesSet方法,会执行里面的checkDaoConfig方法,MapperFactoryBean 重写了checkDaoConfig方法,所以最后会执行MapperFactoryBean#checkDaoConfig方法:

public abstract class DaoSupport implements InitializingBean {
protected final Log logger = LogFactory.getLog(this.getClass()); public DaoSupport() {
} public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
this.checkDaoConfig(); try {
this.initDao();
} catch (Exception var2) {
throw new BeanInitializationException("Initialization of DAO failed", var2);
}
} protected abstract void checkDaoConfig() throws IllegalArgumentException; protected void initDao() throws Exception {
}
}
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  @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);//对接口进行xml解析
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
}
Configuration#addMapper
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
} MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//为每个接口配置一个MapperProxyFactory,供后续Mapper接口进行JDK代理
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
//解析类
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();//XML解析
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}

  @MapperScan 注解是导入了一个 MapperScannerRegistrar(是实现了 ImportBeanDefinitionRegistrar 接口的,作用是动态往 BeanDefinitionMap 添加 BeanDefinition),的作用是对包路径的 Mapper 接口和对应的XML配置信息进行扫描解析,将所有 Mapper 接口的class信息扫描成对应的 BeanDefition, MapperFactoryBean 类型的 BeanDefition,同时为这个BeanDefition提供一个有参构造方法,参数是 class,在后面Mapper接口进行实例化的 JDK 代理时可以根据这个 class 返回对应的代理对象;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
}
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  @Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); // this check is needed in Spring 3.1
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
} Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
} Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
} Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
} Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
} scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef")); List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));//包扫描解析
}
}

  下面是扫描解析包路径的主要逻辑:

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

  综上所述:在 Spring 容器初始化过程中,在对Mapper接口进行实例化的过程中,@MapperScan 注解主要是解析包路径将 Mapper 接口解析成 MapperFactoryBean 的 BeanDefition,这是 Mapper 接口在实例化之前做的事情,在实例化中和之后的过程中,主要利用 Spring的InitializingBean 接口的特性(每个实现了 InitializingBean 接口的子类有一个 afterPropertiesSet 方法,在实例化过程中会执行)来实现对 Mapper 接口信息的初始化,比如 sql 语句的初始化,将这些信息缓存起来放到一个 Map 中:

  上面的是 Mybatis 和 Spring Framework 结合后的初始化流程,那么单独的 Mybatis 初始化流程是怎样的呢?通过下面的案例(前文有详细案例)进行探究:

        String resource = "mybatis-config2.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
mapper.list();
mapper.list();

  通过 debug 出来的方法调用栈可以看出:通过 SqlSessionFactoryBuilder 对 Mapper 接口和 XML 配置进行解析的信息也是存储到 Configuration 的 mappedStatements 中;

  sqlSession.getMapper() 直接从 DefaultSqlSession 中获取,还是跟上面一样在解析完存放到 Configuration 的 MapperRegistry 变量中,代理还是走 JDK 动态代理:

  综上所述: Mybatis 和 Spring Framework 结合后对 Mapper 接口的初始化过程中会转换成 MapperFactoryBean 类型,然后利用 Spring的InitializingBean 接口的特性来实现对 Mapper 接口信息的初始化,再将这些信息缓存起来放到 Configuration 的 mappedStatements 中;而Mybatis直接解析完放到 Configuration 的 mappedStatements 中;

  最后剩余的一个问题:Mapper 接口使用和不使用 @Mapper 注解有什么区别?

  通过查看@Mapper注解的注释,发现这个注解只是一个标识功能,使用与不使用没什么区别,不会影响系统的功能;

Mybatis的初始化和结合Spring Framework后初始化的源码探究的更多相关文章

  1. Mybatis一级缓存和结合Spring Framework后失效的源码探究

    1.在下面的案例中,执行两次查询控制台只会输出一次 SQL 查询: mybatis-config.xml <?xml version="1.0" encoding=" ...

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

    由前文可得知, Spring Framework的自动装配有两种方式:xml配置和注解配置: 自动装配的类型有: (1)xml配置中的byType根据类型查找(@Autowired注解是默认根据类型查 ...

  3. 七、Spring之深入理解AOP源码

    Spring之深入理解AOP源码 ​ 在上一篇博文中,我们对AOP有了初步的了解,那么接下来我们就对AOP的实现原理进行深入的分析. ​ 在之前写的那个AOP示例代码当中有这样一个注解:@Enable ...

  4. Mybatis日志源码探究

    一.项目搭建 1.pom.xml <dependencies> <dependency> <groupId>log4j</groupId> <ar ...

  5. Spring Boot 2.0系列文章(五):Spring Boot 2.0 项目源码结构预览

    关注我 转载请务必注明原创地址为:http://www.54tianzhisheng.cn/2018/04/15/springboot2_code/ 项目结构 结构分析: Spring-boot-pr ...

  6. Spring框架之spring-web http源码完全解析

    Spring框架之spring-web http源码完全解析 Spring-web是Spring webMVC的基础,由http.remoting.web三部分组成. http:封装了http协议中的 ...

  7. Spring框架之spring-web web源码完全解析

    Spring框架之spring-web web源码完全解析 spring-web是Spring webMVC的基础,由http.remoting.web三部分组成,核心为web模块.http模块封装了 ...

  8. 一文读懂Spring动态配置多数据源---源码详细分析

    Spring动态多数据源源码分析及解读 一.为什么要研究Spring动态多数据源 ​ 期初,最开始的原因是:想将答题服务中发送主观题答题数据给批改中间件这块抽象出来, 但这块主要使用的是mq消息的方式 ...

  9. Spring Boot 揭秘与实战 源码分析 - 工作原理剖析

    文章目录 1. EnableAutoConfiguration 帮助我们做了什么 2. 配置参数类 – FreeMarkerProperties 3. 自动配置类 – FreeMarkerAutoCo ...

随机推荐

  1. cve-2018-2893 weblogic -WLS核心组件反序列化

    漏洞分析 https://www.freebuf.com/column/178103.html https://www.freebuf.com/vuls/177868.html 攻击者可以在未授权的情 ...

  2. 【Azure API 管理】APIM CORS策略设置后,跨域请求成功和失败的Header对比实验

    在文章"从微信小程序访问APIM出现200空响应的问题中发现CORS的属性[terminate-unmatched-request]功能"中分析了CORS返回空200的问题后,进一 ...

  3. mysql数据库的数据备份,以及开启日志

    导出数据: location代表需要保存的数据文件的位置,默认保存在 C:\ProgramData\MySQL\MySQL Server 5.7\Data(Windows10系统位置,其他系统位置自行 ...

  4. Educational Codeforces Round 69 (Rated for Div. 2) D. Yet Another Subarray Problem 【数学+分块】

    一.题目 D. Yet Another Subarray Problem 二.分析 公式的推导时参考的洛谷聚聚们的推导 重点是公式的推导,推导出公式后,分块是很容易想的.但是很容易写炸. 1 有些地方 ...

  5. 常用开发库 - 告別BeanUtils拷贝,MapStruct工具库最全详解

    常用开发库 - MapStruct工具库详解 MapStruct是一款非常实用Java工具,主要用于解决对象之间的拷贝问题,比如PO/DTO/VO/QueryParam之间的转换问题.区别于BeanU ...

  6. Java并发编程之并发关键字

    volatile 保证可见性 一个线程修改volatile变量的值时,该变量的新值会立即刷新到主内存中,这个新值对其他线程来说是立即可见的 一个线程读取volatile变量的值时,该变量在本地内存中缓 ...

  7. vue全局错误捕获

    1.errorHandler Vue全局配置 errorHandler可以进行全局错误收集,捕获全局错误抛出,避免前端页面挂掉   export default function errorHandl ...

  8. 文字变图片——GitHub 热点速览 v.21.14

    作者:HelloGitHub-小鱼干 程序的力量,在 deep-daze 体现得淋漓尽致,你用一句话描述下你的图片需求,它就能帮你生成对应图片.同样的,appsmith 的力量在于你只要拖拽即可得到一 ...

  9. 201871030106-陈鑫莲 实验二 个人项目-《D{0-1} KP 问题》项目报告

    项目 内容 课程班级博客链接 班级博客 这个作业要求链接 作业要求 我的课程学习目标 1.掌握软件项目个人开发流程2.掌握Github发布软件项目的操作方法 这个作业在哪些方面帮助我实现学习目标 1. ...

  10. Dynamics CRM安装教程六:CRM产品安装

    接下来就要开始进行CRM产品的安装了 首先要安装IIS,以及.NET FrameWork4.6及相关功能 打开服务器管理器,在添加角色功能向导中勾选IIS,点击添加功能 默认下一步 选择角色服务这里的 ...