笔记-Java源码阅读-动态代理

1. 静态代理

在静态代理中,代理模式的各个角色对象都是事先定义好的。为了方便说明,我们先来定义三个对象:抽象行为、真实对象、代理对象。真实对象和代理对象都是抽象行为的实现,真实对象通过代理对象对外提供服务。一种简单的示例如下:

/**
* 抽象行为
**/
public interface UserService {
    // say Hello
    public void sayHello();
    // say Bye
    public void sayBye();
}
/**
* 真实对象,即被代理的对象
**/
public class UserServiceImpl implements UserService {
    @Override
    public void sayHello(){
        System.out.println("hello world!");
    }
    
    @Override
    public void sayBye(){
        System.out.println("Bye");
    }
}
/**
* 代理对象
**/
public class UserServiceProxy implements UserService {
    
    private UserService target;
    
    public UserServiceProxy(UserService target){
        this.target = target;
    }
    
    // 代理真实对象说话,同时加上自己的行为
    @Override
    public void sayHello(){
        before();
        target.sayHello();
        after();
    }
    
    @Override
    public void sayBye(){
        target.sayBye();
    }
    
    // 说话之前执行
    private void before() {
        System.out.println("UserServiceImpl words start!");
    }
    
    // 说话之后执行
    private void after() {
        System.out.println("UserServiceImpl words end!");
    }
}
/**
* 业务逻辑
**/
public static void main(String[] args) {
    // 构建真实对象 real
    UserServiceImpl real = new UserServiceImpl();
    // 构建代理对象 proxy
    UserServiceProxy proxy = new UserServiceProxy(real);
    // proxy代理real说话:hello world!
    proxy.sayHello();
    // proxy代理real说话:Bye
    proxy.sayBye();
}

控制台打印结果为:

UserServiceImpl words start!
hello world!
UserServiceImpl words end!
Bye

根据上面代码及运行结果,可以得出静态代理的特点:

  1. 代理类和被代理类实现了相同的接口,从而保证二者拥有相同的行为;
  2. 代理类会持有一个被代理对象;
  3. 只处理我们关心的方法,对于不关心的方法,都直接交给被代理类处理;

优点:

  1. 在不修改目标对象代码的情况下,对目标对象的功能进行扩展;
  2. 代理对象能够保护被代理对象;

缺点:

  1. 代理类和被代理类实现了相同的接口,代码重复率高;
  2. 当被代理类添加了新的行为时,代理类也要做出响应的变动,代码维护难度高;

2. 动态代理

所谓动态代理,就是代理类在程序运行期间动态生成代理类的代理方式。动态代理能够解决静态代理的许多缺点,比静态代理具有更广泛的应用场景。在Java中,动态代理主要分为 Java原生动态代理 和 Cglib动态代理 两种方式。

2.1 java原生

Java原生代码中就有对动态代理过程的实现,将静态代理的例子,也可以在动态代理中来实现,其主要过程为:

自定义一个实现了InvocationHandler接口的类,该类主要是用于定义代理类的行为以及持有被代理类对象:

  1. 在其内定义若干个成员变量,用于持有被代理对象,一般通过构造方法初始化;
  2. 实现接口的方法 invoke,定义代理类的代理逻辑,代理类将代理操作委托给这个invoke方法来实现;
/**
* InvocationHandler接口的实现类
* 定义了代理类的行为
**/
public class AgencyInvocationHandler<T> implements InvocationHandler {

    // 持有的被代理类对象
    private T target;

    public AgencyInvocationHandler(T target) {
        this.target = target;
    }

    @Override 
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if("sayHello".equals(method.getName())) {
            before();
            Object res = method.invoke(target,args);
            after();
            return res;
        }else {
            return method.invoke(target,args);
        }
    }
    
    // 说话之前执行
    private void before() {
        System.out.println("UserServiceImpl words start!");
    }
    
    // 说话之后执行
    private void after() {
        System.out.println("UserServiceImpl words end!");
    }
}

在代码流程中实例化上述自定义类,并让其持有被代理对象,同时通过调用Proxy.newProxyInstance方法,在内存中创建代理类的class对象,并根据这个class对象实例化一个代理类。

public static void main(String[] args) {
    // 被代理对象
    UserService userService = new UserServiceImpl();
    // 封装被代理对象的行为
    AgencyInvocationHandler<UserService> invocationHandler = new AgencyInvocationHandler<>(userService);
    // 创建代理对象
    UserService agency = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class[]{UserService.class}, invocationHandler);
    // 运行代理对象
    agency.sayHello();
    agency.sayBye();
}

上述main方法执行结果:

UserServiceImpl words start!
hello world!
UserServiceImpl words end!
Bye

以上是Java原生动态代理的使用,其具体实现流程及原理,可以通过阅读Proxy.newProxyInstance方法的代码可以得知,代码逻辑流程图如下:
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?在这里插入图片描述
对上图进行简化,可以newProxyInstance方法总结为如下流程图:
在这里插入图片描述

2.2 Cglib

CGLIB是一个功能强大、高性能的代码生成包,它为没有实现接口的类提供了动态代理的支持,是JAVA原生动态代理的补充,与Java原生的动态代理不同的是,它是基于子类实现的动态代理,并且能够提供更好的性能支持。

2.2.1 原理

动态生成一个要代理类的子类,并重写要代理的类的所有的非final的方法,拦截父类方法的同时,织入横切逻辑,进而实现动态代理。它的底层原理:使用字节码处理框架ASM,ASM是一个短小的字节码操作框架,来转换字节码并生成新的类。但asm框架晦涩难懂,而且基本代码太少,缺少文档和示例,所以学起来有一定的难度。cglib动态代理体系中,存在两个外部jar包,cglib.jar 和 asm.jar ,其中,cglib依赖于asm,cglib只是规划代理类的结构和行为,它只提供了一份蓝图,而将蓝图实施、在内存中动态生成代理类的class文件的操作,是由asm框架完成的。

由于CGLIB是基于继承机制实现的动态代理,所以,Java继承的限制会体现在CGLIB动态代理上,比如:

  1. Java中final类无法被继承,因此CGLIB无法代理final类,这也是为什么Hibernate无法持久化final class的原因;
  2. Java类的final方法无法被子类重写,因此CGLIB无法代理final方法;
2.2.2 引入
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.1</version>
</dependency>
2.2.3 示例

示例1,基于MethodInterceptor实现的代理

这是一种最基础实现,也是最万能的,后续其他例子,比如基于FixedValue的返回值的代理,也是可以使用MethodInterceptor的代理来实现。

public class Example {

    // 非final方法,可被代理
    void say(){
        System.out.println("hello world!");
    }
    
    // final方法,无法被代理
    public final void jump(){
        System.out.println("up up down");
    }

    // 主函数
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Example.class);
        // 使用MethodInterceptor代理目标类方法
        enhancer.setCallback(new MethodInterceptor() {
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("before method run ...");
                Object res = methodProxy.invokeSuper(o, objects);
                System.out.println("after method run ...");
                return res;
            }
        });
        Example example = (Example) enhancer.create();
        example.say();
        example.jump();
    }
}

运行结果:

before method run ...
hello world!
after method run ...
up up down

在上例中,Enhancer是Cglib中的一个字节码增强器,可以方便的对你想要处理的类进行扩展,我们可以将上述扩展总结成以下几步:

  1. 构建一个Enhancer对象,该对象用于生成代理类;
  2. 调用 setSuperclass 方法,设置被代理类为父类;
  3. 定义一个拦截器,拦截器需要实现MethodInterceptor接口,并重写intercept方法;
  4. 调用 setCallback 方法,设置拦截器;
  5. 调用 create 方法,构建代理类对象;
  6. 执行代理类对象的方法;

示例2,基于FixedValue实现的代理

可以拦截所有方法,使之返回相同的指定值;需要注意的是,这个拦截行为是对被代理类所有方法生效,如果可被拦截的方法中存在一个返回值类型和FixedValue指定的返回值类型不相同的话,可能会由于类型转换失败,抛出异常。

public class Example {
    // 非final方法,可被代理
    public String say(){
        return "hello world!";
    }

    // final方法,无法被代理
    public final String jump(){
        return "up up down";
    }

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Example.class);
        enhancer.setCallback(new FixedValue() {
            public Object loadObject() throws Exception {
                return "fixed value";
            }
        });
        Example example = (Example) enhancer.create();
        System.out.println(example.say());
        System.out.println(example.jump());
    }
}

运行结果:

fixed value
up up down

示例3,基于InvocationHandler实现的代理

CGLIB还支持类似于JVM动态代理的InvocationHandler实现形式,只不过这个接口来自于cglib包,而非jdk,只是名字和用法相同而已。

public static void main(String[] args) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Example.class);
    enhancer.setCallback(new InvocationHandler() {
        public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
            return null;
        }
    });
    Example example = (Example) enhancer.create();
    System.out.println(example.say());
    System.out.println(example.jump());
}

示例4,基于LazyLoader实现的延迟加载

延迟加载又称懒加载,只有在调用代理类方法时,才会实例化被代理类。

public class Example implements Parent{

    public static int i = 0;

    public Example() {
        System.out.println("被代理类被实例化");
    }

    public String say(){
        return "被代理类:hello world!";
    }

    public final String jump(){
        return "被代理类:up up down";
    }

    public static void main(String[] args) {
        System.out.println("实例化一个代理类");
        // 需要注意的是,被代理类必须要实现一个接口,此处是通过接口创建代理类的。
        Parent parent = (Parent) Enhancer.create(Parent.class, new LazyLoader() {
            public Object loadObject() throws Exception {
                return new Example();
            }
        });
        System.out.println("开始执行代理类方法");
        System.out.println(parent.say());
        System.out.println(parent.jump());
    }
}

执行结果:

实例化一个代理类
开始执行代理类方法
被代理类被实例化
被代理类:hello world!
被代理类:up up down

由执行结果可见,代理类实例化时,被代理类未被实例化,只有当调用被代理类方法时,被代理类才会被实例化,即延迟加载。

2.2.4 底层实现

以 2.2.3 的例子,按照代码的顺序来了解cglib的底层实现。

先来介绍几个比较重要Enhancer类成员:

// 代理类实现的接口,代理类可以实现多个接口,因此采用数组形式存储接口的Class对象
private Class[] interfaces;
// 代理类继承的父类,代理类只有有一个父类
private Class superclass;
// 回调对象数组,可以用于拦截被代理类的方法调用
private Callback[] callbacks;
// 回调过滤器
private CallbackFilter filter;
// key工厂,按照一定规则生成key
private static final Enhancer.EnhancerKey KEY_FACTORY;

还有其父类AbstractClassGenerator类的几个比较重要的成员:

// 代理类的前缀名,代理类的类名将以此为前缀
private String namePrefix;
// 是否使用缓存,若为true,生产的代理类将会被存储到下面的source成员中
private boolean useCache;
/*
* 提供缓存能力,本质为WeakHashMap构成的二级缓存
* 一级键为ClassLoader
* 二级键由Enhancer类的KEY_FACTORY成员的方法生成
* 值是一个HashSet,其内存储代理类class
*/
private AbstractClassGenerator.Source source;

Enhancer.setSuperclass(Class superclass)

该方法用于设置代理类的父类,入参是父类的class。setSuperClass 方法可以总结为三个规则:

  • 如果入参为接口的class,则通过调用setInterface方法将其存入Enhancer类的成员 interfaces 内 ;
  • 如果入参为 Object类的class,则将Enhancer的成员 superclass 赋值为null;
  • 除上述情况外,入参被直接赋值给Enhancer的成员 superclass ;
public void setSuperclass(Class superclass) {
    if (superclass != null && superclass.isInterface()) {
        this.setInterfaces(new Class[]{superclass});
    } else if (superclass != null && superclass.equals(Object.class)) {
        this.superclass = null;
    } else {
        this.superclass = superclass;
    }
}

Enhancer.setCallbacks(Callback[] callbacks)

Enhancer类生成代理类(本质是被代理类的子类)时,会重写父类的所有方法,重写的方法里的代码主要做的事,就是父类Method、子类Method以及子类方法入参等作为参数,去调用(我们这个setCallbakc方法传入的)Callback对象的 intercept方法 。也就是说,为了定义生成的代理类的代理行为逻辑,我们需要实现 Callback接口并重写intercept方法。这个方法只是把入参传入的Callback对象数组赋值给 Enhancer的成员 Callback[] callbacks。

Enhancer.create(Class[] argumentTypes, Object[] arguments)

cglib的可供外部调用的核心方法,用于构建代理类对象。能够允许调用者设置参数及其类型,它是调用 Enhancer.createHelper() 来实现代理类的创建。createHelper方法主要做了两件事,一个是数据校验,一个是设置Enhancer父类(AbstractClassGenerator)的成员 namePrefix 的值;然后调用父类的create方法完成代理类对象的创建。

private Object createHelper() {
    this.validate();
    if (this.superclass != null) {
        this.setNamePrefix(this.superclass.getName());
    } else if (this.interfaces != null) {
        /*
        * ReflectUtils.findPackageProtected(Class[] classes) 
        * 该方法会定位入参数组中第一个非public接口的位置
        * -> 若找到,则返回这个接口在数组中的位置
        * -> 若未找到,则返回 0 
        */
        this.setNamePrefix(this.interfaces[ReflectUtils.findPackageProtected(this.interfaces)].getName());
    }
    return super.create(/** 参数太多,略 **/);
}

3. 补充知识

3.1 ASM框架

ASM框架是一个短小精悍的字节码操作框架,它能以二进制形式修改已有类或者动态生成类,它能够让你内存中直接产生二进制class文件,或者,在类被加载到JVM虚拟机之前,改变类的行为。ASM使用类似SAX的解析器来实现高性能。

3.2 JVM和CGLIB对比

  1. JVM动态代理(也叫JDK动态代理)是Java JDK自带的动态代理实现,无需引入外部jar包;CGLIB需要引入两个jar包,cglib和asm;
  2. JVM动态代理只能通过接口来生成代理类,如果目标类没有实现接口,那么JVM的动态代理就无法使用了;
  3. CGLIB动态代理采用的是继承父类的方式来实现代理,不会存在短板问题;
  4. Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;
  5. CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效

3.3 CGLIB注意事项

  1. 由于CGLIB大部分类都是直接对Java字节码进行操作,这样生成的类会在Java永久堆中,如果动态代理过多,会造成永久堆满,触发OutOfMemory异常。
    SM框架是一个短小精悍的字节码操作框架,它能以二进制形式修改已有类或者动态生成类,它能够让你内存中直接产生二进制class文件,或者,在类被加载到JVM虚拟机之前,改变类的行为。ASM使用类似SAX的解析器来实现高性能。

3.2 JVM和CGLIB对比

  1. JVM动态代理(也叫JDK动态代理)是Java JDK自带的动态代理实现,无需引入外部jar包;CGLIB需要引入两个jar包,cglib和asm;
  2. JVM动态代理只能通过接口来生成代理类,如果目标类没有实现接口,那么JVM的动态代理就无法使用了;
  3. CGLIB动态代理采用的是继承父类的方式来实现代理,不会存在短板问题;
  4. Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;
  5. CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效

3.3 CGLIB注意事项

  1. 由于CGLIB大部分类都是直接对Java字节码进行操作,这样生成的类会在Java永久堆中,如果动态代理过多,会造成永久堆满,触发OutOfMemory异常。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值