动态代理有哪些神仙

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

动态代理分为基于接口的动态代理(被代理的对象必须实现一个或多个接口)和基于类的动态代理(这种代理方式不依赖于接口,而是直接基于类来实现,基于的类不能为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等编程技术的技巧经验分享),若作品对您有帮助,请关注、分享、点赞、收藏、在看、喜欢,您的支持是我们为您提供帮助的最大动力。

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

相关推荐
张胤尘4 小时前
C/C++ | 每日一练 (2)
c语言·c++·面试
超爱吃士力架5 小时前
MySQL 中的回表是什么?
java·后端·面试
Cutey9168 小时前
Vue 3 中 watch 的报错解析
前端·面试
一小路一8 小时前
Go Web 开发基础:从入门到实战
服务器·前端·后端·面试·golang
纯粹要努力8 小时前
前端跨域问题及解决方案
前端·javascript·面试
uhakadotcom13 小时前
Next.js中生成sitemap的简单方法
前端·面试·架构
菠菠萝宝16 小时前
【Java八股文】08-计算机网络面试篇
java·计算机网络·http·面试·https·udp·tcp
无名之逆18 小时前
探索 Hyperlane:高性能 Rust Web 框架的崛起
java·开发语言·后端·python·算法·面试·rust
小钊(求职中)1 天前
Java开发实习面试笔试题(含答案)
java·开发语言·spring boot·spring·面试·tomcat·maven
小小码农(找工作版)1 天前
JavaScript 前端面试 4(作用域链、this)
前端·javascript·面试