Fork me on GitHub

Mybatis原理-动态代理

回想一下咱使用mybatis的时候,只定义了操作表的接口以及方法,并没有定义接口的实现类,一行jdbc代码都没写,就可以对数据库增删查改。哪有什么岁月静好,不过是有人替你负重前行。是勤恳的mybatis帮我们做了实现类,写了jdbc代码,并把mapper里的sql注入进去了。

梳理一下,框架的使用者需要做的事情包括:

  1. 定义表的接口(如ActorPublicNoticeDAO)。
  2. 定义表接口对应的XML,XML中包含查询SQL所需的参数,如表名、字段名等。
  3. 调用表的接口方法,无需实现接口,直接调用即可。

框架的做的事包括:

  1. 解析使用者定义的接口和XML配置。
  2. 实现接口方法,在方法内部加入具体的JDBC执行代码,根据XML配置获取参数设置到JDBC执行代码中。

现在有一个很明显的问题,先现有框架,后有使用者,写框架的时候还不知道有什么接口,怎么写接口的实现类?

答:其实很简单,不知道有什么接口就约定一个指定路径,路径下的接口都扫描出来。

那怎么写接口的实现类呢?虽然写框架的时候不知道有什么接口,但是调用的时候就知道了呀。这不由让人想起来一句典型的定义:一种在运行时创建代理对象的机制,而不是在编译时手动编写代理类,这就是动态代理技术。

要使用Java动态代理,需要做以下几个步骤:

一、创建接口和方法。在这个项目里就是ActorPublicNoticeMapper#getInfoById。

1
2
3
4

public interface ActorPublicNoticeMapper {
String getInfoById(Integer id);
}

二、使用Proxy.newProxyInstance()方法创建接口的实例(即代理对象)。咱定义一个SqlSession类专门做这件事,你给它一个接口mapper,它给你返回一个mapper实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SqlSession {
/**
* 返回代理类
*
* @param clazz
* @param dbConfig
* @param mapperBean
* @return : T
*/
public <T> T getMapper(Class<T> clazz, DbConfig dbConfig, MapperBean mapperBean) {
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new MyInvocationHandler(dbConfig, mapperBean));
}

}

三、创建一个实现InvocationHandler接口的类,调用接口时,实际上是调用InvocationHandler中的invoke()方法。在这个项目里就是MyInvocationHandler。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

public class MyInvocationHandler implements InvocationHandler {

private DbConfig dbConfig;
private MapperBean mapperBean;


public MyInvocationHandler(final DbConfig dbConfig, final MapperBean mapperBean) {
this.dbConfig = dbConfig;
this.mapperBean = mapperBean;
}


@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) {
try (Connection conn = DriverManager.getConnection(this.dbConfig.getUrl(), this.dbConfig.getUsername(), this.dbConfig.getPassword())) {
Statement stmt = conn.createStatement();
Map<String, MySqlFunction> functionMap = this.mapperBean.getFunctionMap();
MySqlFunction mySqlFunction = functionMap.get(method.getName());
String sql = parseSql(mySqlFunction.getSql(), args);
ResultSet rs = stmt.executeQuery(sql);

while (rs.next()){
// 处理结果集
return rs.getString("actorId");
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}


public String parseSql(String sql, Object[] args) {
//do something...
}


}

Proxy.newProxyInstance()底层到底做了什么事呢,点进去看看这个方法吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);

final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}

/*
* Look up or generate the designated proxy class.
*/
//这里生成了一个类
Class<?> cl = getProxyClass0(loader, intfs);

/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}

final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//利用反射生产了这个类的对象
return cons.newInstance(new Object[]{h});
}

在编译时不知道类还需要构造一个类的对象好说,用反射生成。那么是怎么生成类的呢。一般的类都是我们编译前就写好的.java文件,jvm把.java文件编译成字节码,并以.class文件存储。而动态代理是在运行时直接生成了.class文件。让我们点进代码一探究竟。

分别跳入如下方法:

java.lang.reflect.Proxy#newProxyInstance

java.lang.reflect.Proxy#getProxyClass0

java.lang.reflect.WeakCache#get

这里的代码比较绕,加入了一些缓存相关的处理,我们只需要抓住核心流程。

java.lang.reflect.Proxy.ProxyClassFactory#apply

sun.misc.ProxyGenerator#generateProxyClass(java.lang.String, java.lang.Class<?>[], int)

sun.misc.ProxyGenerator#generateClassFile 这个方法就是动态代理生成字节码的核心代码,主要干的事有:

  • 把接口所有的方法都生成了代理方法,还默认带了3个方法hashCodeMethod、equalsMethod、toStringMethod。
  • 还生成了魔术头、小版本号、主版本号、类方法标志、当前类、超类、接口个数、每个实现接口、属性个数、每个属性、方法个数、每个方法等信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
private byte[] generateClassFile() {

/* ============================================================
* Step 1: Assemble ProxyMethod objects for all methods to
* generate proxy dispatching code for.
*/

/*
* Record that proxy methods are needed for the hashCode, equals,
* and toString methods of java.lang.Object. This is done before
* the methods from the proxy interfaces so that the methods from
* java.lang.Object take precedence over duplicate methods in the
* proxy interfaces.
*/
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);

/*
* Now record all of the methods from the proxy interfaces, giving
* earlier interfaces precedence over later ones with duplicate
* methods.
*/
for (Class<?> intf : interfaces) {
for (Method m : intf.getMethods()) {
addProxyMethod(m, intf);
}
}

/*
* For each set of proxy methods with the same signature,
* verify that the methods' return types are compatible.
*/
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
checkReturnTypes(sigmethods);
}

/* ============================================================
* Step 2: Assemble FieldInfo and MethodInfo structs for all of
* fields and methods in the class we are generating.
*/
try {
methods.add(generateConstructor());

for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
for (ProxyMethod pm : sigmethods) {

// add static field for method's Method object
fields.add(new FieldInfo(pm.methodFieldName,
"Ljava/lang/reflect/Method;",
ACC_PRIVATE | ACC_STATIC));

// generate code for proxy method and add it
methods.add(pm.generateMethod());
}
}

methods.add(generateStaticInitializer());

} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}

if (methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
}
if (fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
}

/* ============================================================
* Step 3: Write the final class file.
*/

/*
* Make sure that constant pool indexes are reserved for the
* following items before starting to write the final class file.
*/
cp.getClass(dotToSlash(className));
cp.getClass(superclassName);
for (Class<?> intf: interfaces) {
cp.getClass(dotToSlash(intf.getName()));
}

/*
* Disallow new constant pool additions beyond this point, since
* we are about to write the final constant pool table.
*/
cp.setReadOnly();

ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);

try {
/*
* Write all the items of the "ClassFile" structure.
* See JVMS section 4.1.
*/
// u4 magic;
dout.writeInt(0xCAFEBABE);
// u2 minor_version;
dout.writeShort(CLASSFILE_MINOR_VERSION);
// u2 major_version;
dout.writeShort(CLASSFILE_MAJOR_VERSION);

cp.write(dout); // (write constant pool)

// u2 access_flags;
dout.writeShort(accessFlags);
// u2 this_class;
dout.writeShort(cp.getClass(dotToSlash(className)));
// u2 super_class;
dout.writeShort(cp.getClass(superclassName));

// u2 interfaces_count;
dout.writeShort(interfaces.length);
// u2 interfaces[interfaces_count];
for (Class<?> intf : interfaces) {
dout.writeShort(cp.getClass(
dotToSlash(intf.getName())));
}

// u2 fields_count;
dout.writeShort(fields.size());
// field_info fields[fields_count];
for (FieldInfo f : fields) {
f.write(dout);
}

// u2 methods_count;
dout.writeShort(methods.size());
// method_info methods[methods_count];
for (MethodInfo m : methods) {
m.write(dout);
}

// u2 attributes_count;
dout.writeShort(0); // (no ClassFile attributes for proxy classes)

} catch (IOException e) {
throw new InternalError("unexpected I/O Exception", e);
}

return bout.toByteArray();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class ActorPublicNoticeMapper extends Proxy {
private static Method m1;
private static Method m2;
private static Method m0;

public ActorPublicNoticeMapper(InvocationHandler var1) throws {
super(var1);
}

public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
1
```

`