目录
前言
在开发中使用代理模式,可以在不修改原始对象代码的情况下,为其增加新的操作或功能,符合软件设计的开闭原则。
代理模式可分为静态代理和动态代理
静态代理
在编译时就已经确定代理类和真实对象类。这种方式只能用在功能固定,不会频繁变动的场景,在实际使用中并不常见。而且在静态代理中,代理类直接依赖了具体的真实对象类,这也违背了依赖倒置原则,故静态代理只需简单了解即可。
动态代理
是一种更加灵活的代理模式实现方式。在动态代理中,代理类是在运行时动态生成的,而不需要提前编写好代理类的代码,相比静态代理,更加通用。
动态代理两种实现方式
JDK 动态代理,实现被代理对象的接口,生成代理类效率高,执行方法效率低。
Cglib 动态代理,继承被代理对象,生成代理类效率低,执行方法效率高。
两种动态代理方式都能实现目的,只有在特殊情况下才需要使用特定的方式,例如被代理的类没有实现接口,那么就只能够使用 cglib 动态代理了。在 spring aop 中,就是当代理类没有实现接口时,就使用 cglib 动态代理,否则使用 JDK 动态代理。
UML
plantuml
@startuml 'https://plantuml.com/class-diagram interface Subject { + method() : void } class RealSubject { + method() : void } class Proxy { - realSubject : RealSubject + Proxy(RealSubject) + method() : void } class Client {} Subject <|.. RealSubject Subject <|.. Proxy Proxy "1" --> "1" RealSubject Client ..> Subject @enduml
类图
实战代码
mybatis
mybatis 可以配置 是否开启 debug 日志,如果开启了,那么在查询时则会在控制台输出相应的操作日志。这个功能就是用 JDK 动态代理实现的
核心方法 BaseExecutor 的 getConnection 方法
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
//如果开启了debug,则返回代理对象,在每次操作前都输出日志
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
}
return connection;
}
ConnectionLogger
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 {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
if ("prepareStatement".equals(method.getName()) || "prepareCall".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeExtraWhitespace((String) params[0]), true);
}
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
return PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
}
if ("createStatement".equals(method.getName())) {
Statement stmt = (Statement) method.invoke(connection, params);
return StatementLogger.newInstance(stmt, statementLog, queryStack);
} else {
return method.invoke(connection, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
/**
* Creates a logging version of a connection.
*
* @param conn
* the original connection
* @param statementLog
* the statement log
* @param queryStack
* the query stack
*
* @return the connection with logging
*/
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);
}
/**
* return the wrapped connection.
*
* @return the connection
*/
public Connection getConnection() {
return connection;
}
}
spring aop
spring 的 aop 就是通过动态代理实现的
动态代理在实际工作中的运用,大多数都可以通过 spring 的 aop 来实现。例如:
- 记录日志
- 查询缓存
- 校验权限
使用spring 的 aop,使得开发人员能更加关注具体功能的实现,不用关心代理类如何生成,更加关注业务。这里只列举动态代理能实现的功能,aop 则不在这里详述。
JDK动态代理
spring 的 aop 是另外的专题了,这里重新回到动态代理的实现,在不用 aop 的前提下,使用 JDK动态代理,实现输出方法耗时的功能增强
public class JdkAopTest {
interface IService {
void m();
}
public static class Service implements IService {
@Override
public void m() {
System.out.println("我是m");
}
}
public static class CostTimeInvocationHandler implements InvocationHandler {
private final Object target;
public CostTimeInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.nanoTime();
Object result = method.invoke(this.target, args); //将请求转发给target去处理
System.out.println(method + ",耗时(纳秒):" + (System.nanoTime() - startTime));
return result;
}
}
public static void main(String[] args) {
Service target = new Service();
CostTimeInvocationHandler costTimeInvocationHandler = new CostTimeInvocationHandler(target);
//创建代理对象
Object proxyObject = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{IService.class},
costTimeInvocationHandler);
//将代理转换为IService类型
IService service = (IService) proxyObject;
//调用IService的m方法
service.m();
}
}
cglib动态代理
cglib动态代理不用依赖真实对象实现的接口就能创建代理类,而且还能实现延迟加载的功能。
例如在处理大文件时,先返回代理对象,然后在用户实际需要查看时才加载,从而减少了初始化加载时间以及不必要的资源浪费。
public class LazyLoaderTest {
public static class ContentModel {
private String content;
public ContentModel() {}
public void setContent(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ContentModel.class);
//创建一个LazyLoader对象
LazyLoader lazyLoader = () -> {
System.out.println("调用LazyLoader.loadObject()方法");
//此处模拟从数据库中获取内容
System.out.println("开始从数据库中获取内容.....");
ContentModel contentModel = new ContentModel();
contentModel.setContent("many many char");
return contentModel;
};
enhancer.setCallback(lazyLoader);
Object proxy = enhancer.create();
ContentModel contentModel = (ContentModel) proxy;
System.out.println("第1次调用getContent方法");
System.out.println(contentModel.getContent());
System.out.println("第2次调用getContent方法");
System.out.println(contentModel.getContent());
}
}