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
32
33
34
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
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 }