【Java高级】深度解析Java动态代理(实战MyBatis手动
本文为原创文章,转载请注明出处
查看[Java]系列内容请点击:https://www.jianshu.com/nb/45938443
我们知道,代理模式一般分为静态代理和动态代理模式,静态代理模式这里还是简单提一下:
我们在以前进行MVC编程的时候,一个好的编程习惯是:从数据库里面查询需要有三个组件:
- 一个接口A:A是用来定义对数据进行操作的接口
- 一个数据实现
Class AImpl implements A
:即MVC中的Model层,用来实际对数据库进行操作和数据处理- 一个代理
Class AProxy implements A
:用来代理AImpl
,从而为Model提供数据源、提供更多处理等操作
在真正使用的时候一般使用AProxy来进行数据操作,这样就构成了整个MVC的Model层。
上面的方法是静态代理。
动态代理
动态代理与静态代理不同,想想看,静态代理是自己写的代理类,在程序加载之前代理类就存在了,而动态代理是在运行时动态的生成的一种代理类,它可以为代理的类提供更多的额外服务。
一个简单的例子,拿大家最经常使用的MyBatis来说,在MyBatis里面获取一个Mapper
是怎么获取的呢?我们来手动获取一个SqlSession
和Mapper
:
public static <T> T getMapper(Class<T> var) {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
org.apache.ibatis.session.SqlSession session = sqlSessionFactory.openSession(true); // 自动提交,但不会自动关闭
return session.getMapper(var);
}
可以看到,这里获取SqlSession
只会自动提交SQL,不会自动关闭SqlSession
,这里我们仅仅为了执行一次SQL,所以只获取了Mapper而没有获取到SqlSession,所以我们在使用的时候也无法进行关闭SqlSession
MyBatis是如何获取Mapper的?
我们看这一句:
return session.getMapper(var);
这里就是调用了Mybatis的MapperProxy
通过动态代理的方式获取的:
package org.apache.ibatis.binding;
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;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = 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);
}
// more methods...
}
可以看到,MapperProxy
类继承了 InvocationHandler
接口,并实现了invoke
方法,并在invoke
方法中通过反射的方式对需要调用的方法进行了进一步的处理。
动态代理要做什么?
动态代理实际上就是要在一个对象obj1
的基础上重新生成一个对象obj2
,obj2
与obj1
具有相同的类型,不同的是,obj2
可以在invoke
方法中对obj1
的方法进行一些其他的操作。
动态代理主要依赖Proxy
类和InvocationHandler
接口,下面的程序演示了将Apple
对象代理到另外一个对象,并在调用Apple
对象的方法之前和之后进行一些打印操作。
/**
* 通用接口
* */
public interface Fruit {
String getName();
String getColor();
}
/**
* 通用接口
* */
public class Apple implements Fruit {
@Override
public String getName() {
return "apple";
}
@Override
public String getColor() {
return "red";
}
}
/**
* 代理类
* */
public class FruitProxy implements InvocationHandler {
private Object obj1; // obj1
public FruitProxy(Object o) {
this.obj1 = o;
}
/**
* @param proxy 被代理后的对象,而不是代理之前的对象,这里就是指的obj2对象
* @param method 被代理obj1的方法
* @param args 被代理的方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("--- before ---");
Object result = method.invoke(this.obj1, args); // 调用obj1对象的方法
System.out.println("method:" + method.getName() + " result:" + result); // 打印被调用的方法信息
System.out.println("--- after ---");
return result;
}
}
/**
* 测试程序
* */
public class Main {
public static void main(String[] args) {
Fruit obj1 = new Apple();
Fruit obj2 = (Fruit) Proxy.newProxyInstance(FruitProxy.class.getClassLoader(), new Class[]{Fruit.class}, new FruitProxy(obj1));
System.out.println(obj2.getName());
System.out.println(obj2.getColor());
}
}
输出结果为:
--- before ---
method:getName result:apple
--- after ---
apple
--- before ---
method:getColor result:red
--- after ---
red
可以看到,在进行方法调用的时候,已经在前后进行了更多的处理。由于invoke
方法的参数在注释中介绍了,下面着重介绍Proxy
的方法:
Proxy.newProxyInstance方法:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
- 该方法第一个参数是类加载器,无论使用当前类的类加载器还是被代理的类的类加载器,一般使用的都是应用类加载器,效果都一样,这里不再过多介绍。
- 第二个参数是被代理的接口列表,是一个
Class
数组,是被代理的对象锁继承的接口列表,注意必须是接口才行!!!不是接口必须使用CGLIB代理(这里不介绍),Proxy
会通过反射机制来获取其中的方法,并进行代理。这里也是使用了运行时多态。- 第三个参数是代理处理器的对象,也就是继承了
InvocationHandler
接口的对象,Proxy
会调用其中的invoke
方法。
动态代理MapperProxy
好了,有了上述的基础后,我们基于Mybatis来实现一个可自动关闭的Mapper获取方法,想要实现的效果是:每次都重新获取一个SqlSession
,执行完一条SQL后自动关闭:
首先来代理Mybatis的MapperProxy
public class MapperProxy<T> extends org.apache.ibatis.binding.MapperProxy<T> {
private static final Logger log = Logger.getLogger(MapperProxy.class);
private SqlSession sqlSession;
private Class<T> mapperInterface;
private static Map<Class, Map<String, Object>> methodsMap = new HashMap<>(); // 记录需要close的方法的名称
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
super(sqlSession, mapperInterface, methodCache);
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
// 设置需要关闭的接口名称
if (!methodsMap.containsKey(mapperInterface)) {
Map<String, Object> methodNames = new HashMap<>();
Arrays.stream(mapperInterface.getDeclaredMethods()).forEach(m -> methodNames.put(m.getName(), null));
methodsMap.put(mapperInterface, methodNames);
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object obj = null;
try {
obj = super.invoke(proxy, method, args);
} catch (Throwable t) {
throw t;
} finally {
if (methodsMap.containsKey(mapperInterface) && methodsMap.get(mapperInterface).containsKey(method.getName())) {
// log.info("now close sqlsession");
sqlSession.close();
}
}
return obj;
}
}
我们自己写的MapperProxy
代理了Mybatis的MapperProxy
,在每次调用Mapper的方法完成后会在finally
里面关闭SqlSession
使用方法:
/**
* 一次性执行的Mapper
*/
public static <T> T getMapper(Class<T> var1) {
if (sqlSessionFactory == null) {
initSession();
}
org.apache.ibatis.session.SqlSession session = sqlSessionFactory.openSession(true);
// 利用动态代理添加自动关闭 SqlSession
MapperProxy<T> mapperProxy = new MapperProxy<T>(session, var1, new HashMap<>());
return (T) Proxy.newProxyInstance(var1.getClassLoader(), new Class[]{var1}, mapperProxy);
}
好了,这样,在每次执行Mapper
的方法的时候就会自动关闭了。
实际上这里是有两次代理,一次是Mybatis自己的MapperProxy
的代理,生成了Mapper
,另外一次是我们自己的代理,将Mapper
代理成可以自动关闭的Mapper