Java 代理

1. 概念

Java代理是一种设计模式,它允许在不改变原始类或接口的情况下对其进行增强或修改在运行时通过创建一个代理对象控制对原始对象的访问,并允许我们在访问原始对象之前或之后执行一些额外的逻辑,从而实现一些特定的功能,如日志记录、安全控制、缓存处理、远程调用、延迟加载和事务处理等。

1.1 应用场景
  1. 日志记录:通过代理对象在方法调用前后记录日志。

  2. 安全控制:通过代理对象控制对原始对象的访问权限。

  3. 缓存处理:通过代理对象对某些方法的调用结果进行缓存,避免重复计算。

  4. 远程调用:通过代理对象实现远程方法调用。

  5. 延迟加载:通过代理对象延迟加载某些对象,提高程序的性能。

  6. 事务处理:通过代理对象实现事务的提交和回滚。

    ​ ……

1.2 案例解析

不使用代理的对象增强

假设我们有一个名为 UserService 的类,它提供了两个方法 addUserdeleteUser,代码如下:

public class UserService{

    public void addUser(String name) {
        System.out.println("添加用户:" + name);
    }

    public void deleteUser(String name) {
        System.out.println("删除用户:" + name);
    }
}

现在,我们需要在 addUserdeleteUser 方法执行前后打印日志。一种实现方式是在每个方法的前后都打印日志,如下所示:

public class UserService{

   public void addUser(String name) {
        System.out.println("Before addUser");
        userService.addUser(name);
        System.out.println("After addUser");
    }

    public void deleteUser(String name) {
        System.out.println("Before deleteUser");
        userService.deleteUser(name);
        System.out.println("After deleteUser");
    }
}

在上面的代码中,我们直接在原有类中修改,并在每个方法的前后都打印了日志,这样就能够实现对原有对象的增强。

现在有个问题,当产品经理想加需求,比如在所有操作前(插入或删除)先认证你的权限,没有权限不让你操作。

所以我们需要在原有类中继续修改代码。

那如果再加十个需求呢? 这时候就有两种解决方案,一种是解决产品经理,一种是解决需求。

第一种我就不教了,第二种就是继续改原有类咯。

虽然上面的方式可以实现对象增强,但它缺点也很明显,增强逻辑与目标方法紧密耦合在一起,侵入性较高,不易扩展和维护。

1.3 为什么要使用代理

为了避免上述问题,我们可以使用代理模式,它将对象的访问控制和附加操作分离出来,可以在不修改原有代码的情况下增加额外的功能或者对原有功能进行增强。代理模式能够更好的解耦:代理对象和被代理对象是相互独立的,这意味着我们可以在不影响被代理对象的情况下,对代理对象进行修改和优化。

2.静态代理


静态代理是在编译时创建的代理对象,需要手动编写代理类。它需要实现与原始类相同的接口,并在代理类中调用原始类的方法。

以下是一个简单的静态代理实现示例,用于在调用原始对象之前和之后记录日志:

// 定义接口
public interface UserService {
    void addUser(String name);
}

// 原始类
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String name) {
        System.out.println("添加用户:" + name);
    }
}

// 代理类
public class UserServiceProxy implements UserService {
    private UserService userService;

    public UserServiceProxy(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void addUser(String name) {
        System.out.println("添加用户之前的日志记录");
        userService.addUser(name);
        System.out.println("添加用户之后的日志记录");
    }
}

// 使用代理
public static void main(String[] args) {
    UserService userService = new UserServiceImpl();
    UserService proxy = new UserServiceProxy(userService);
    proxy.addUser("张三");
}

在上面的示例中,我们定义了一个接口UserService和一个实现类UserServiceImpl。我们创建了一个代理类UserServiceProxy,它实现了UserService接口并持有一个UserService对象。在代理类的addUser方法中,我们先记录日志,然后调用原始对象的addUser方法,最后再记录一次日志。

在使用代理时,我们创建了一个原始对象UserServiceImpl,然后将它传递给代理类UserServiceProxy的构造函数。我们最终使用代理对象proxy调用addUser方法。

相比之下,使用静态代理实现方法增强的主要优点是增强逻辑与目标方法分离,代理对象负责将增强逻辑织入到目标方法中。

静态代理有一些缺点,主要包括:

  1. 静态代理类和被代理类实现相同的接口,导致接口的数量增加,系统更加臃肿。

  2. 每一个代理类只能代理一个接口,如果要代理多个接口就需要创建多个代理类。

  3. 代理类需要手动编写,代码量较大,且容易出错。

3. 动态代理


为了解决静态代理的问题,我们使用动态代理。动态代理可以通过Java的反射机制运行时动态地生成代理类,无需手动编写代理类。动态代理可以代理多个接口,且可以实现通用的代理逻辑,避免了代码重复,同时也避免了静态代理中需要手动编写代理类的缺点。

Java提供了java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现动态代理。

以下是一个简单的动态代理示例,用于在调用原始对象之前和之后记录日志:

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

// 定义接口
public interface UserService {
    void addUser(String name);
}

// 原始类
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String name) {
        System.out.println("添加用户:" + name);
    }
}

// 实现InvocationHandler接口
public class UserServiceProxy implements InvocationHandler {
    private Object target;

    public UserServiceProxy(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("添加用户之前的日志记录");
        Object result = method.invoke(target, args);
        System.out.println("添加用户之后的日志记录");
        return result;
    }
}

// 使用代理
public static void main(String[] args) {
    UserService userService = new UserServiceImpl();
    InvocationHandler handler = new UserServiceProxy(userService);
    UserService proxy = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class<?>[]{UserService.class}, handler);
    proxy.addUser("张三");
}

在上面的示例中,我们使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现了动态代理。我们定义了一个接口UserService和一个实现类UserServiceImpl,然后创建了一个实现了InvocationHandler接口的代理类UserServiceProxy

在代理类的invoke方法中,我们先记录日志,然后调用method.invoke(target, args)来执行原始对象的方法,最后再记录一次日志。在使用代理时,我们使用Proxy.newProxyInstance方法来创建代理对象,传递了UserService接口和代理类UserServiceProxy的实例。

相比静态代理,动态代理有以下优点

  1. 可以动态代理多个接口:动态代理使用的是接口代理,而不是类代理,因此可以代理多个接口。这对于需要实现多个接口的类来说非常方便。
  2. 可以实现通用的代理逻辑:动态代理可以通过 InvocationHandler 接口实现通用的代理逻辑,避免了代码重复。例如,我们可以实现一个记录方法执行时间的代理逻辑,然后用它来代理多个类的方法。
  3. 避免了静态代理中需要手动编写代理类的缺点:动态代理是在运行时生成代理类的技术,可以避免静态代理中需要手动编写代理类的缺点。
  4. 代码更简洁:由于动态代理可以实现通用的代理逻辑,因此代码更加简洁。
  5. 可以支持 AOP 编程:动态代理可以实现 AOP 编程,即在不修改原有代码的情况下,增加日志、事务等功能。

总之,动态代理相比静态代理更加灵活、方便、简洁,可以避免代码重复,实现通用的代理逻辑,支持 AOP 编程。因此,在实际开发中,我们应该尽量使用动态代理,避免使用静态代理。

在使用代理时,需要注意代理类和原始类必须实现同样的接口,以便代理类可以代理原始类的方法。此外,代理类也可以继承原始类,从而直接调用原始类的方法,但这样可能会破坏代理的透明性和可移植性。

3.1实现动态代理的两种方法
  1. JDK动态代理
  2. CGLib代理
3.2 JDK动态代理

jdk动态代理是通过2个类:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler来实现的。具体实现步骤如下:

  1. 定义一个实现了InvocationHandler接口的代理类,这个类中需要实现invoke方法,在该方法中实现对目标对象方法的调用和增强逻辑的织入

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public 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");
    
            // 调用目标方法
            Object result = method.invoke(target, args);
    
            // 执行增强逻辑
            System.out.println("after method");
    
            return result;
        }
    }
    
  2. 在代理类中,通过调用Proxy.newProxyInstance()方法动态生成代理对象,并将代理对象返回。

import java.lang.reflect.Proxy;

public class MyProxy {
    public static Object getProxy(Object target) {
    	return Proxy.newProxyInstance(
    		target.getClass().getClassLoader(),
    		target.getClass().getInterfaces(),
    		new MyInvocationHandler(target));
    }
}
  1. 调用代理对象的方法时,实际上是调用了MyInvocationHandler的invoke()方法,在该方法中实现了对目标方法的调用和增强逻辑的织入
public class Main {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserService proxy = (UserService) MyProxy.getProxy(userService);
        proxy.addUser();
    }
}

java.lang.reflect.Proxy类中常用静态方法:

getProxyClass方法

public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces);

为指定的接口创建代理类,返回代理类的Class对象

参数说明:

loader:定义代理类的类加载器

interfaces:指定需要实现的接口列表,创建的代理默认会按顺序实现interfaces指定的接口

newProxyInstance方法

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

创建代理类的实例对象

这个方法先为指定的接口创建代理类,然后会生成代理类的一个实例。当调用代理对象的方法时,会被InvocationHandler接口的invoke方法处理。

getInvocationHandler方法

public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException;

获取代理对象的InvocationHandler对象

JDK动态代理的优点在于可以代理任意实现了接口的类,不需要针对特定的类编写代理代码。但是,它只能代理实现了接口的类,无法代理没有实现接口的类。

3.3 CGLib代理

CGLib是一个开源的高性能代理框架,底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类,它可以在运行期动态地生成某个类的子类,且不需要编写字节码,它广泛地被许多AOP的框架使用。

相比于JDK动态代理,CGLib可以对任意类进行代理,而不仅仅是接口。

使用CGLib实现动态代理的步骤如下:

  1. 引入CGLib的依赖库
<dependency>    
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency> 
  1. 定义一个被代理的类 UserServiceImpl,它实现了一个接口 UserService,并且包含一个方法 save,用于保存用户信息。
public interface UserService {
    void save();
}

public class UserServiceImpl implements UserService {
    @Override
    public void save() {
        System.out.println("保存用户信息");
    }
}
  1. 定义一个MethodInterceptor接口的实现类,实现intercept()方法,在该方法中实现对目标对象方法的调用和增强逻辑的织入。
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 执行增强逻辑
        System.out.println("before method");

        // 调用目标方法
        Object result = proxy.invokeSuper(obj, args);

        // 执行增强逻辑
        System.out.println("after method");

        return result;
    }
}
  1. 通过CGLib库中的Enhancer类,动态生成代理对象。
import net.sf.cglib.proxy.Enhancer;

public class MyProxy {
    public static Object getProxy(Class<?> clazz) {
        //1.创建Enhancer对象
        Enhancer enhancer = new Enhancer();
        //2.通过setSuperclass来设置父类型,即需要给哪个类创建代理类
        enhancer.setSuperclass(clazz);
        /*3.设置回调,需实现org.springframework.cglib.proxy.Callback接口,
        此处使用的是org.springframework.cglib.proxy.MethodInterceptor,也是一个接口,实现了Callback接口,
        当调用代理对象的任何方法的时候,都会被MethodInterceptor接口的invoke方法处理*/
        enhancer.setCallback(new MyMethodInterceptor());
        //4.获取代理对象,调用enhancer.create方法获取代理对象,这个方法返回的是Object类型.
        return enhancer.create();
    }
}

调用代理对象的方法时,实际上是调用了MyMethodInterceptor的intercept()方法,在该方法中实现目标类方法的调用和增强

  1. 创建了一个UserService实现类,然后创建了一个CGLibProxy代理对象,最后使用cgLibProxy.getProxy方法获取代理对象,调用代理对象的saveUser方法。
public class CGLibProxyTest {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        CGLibProxy cgLibProxy = new CGLibProxy();
        //方法返回的是Object类型,需要转型
        UserService userServiceProxy = (UserService) cgLibProxy.getProxy(userService.class);
        userServiceProxy.saveUser();
    }
}

使用CGLIB代理的缺点是,由于它是通过生成目标类的子类来完成代理,所以它会比JDK动态代理更加耗费内存和时间。另外,CGLIB代理只能对非final类进行代理。

代码示例

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

import java.lang.reflect.Method;

// 定义一个目标类
class Target {
    public void sayHello() {
        System.out.println("Hello");
    }
}

// 定义一个拦截器
class Interceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before invoke " + method);
        Object result = proxy.invokeSuper(obj, args); // 调用目标方法
        System.out.println("After invoke " + method);
        return result;
    }
}

// 测试类
public class CglibDemo {
    public static void main(String[] args) {
        // 创建Enhancer对象
        Enhancer enhancer = new Enhancer();
        // 设置目标类为父类
        enhancer.setSuperclass(Target.class);
        // 设置拦截器为回调函数
        enhancer.setCallback(new Interceptor());
        // 创建代理对象
        Target proxy = (Target) enhancer.create();
        // 调用代理对象的方法
        proxy.sayHello();
    }
}

输出结果

Before invoke public void Target.sayHello()
Hello
After invoke public void Target.sayHello()
3.4 总结

JDK动态代理和CGLib代理有以下几个主要区别:

  • Java的动态代理是基于接口的,它要求目标类必须实现一个或多个接口,而CGLib是基于类的,它不要求目标类实现任何接口。
  • Java的动态代理是通过反射机制来创建代理对象和调用目标方法的,而CGLib是通过字节码生成技术来创建代理对象和调用目标方法的。
  • Java的动态代理会生成一个实现了目标类所有接口的新类作为代理类,而CGLib会生成一个继承了目标类的子类作为代理类。
  • Java的动态代理使用InvocationHandler接口来实现方法拦截,而CGLib使用MethodInterceptor接口来实现方法拦截。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐