Index / Java

Java 方法代理实例操作,静态代理、JDK动态代理、CGLIB动态代理

原文:https://ichochy.com/posts/java/20200824.html

方法的代理可以在调用方法时进行其它的相关操作,并减少代码的入侵和偶合。很多框架都用到了动态代理,并提供了减化代理操作,如:Spring 的 AOP。

静态代理

以电脑为模型,我们都知道,电脑是由 CPU、GPU、DISK 多个设备组装的,它们都是通过接口相连接。现在模拟一台电脑(Computer)通过接口设备(Device),代理(Proxy)组装不同的设备(CPU、GPU),并运行(run)。

接口设备Device

/*
 * File:Device.java
 * User:iChochy
 * URL:https://ichochy.com
 * Copyright (c) 2020
 * Date:2020/08/31 17:49:31
 */

package com.ichochy.proxy;

public interface Device {

    public void run();
}

设备 CPU

/*
 * File:CPU.java
 * User:iChochy
 * URL:https://ichochy.com
 * Copyright (c) 2020
 * Date:2020/08/31 17:52:31
 */

package com.ichochy.proxy;

public class CPU implements Device {


    @Override
    public void run() {
        System.out.println("Game");
    }

}

设备 GPU

/*
 * File:GPU.java
 * User:iChochy
 * URL:https://ichochy.com
 * Copyright (c) 2020
 * Date:2020/08/31 17:52:31
 */

package com.ichochy.proxy;

public class GPU implements Device {

    @Override
    public void run() {
        System.out.println("Display");
    }

}

电脑Computer

Computerstart方法代理执行接口类方法

/*
 * File:SimpleProxy.java
 * User:iChochy
 * URL:https://ichochy.com
 * Copyright (c) 2020
 * Date:2020/08/31 17:54:31
 */

package com.ichochy.proxy;

public class Computer {

    private Device device;

    public Device start(){
        System.out.println("Start Computer");
        device.run();
        return device;
    }

    public Device getDevice() {
        return device;
    }

    public void setDevice(Device device) {
        this.device = device;
    }
}

运行电脑

通过类Computerstart方法代理执行接口类方法

package com.ichochy;

import com.ichochy.proxy.CPU;
import com.ichochy.proxy.Computer;
import com.ichochy.proxy.GPU;

public class App {
    public static void main(String[] args) {
        Computer proxy = new Computer();
        proxy.setDevice(new CPU());
        proxy.start();
        proxy.setDevice(new GPU());
        proxy.start();
    }
}

运行情况

Start Computer
Game
Start Computer
Display

小结

静态代理可以代理某个方法,实现AOP操作,代理需求变更只需修改代理类,实现了解偶的效果。但不同的接口多个方法就要重复的编写代理类,来实现方法代理操作。

JDK动态代理

实现接口InvocationHandlerinvoke方法,通过ProxynewProxyInstance方法,构建代理接口实例。相比静态代理更加灵活,动态代理不同的接口和接口中的方法。

改进电脑Computer

实现接口InvocationHandlerinvoke方法,运用反射,动态执行代理方法

/*
 * File:SimpleProxy.java
 * User:iChochy
 * URL:https://ichochy.com
 * Copyright (c) 2020
 * Date:2020/08/31 17:54:31
 */

package com.ichochy.proxy;

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

public class Computer implements InvocationHandler {

    private Device device;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Start...");
        //运用反射,动态执行代理方法,并返回方法执行结果
        Object result = method.invoke(device, args);
        System.out.println("End");
        return result;
    }

    public Device getDevice() {
        return device;
    }

    public void setDevice(Device device) {
        this.device = device;
    }


}

动态运行电脑

通过类ProxynewProxyInstance方法构建代理接口类,实现方法的代理执行

/*
 * File:App.java
 * User:iChochy
 * URL:https://ichochy.com
 * Copyright (c) 2020
 * Date:2020/09/01 12:46:01
 */

package com.ichochy;

import com.ichochy.proxy.CPU;
import com.ichochy.proxy.Computer;
import com.ichochy.proxy.Device;

import java.lang.reflect.Proxy;

public class App {

    public static void main(String[] args) throws Exception {
        CPU cpu = new CPU();
        Computer computer = new Computer();
        computer.setDevice(cpu);
        //获取代理接口实例
        Device device = (Device) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), cpu.getClass().getInterfaces(), computer);
        device.run();
    }
}

运行情况

Start... 
Game
End

小结

运用反射,动态代理可以代理不同的接口的多个方法,不必修改代码。但只能用于接口方法的代理,无法实现所有类方法。

CGLIB动态代理

CGLIB库是用于生成和转换Java字节码的高级API,它允许运行时对字节码进行修改和动态生成,通过继承方式实现动态代理。

引入CGLIB

通过Maven库管理引入第三方CGLIB

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

改进电脑Computer

实现接口MethodInterceptorintercept方法,运用反射,动态执行代理方法(原父类方法)

/*
 * File:SimpleProxy.java
 * User:iChochy
 * URL:https://ichochy.com
 * Copyright (c) 2020
 * Date:2020/08/31 17:54:31
 */

package com.ichochy.proxy;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class Computer implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("Start...");
        //运用反射,动态执行代理方法,并返回方法执行结果
        Object result = methodProxy.invokeSuper(obj, args);
        System.out.println("End");
        return result;
    }
}

动态运行电脑

通过类Enhancercreate方法构建代理类,实现方法的代理执行

/*
 * File:App.java
 * User:iChochy
 * URL:https://ichochy.com
 * Copyright (c) 2020
 * Date:2020/09/01 12:46:01
 */

package com.ichochy;

import com.ichochy.proxy.CPU;
import com.ichochy.proxy.Computer;
import net.sf.cglib.proxy.Enhancer;

public class App {

    public static void main(String[] args) throws Exception {
        Computer computer = new Computer();
        Enhancer enhancer = new Enhancer();
        //设置要代理超类
        enhancer.setSuperclass(CPU.class);
        //设置回调处理类
        enhancer.setCallback(computer);
        //构建代理类
        CPU cpu = (CPU)enhancer.create();
        cpu.run();
    }
}

运行情况

Start... 
Game
End

小结

通过CGLIB库可以很方便的实现方法的动态代理,实现AOP操作。CGLIB库构建代理类的子类,并重写代理父类的方法,通过执行子类方法实现动态代理操作。

总结

当我们要对一类方法或所有方法进行相同操作时,运用方法代理可以很好实现我们的需求,并不用去重写以前的业务方法,如:事务处理、日志监控、权限管理、异常捕捉及处理。

总结:方法代理,实现AOP操作。