5

动态代理-mybatis中的应用

 3 years ago
source link: https://www.okayjam.com/%e5%8a%a8%e6%80%81%e4%bb%a3%e7%90%86-mybatis%e4%b8%ad%e7%9a%84%e5%ba%94%e7%94%a8/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

动态代理-mybatis中的应用

发表于2019年12月9日

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

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

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

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));

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

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

欢迎关注我的公众号

只说一点点点点

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK