View Javadoc
1   /*
2    *    Copyright 2009-2021 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    *       http://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  
18  import java.io.Serializable;
19  import java.lang.invoke.MethodHandle;
20  import java.lang.invoke.MethodHandles;
21  import java.lang.invoke.MethodHandles.Lookup;
22  import java.lang.invoke.MethodType;
23  import java.lang.reflect.Constructor;
24  import java.lang.reflect.InvocationHandler;
25  import java.lang.reflect.InvocationTargetException;
26  import java.lang.reflect.Method;
27  import java.util.Map;
28  
29  import org.apache.ibatis.reflection.ExceptionUtil;
30  import org.apache.ibatis.session.SqlSession;
31  import org.apache.ibatis.util.MapUtil;
32  
33  /**
34   * @author Clinton Begin
35   * @author Eduardo Macarron
36   */
37  public class MapperProxy<T> implements InvocationHandler, Serializable {
38  
39    private static final long serialVersionUID = -4724728412955527868L;
40    private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
41        | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
42    private static final Constructor<Lookup> lookupConstructor;
43    private static final Method privateLookupInMethod;
44    private final SqlSession sqlSession;
45    private final Class<T> mapperInterface;
46    private final Map<Method, MapperMethodInvoker> methodCache;
47  
48    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
49      this.sqlSession = sqlSession;
50      this.mapperInterface = mapperInterface;
51      this.methodCache = methodCache;
52    }
53  
54    static {
55      Method privateLookupIn;
56      try {
57        privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
58      } catch (NoSuchMethodException e) {
59        privateLookupIn = null;
60      }
61      privateLookupInMethod = privateLookupIn;
62  
63      Constructor<Lookup> lookup = null;
64      if (privateLookupInMethod == null) {
65        // JDK 1.8
66        try {
67          lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
68          lookup.setAccessible(true);
69        } catch (NoSuchMethodException e) {
70          throw new IllegalStateException(
71              "There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
72              e);
73        } catch (Exception e) {
74          lookup = null;
75        }
76      }
77      lookupConstructor = lookup;
78    }
79  
80    @Override
81    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
82      try {
83        if (Object.class.equals(method.getDeclaringClass())) {
84          return method.invoke(this, args);
85        } else {
86          return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
87        }
88      } catch (Throwable t) {
89        throw ExceptionUtil.unwrapThrowable(t);
90      }
91    }
92  
93    private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
94      try {
95        return MapUtil.computeIfAbsent(methodCache, method, m -> {
96          if (m.isDefault()) {
97            try {
98              if (privateLookupInMethod == null) {
99                return new DefaultMethodInvoker(getMethodHandleJava8(method));
100             } else {
101               return new DefaultMethodInvoker(getMethodHandleJava9(method));
102             }
103           } catch (IllegalAccessException | InstantiationException | InvocationTargetException
104               | NoSuchMethodException e) {
105             throw new RuntimeException(e);
106           }
107         } else {
108           return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
109         }
110       });
111     } catch (RuntimeException re) {
112       Throwable cause = re.getCause();
113       throw cause == null ? re : cause;
114     }
115   }
116 
117   private MethodHandle getMethodHandleJava9(Method method)
118       throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
119     final Class<?> declaringClass = method.getDeclaringClass();
120     return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(
121         declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
122         declaringClass);
123   }
124 
125   private MethodHandle getMethodHandleJava8(Method method)
126       throws IllegalAccessException, InstantiationException, InvocationTargetException {
127     final Class<?> declaringClass = method.getDeclaringClass();
128     return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);
129   }
130 
131   interface MapperMethodInvoker {
132     Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
133   }
134 
135   private static class PlainMethodInvoker implements MapperMethodInvoker {
136     private final MapperMethod mapperMethod;
137 
138     public PlainMethodInvoker(MapperMethod mapperMethod) {
139       super();
140       this.mapperMethod = mapperMethod;
141     }
142 
143     @Override
144     public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
145       return mapperMethod.execute(sqlSession, args);
146     }
147   }
148 
149   private static class DefaultMethodInvoker implements MapperMethodInvoker {
150     private final MethodHandle methodHandle;
151 
152     public DefaultMethodInvoker(MethodHandle methodHandle) {
153       super();
154       this.methodHandle = methodHandle;
155     }
156 
157     @Override
158     public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
159       return methodHandle.bindTo(proxy).invokeWithArguments(args);
160     }
161   }
162 }