前言
本文,从 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
,则是封装了与数据库的交互。