View Javadoc
1   /*
2    *    Copyright 2009-2023 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  
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        }
86        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
87      } catch (Throwable t) {
88        throw ExceptionUtil.unwrapThrowable(t);
89      }
90    }
91  
92    private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
93      try {
94        return MapUtil.computeIfAbsent(methodCache, method, m -> {
95          if (!m.isDefault()) {
96            return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
97          }
98          try {
99            if (privateLookupInMethod == null) {
100             return new DefaultMethodInvoker(getMethodHandleJava8(method));
101           }
102           return new DefaultMethodInvoker(getMethodHandleJava9(method));
103         } catch (IllegalAccessException | InstantiationException | InvocationTargetException
104             | NoSuchMethodException e) {
105           throw new RuntimeException(e);
106         }
107       });
108     } catch (RuntimeException re) {
109       Throwable cause = re.getCause();
110       throw cause == null ? re : cause;
111     }
112   }
113 
114   private MethodHandle getMethodHandleJava9(Method method)
115       throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
116     final Class<?> declaringClass = method.getDeclaringClass();
117     return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(
118         declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
119         declaringClass);
120   }
121 
122   private MethodHandle getMethodHandleJava8(Method method)
123       throws IllegalAccessException, InstantiationException, InvocationTargetException {
124     final Class<?> declaringClass = method.getDeclaringClass();
125     return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);
126   }
127 
128   interface MapperMethodInvoker {
129     Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
130   }
131 
132   private static class PlainMethodInvoker implements MapperMethodInvoker {
133     private final MapperMethod mapperMethod;
134 
135     public PlainMethodInvoker(MapperMethod mapperMethod) {
136       this.mapperMethod = mapperMethod;
137     }
138 
139     @Override
140     public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
141       return mapperMethod.execute(sqlSession, args);
142     }
143   }
144 
145   private static class DefaultMethodInvoker implements MapperMethodInvoker {
146     private final MethodHandle methodHandle;
147 
148     public DefaultMethodInvoker(MethodHandle methodHandle) {
149       this.methodHandle = methodHandle;
150     }
151 
152     @Override
153     public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
154       return methodHandle.bindTo(proxy).invokeWithArguments(args);
155     }
156   }
157 }