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