1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
35
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
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 }