代理模式实战运用(动态代理)

目录

前言

静态代理

动态代理

UML

plantuml

类图

实战代码

mybatis

spring aop

JDK动态代理

cglib动态代理


前言

在开发中使用代理模式,可以在不修改原始对象代码的情况下,为其增加新的操作或功能,符合软件设计的开闭原则。

代理模式可分为静态代理和动态代理

静态代理

在编译时就已经确定代理类和真实对象类。这种方式只能用在功能固定,不会频繁变动的场景,在实际使用中并不常见。而且在静态代理中,代理类直接依赖了具体的真实对象类,这也违背了依赖倒置原则,故静态代理只需简单了解即可。

动态代理

是一种更加灵活的代理模式实现方式。在动态代理中,代理类是在运行时动态生成的,而不需要提前编写好代理类的代码,相比静态代理,更加通用。

动态代理两种实现方式

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 来实现。例如:

  1. 记录日志
  2. 查询缓存
  3. 校验权限

使用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());
    }
}

  • 30
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值