『深入学习 Spring Boot』(十六) 从 Mybatis 浅看 Spring Factory Bean 与 Java 代理机制

·  阅读 465

前言

本文,从 Mybatis的角度出发,探索了 Spring Factory Bean 与 Java 代理联合使用机制。

依赖

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>
复制代码

加载 Mybatis Bean 过程

自动配置类 MybatisAutoConfiguration

@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {
    // ………………
        @Configuration
    @Import({MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class})
    @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
    public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
        public MapperScannerRegistrarNotFoundConfiguration() {}
        public void afterPropertiesSet() {
            // ...
        }
    }
 
    public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, EnvironmentAware, ImportBeanDefinitionRegistrar {
        private BeanFactory beanFactory;
        private Environment environment;
 
        public AutoConfiguredMapperScannerRegistrar() {}
 
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            if (!AutoConfigurationPackages.has(this.beanFactory)) {
                // ...
            } else {
                // ....
                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
                builder.addPropertyValue("processPropertyPlaceHolders", true);
                builder.addPropertyValue("annotationClass", Mapper.class);
                builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
                // builder add 一些基础属性
                builder.setRole(2);
                // 注册 MapperScannerConfigurer
                registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
            }
        }
    }
}
复制代码

MybatisAutoConfiguration 主要做了两件事:

  • 加载配置文件中的属性
  • 注册 MapperScannerConfigurer

MapperScannerConfigurer

MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor

我们知道 Spring Boot 在启动过程中,会遍历BeanDefinitionRegistryPostProcessor # postProcessBeanDefinitionRegistry()方法。

该方法的主要作用是添加BeanDefinition

对此处不太了解的,可以参照《『深入学习 Spring Boot』(七) refresh 方法 》

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
 
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
        // 处理属性值
      processPropertyPlaceHolders();
    }
 
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    // scanner.setXXXX()
    // ...
    scanner.registerFilters();
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
}
复制代码

这里就调用 scanner.scan()方法,开始扫包了。默认basePackage,是启动类所在的包。

ClassPathMapperScanner

此类主要作用,将包内的类,扫描为BD。以待后续处理。

public int scan(String... basePackages) {
    // ...
    doScan(basePackages);
    // ...
}
 
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    // ...
    processBeanDefinitions(beanDefinitions);
    return beanDefinitions;
  }
 
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    AbstractBeanDefinition definition;
    BeanDefinitionRegistry registry = getRegistry();
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (AbstractBeanDefinition) holder.getBeanDefinition();
      boolean scopedProxy = false;
      // ...
      String beanClassName = definition.getBeanClassName();
      // ...
      // 添加实际类型
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      try {
        definition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName));
      } catch (ClassNotFoundException ignore) {
      }
 
      // !!!重要!!!
      // 本来我们扫描的类是 XxxDao,这里把 BeanClass 修改为了 MapperFactoryBean.class。
      // 这样,在后续初始化 Bean 的时候,会初始化 MapperFactoryBean 实例, 然后通过 MapperFactoryBean # getObject() 方法来获取 Bean
      definition.setBeanClass(this.mapperFactoryBeanClass);
 
      definition.getPropertyValues().add("addToConfig", this.addToConfig);
      definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);
      // ...
    }
  }
复制代码

注意!到这里,Mybatis 相关 Bean 的 BeanDefinition 就被加载到 Srping 中了。

但是在添加过程中,processBeanDefinitions()方法,把 BD 中的 BeanClass 修改为了 MapperFactoryBean.class,这将会导致后面 Bean 初始化时,通过 MapperFactoryBean # getObject() 方法来获取 Bean。

Bean 初始化过程不了解的,可以参考 《『深入学习 Spring Boot』(八) Bean 实例化流程 》

如果项目中有这么一个类:

public class GoodsServiceImpl implements GoodsService {
    @Resource
    GoodsDao goodsDao;
    public void get() {
        List<Goods> all = goodsDao.getAll();
    }
}
复制代码

此类需要注入GoodsDao类,Spring 在 BeanFactory 中查找 Bean 时,发现此 Bean 是 FactoryBean,则会执行getObject(),来获取被注入的实例(此实例一般是代理类)。

MapperFactoryBean

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    // ...
    public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
    // ...
}
复制代码

经过源码跟踪,getObject()最终执行了MapperRegistry#getMapper()方法。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  // ....
  try {
    // 代理工厂创建代理类。即 MapperProxy 类。
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}
复制代码

这里是一个工厂模式,获取我们需要的 Mybatis 代理类MapperProxy,所有 Mybatis Mapper Bean 最终都会被 MapperProxy所代理。

MapperProxy 代理类

public class MapperProxy<T> implements InvocationHandler, Serializable {
 
  // ...
 
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     try {
       if (Object.class.equals(method.getDeclaringClass())) {
         // Object 的方法,原样执行
         return method.invoke(this, args);
       } else {
         // 执行代理逻辑:执行 SQL 语句,返回执行直接
         return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
       }
     } catch (Throwable t) {
       throw ExceptionUtil.unwrapThrowable(t);
     }
   }
}
复制代码

这里就是一个简单的代理类,实现了InvocationHandler接口。

Mybatis 具体是怎么执行 SQL 的,并不在本文关心范围之类,以后有机会再写文章共同探索。

写一个简单的代理 DEMO

被代理类 GoodsDao

public interface GoodsDao {
    Integer add(String a);
}
复制代码

代理类MyMapperProxy

实现一个简单的代理效果:把传入的参数+1,然后返回。

public class MyMapperProxy implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 
        System.out.println("-----代理开始------");
        System.out.println("真实参数:"+ Arrays.toString(args));
        String s = String.valueOf(args[0]);
        return Integer.parseInt(s) + 1;
    }
}
复制代码

执行结果

public static void main(String[] args) {
    MyMapperProxy myMapperProxy = new MyMapperProxy();
    Object o = Proxy.newProxyInstance(GoodsDao.class.getClassLoader(), new Class[]{GoodsDao.class}, myMapperProxy);
    GoodsDao gProxy = (GoodsDao) o;
    Integer all = gProxy.add("1");
    System.out.println(all);
}
 
// ------------执行结果----------------
-----代理开始------
真实参数:[1]
2
复制代码

这里,我们就简单完成了一个代理。也对 Mybatis 中使用的代理,加深了一些理解。

小结

这一小节,我们从 Mybatis 入手,对 Spring 的 FactoryBean 与 Java 的代理机制,有了一些了解。

同时,我们通过这两种机制的实用场景,也多了一些认知。

其实想想,我们用到的 OpenFeign内部,也是这种机制。

  • 在 Spring 框架层面,使用 FactoryBean 机制注入代理类。
  • 在 Java 层面,使用代理去封装行为。在 OpenFeign上,封装了 Http 请求;在Mybatis,则是封装了与数据库的交互。
分类:
后端
收藏成功!
已添加到「」, 点击更改