Java代理的几种实现方式


铿然架构 | 作者 / 铿然 这是 铿然架构 的第 120 篇原创文章 ***

1. 简介

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

1.1 例子应用场景

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

1.2 被代理对象代码

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

● 被代理对象接口

java 复制代码
public interface Animal {
    void say();
}

● 被代理对象Dog类

java 复制代码
import lombok.extern.slf4j.Slf4j;

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

● 被代理对象Duck类

java 复制代码
import lombok.extern.slf4j.Slf4j;

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

2. 静态代理

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

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

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

2.1 代码示例

2.1.1 DogStaticProxy

Dog代理类:

java 复制代码
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代理类:

java 复制代码
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

代理工厂类:

java 复制代码
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

测试代码:

java 复制代码
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 输出日志

yaml 复制代码
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的实现类
Proxy JDK代理工具类,用于生成代理对象,生成代理对象时会用到RealSubject和InvocationHandler实现类。

3.1 代码示例

3.1.1 AnimalInvocationHandler

InvocationHandler的实现类:

java 复制代码
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

代理工厂类:

java 复制代码
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

测试类:

java 复制代码
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 输出日志

java 复制代码
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,重构通过泛型实现:

java 复制代码
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

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

java 复制代码
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

java 复制代码
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

java 复制代码
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 打印日志

java 复制代码
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动态代理:

java 复制代码
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Cat {

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

3.1.1.2 CatInvocationHandler

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

java 复制代码
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.1.1.3 ErrorJDKProxyFactory

代理工厂类:

java 复制代码
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.1.1.4 ErrorJDKProxyFactoryTest

测试类:

java 复制代码
import org.junit.jupiter.api.Test;

class ErrorJDKProxyFactoryTest {

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

3.1.1.5 输出日志

java 复制代码
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.1.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的实现类:

java 复制代码
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代理工厂类,抽象出通用方法:

java 复制代码
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工厂类:

java 复制代码
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

java 复制代码
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 输出日志

java 复制代码
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. 思考

● 静态代理为什么能代理类?直接代理类有什么弊端?

● 有了动态代理,什么场景使用静态代理?

● 什么场景使用JDK动态代理和CGLIB代理?


其他阅读:

萌新快速成长之路
如何编写软件设计文档
Spring Cache架构、机制及使用
布隆过滤器适配Spring Cache及问题与解决策略
JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
Java编程思想(七)使用组合和继承的场景
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(五)函数式接口-复用,解耦之利刃

相关推荐
hqxstudying12 分钟前
java依赖注入方法
java·spring·log4j·ioc·依赖
·云扬·20 分钟前
【Java源码阅读系列37】深度解读Java BufferedReader 源码
java·开发语言
martinzh1 小时前
Spring AI 项目介绍
后端
Bug退退退1231 小时前
RabbitMQ 高级特性之重试机制
java·分布式·spring·rabbitmq
小皮侠1 小时前
nginx的使用
java·运维·服务器·前端·git·nginx·github
前端付豪1 小时前
20、用 Python + API 打造终端天气预报工具(支持城市查询、天气图标、美化输出🧊
后端·python
爱学习的小学渣1 小时前
关系型数据库
后端
武子康2 小时前
大数据-33 HBase 整体架构 HMaster HRegion
大数据·后端·hbase
前端付豪2 小时前
19、用 Python + OpenAI 构建一个命令行 AI 问答助手
后端·python
凌览2 小时前
斩获 27k Star,一款开源的网站统计工具
前端·javascript·后端