本章只介绍简单的原理,思路如下,先谈谈动态代理,以熟悉的mybatis举例,如何做到注入mapper的,引申到dubbo。。

代理模式

本节知识点陈旧,可以跳过。先回顾一下什么是代理,代理模式是指给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。这种模式有什么用呢?它可以在原对象的基础上增强原对象的功能,比如在原对象调用一个方法的前后进行日志、事务操作等。Spring AOP就使用了代理模式。
代理分静态代理、动态代理。下面分别使用两种方式代理以下接口

1
2
3
public interface People {
void say();
}

静态代理

静态代理是指我们设计模式中用到的那样,新建一个代理类,把要代理对象的传入到这个类中,手动调用这个代理类的方法。稍微感受一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class PeopleProxy implements People{
private People target;

public PeopleProxy(People target) {
this.target = target;
}

public void say() {
System.out.println("Do something before call");
target.say();
System.out.println("Do something after call");
}
}

动态代理

动态代理主要分为JDK动态代理和cglib动态代理两大类,本文主要对JDK动态代理进行探讨。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ProxyFactory implements InvocationHandler {

public <T> T getProxyObj(Class clazz) {
return (T) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{clazz}, this);
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy the method: " + method.getDeclaringClass().getName() + "." + method.getName() + "();");
// do something.
return null;
}
}

public class Main {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory();
People proxyObj = proxyFactory.getProxyObj(People.class);
proxyObj.say();
}
}

以上代码正确执行并输出proxy the method:xxx;,代码中没有写任何People的实现类,更不要说People的实例了,却能调用到say()方法。

总结一下,动态代理可以帮我们根据接口生成对应的接口的代理对象,而在这个代理对象执行时,我们还可以做一些增强处理。

mybatis原理

上面了解回顾动态代理实现和作用。是不是马上想到了,和我们在mybatis中写Mapper类很像。那么接下来我们寻找mybatis源码,一起验证上面的猜想。

在日常开发中,我们都或多或少了解到,mybatis为了代替手工使用 SqlSessionDaoSupport 或 SqlSessionTemplate 编写数据访问对象(DAO)的代码,MyBatis-Spring 提供了一个动态代理的实现MapperFactoryBean。那么我们从这下手一定可以找到怎么生成代理对象的,并且在代理对象中怎么执行sql的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

private Class<T> mapperInterface;

private boolean addToConfig = true;

public MapperFactoryBean() {
//intentionally empty
}

public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}

/**
* {@inheritDoc}
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
}

以上代码为MapperFactoryBean部分源码,它提供一个构造方法传入接口的Class对象,在getObject()方法中返回这个接口的代理对象,那么我们着重看getMapper()方法,一直往下,我们会找到调用MapperRegistry.getMapper()方法。

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
public class MapperRegistry {

private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
}

public class MapperProxyFactory<T> {

@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

}

走到这里,进入到mapperProxyFactory.newInstance();方法中,就会看到我们开章举例的动态代理代码。

至此,生成代理对象的过程到此结束,这里解释了为什么我们使用mybatis,只写一个对应的接口,为什么spring能注入这个接口的对象。

那么剩下的问题就是,怎么执行对应sql语句的。知道了mybatis通过动态代理生成代理对象的,那么我们推测应该是通过实现InvocationHandler接口的invoke方法中来统一执行sql的。那么接下来我们找到一个目标MapperProxy

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
public class MapperProxy<T> implements InvocationHandler, Serializable {

private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}

}

我们可以看到invoke方法。由于mapper是一个代理对象,那么它就会运行到invoke方法里面,invoke首先判断是否一个类,显然这里mapper是一个接口,不是类所以判定失败。那么跟着就会生成MapperMethod对象,它是通过cachedMapperMethod方法对其初始化的,然后执行execute方法,把sqlSession和当前运行的参数传递进去。

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
public class MapperMethod {

private final SqlCommand command;
private final MethodSignature method;

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}

public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
}

至此,mybatis原理,真相大白。在execute方法中,判断需要执行的sql是insert、update、select,做对应的处理,通过sqlSession执行对应的sql语句。

这也就解释了为什么xml中的命名空间要和接口全名对应,sql的id要和方法名对应。因为mybatis是在这里通过类名+方法名,定位到某一条sql的。

dubbo基础原理

本节本来想借助上面mybatis的原理,我们写一个类似dubbo的rpc框架,但是篇幅内容过长,解释不清,下一次我们一起写一个rpc了解dubbo执行原理。点击查看 - 手写dubbo