Java 代理模式

代理模式简介

代理模式简单来说就是使用代理对象来代替对真实对象的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能

代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后可以增加一些自定义操作。

代理模式有静态代理动态代理两种实现方式。

静态代理

静态代理中,我们对目标对象的每个方法的增强都是手动完成的,非常不灵活(比如接口一旦增加新的方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类)

从JVM层面来说,静态代理在编译时将接口、实现类、代理类这些都变成了一个个实际的class文件

静态代理实现步骤:

  • 定义一个接口及其实现类
  • 创建一个代理类同样实现这个接口
  • 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法,这样的话,就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。

实现

  • 定义发送短信的接口
java 复制代码
public interface SmsService {
    String send(String message);
}
  • 实现发送短信的接口
java 复制代码
public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}
  • 创建代理类并且同样实现发送短信的接口
java 复制代码
public class SmsProxy implements SmsService {
    private final SmsService smsService;
    public SmsProxy(SmsService smsService) {
        this.smsService = smsService;
    }
    @Override
    public String send(String message) {
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method send()");
        smsService.send(message);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method send()");
        return null;
    }
}
  • 实际使用
java 复制代码
public class Main {
    public static void main(String[] args) {
        SmsService smsService = new SmsServiceImpl();
        SmsProxy smsProxy = new SmsProxy(smsService);
        smsProxy.send("java");
    }
}

动态代理

相比于静态代理而言,动态代理更加灵活,不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,可以直接代理实现类(CGLIB动态代理机制)。

从JVM角度来说,动态代理是在运行时动态生成类字节码,并加载到JVM中的

说到动态代理,Spring AOP、RPC框架应该是两个不得不提的,他们的实现都依赖了动态代理。

就Java而言,动态代理的实现方式有多种,最主要的如JDK动态代理、CGLIB动态代理

JDK动态代理

介绍

在JDK动态代理机制中,InvocationHandler接口和Proxy类是核心。

Proxy类中使用频率最高的方法是newProxyInstance(),这个方法主要用来生成一个代理对象。

java 复制代码
public static Object newProxyInstance(ClassLoader loader,
  Class<?>[] interfaces, InvocationHandler h)throws IllegalArgumentException{
  ......
}

这个方法一共有三个参数:

  • loader:类加载器,用于加载代理对象
  • interfaces:被代理类实现的一些接口
  • h:实现了InvocationHandler接口的对象。

要实现动态代理的话,还必须实现InvocationHandler来自定义处理逻辑。当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用。

java 复制代码
public interface InvocationHandler {
    /**
     * 当你使用代理对象调用方法的时候实际会调用到这个方法
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

invoke()方法三个参数

  • proxy:动态生成的代理类
  • method:与代理类对象调用的方法相对应
  • args: 当前method方法的参数

当通过Proxy类的newProxyInstance()创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler接口的类的invoke()方法。可以在invoke()方法中自定义处理逻辑。

使用步骤及代码示例

1. 定义一个接口及其实现类

  • 定义接口
java 复制代码
public interface SmsService {
    String send(String message);
}
  • 定义实现类
java 复制代码
public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

2.自定义InvocationHandler并重写invoke方法,在invoke方法中调用原生方法病自定义一些处理逻辑

java 复制代码
mport java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author shuang.kou
 * @createTime 2020年05月11日 11:23:00
 */
public class DebugInvocationHandler implements InvocationHandler {
    /**
     * 代理类中的真实对象
     */
    private final Object target;

    public DebugInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method " + method.getName());
        Object result = method.invoke(target, args);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method " + method.getName());
        return result;
    }
}

3. 通过Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)方法创建代理对象

java 复制代码
public class JdkProxyFactory {

    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // 目标类的类加载器
                target.getClass().getInterfaces(),  // 代理需要实现的接口,可指定多个
                new DebugInvocationHandler(target)   // 代理对象对应的自定义InvocationHandler 
        );
    }
}

4. 实际使用

java 复制代码
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java")

CGLIB动态代理

JDK动态代理有一个最致命的问题就是其只能代理实现了接口的类

为了解决这个问题,我们可以用CGLIB动态代理机制来避免。

CGLIB是一个基于ASM的字节码生成库,他允许我们在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理。很多知名的框架都使用到了CGLIB。如Spring中的AOP模块:如果目标对象实现了接口,则默认采用JDK动态代理,否则采用CGLIB动态代理。

在CGLIB动态代理机制中MethodInterceptor接口和Enhancer类是核心。

需要自定义MethodInterceptor并重写intercept方法,intercept用于拦截增强被代理类的方法。

java 复制代码
public interface MethodInterceptor extends Callback{
    // 拦截被代理类中的方法
    public Object intercept(Object obj, Method method, 
    Object[] args,MethodProxy proxy) throws Throwable;
}
  • obj: 被代理的对象
  • method:被拦截的方法
  • args: 方法入参
  • proxy:用于调用原始方法

你可以通过Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是MethodInterceptor中的intercept方法。

CGLIB动态代理的使用
  • 定义一个类
  • 自定义MethodInterceptor并重写intercept方法,intercept用于拦截增强被代理的方法,如JDK动态代理中的invoke方法。
  • proxy:用于调用原始方法

代码示例

  1. 添加依赖
xml 复制代码
<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>
  1. 实现一个短信发送的类
java 复制代码
package github.javaguide.dynamicProxy.cglibDynamicProxy;

public class AliSmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}
  1. 自定义MethodInterceptor(方法拦截器)
java 复制代码
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
 * 自定义MethodInterceptor
 */
public class DebugMethodInterceptor implements MethodInterceptor {
    /**
     * @param o           被代理的对象(需要增强的对象)
     * @param method      被拦截的方法(需要增强的方法)
     * @param args        方法入参
     * @param methodProxy 用于调用原始方法
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, 
    MethodProxy methodProxy) throws Throwable {
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method " + method.getName());
        Object object = methodProxy.invokeSuper(o, args);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method " + method.getName());
        return object;
    }
}
  1. 获取代理类
java 复制代码
import net.sf.cglib.proxy.Enhancer;

public class CglibProxyFactory {

    public static Object getProxy(Class<?> clazz) {
        // 创建动态代理增强类
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 设置被代理类
        enhancer.setSuperclass(clazz);
        // 设置方法拦截器
        enhancer.setCallback(new DebugMethodInterceptor());
        // 创建代理类
        return enhancer.create();
    }
}
  1. 实际使用
java 复制代码
AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");

JDK动态代理和CGLIB动态代理对比

  1. JDK动态代理只能代理实现了接口的类或者直接代理接口,而CGLIB可以代理未实现任何接口的类。另外,CGLIB动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为final类型的类和方法。

  2. 就二者的效率来说,大部分情况是JDK动态代理更优秀。

静态代理和动态代理对比

  1. 灵活性:动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且不需要对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的。
  2. JVM层面:静态代理在编译时就将接口、实现类、代理类都变成了一个个实际的class文件,而动态代理是在运行时动态生成类字节码,并加载到JVM中的。
相关推荐
数据小爬虫@6 分钟前
如何高效利用Python爬虫按关键字搜索苏宁商品
开发语言·爬虫·python
ZJ_.8 分钟前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
Narutolxy13 分钟前
深入探讨 Go 中的高级表单验证与翻译:Gin 与 Validator 的实践之道20241223
开发语言·golang·gin
Hello.Reader21 分钟前
全面解析 Golang Gin 框架
开发语言·golang·gin
禁默32 分钟前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
Cachel wood38 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Code哈哈笑41 分钟前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
gb421528744 分钟前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
程序猿进阶44 分钟前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
qq_433618441 小时前
shell 编程(二)
开发语言·bash·shell