代理模式及FactoryBean的实际运用--Mybatis Mapper文件的华丽转身

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的结合过程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值