动态代理有哪些神仙

动态代理是一种以动态方式创建代理对象的设计模式,它可以在运行时根据需要实现代理对象的创建、调用和销毁,在使用时再创建代理类和实例。

动态代理分为基于接口的动态代理(被代理的对象必须实现一个或多个接口)和基于类的动态代理(这种代理方式不依赖于接口,而是直接基于类来实现,基于的类不能为final)。

根据实现方式的不同,动态代理主要可以分为以下几类:

1.JDK自带的动态代理

这种代理方式主要依赖于Java的反射机制。在Java中,动态代理主要通过两个核心类来实现:Proxy和InvocationHandler。Proxy类用于动态创建代理类,而InvocationHandler接口负责处理代理对象的方法调用。通过实现InvocationHandler接口,开发人员可以在代理对象的方法调用前后添加自定义的逻辑,实现对原始对象的控制和增强。这种代理方式要求被代理的对象必须实现一个或多个接口。这种代理是基于接口的动态代理。

1)优点

只需要1个动态代理类就可以解决创建多个静态代理的问题,避免重复、多余代码,更强的灵活性。

2)缺点

效率低,相比静态代理中 直接调用目标对象方法,动态代理则需要先通过Java反射机制 从而 间接调用目标对象的方法,因为 Java 的单继承特性(每个代理类都继承了 Proxy 类),即只能针对接口 创建 代理类,不能针对类创建代理类。

java 复制代码
// 定义一个接口  
interface MyInterface {  
    void execute();  
}  
  
// 实现InvocationHandler接口  
class MyInvocationHandler implements InvocationHandler {  
    private Object target;  
  
    public MyInvocationHandler(Object target) {  
        this.target = target;  
    }  
  
    @Override  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
		//调用目标对象的方法之前处理
        System.out.println("Before method invocation");  
		// 调用目标对象的方法
        Object result = method.invoke(target, args);   
		//调用目标对象的方法之后处理
        System.out.println("After method invocation");  
        return result;  
    }  
}  
  
public class JDKDynamicProxyTest {  
    public static void main(String[] args) {  
        // 创建目标对象  
        MyInterface myObject = new MyInterface() {  
            @Override  
            public void execute() {  
                System.out.println("do something...");  
            }  
        };  
  
        // 创建InvocationHandler实例  
        InvocationHandler handler = new MyInvocationHandler(myObject);  
  
        // 创建动态代理实例  
        MyInterface proxy = (MyInterface) Proxy.newProxyInstance(  
                MyInterface.class.getClassLoader(),  
                new Class<?>[]{MyInterface.class},  
                handler  
        );  
  
        // 通过代理实例调用方法,会触发InvocationHandler的invoke方法  
        proxy.execute();  
    }  
}

2.CGLib动态代理

CGLib(Code Generation Library)是一个强大的高性能的代码生成库,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为它们提供方法的拦截。

CGLib 代理可以代理没有接口的类,但无法代理 final 类和方法。

java 复制代码
public class CGLibProxyTest implements MethodInterceptor {
        private Object target; // 代理对象

        public Object getInstance(Object target) {
            this.target = target;
            Enhancer enhancer = new Enhancer();
            // 设置父类
            enhancer.setSuperclass(this.target.getClass());
            // 回调方法
            enhancer.setCallback(this);
            // 创建代理对象
            return enhancer.create();
        }

        @Override
        public Object intercept(Object o, Method method,
                                Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("方法调用前处理");
            Object result = methodProxy.invokeSuper(o, objects); // 执行方法调用
            return result;
        }
}

3.Javassist动态代理

Javassist(Java programming assistant)是一个开源的分析、编辑和创建Java字节码的库。它已经被许多其他开源项目所使用,例如CGLib。Javassist是jboss的一个子项目,其主要的优点,在于简单快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

java 复制代码
public class JavassistTest {  
    public static void main(String[] args) throws Exception {  
        // 获取Javassist的ClassPool  
        ClassPool pool = ClassPool.getDefault();  
  
        // 创建一个新的类  
        CtClass cc = new CtClass(pool, "com.xx.User");  
  
        // 为新类添加一个新方法  
        CtMethod newMethod = new CtMethod(CtClass.voidType, "newMethod", new CtClass[]{}, cc);  
        newMethod.setModifier(Modifier.PUBLIC);  
        newMethod.setBody("{ System.out.println(\"Hello world!\"); }");  
        cc.addMethod(newMethod);  
  
        // 生成类的.class文件  
        cc.writeFile("/home/xx/directory");  
  
    }  
}

4.AspectJ

AspectJ是一个强大的面向切面编程(AOP)框架,它可以在编译时或运行时修改Java字节码,从而实现更为复杂的代理功能。

java 复制代码
@Aspect  
public class UserAspect {  
  
    // 定义切点,匹配 TestService 类中的所有方法  
    @Pointcut("execution(* com.xx.TestService.*(..))")  
    public void aMethod() {}  
  
    // 在方法执行前执行
    @Before("aMethod()")  
    public void beforeMethod(JoinPoint joinPoint) {  
        System.out.println("Before method: " + joinPoint.getSignature().getName());  
    }  
  
    // 在方法执行后执行
    @After("aMethod()")  
    public void afterMethod(JoinPoint joinPoint) {  
        System.out.println("After method: " + joinPoint.getSignature().getName());  
    }  
}

5.Spring AOP

Spring框架也提供了对AOP的支持,它基于AspectJ的切点表达式语言,但不需要使用AspectJ的编译器或织入器。Spring AOP通过代理模式实现,可以在运行时为指定的bean创建代理。

Spring 框架中同时使用了两种动态代理 JDK Proxy 和 CGLib,当 Bean 实现了接口时,Spring 就会使用 JDK Proxy,在没有实现接口时就会使用 CGLib,我们也可以在配置中指定强制使用 CGLib,只需要在 Spring 配置中添加 <aop:aspectj-autoproxy proxy-target-class="true"/> 即可。

java 复制代码
@Aspect
public class AroundTest {
    @Pointcut("execution(* com.xx.ClassName.perform(..))")
    public void perform() {
    }

    @Around("perform()")
    public void executeMethod(ProceedingJoinPoint joinPoint) {
        try {
            System.out.println("abc");

            joinPoint.proceed();

            System.out.println("over");
        } catch (Throwable throwable) {
            System.out.println("exception occurred");
        } finally {
            System.out.println("finish");
        }
    }
}

6.ASM 字节码操作库

ASM 是一个低级的字节码操作和分析框架,可以直接生成或修改字节码文件。虽然不是专门用于动态代理的库,但可以通过它来手动编写字节码生成器,实现高度定制化的代理功能。

7.ByteBuddy

ByteBuddy 是一个更高层次的代码生成和操作库,它提供了简洁的 API 来创建和修改 Java 类和方法。它提供了更强大的代理功能,包括基于接口和基于类的代理,以及对方法拦截、字段操作等的全面支持。ByteBuddy 还支持与 Java 动态代理、CGLib等现有代理机制的无缝集成。

java 复制代码
@Test
public void testByteBuddy() throws IllegalAccessException, InstantiationException {
    String s = new ByteBuddy()
    // 指定父类
    .subclass(Object.class)
    // 指定生成类的名称
    .name("Plant")
    // 按名称 拦截该类的 toString()
    .method(ElementMatchers.named("toString"))
    // 拦截方法调用 返回固定值
    .intercept(FixedValue.value("Hello plant!"))
    // 产生字节码
    .make()
    // classLoader 加载字节码到内存
    .load(ByteBuddy.class.getClassLoader())
    // 获得class对象
    .getLoaded()
    .newInstance()
    .toString();

    System.out.println(s);
}

总之,动态代理的主要目的是在不修改原始类和接口的情况下,对原始对象进行额外的操作或增强。


微风不燥,阳光正好,你就像风一样经过这里,愿你停留的片刻温暖舒心。

我是程序员小迷(致力于C、C++、Java、Kotlin、Android、Shell、JavaScript、TypeScript、Python等编程技术的技巧经验分享),若作品对您有帮助,请关注、分享、点赞、收藏、在看、喜欢,您的支持是我们为您提供帮助的最大动力。

欢迎关注。助您在编程路上越走越好!

相关推荐
帅次27 分钟前
Android 高级工程师面试:网络与数据层 近1年高频追问 18 题
android·网络·okhttp·面试·retrofit·gilde
奔跑的蜗牛ing15 小时前
CentOS Nginx 安装与静态文件服务器配置指南
前端·面试·架构
KaifuZeng18 小时前
通信与接口协议面试五、RS422
嵌入式硬件·面试·通信与接口
SamDeepThinking19 小时前
组合还是继承?我在项目里主要看这两个判断
java·后端·面试
wear工程师19 小时前
setState 到底是不是异步的?这题从 React 16 问到 18,标准答案早就变了
react.js·面试
SamDeepThinking1 天前
为什么很多人学了Java,还是没真正理解类和对象?
java·后端·面试
阿新聊ai1 天前
Claude Code 为什么总不懂你的项目?缺一张「项目地图」
前端·后端·面试
用户852495071841 天前
Promise 全解析:从三状态到 Promise.all
面试
嘟嘟07171 天前
Node.js spawn 实现迷你 Cursor 自动化终端|子进程完整解析
前端·后端·面试
weedsfly1 天前
纯函数与副作用——前端开发中最容易被忽视的质量保证
前端·javascript·面试