静态代理和动态代理

Java 静态代理

静态代理通常用于对原有业务逻辑的扩充。比如持有二方包的某个类,并调用了其中的某些方法。然后出于某种原因,比如记录日志、打印方法执行时间,但是又不好将这些逻辑写入二方包的方法里。所以可以创建一个代理类实现和二方方法相同的方法,通过让代理类持有真实对象,然后在原代码中调用代理类方法,来达到添加我们需要业务逻辑的目的。

这其实也就是代理模式的一种实现,通过对真实对象的封装,来实现扩展性。

一个典型的代理模式通常有三个角色,这里称之为代理三要素

共同接口

public interface Action {
    public void doSomething();
}

真实对象

public class RealObject implements Action{

    public void doSomething() {
        System.out.println("do something");
    }
}

代理对象

public class Proxy implements Action {
    private Action realObject;

    public Proxy(Action realObject) {
        this.realObject = realObject;
    }
    public void doSomething() {
        System.out.println("proxy do");
        realObject.doSomething();
    }
}

运行代码

Proxy proxy = new Proxy(new RealObject());
proxy.doSomething();

这里写图片描述

这种代理模式也最为简单,就是通过proxy持有realObject的引用,并进行一层封装。

静态代理的优点和缺点

优点: 扩展原功能,不侵入原代码。

缺点:

假如有这样一个需求,有十个不同的RealObject,同时我们要去代理的方法是不同的,比要代理方法:doSomething、doAnotherThing、doTwoAnotherThing,添加代理前,原代码可能是这样的:

realObject.doSomething();
realObject1.doAnotherThing();
realObject2.doTwoAnother();

为了解决这个问题,我们有方案一:

为这些方法创建不同的代理类,代理后的代码是这样的:

proxy.doSomething();
proxy1.doAnotherThing();
proxy2.doTwoAnother();

当然,也有方案二:

通过创建一个proxy,持有不同的realObject,实现Action1、Action2、Action3接口,来让代码变成这样:

proxy.doSomething();
proxy.doAnotherThing();
proxy.doTwoAnother();

于是你的代理模型会变成这样:
这里写图片描述

毫无疑问,仅仅为了扩展同样的功能,在方案一种,我们会重复创建多个逻辑相同,仅仅RealObject引用不同的Proxy。

而在方案二中,会导致proxy的膨胀,而且这种膨胀往往是无意义的。此外,假如方法签名是相同的,更需要在调用的时候引入额外的判断逻辑。

Java 动态代理

搞清楚静态代理的缺点十分重要,因为动态代理的目的就是为了解决静态代理的缺点。通过使用动态代理,我们可以通过在运行时,动态生成一个持有RealObject、并实现代理接口的Proxy,同时注入我们相同的扩展逻辑。哪怕你要代理的RealObject是不同的对象,甚至代理不同的方法,都可以动过动态代理,来扩展功能。

简单理解,动态代理就是我们上面提到的方案一,只不过这些proxy的创建都是自动的并且是在运行期生成的。

动态代理的基本用法

动态代理可通过jdk动态代理和cglib动态代理来实现。

  • jdk动态代理:由java内部的反射机制来实现,基于接口
  • cglib动态代理:通过asm来实现,基于类

使用动态代理,需要将要扩展的功能写在一个InvocationHandler实现类里:

public class DynamicProxyHandler implements InvocationHandler {
    private Object realObject;

    public DynamicProxyHandler(Object realObject) {
        this.realObject = realObject;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //代理扩展逻辑
        System.out.println("proxy do");

        return method.invoke(realObject, args);
    }
}

这个Handler中的invoke方法中实现了代理类要扩展的公共功能。

到这里,需要先看一下这个handler的用法:

public static void main(String[] args) {
        RealObject realObject = new RealObject();
        Action proxy = (Action) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Action.class}, new DynamicProxyHandler(realObject));
        proxy.doSomething();
}

Proxy.newProxyInstance 传入的是一个ClassLoader, 一个代理接口,和我们定义的handler,返回的是一个Proxy的实例。

仔细体会这个过程,其实有点类似我们在静态代理中提到的方案一,生成了一个包含我们扩展功能,持有RealObject引用,实现Action接口的代理实例Proxy。只不过这个Proxy不是我们自己写的,而是java帮我们生成的,有没有一点动态的味道。
这里写图片描述
让我们再回顾一下代理三要素:

  • 真实对象:RealObject
  • 代理接口:Action
  • 代理实例:Proxy

上面的代码实含义也就是,输入 RealObject、Action,返回一个Proxy。妥妥的代理模式。

综上,动态生成+代理模式,也就是动态代理。

动态代理的优点和美中不足

  • 优点:

动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。在本示例中看不出来,因为invoke方法体内嵌入了具体的外围业务(记录任务处理前后时间并计算时间差),实际中可以类似Spring AOP那样配置外围业务。

  • 美中不足:

诚然,Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。

转载自《静态代理和动态代理的理解》

### 静态代理动态代理的概念、区别及使用场景 #### 1. 概念 静态代理动态代理代理模式的两种实现方式。代理模式的核心思想是通过一个代理对象来控制对目标对象的访问,从而实现功能扩展或访问控制。 - **静态代理**是指在编译时就已经确定了代理代理类的关系[^1]。代理类需要手动编写,并且必须实现与被代理类相同的接口。 - **动态代理**则是在运行时动态生成代理类[^2],无需在编译前手动编写代理类代码。动态代理通常通过 Java 的 `java.lang.reflect.Proxy` 类或其他框架(如 CGLIB)实现。 #### 2. 区别 静态代理动态代理的主要区别体现在以下几个方面: - **定义时间**: - 静态代理代理类在编译时就已经确定[^3]。 - 动态代理代理类是在运行时动态生成的[^1]。 - **灵活性**: - 静态代理由于代理类是固定的,因此不够灵活,每次新增接口或修改接口时都需要重新编写代理类。 - 动态代理可以适应多种接口的变化,具有更高的灵活性可扩展性[^3]。 - **性能**: - 静态代理因为代理类是提前编译好的,所以在执行效率上可能略高于动态代理。 - 动态代理由于需要在运行时生成代理类,可能会引入一定的性能开销,但现代 JVM 已经对此进行了优化[^1]。 - **适用场景**: - 静态代理适合于接口较少且相对固定的情况。 - 动态代理更适合于接口较多或经常变化的场景,能够减少代码冗余并提高开发效率[^3]。 #### 3. 使用场景 - **静态代理**: - 当代理类的功能较为简单,且接口不常变化时,可以使用静态代理[^1]。 - 示例:日志记录、权限校验等简单的功能增强[^2]。 - **动态代理**: - 在需要为多个类提供统一的代理逻辑时,动态代理更加合适[^3]。 - 示例:AOP(面向切面编程)、事务管理、远程方法调用等复杂场景。 #### 示例代码 以下分别展示了静态代理动态代理的实现方式。 ##### 静态代理示例 ```java // 定义接口 interface Rent { void rent(); } // 真实主题类 class RealRent implements Rent { @Override public void rent() { System.out.println("房东出租房子"); } } // 静态代理类 class ProxyRent implements Rent { private Rent realRent; public ProxyRent(Rent realRent) { this.realRent = realRent; } @Override public void rent() { System.out.println("中介开始找房..."); realRent.rent(); System.out.println("中介收取佣金"); } } // 测试 public class StaticProxyTest { public static void main(String[] args) { Rent realRent = new RealRent(); Rent proxyRent = new ProxyRent(realRent); proxyRent.rent(); // 输出:中介开始找房... 房东出租房子 中介收取佣金 } } ``` ##### 动态代理示例 ```java import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; // 定义接口 interface Rent { void rent(); } // 真实主题类 class RealRent implements Rent { @Override public void rent() { System.out.println("房东出租房子"); } } // 动态代理类 class DynamicProxyHandler implements InvocationHandler { private Object target; public DynamicProxyHandler(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 class DynamicProxyTest { public static void main(String[] args) { Rent realRent = new RealRent(); InvocationHandler handler = new DynamicProxyHandler(realRent); Rent proxyRent = (Rent) Proxy.newProxyInstance( realRent.getClass().getClassLoader(), realRent.getClass().getInterfaces(), handler ); proxyRent.rent(); // 输出:中介开始找房... 房东出租房子 中介收取佣金 } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值