动态代理-mybatis中的应用

概念

代理模式:为另一个对象提供一个替身或占位符以控制对这个对象的访问。

静态代理: 若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理

动态代理: 代理类在程序运行时创建的代理方式被成为 动态代理。

Java中代理的实现一般分为三种:JDK静态代理、JDK动态代理以及CGLIB动态代理。

动态代理

JDK动态代理

jdk动态代理的主要接口:

public interface InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

如果需要使用jdk动态代理,就需要实现这个接口,然后使用Proxy的newProxyInstance 生成代理类

public static Object newProxyInstance(ClassLoader loader,  Class<?>[] interfaces,  InvocationHandler h)

其中,interfaces 需要是接口类,所以jdk动态代理需要实现接口。如果没有接口那就需要cglib代理了。

下面看先jdk动态代理的简单用法

假设我们有一个接口

public interface IService {    
    public void sayHello();    
}

还有一个简单的实现

public class IServiceImpl implements IService{   
    @Override    public void sayHello() {        
        System.out.println("Hello World!");    
    }  
}

实现InvocationHandler接口

public class InvoHandler implements InvocationHandler {

    Object realObject;

    public InvoHandler(Object realObject) {
        this.realObject = realObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Start invoke");
        Object re = method.invoke(realObject,args);
        System.out.println(method.getDeclaringClass());
       return re;
    }
}

生成动态代理的例子

public class ProxyDemo {

    public static void main(String[] args) {
        IService proxyService = getProxy(IService.class, new IServiceImpl());
        proxyService.sayHello();
    }

    /**
     * 复用代理
     *
     * @param inf     接口的类
     * @param realObj 具体实现的对象
     * @param <T>     类型
     * @return 返回代理对象
     */
    private static <T> T getProxy(Class<T> inf, T realObj) {
        return (T) Proxy.newProxyInstance(inf.getClassLoader()
                                          , new Class<?>[]{inf}
                                          , new InvoHandler(realObj));
    }
}

CGLIB动态代理

如果没有实现接口,可以使用CGLIB进行代理。

CGLIB(Code Generation Library) 是一个高性能的,底层基于 ASM 框架( ASM框架是一个致力于字节码操作和分析的框架,它可以用来修改一个已存在的类或者动态产生一个新的类。ASM提供了一些通用的字节码转换和分析算法,通过这些算法可以定制更复杂的工具。 )的一个代码生成框架,它完美的解决了 JDK 版本的动态代理只能为接口方法代理的单一性不足问题 。

这里给出代理的方法

    private static<T> T getProxy(Class<T> inf) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(inf);
        enhancer.setCallback(new CglibInterceptor());
        return (T) enhancer.create();
    }

调用方式很简单,注意cglib动态代理是不需要实现接口的

public class ProxyDemo {

public static void main(String[] args) {
    IServiceImpl proxyService = getProxy(IServiceImpl.class);
    proxyService.sayHello();
}

MyBatis 中动态代理的应用

首先我们看一段经典的mybatis的代码

public class Main { 
    public static void main(String[] args) throws IOException { 
        String resource = "mybatis.xml"; 
        InputStream inputStream = Resources.getResourceAsStream(resource); 
        SqlSessionFactory sqlSessionFactory = 
                new SqlSessionFactoryBuilder().build(inputStream); 
        SqlSession session = sqlSessionFactory.openSession(); 
        UserMapper mapper = session.getMapper(UserMapper.class); 
} 

上面的代码实现了从xml文件读取mapper配置,并且得到mapper对象,mapper我们只定义了接口,没有具体实现,我们接着看mybatis是如何利用jdk的动态代理实现的。

我们可以看到得到mapper的主要操作在于这里

session.getMapper(UserMapper.class)

我们跟随源码进入getMapper方法,发现也是一个调用

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {  
return mapperRegistry.getMapper(type, sqlSession);
}

原来是使用了MapperRegistry进行注册

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    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);
    }
  }

  public <T> boolean hasMapper(Class<T> type) {
    return knownMappers.containsKey(type);
  }

  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 {
        knownMappers.put(type, new MapperProxyFactory<>(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();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

  /**
   * @since 3.2.2
   */
  public Collection<Class<?>> getMappers() {
    return Collections.unmodifiableCollection(knownMappers.keySet());
  }

  /**
   * @since 3.2.2
   */
  public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }

  /**
   * @since 3.2.2
   */
  public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }

}

通过分析发现,getmapper要通过接口类进行查找mapperProxyFactory,然后通过mapperProxyFactory.newInstance(sqlSession)得到代理类,那么在什么时候注册了mapper呢?这个问题我们后面再讨论。我们先看一下这个proxyFactory是怎么生成代理的。

看一下这个类


//这个类负责创建具体Mapper接口代理对象的工厂类
public class MapperProxyFactory<T> {
  //具体Mapper接口的Class对象
  private final Class<T> mapperInterface;
  //该接口下面方法的缓存 key是方法对象 value是对接口中方法对象的封装
  private 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) {
    //创建了一个代理类并返回
    //关于Proxy的API 可以查看java官方的API
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
  //在这里传入sqlSession 创建一个Mapper接口的代理类
  public T newInstance(SqlSession sqlSession) {
    //在这里创建了MapperProxy对象 这个类实现了JDK的动态代理接口 InvocationHandler
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    //调用上面的方法 返回一个接口的代理类
    return newInstance(mapperProxy);
  }
}

在newInstance 方法中,我们看到了jdk动态代理熟悉的身影。但是实现了jdk动态代理接口的类是MapperProxy, 也就是说代理的对象是MapperProxy。

//实现了JDK动态代理的接口 InvocationHandler
//在invoke方法中实现了代理方法调用的细节
public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  //接口的类型对象
  private final Class<T> mapperInterface;
    //接口中方法的缓存 有MapperProxyFactory传递过来的。
  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 {
        //判断是不是基础方法 比如toString() hashCode()等,这些方法直接调用不需要处理
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
        // 判断是不是默认方法,1.8之后接口就有默认方法了
      } else if (method.isDefault()) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
        //这里进行缓存
    final MapperMethod mapperMethod = cachedMapperMethod(method);
  //调用mapperMethod.execute 核心的地方就在这个方法里,这个方法对才是真正对SqlSession进行的包装调用
    return mapperMethod.execute(sqlSession, args);
  }

    //缓存处理
  private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }

    // 默认方法调用
  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);
  }
}

可以看到,如果我们执行代理方法的时候,其实是调用了MapperMethod 的execute 方法。

MapperMethod 的execute是执行sql的主要方法,还有其他方法没列出来,有两个子类 SqlCommand 和MethodSignature。

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

  private Object rowCountResult(int rowCount) {
    final Object result;
    if (method.returnsVoid()) {
      result = null;
    } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
      result = rowCount;
    } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
      result = (long)rowCount;
    } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
      result = rowCount > 0;
    } else {
      throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
    }
    return result;
  }
}

追踪 MapperProxyFactory 的生成

回到刚才的问题,MapperProxyFactory是什么时候生成的呢?通过代码追踪可以找到是生成SqlSessionFactory时生成的。

SqlSessionFactory sqlSessionFactory =        new SqlSessionFactoryBuilder().build(inputStream);

进入build方法里面

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        // 这里的方法开始读取配置文件
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

在XMLConfigBuilder 的 parser.parse() 中可以看到

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
        // 这里是关键
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

最终在XMLConfigBuilder 的 mapperElement 中发现 addMappers 和 addMapper 方法

  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
              // 这里出现了addMapper
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

这里就回到了MapperRegistry注册mapper方法里面

  public void addMappers(String packageName) {
      // 根据包名扫描
    mapperRegistry.addMappers(packageName);
  }

  public <T> void addMapper(Class<T> type) {
        // 通过类型注册
      mapperRegistry.addMapper(type);
  }

addMapper方法主要内容是判断是否是接口,如果是新增MapperProxyFactory并放到map里面。然后在getMapper通过查询这个map获取对用的MapperProxyFactory,这样就关联起来了。

knownMappers.put(type, new MapperProxyFactory<>(type));

ref.

https://blog.csdn.net/joenqc/article/details/80233637

https://blog.csdn.net/xiaokang123456kao/article/details/76228684



欢迎关注我的公众号
只说一点点点点

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据