Mybatis技术内幕-设计模式与应用场景总结
2、代理模式和JDK动态代理
静态代理模式
静态代理模式中涉及到的角色:
Suject:是程序中的业务逻辑接口;
RealSubject:是实现了Subject接口的真正业务类;
Proxy:是实现了Subject接口的代理类,其中封装了RealSubject对象。在程序中不会直接调动RealSubject对象的方法,而是使用Proxy对象实现相关功能。
代理模式:Proxy.operation()方法的实现会调用RealSubject对象的operation()方法执行真正的逻辑,但是处理完业务逻辑。Proxy.operation()会在RealSubject.operation()方法调用前后进行预处理和相关的后置处理。
作用:可以控制程序对RealSubject对象的访问,或是在执行业务处理的前后进行预处理和后置处理。
应用:延迟加载;
缺点:在编译阶段就要为每个RealSuject类创建一个对应的Proxy类,当需要代理的类很多时,这就出现大量的Proxy类。
演示代码:
public interface Subject {
void operation();
}
public class RealSubject implements Subject {
@Override
public void operation() {
System.out.println("realSubject operation");
}
}
public class Proxy implements Subject {
RealSubject realSubject;
@Override
public void operation() {
System.out.println("before.....");
realSubject.operation();
System.out.println("after.....");
}
}
动态代理模式
解决静态中代理类过多的问题,通过动态创建代理类并且通过指定类加载器加载,然后在创建代理对象时将InvokerHandler.invoke()方法,并最终调用真正业务对象的相应方法。
代码演示:
/**
* 对于需要相同代理行为的业务类,只需要提供一个InvocationHandler实现即可。
* 在程序运行时,JDK会为每一个RealSubject类动态生成代理类并加载到JVM中,之后创建对应的代理对象
*/
public class TestInvokerHandler implements InvocationHandler {
//真正的业务对象,也就是RealSubject对象
private Object target;
public TestInvokerHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("在执行业务方法之前的前置处理....");
//真正的业务执行过程
Object result = method.invoke(target, args);
System.out.println("在执行业务方法之后的后置处理....");
return result;
}
public Object getProxy() {
/**
* 创建代理对象
* 1、加载动态生成的代理类的类加载器
* 2、业务类实现的接口
* 3、InvokerHandler对象(JDK动态代理的核心)
*/
return java.lang.reflect.Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
target.getClass().getInterfaces(), this);
}
public static void main(String[] args) {
Subject subject = new RealSubject();
TestInvokerHandler testInvokerHandler = new TestInvokerHandler(subject);
//获取代理对象
Subject proxy = (Subject) testInvokerHandler.getProxy();
//调用代理对象的方法,它会调用TestInvokeHandler.invoke()方法
//invoke中会调用目标类的真正业务执行方法
proxy.operation();
}
}
Mybatis场景
JDBC调试:在Mybatis的日志模块中有一个JDBC包,它并不是将日志信息通过JDBC保存到数据库中,而是通过JDK动态代理的方式,将JDBC操作通过指定的日志框架打印出来,这个功能通常在开发阶段使用,它可以输出SQL语句,用户传入的绑定参数,SQL语句影响行数等等信息,对调试程序来说是很重要的。
ConnectionLogger继承了BaseJdbcLogger抽象类,其中封装了Connection对象并且同时实现了InvocationHandler接口。ConnectionLogger.newInstance()方法会为其封装的Connection对象创建相应的代理对象。
invoke()方法是代理对象的核心方法,它为preparedStatement(),preparedCall(),createStatement()等等方法了代理。
源码:
package org.apache.ibatis.logging.jdbc;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.reflection.ExceptionUtil;
public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {
private final Connection connection;
private ConnectionLogger(Connection conn, Log statementLog, int queryStack) {
super(statementLog, queryStack);
this.connection = conn;
}
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
//如果调用的是从Object继承的方法,则直接调用,不做任何处理
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
//调用指定方法的时候,创建相应的Statement对象,并为该Statement创建代理对象并返回该代理对象
if ("prepareStatement".equals(method.getName()) || "prepareCall".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeExtraWhitespace((String) params[0]), true);
}
//调用Connection封装的preparedStatement()方法,得到PreparedStatement对象
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
//为该PreparedStatement对象创建代理对象
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("createStatement".equals(method.getName())) {
Statement stmt = (Statement) method.invoke(connection, params);
stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else {
//其他方法则直接调用底层Connection对象的相应方法
return method.invoke(connection, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
ClassLoader cl = Connection.class.getClassLoader();
return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);
}
public Connection getConnection() {
return connection;
}
}