Java代理的几种实现方式总结

 更新时间:2023年12月29日 10:45:46   作者:铿然架构  
本文将通过例子说明java代理的几种实现方式,并比较它们之间的差异,文中通过代码示例给大家介绍的非常详细,对大家的学习或工作有一定的参考价值,需要的朋友可以参考下

1. 简介

本文将通过例子说明java代理的几种实现方式,并比较它们之间的差异。

1.1 例子应用场景

代理对象给被代理对象(目标对象),增加一个目标对象方法被调用的日志,用于后续通过日志采集统计方法被调用次数。

1.2 被代理对象代码

后面所有例子均使用这些相同代码。

● 被代理对象接口

public interface Animal {
    void say();
}

● 被代理对象Dog类

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Dog implements Animal {
    @Override
    public void say() {
        log.info("汪汪");
    }
}

2. 静态代理

简单点说,静态代理就是在编码阶段已经确定了代理对象。

这里简单回顾下代理模式的基本类结构:

描述
Subject被代理目标类的接口。
RealSubject被代理目标类。
ProxySubject代理类,需要实现被代理目标类接口,同时持有被代理目标类对象实例,实际调用时,通过委托的方式调用被代理目标类。
SubjectFactory被代理目标类的工厂类,通过工厂类屏蔽创建Subject的细节,客户端看到的是Subject,至于具体是哪个实现类不需要关心。

2.1 代码示例

2.1.1 DogStaticProxy

Dog代理类:

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@AllArgsConstructor
// 代理类,实现Subject接口
public class DogStaticProxy implements Animal {
    // 持有RealSubject对象实例
    private Dog dog;

    @Override
    public void say() {
        // 增强的能力,打印方法调用日志
        log.info("Dog.say() called.");
        // 通过委托的方式调用RealSubject对象实例方法
        dog.say();
    }
}

2.1.2 DuckStaticProxy

Duck代理类:

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@AllArgsConstructor
public class DuckStaticProxy implements Animal {
    private Duck duck;

    @Override
    public void say() {
        log.info("Dog.say() called.");
        duck.say();
    }
}

2.1.3 StaticProxyFactory

代理工厂类:

public class StaticProxyFactory {
    
    // 屏蔽创建Subject实例的细节,使用者看到的只有Subject接口,即Animal
    public static Animal createDog() {
        return new DogStaticProxy(new Dog());
    }

    public static Animal createDuck() {
        return new DuckStaticProxy(new Duck());
    }
}

2.1.4 StaticProxyFactoryTest

测试代码:

import org.junit.jupiter.api.Test;

class StaticProxyFactoryTest {
    @Test
    public void exec() {
        Animal dog = StaticProxyFactory.createDog();
        Animal duck = StaticProxyFactory.createDuck();

        dog.say();
        duck.say();
    }
}

2.1.5 输出日志

2023-12-28 12:00:35 INFO  DogStaticProxy:16 - Dog.say() called.
2023-12-28 12:00:35 INFO  Dog:10 - 汪汪
2023-12-28 12:00:35 INFO  DuckStaticProxy:13 - Dog.say() called.
2023-12-28 12:00:35 INFO  Duck:9 - 嘎嘎

3. JDK动态代理

动态代理就是在编码阶段还没有确定代理对象,在运行期才动态确定。

如下是JDK动态代理的类结构:

描述
Subject被代理目标类的接口。
RealSubject被代理目标类。
InvocationHandler可以理解为一个拦截器,被代理对象方法调用时都会先调用此接口的invoke方法,在invoke方法内部去调用被代理对象方法。
InvocationHandler实现类InvocationHandler的实现类
ProxyJDK代理工具类,用于生成代理对象,生成代理对象时会用到RealSubject和InvocationHandler实现类。

3.1 代码示例

3.1.1 AnimalInvocationHandler

InvocationHandler的实现类:

import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

@Slf4j
public class AnimalInvocationHandler implements InvocationHandler {
    private Animal animal;

    // 通过构造器传入目标类实例对象
    public AnimalInvocationHandler(Animal animal) {
        this.animal = animal;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 增强能力,打印方法调用日志,采集后统计调用次数
        log.info(getCalledMethodInfo(method));
        // 调用目标类方法,参数是目标类实例和对应的方法参数
        return method.invoke(animal, args);
    }

    // 辅助方法,返回被调用方法信息
    private String getCalledMethodInfo(Method method) {
        StringBuilder builder = new StringBuilder();
        builder.append(animal.getClass().getSimpleName())
                .append(".")
                .append(method.getName())
                .append("() called.");
        return builder.toString();
    }
}

3.1.2 JDKProxyFactory

代理工厂类:

import java.lang.reflect.Proxy;

public class JDKProxyFactory {

    public static Animal createDog() {
        return getProxy(new Dog());
    }

    public static Animal createDuck() {
        return getProxy(new Duck());
    }

    private static Animal getProxy(Animal implInstance) {
        AnimalInvocationHandler handler = new AnimalInvocationHandler(implInstance);
        // 创建代理对象
        Object proxy = Proxy.newProxyInstance(implInstance.getClass().getClassLoader(),
                implInstance.getClass().getInterfaces(),
                handler);
        return (Animal) proxy;
    }
}

3.1.3 JDKProxyFactoryTest

测试类:

import org.junit.jupiter.api.Test;

class JDKProxyFactoryTest {

    @Test
    public void exec() {
        Animal dog = JDKProxyFactory.createDog();
        Animal duck = JDKProxyFactory.createDuck();

        dog.say();
        duck.say();
    }
}

3.1.4 输出日志

2023-12-28 12:27:37 INFO  AnimalInvocationHandler:19 - Dog.say() called.
2023-12-28 12:27:37 INFO  Dog:10 - 汪汪
2023-12-28 12:27:37 INFO  AnimalInvocationHandler:19 - Duck.say() called.
2023-12-28 12:27:37 INFO  Duck:9 - 嘎嘎

3.2 通用能力重构

打印方法调用日志不仅仅适用于Animal,也适用于其他所有有此需求的类,可以优化一下。

注:此优化非JDK动态代理的能力,存粹就是顺便处理一下,重构无处不在,只要你想做。

3.2.1 代码

3.2.1.1 MethodPrinterInvocationHandler

AnimalInvocationHandler只能适用于Animal,重构通过泛型实现:

import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

@Slf4j
// 使用泛型,只要是有接口的实现类都可以使用
public class MethodPrinterInvocationHandler<T> implements InvocationHandler {
    private T t;
    public MethodPrinterInvocationHandler(T t) {
        this.t = t;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info(getCalledMethod(method));
        return method.invoke(t, args);
    }

    private String getCalledMethod(Method method) {
        StringBuilder builder = new StringBuilder();
        builder.append(t.getClass().getSimpleName())
                .append(".")
                .append(method.getName())
                .append("() called.");
        return builder.toString();
    }
}

3.2.1.2 MethodPrinterJDKProxyFactory

代理工厂类同样通过泛型实现:

import java.lang.reflect.Proxy;

public class MethodPrinterJDKProxyFactory {

    // 使用泛型,除了Animal接口和其子类,其他接口和子类也可以使用
    public static <T, R> R getProxy(T implInstance ) {
        MethodPrinterInvocationHandler<T> handler = new MethodPrinterInvocationHandler(implInstance);
        return (R) Proxy.newProxyInstance(implInstance.getClass().getClassLoader(),
                implInstance.getClass().getInterfaces(),
                handler);
    }
}

3.2.1.3 AnimalJDKProxyFactory

public class AnimalJDKProxyFactory {
    public static Animal createDuck() {
        return MethodPrinterJDKProxyFactory.getProxy(new Duck());
    }

    public static Animal createDog() {
        return MethodPrinterJDKProxyFactory.getProxy(new Dog());
    }
}

3.2.1.4 MethodPrinterJDKProxyFactoryTest

import org.junit.jupiter.api.Test;

class MethodPrinterJDKProxyFactoryTest {

    @Test
    public void exec() {
        Animal dog = AnimalJDKProxyFactory.createDog();
        Animal duck = AnimalJDKProxyFactory.createDuck();
        dog.say();
        duck.say();
    }
}

3.2.1.5 打印日志

2023-12-28 12:54:00 INFO  MethodPrinterInvocationHandler:18 - Dog.say() called.
2023-12-28 12:54:00 INFO  Dog:10 - 汪汪
2023-12-28 12:54:00 INFO  MethodPrinterInvocationHandler:18 - Duck.say() called.
2023-12-28 12:54:00 INFO  Duck:9 - 嘎嘎

3.3 JDK动态代理限制

JDK动态代理只能代理接口,无法直接代理类,下面看个例子。

3.3.1 代码

3.3.1.1 Cat

新增一个类,没有实现Animal接口,用于证明没有接口无法使用JDK动态代理:

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Cat {

    public void say() {
        log.info("喵喵");
    }
}

3.3.1.2 CatInvocationHandler

拦截器也要调整,将Cat作为构造器参数,不能再使用Animal:

import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

@Slf4j
public class CatInvocationHandler implements InvocationHandler {
    private Cat cat;
    
    public CatInvocationHandler(Cat cat) {
        this.cat = cat;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info(getCalledMethod(method));
        return method.invoke(cat, args);
    }

    private String getCalledMethod(Method method) {
        StringBuilder builder = new StringBuilder();
        builder.append(cat.getClass().getSimpleName())
                .append(".")
                .append(method.getName())
                .append("() called.");
        return builder.toString();
    }
}

3.3.1.3 ErrorJDKProxyFactory

代理工厂类:

import java.lang.reflect.Proxy;

public class ErrorJDKProxyFactory {

    public static Cat createCat() {
        return getProxy(new Cat());
    }

    private static Cat getProxy(Cat cat) {
        CatInvocationHandler handler = new CatInvocationHandler(cat);
        // 这里的写法仍然不变,和之前一样,只是类型变化
        return (Cat) Proxy.newProxyInstance(cat.getClass().getClassLoader(), cat.getClass().getInterfaces(), handler);
    }
}

3.3.1.4 ErrorJDKProxyFactoryTest

测试类:

import org.junit.jupiter.api.Test;

class ErrorJDKProxyFactoryTest {

    @Test
    void createCat() {
        Cat cat = ErrorJDKProxyFactory.createCat();
        cat.say();
    }
}

3.3.1.5 输出日志

java.lang.ClassCastException: com.sun.proxy.$Proxy8 cannot be cast to com.kengcoder.javaawsome.proxy.jdkproxy.Cat

	at com.kengcoder.javaawsome.proxy.jdkproxy.ErrorJDKProxyFactory.getProxy(ErrorJDKProxyFactory.java:13)
	at com.kengcoder.javaawsome.proxy.jdkproxy.ErrorJDKProxyFactory.createCat(ErrorJDKProxyFactory.java:8)

3.3.1.6 原因分析

● 通过类代理

两个对象类型不一致,没法做强转。

● 通过接口代理

代理对象类型为Animal,可以强转为Animal接口。

4. CGLIB动态代理

CGLIB动态代理是三方库实现,和JDK动态代理的区别是:既可以代理接口,也可以代理类。

CGLIB动态代理实现的类结构如下:

描述
Subject被代理目标类的接口。
RealSubject被代理目标类。
MethodInterceptor可以理解为一个拦截器,被代理对象方法调用时都会先调用此接口的intercept方法,在intercept方法内部去调用被代理对象方法。
MethodInterceptor实现类MethodInterceptor的实现类
Enhancer代理工具类,用于生成代理对象,生成代理对象时会用到RealSubject和MethodInterceptor实现类。

4.1 代码

4.1.1 CglibInterceptor

MethodInterceptor的实现类:

import lombok.extern.slf4j.Slf4j;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

@Slf4j
public class CglibInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        log.info(getCalledMethod(method));
        return methodProxy.invokeSuper(target, args);
    }

    private String getCalledMethod(Method method) {
        StringBuilder builder = new StringBuilder();
        builder.append(method.getDeclaringClass().getSimpleName())
                .append(".")
                .append(method.getName())
                .append("() called.");
        return builder.toString();
    }
}

4.1.2 CglibProxyFactory

CGLIB代理工厂类,抽象出通用方法:

import net.sf.cglib.proxy.Enhancer;

public class CglibProxyFactory {

    public static <T> T getProxy(Class<T> clazz) {
        Enhancer enhancer = new Enhancer();
        // 设置超类,字面看就是代理对象是目标类的子类,通过继承方式实现,而不是组合方式实现。
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(new CglibInterceptor());
        return (T)enhancer.create();
    }
}

4.1.3 AnimalCglibProxyFactory

Animal工厂类:

public class AnimalCglibProxyFactory {
    public static Animal createDog() {
        return CglibProxyFactory.getProxy(Dog.class);
    }

    public static Animal createDuck() {
        return CglibProxyFactory.getProxy(Duck.class);
    }
}

4.1.4 CglibProxyFactoryTest

import org.junit.jupiter.api.Test;

class CglibProxyFactoryTest {
    @Test
    public void exec() {
        Animal dog = AnimalCglibProxyFactory.createDog();
        Animal duck = AnimalCglibProxyFactory.createDuck();

        dog.say();
        duck.say();
    }
}

4.1.5 输出日志

2023-12-28 13:23:56 INFO  CglibInterceptor:13 - Dog.say() called.
2023-12-28 13:23:56 INFO  Dog:10 - 汪汪
2023-12-28 13:23:56 INFO  CglibInterceptor:13 - Duck.say() called.
2023-12-28 13:23:56 INFO  Duck:9 - 嘎嘎

5. 总结

本文介绍了几种java代理的实现方式,在过程中通过重构展示了如何通过java泛型实现通用能力,以及通过工厂模式屏蔽类实例创建细节,提升扩展性。

几种java代理的差别如下:

比较项静态代理JDK动态代理CGLIB动态代理
代理对象接口和类接口接口和类
实现接口方法需手动一一实现自动实现自动实现
拦截方法每个方法独立实现,完全隔离开。都在一处实现,当指定方法拦截时要增加判断逻辑。同静态代理。

6. 最后

以上就是Java代理的几种实现方式总结的详细内容,更多关于Java代理实现方式的资料请关注脚本之家其它相关文章!

相关文章

  • Jmeter内置变量vars和props的使用详解

    Jmeter内置变量vars和props的使用详解

    JMeter是一个功能强大的负载测试工具,它提供了许多有用的内置变量来支持测试过程,其中最常用的变量是 vars 和 props,本文通过代码示例详细给大家介绍了Jmeter内置变量vars和props的使用,需要的朋友可以参考下
    2024-08-08
  • Java执行shell命令的实现

    Java执行shell命令的实现

    本文主要介绍了Java执行shell命令的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01
  • 基于RabbitMQ几种Exchange 模式详解

    基于RabbitMQ几种Exchange 模式详解

    下面小编就为大家带来一篇基于RabbitMQ几种Exchange 模式详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-11-11
  • spring Security的自定义用户认证过程详解

    spring Security的自定义用户认证过程详解

    这篇文章主要介绍了spring Security的自定义用户认证过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • java线程并发控制同步工具CountDownLatch

    java线程并发控制同步工具CountDownLatch

    这篇文章主要为大家介绍了java线程并发控制同步工具CountDownLatch使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • Java设计模式之观察者模式

    Java设计模式之观察者模式

    本文详细讲解了Java设计模式之观察者模式,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-09-09
  • Java二叉树查询原理深入分析讲解

    Java二叉树查询原理深入分析讲解

    这篇文章主要介绍了Java二叉树查询原理,二叉查找树,又称二叉排序树,亦称二叉搜索树,是数据结构中的一类。在一般情况下,查找效率比链表结构要高
    2022-11-11
  • mybatis-plus处理blob字段的完整示例代码

    mybatis-plus处理blob字段的完整示例代码

    在Spring Boot项目中使用MyBatis-Plus处理longblob字段时,我们可以按照本文的步骤进行操作,假设 longblob 存储的是字符串数据,本文给大家提供完整示例代码,感兴趣的朋友参考下
    2023-12-12
  • SpringBoot Validation快速实现数据校验的示例代码

    SpringBoot Validation快速实现数据校验的示例代码

    在实际开发中,肯定会经常遇到对参数字段进行校验的场景,通常我们只能写大量的if else来完成校验工作,而如果使用SpringBoot Validation则可以轻松的通过注解来完成,接下来小编给大家介绍下利用SpringBoot Validation快速实现数据校验的示例代码,需要的朋友参考下吧
    2022-06-06
  • 如何通过ServletInputStream读取http请求传入的数据

    如何通过ServletInputStream读取http请求传入的数据

    这篇文章主要介绍了如何通过ServletInputStream读取http请求传入的数据,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10

最新评论