动态代理详解

推荐你阅读
互联网大厂万字专题总结
Redis总结
JUC总结
操作系统总结
JVM总结
Mysql总结
微服务总结
互联网大厂常考知识点
什么是系统调用
CPU底层锁指令有哪些
AQS与ReentrantLock原理
旁路策略缓存一致性
Java通配符看这一篇就够
Java自限定泛型
动态代理详解
技术分享
如何vscode中刷力扣
如何用Jmeter压测
如何将wsl配置为一台局域网服务器

Apple 类实现了 Fruit 接口,我们对 Apple 类进行代理。无论是静态代理还是动态代理,代理的本质都是我们不需要修改被代理类源码不需要重新编译被代理类,通过增加代理类的方式来完成被代理类功能的修改增强

interface Fruit {
    public void eat();
}

class Apple implements Fruit {

    @Override
    public void eat() {
        System.out.println("eat apple");
    }
}

静态代理

静态代理需要自己编写代理类代码,经过编译生成代理类字节码文件,再通过类加载器将字节码文件加载到内存。缺点是每当我们想要去代理某个类的时候都要编写一个代理类,还有就是当被代理类修改的时候,代理类的代码也需要对应修改。

public class Solution {
    public static void main(String[] args) {
        Fruit fruit = new Proxy(new Apple());
        fruit.eat();
    }
}

class Proxy implements Fruit {
    private Apple apple;

    public Proxy(Apple apple) {
        this.apple = apple;
    }

    @Override
    public void eat() {
        before();
        apple.eat();
        after();
    }

    private void before() {
        System.out.println("eat before");
    }

    private void after() {
        System.out.println("eat after");
    }
}

动态代理

jdk 动态代理

只能代理增强接口方法,在内存中动态生成字节码文件并进行类加载,采用反射调用的方式因此生成字节码较快但代理方法执行较慢。

原理

JVM 动态生成一个类也实现了这个接口,类中静态属性保存了该接口的所有 method 对象,每个接口方法的实现都是调用 InvocationHandler 的 invoke 方法并传入接口方法的 method 形参 args,我们需要实现 InvocationHandler 的 invoke 方法并在其中利用反射完成被代理对象的方法调用,在此基础上可以自由地进行增强。
Proxy.newProxyInstance 的第一个参数是被代理接口的类加载器对象,第二个参数是被代理接口的 class 对象,第三个参数是 InvocationHandler 的实现对象。

jdk 动态代理相比静态代理的优势

jdk 动态代理相比于静态代理的优势在于,不需要自己编写代理类了,通过实现 InvocationHandler 接口的方式即可灵活完成任意类任意方法的代理增强。

public class Solution {
    public static void main(String[] args) {
        FruitHandler fruitHandler = new FruitHandler(new Apple());
        Fruit fruit = (Fruit) Proxy.newProxyInstance(Fruit.class.getClassLoader(), new Class[]{Fruit.class}, fruitHandler);
        fruit.eat();
    }
}

class FruitHandler implements InvocationHandler {
    private Object target;

    public FruitHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(this.target, args);
        after();
        return result;
    }

    private void before() {
        System.out.println("eat before");
    }

    private void after() {
        System.out.println("eat after");
    }
}

我们在虚拟机参数中加入-Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true,可以保存动态生成的代理类字节码文件,反编译可以看到如下结果。从中可以清晰的看到上述原理提到的整个过程,也可以看出是每一个接口对应生成一个代理类。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

final class $Proxy0 extends Proxy implements Fruit {
    private static final Method m0;
    private static final Method m1;
    private static final Method m2;
    private static final Method m3;

    public $Proxy0(InvocationHandler var1) {
        super(var1);
    }

    public final int hashCode() {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final boolean equals(Object var1) {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void eat() {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("Fruit").getMethod("eat");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

    private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup var0) throws IllegalAccessException {
        if (var0.lookupClass() == Proxy.class && var0.hasFullPrivilegeAccess()) {
            return MethodHandles.lookup();
        } else {
            throw new IllegalAccessException(var0.toString());
        }
    }
}

cglib 动态代理

cglib 是利用 ASM 框架实现的字节码增强,也是在内存中动态生成字节码文件并进行类加载,但是采用直接增强被代理类的方式,不经过反射因此生成字节码较慢但代理方法执行较快。

原理

按照 MethodInterceptor 的 intercept 方法中所编写的具体增强方式,利用字节码生成技术直接生成被代理类的子类并完成相应方法的增强。
使用时要引入 maven 包

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>

具体方式如下

public class CglibProxy {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(Apple.class.getClassLoader());
        enhancer.setSuperclass(Apple.class);
        enhancer.setCallback(new AppleMethodIntercepter());
        Apple apple = (Apple) enhancer.create();
        String result = apple.eat("water");
        System.out.println("result is " + result);
    }

    private static class AppleMethodIntercepter implements MethodInterceptor {

        @Override
        public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            before();
            Object result = methodProxy.invokeSuper(o, args);
            after();
            return result;
        }

        private void before() {
            System.out.println("before...");
        }

        private void after() {
            System.out.println("after...");
        }
    }
}
class Apple implements Fruit {
    @Override
    public String eat(String food) {
        System.out.println("eat " + food);
        return "eat " + food;
    }
}

jdk 动态代理对比 cglib 动态代理

jdk 动态代理只可以增强接口方法,由于涉及反射调用因此生成快调用慢,cglib 动态代理可以增强任意类方法,其利用 asm 框架实现,不属于 jdk 原生实现,采用直接增强方式因此生成慢调用快

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值