在介绍MyBatis动态代理前。我们先介绍一下什么是代理模式。
代理模式
代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。
代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性。
代理模式结构图
动态代理
代理模式分为两种:静态代理和动态代理。静态代理是程序编译阶段确定代理类,而动态代理是在程序运行时确定代理类,代理类在程序运行时创建的代理方式被成为动态代理,动态代理是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
JDK自带的动态代理方式(必须有接口)
先实现一个接口和具体的实现类
#####接口
```
public interface IUser {
void talk();
}
```
#####实现类
```
public class User implements IUser {
@Override
public void talk() {
System.out.println("doing User.talk");
}
}
```
要是先动态代理,首先创建一个实现InvocationHandler这个接口的类
```
public class UserProxy implements InvocationHandler {
private Object object;
public UserProxy(Object object){
super();
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("doing UserProxy.invoke");
method.invoke(object, args);
System.out.println("doing UserProxy.invoke end");
return null;
}
/**
* 实现InvocationHandler接口要重写invoke方法,方法的三个参数分别:
* proxy:就是动态代理生成的代理类对象
* method:就是调用的方法
* args:表示该方法的参数
*/
}
```
在main函数中创建代理对象,通过该对象,调用委托类的方法,每个方法的调用JVM都会给我们调用上面实现
InvocationHandler接口的类对象的invoke方法
```
public static void main(String[] args) {
IUser user = (IUser) Proxy.newProxyInstance(ProxyDemo.class.getClassLoader(), new Class[]{IUser.class}, new UserProxy(new User()));
user.talk();
}
```
输出结果
```
doing UserProxy.invoke
doing User.talk
doing UserProxy.invoke end
```
JDK动态代理原理
main中调用了代码user.talk()方法后,JVM会帮助自动实现invoke调用呢?在上面main添加一个监控
public static void main(String[] args) {
//可以查看JDK生成的动态代理类
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
IUser user = (IUser) Proxy.newProxyInstance(ProxyDemo.class.getClassLoader(), new Class[]{IUser.class}, new UserProxy(new User()));
user.talk();
}
生成一个$Proxy().class文件,代理类的源码,public final class $Proxy0 extends Proxy implements IUser$Proxy()的定义,确实实现了IUser接口,和代理模式下的代理类完全一样,因此当user.talk()调用时,根据JAVA的多态原理,调用的应该是代理对象$Proxy()的talk()方法,看$Proxy()重写的talk方法。
public final void talk() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
当调用IUser接口的引用变量user调用talk的时候,其实调用代理对象重写的talk方法,用传入的InvocationHandler对象,并调用了它的invoke方法。
CGLib动态代理(实际上要用动态代理技术)
代理提供了一种可扩展的机制来控制被代理的对象的访问,就是在对象访问的时候加了一层封装,JDK从1.3版本开始提供了上述动态代理机制,使用简单,缺点显而易见,需要目标对象实现一个或者多个接口,当代理没有接口的类,此时Proxy和InvocationHandler机制不能使用了,此时可以使用CGLib库,CGLib采用非常底层的字节码技术,原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术,拦截所有父类方法的调用,顺势织入横切逻辑,JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
public class CGLibSuper {
public void doing() {
System.out.println("CGLibSuper.doing");
}
}
/**
* 该类实现了创建子类的方法与代理的方法,getProxy(CGLibSuper.class)方法通过入参即父类的字节码,
* 通过扩展父类的class来创建代理对象,intercept()方法拦截所有目标类方法的调用,Object表示目标类的实例,
* method为目标类方法的反射对象,objects为方法的动态入参,methodProxy为方法代理
* methodProxy.invokeSuper(o, objects)通过代理类来调用父类的方法
*/
public class CGLibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public <T> T getProxy(Class<T> clazz) {
//设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通过字节码结束动态的创建子类实例
return (T)enhancer.create();
}
//实现MethodInterceptor
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("CGLibProxy.intercept");
Object o1 = methodProxy.invokeSuper(o, objects);
System.out.println("CGLibProxy.intercept end");
return o1;
}
}
public static void main(String[] args) {
CGLibProxy cgLibProxy = new CGLibProxy();
CGLibSuper proxy = cgLibProxy.getProxy(CGLibSuper.class);
proxy.doing();
}
动态代理源码分析
定义接口 & 实现类
public interface Subject {
int add(int x, int y);
}
public class RealSubject implements Subject {
@Override
public int add(int x, int y) {
System.out.println("计算两个数的和,结果为:" + (x + y));
return x + y;
}
}
定义代理类,实现动态代理接口
public class MyProxy implements InvocationHandler{
private Object targer;
public MyProxy(Object targer) {
this.targer = targer;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before method do something");
Object result = method.invoke(targer, args);
System.out.println("after method do something");
return result;
}
}
获取代理类
public class Client {
public static void main(String[] args) {
Subject target = new RealSubject();
MyProxy h = new MyProxy(target);
Subject proxy = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),
new Class[]{Subject.class}, h);
int result = proxy.add(1,2);
}
}
简单三步即可获取到代理对象,调用代理对象的任何方法都会执行 invoke 方法。从以上代码可以看出,JDK 动态代理只能代理接口,而且还需要定义实现类,那 mybatis 是如何做到不需要实现类就轻松获取到代理对象的呢?
Mybatis的动态代理
mybatis中的getMapper方法的实现方式理解为动态代理方法的实现方式理解为动态代理
mybatis中的getMapper的调用为
sqlSession.getMapper(OrdersMapper1.class);
mapper是如何添加进去的呢?
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(OrdersMapper1.class);// 添加Mapper接口
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
等价于
String resource = "mybatis-config.xml"; // xml内容就不贴了
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSessionFactory采用以上方式,分析上面代码生成sqlSessionFactory过程有这么一行:
sqlSession类:
configuration.addMapper(OrdersMapper1.class);// 添加Mapper接口
addMapper 方法的代码实现
Configuration类:
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
mapper 实际上被添加到 mapperRegissry 中。继续跟进代码:
MapperRegistry类:
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {// 只添加接口
if (hasMapper(type)) {// 不允许重复添加
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));// 注意这里
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
所执行的configuration.addMapper(BlogMapper.class); 其实最终被放到了HashMap中,其名为knownMappers ,knowMappers是MapperRegistry 类的一个私有属性,它是一个HashMap 。其Key 为当前Class对象,value 为一个MapperProxyFactory 实例。
总结一下: 诸如OrdersMapper1之类的Mapper接口被添加到了MapperRegistry 中的一个HashMap中。并以 Mapper 接口的 Class 对象作为 Key , 以一个携带Mapper接口作为属性的MapperProxyFactory 实例作为value 。MapperProxyFacory从名字来看,好像是一个工厂,用来创建Mapper Proxy的工厂。
从源码上我们可以看到getMapper方法会去调用Configuration类的getMapper方法。
DefaultSqlSession类:
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
Configuration类里面存放了所有关于XML文件的配置信息。从参数上我们可以看到他要我们传入一个Class<T>类型。这已经可以看到后面一定要用到反射机制和动态生成相应的类实例
Configuration类:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
代码所示,这里的 getMapper 调用了 configuration.getMapper , 这一步操作其实最终是调用了MapperRegistry,而此前我们已经知道,MapperRegistry是存放了一个HashMap的。
MapperRegistry类:
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
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);
}
}
我们调用的session.getMapper(OrdersMapper1.class);最终会到达上面这个方法,这个方法根据OrdersMapper1的class对象,以它为key在knowMappers 中找到了对应的value —— MapperProxyFactory(BlogMapper) 对象,然后调用这个对象的newInstance()方法。根据这个名字,我们就能猜到这个方法是创建了一个对象。
public class MapperProxyFactory<T> { //映射器代理工厂
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
//删除不必要代码
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//使用了JDK自带的动态代理生成映射器代理类的对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
最终是通过Proxy.newProxyInstance产生了一个OrdersMapper1的代理对象。Mybatis 为了完成 Mapper 接口的实现,运用了代理模式。具体是使用了JDK动态代理,这个Proxy.newProxyInstance方法生成代理类的三个要素是:
ClassLoader —— 指定当前接口的加载器即可
当前被代理的接口是什么 —— 这里就是OrdersMapper1
代理类是什么 —— 这里就是 MapperProxy
代理模式中,代理类(MapperProxy)中才真正的完成了方法调用的逻辑。我们贴出MapperProxy的代码
public class MapperProxy<T> implements InvocationHandler, Serializable {
//实现了InvocationHandler接口
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
//代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
try {
return method.invoke(this, args);// 注意1
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method); // 使用了缓存
//执行CURD
return mapperMethod.execute(sqlSession, args);// 注意2
}
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;
}
}
Blog blog =mapper.getOrdersById(3); 实际上最后是会调用这个MapperProxy的invoke方法。这段代码中,if 语句先判断,我们想要调用的方法是否来自Object类,这里的意思就是,如果我们调用toString()方法,那么是不需要做代理增强的,直接还调用原来的method.invoke()就行了。只有调用getOrdersById之类的方法的时候,才执行增强的调用——即mapperMethod.execute(sqlSession, args);
而mapperMethod.execute(sqlSession, args);这句最终就会执行增删改查了
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;
}
// 删除部分代码
}
// 删除部分代码
return result;
}
Mapper接口是如何注册的,Mapper接口是如何产生动态代理对象的,Maper接口方法最终是如何执行的。总结起来主要就是这几个点:
- Mapper 接口在初始SqlSessionFactory 注册的。
- Mapper 接口注册在了名为 MapperRegistry 类的 HashMap中, key = Mapper class value = 创建当前Mapper的工厂。
- Mapper 注册之后,可以从SqlSession中get
- SqlSession.getMapper 运用了 JDK动态代理,产生了目标Mapper接口的代理对象。
- 动态代理的 代理类是 MapperProxy ,这里边最终完成了增删改查方法的调用