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 }
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 }