Mybatis的Mapper我们很熟悉,我们只需要提供sql和一个接口,其余与数据库的交互全部交给了Mybatis解决,但是,mapper文件我们只提供一个接口,这个接口是怎么和spring结合完成我们预期的要求?
FactoryBean
一般情况下,Spring通过反射机制利用的class属性指定实现类实例化Bean,在某些情况下,实例化Bean过程比较复杂,如果按照传统的方式,则需要在中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。从Spring3.0开始,FactoryBean开始支持泛型,即接口声明改为FactoryBean的形式.
看到上面对FactoryBean的简介,我们直接用Mybatis 的源码看它是怎么应用FactoryBean的。
MapperFactoryBean直接负责初始化Mybatis Mapper的接口,因为spring会调用getObject的方法来完成创建bean,但是创建逻辑全部交给你来维护,这就给初始化bean给了很大的自由。
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
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
}
我们把debug断点打在getObject方法,发现操作跟我们刚学Mybatis写的demo一模一样。通过传入接口实例返回经过包装的代理类。
我们暂时别跟着断点往下走,我们可能想知道spring是如何加载这些Mapper接口和MapperFactoryBean 。起点就在MapperScan注解。看MapperScan源码,这个注解为 Import 了MapperScannerRegistrar类。
@MapperScan(basePackages = "包名")
MapperScannerRegistrar类的registerBeanDefinitions方法利用ClassPathMapperScanner对象读取解析了指定包名下所有的接口,并且通过构建BeanDefinitions对象,让每个MapperFactoryBean拥有不同的mapperInterface class属性。
前面都在收集BeanDefinitions,也就是spring初始化实例时会先收集对象信息(BeanDefinitions),以供后面spring实例化。中间的调用链就非常多了,而且对于spring,特别是从springboot开始,SPI机制非常常用(为某个接口寻找服务实现的机制,可拓展性极强),并且BeanDefinitions收集的途径非常多,比如还有BeanDefinitionRegistryPostProcessors动态注入BeanDefinition。对于spring ioc初始化的过程 比较模糊的同学,可以看看下面的系列化文章补补基础,写的非常好:
【死磕 Spring】—– IOC 总结
前面提到的收集BeanDefinitions的步骤,都在AbstractApplicationContext的refresh方法中,,而当执行到finishBeanFactoryInitialization方法的时候,也就是初始化剩下的单例Bean(非延迟加载的)时,就会调用getBean方法正式进入到 开启 bean加载
的过程。
在AbstractBeanFactory类的doGetBean方法里,会先创建MapperFactoryBean实例。看下图,mbd变量就是之前定义的BeanDefinitions,根据对象定义来创建对象。
创建完MapperFactoryBean工厂类我们根据下面的调用链看一下getObjectForBeanInstance方法。
解释一下下面这段话的意思:这个方法就是 即使你前面用createBean返回了一个对象,但是如果是FactoryBean接口的实现类,那就还是抛弃前面createBean创建的对象,反之,直接调用FactoryBean的getObject去获取新的对象。
protected Object getObjectForBeanInstance(
Object beanInstance, String name, String beanName, RootBeanDefinition mbd) {
//如果类名是&打头,但bean对象不是FactoryBean,抛出异常
if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
}
//如果Bean实例不是FactoryBean,或者类名不是&打头,也就是普通的bean调用,则直接返回当前的Bean实例
if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
return beanInstance;
}
//处理对FactoryBean的调用
Object object = null;
if (mbd == null) {
//从Bean工厂缓存中获取给定名称的实例对象
object = getCachedObjectForFactoryBean(beanName);
}
if (object == null) {
// Return bean instance from factory.
FactoryBean factory = (FactoryBean) beanInstance;
//如果从Bean工厂生产的Bean是单态模式的,则缓存
if (mbd == null && containsBeanDefinition(beanName)) {
mbd = getMergedLocalBeanDefinition(beanName);
}
boolean synthetic = (mbd != null && mbd.isSynthetic());
//调用FactoryBeanRegistrySupport类的getObjectFromFactoryBean方法,实现FactoryBean生产Bean对象实例的过程
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
}
继续走读源码,我们会看到终于调用了FactoryBean的getObject方法,而我们也开始介绍下一章。
动态代理模式的运用
当断点来到getObject方法,继续跟着断点往里走,MapperProxyFactory类里面就展示了Mapper 接口的真面目,newInstance方法展示了一个典型的动态代理用法–Proxy.newProxyInstance。Proxy做了什么事可以看我之前的博文
从代理模式再出发!Proxy.newProxyInstance的秘密
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@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<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
MapperProxy实现了InvocationHandler,具备了jdk动态代理的条件。代码用sqlSession完成了数据库操作。所以我们定义的接口在最后成了一个代理类,Mybatis 帮我们悉心做好了一切。
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
@UsesJava7
private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
throws Throwable {
final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
final Class<?> declaringClass = method.getDeclaringClass();
return constructor
.newInstance(declaringClass,
MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
}
/**
* Backport of java.lang.reflect.Method#isDefault()
*/
private boolean isDefaultMethod(Method method) {
return ((method.getModifiers()
& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)
&& method.getDeclaringClass().isInterface();
}
}
结语
这篇文章不是详细介绍Mybatis的启动过程,而摘取介绍了一部分Mybatis和spring的结合过程。让我们了解Mapper接口是如何从一个没有实现类的接口到拥有丰富功能 的一次华丽转身。
修订
- 2019.10.23 补充了spring ioc方面的知识,更有利于理解spring与Mybatis的结合过程。