MapperProxy.java

  1. /*
  2.  *    Copyright 2009-2024 the original author or authors.
  3.  *
  4.  *    Licensed under the Apache License, Version 2.0 (the "License");
  5.  *    you may not use this file except in compliance with the License.
  6.  *    You may obtain a copy of the License at
  7.  *
  8.  *       https://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  *    Unless required by applicable law or agreed to in writing, software
  11.  *    distributed under the License is distributed on an "AS IS" BASIS,
  12.  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  *    See the License for the specific language governing permissions and
  14.  *    limitations under the License.
  15.  */
  16. package org.apache.ibatis.binding;

  17. import java.io.Serializable;
  18. import java.lang.invoke.MethodHandle;
  19. import java.lang.invoke.MethodHandles;
  20. import java.lang.invoke.MethodHandles.Lookup;
  21. import java.lang.invoke.MethodType;
  22. import java.lang.reflect.Constructor;
  23. import java.lang.reflect.InvocationHandler;
  24. import java.lang.reflect.InvocationTargetException;
  25. import java.lang.reflect.Method;
  26. import java.util.Map;

  27. import org.apache.ibatis.reflection.ExceptionUtil;
  28. import org.apache.ibatis.session.SqlSession;
  29. import org.apache.ibatis.util.MapUtil;

  30. /**
  31.  * @author Clinton Begin
  32.  * @author Eduardo Macarron
  33.  */
  34. public class MapperProxy<T> implements InvocationHandler, Serializable {

  35.   private static final long serialVersionUID = -4724728412955527868L;
  36.   private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
  37.       | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
  38.   private static final Constructor<Lookup> lookupConstructor;
  39.   private static final Method privateLookupInMethod;
  40.   private final SqlSession sqlSession;
  41.   private final Class<T> mapperInterface;
  42.   private final Map<Method, MapperMethodInvoker> methodCache;

  43.   public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
  44.     this.sqlSession = sqlSession;
  45.     this.mapperInterface = mapperInterface;
  46.     this.methodCache = methodCache;
  47.   }

  48.   static {
  49.     Method privateLookupIn;
  50.     try {
  51.       privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
  52.     } catch (NoSuchMethodException e) {
  53.       privateLookupIn = null;
  54.     }
  55.     privateLookupInMethod = privateLookupIn;

  56.     Constructor<Lookup> lookup = null;
  57.     if (privateLookupInMethod == null) {
  58.       // JDK 1.8
  59.       try {
  60.         lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
  61.         lookup.setAccessible(true);
  62.       } catch (NoSuchMethodException e) {
  63.         throw new IllegalStateException(
  64.             "There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
  65.             e);
  66.       } catch (Exception e) {
  67.         lookup = null;
  68.       }
  69.     }
  70.     lookupConstructor = lookup;
  71.   }

  72.   @Override
  73.   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  74.     try {
  75.       if (Object.class.equals(method.getDeclaringClass())) {
  76.         return method.invoke(this, args);
  77.       }
  78.       return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
  79.     } catch (Throwable t) {
  80.       throw ExceptionUtil.unwrapThrowable(t);
  81.     }
  82.   }

  83.   private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
  84.     try {
  85.       return MapUtil.computeIfAbsent(methodCache, method, m -> {
  86.         if (!m.isDefault()) {
  87.           return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  88.         }
  89.         try {
  90.           if (privateLookupInMethod == null) {
  91.             return new DefaultMethodInvoker(getMethodHandleJava8(method));
  92.           }
  93.           return new DefaultMethodInvoker(getMethodHandleJava9(method));
  94.         } catch (IllegalAccessException | InstantiationException | InvocationTargetException
  95.             | NoSuchMethodException e) {
  96.           throw new RuntimeException(e);
  97.         }
  98.       });
  99.     } catch (RuntimeException re) {
  100.       Throwable cause = re.getCause();
  101.       throw cause == null ? re : cause;
  102.     }
  103.   }

  104.   private MethodHandle getMethodHandleJava9(Method method)
  105.       throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
  106.     final Class<?> declaringClass = method.getDeclaringClass();
  107.     return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(
  108.         declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
  109.         declaringClass);
  110.   }

  111.   private MethodHandle getMethodHandleJava8(Method method)
  112.       throws IllegalAccessException, InstantiationException, InvocationTargetException {
  113.     final Class<?> declaringClass = method.getDeclaringClass();
  114.     return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);
  115.   }

  116.   interface MapperMethodInvoker {
  117.     Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
  118.   }

  119.   private static class PlainMethodInvoker implements MapperMethodInvoker {
  120.     private final MapperMethod mapperMethod;

  121.     public PlainMethodInvoker(MapperMethod mapperMethod) {
  122.       this.mapperMethod = mapperMethod;
  123.     }

  124.     @Override
  125.     public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
  126.       return mapperMethod.execute(sqlSession, args);
  127.     }
  128.   }

  129.   private static class DefaultMethodInvoker implements MapperMethodInvoker {
  130.     private final MethodHandle methodHandle;

  131.     public DefaultMethodInvoker(MethodHandle methodHandle) {
  132.       this.methodHandle = methodHandle;
  133.     }

  134.     @Override
  135.     public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
  136.       return methodHandle.bindTo(proxy).invokeWithArguments(args);
  137.     }
  138.   }
  139. }