静态代理、JDK/CGLIB 动态代理

1、代理模式

简单来说就是我们使用代理对象代替对真实对象的访问,在不改变真实对象的前提下,提供额外的功能操作,增强目标对象的功能。
代理模式可分为静态代理和动态代理两种方式

2、静态代理

2.1 描述

静态代理需要手写代理实现类去实现接口,并去重写接口中的每个方法。
非常不灵活且麻烦,因为需要对每一个目标对象创建一个实现类;而且一旦接口中新增一个方法,目标对象和实现类都需要进行相应的调整。
从 JVM 层面来说,静态代理在编译期就已经将接口、实现类、代理类编译成 .class 文件了。

2.2 实现步骤

  • 定义一个接口和实现类;
  • 创建一个代理实现类同样实现这个接口;
  • 将目标对象注入到代理类,然后在代理类的对应方法调用目标对象的相应方法

2.3 场景实践

现在设计一个发送短信的场景,对发送短信接口作增强,手写一个发短信增强的静态代理类

2.3.1 定义发送短信接口
public interface SmsService {

    String send(String message);
}
2.3.2 定义发送短信接口实现类
public class SmsServiceImpl implements SmsService {

    @Override
    public String send(String message) {
        System.out.println("发送短信内容:" + message);
        return message;
    }
}
2.3.3 创建代理类并实现同样的接口
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("发送短信之前,可添加操作");
        smsService.send(message);
        System.out.println("发送短信之后,可添加操作");
        return message;
    }
}
2.3.4 代码测试
public class ProxyTest {

    public static void main(String[] args) {
        // 1、静态代理
		staticProxy();
    }

    /**
     * 静态代理测试
     */
    public static void staticProxy() {
        // 真实对象
        SmsService smsService = new SmsServiceImpl();
        // 代理对象
        SmsProxy smsProxy = new SmsProxy(smsService);
        // 使用代理对象代替对真实对象的访问,
        // 在不改变原目标对象的情况下,提供额外的功能操作,扩展目标对象的功能;
        smsProxy.send("静态代理--发短信给小马");
    }
}

方法运行后输出内容:

发送短信之前,可添加操作
发送短信内容:静态代理--发短信给小马
发送短信之后,可添加操作

从输出结果可以清晰的看到,在执行发送短信业务前后分别执行了我们设计的增强功能。

3、动态代理

与静态代理相比,动态代理更加灵活。我们不需要针对每一个目标对象都单独创建一个代理类,并且也不必须要实现接口,可以直接代理类。
JVM 层面来说,动态代理是在运行时期动态生成类字节码并加载到 JVM 中去。
动态代理的实现方式有很多种,最主要的还是 JDK 动态代理CGLIB 动态代理

3.1 JDK 动态代理

JDK 动态代理是对实现了接口的实现类进行代理。
JDK 动态代理的核心是 InvocationHandleer 接口Proxy 类

3.1.1 Proxy 类

Proxy 类中使用频率最高的方法是 newProxyInstance,用来生成一个代理对象。
参数:

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

要实现 JDK 动态代理,还需要实现 InvocationHandler 接口来自定义增强逻辑,当我们代理对象调用一个方法时,方法的调用会自动转到 InvocationHandler 实现类的 invoke() 方法上。

参数:

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

当你通过 Proxy 类的 newProxyInstance 方法创建的代理对象调用一个方法时,实际上会调用到 InvocationHandler 实现类的 invoke() 方法,可以 在 invoke() 方法内增加自定义的逻辑来增强原生方法。

实现步骤:

  • 定义一个接口和实现类;
  • 定义一个类去实现 InvocationHandler 接口并重写 invoke() 方法,在 invoke() 方法内调用原生方法并自定义增强该方法;
  • 通过 Proxy 类的 newProxyInstance 方法创建一个代理对象

场景实践:

接口和实现类复用上面静态代理的方法。

创建一个 InvocationHandler 的实现类

package com.dbapp.flyDemo.proxy.dynamicproxy;

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

/**
 * 自定义动态代理接口
 *
 * @author liz
 * @date 2022/3/14-15:39
 */
public class DebugInvocationHandler implements InvocationHandler {

    private final Object target;

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

    /**
     *
     * @param proxy 动态生成的代理类
     * @param method 与代理类对象调用的方法相对应
     * @param args 当前方法 method 的参数
     * @return
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        System.out.println("----调用方法之前可以添加操作:" + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("调用方法之后可以添加操作----:" + method.getName());
        return result;
    }
}

创建一个代理对象的工厂类

package com.dbapp.flyDemo.proxy.dynamicproxy;

import java.lang.reflect.Proxy;

/**
 * JDK代理工厂
 *
 * @author liz
 * @date 2022/3/14-15:45
 */
public class JdkProxyFactory {

    public static Object getProxy(Object target) {

        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new DebugInvocationHandler(target));
    }
}

代码测试

public static void main(String[] args) {
    // 2、JDK动态代理
    jdkDynamicProxy();
}

/**
 * JDK动态代理测试
 */
public static void jdkDynamicProxy() {
    SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
    smsService.send("JDK动态代理--发短信给小马");
}

代码执行输出内容

----调用方法之前可以添加操作:send
发送短信内容:JDK动态代理--发短信给小马
调用方法之后可以添加操作----:send

3.2 CGLIB 动态代理

JDK 动态代理有一个问题就是只能代理实现了接口的类,而 CGLIB 动态代理可以直接代理未实现接口的类,我们可以通过 CGLIB 解决这个问题。

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

3.2.1 MethodInterceptor 接口

需要自定义 MethodInterceptor 实现 MethodInterceptor 接口并重写 intercept 方法,intercept 是用来拦截被代理类的方法;

public interface MethodInterceptor extends Callback {

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable;
}

参数:

  • o:被代理的对象(需要增强的对象)
  • method: 被代理的方法(需要增强的方法)
  • objects:方法入参
  • methodProxy:用来调用原始方法
3.2.2 Enhancer 类

通过 Enhancer 类生成代理类,当代理类调用方法时,实际上调用的是 MethodInterceptor 实现类的 intercept 方法。

实现步骤:

  • 定义一个类(被代理的类)
  • 定义一个类实现 MethodInterceptor 接口并重写 intercept 方法,方法内通过 methodProxy 调用原始方法,可以在方法调用前后自定义增强逻辑
  • 通过 Enhancer 类的 create() 方法创建代理类

场景实践:

1、实现一个发送短信的类

package com.dbapp.flyDemo.proxy.dynamicproxy;

/**
 * Cglib 测试类
 *
 * @author liz
 * @date 2022/3/14-16:16
 */
public class CglibSmsService {

    public String send(String messgae) {
        System.out.println("短信发送内容:" + messgae);
        return messgae;
    }

}

2、自定义 MethodInterceptor 实现类(方法拦截器)

package com.dbapp.flyDemo.proxy.dynamicproxy;

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * Cglib 自定义方法拦截器
 *
 * @author liz
 * @date 2022/3/14-16:18
 */
public class DebugMethodIntercept implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("----调用方法之前可以添加操作:" + method.getName());
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("调用方法之后可以添加操作----:" + method.getName());
        return result;
    }
}

3、定义一个生成代理对象类

package com.dbapp.flyDemo.proxy.dynamicproxy;

import org.springframework.cglib.proxy.Enhancer;

/**
 * Cglib 代理工厂
 *
 * @author liz
 * @date 2022/3/14-16:28
 */
public class CglibProxyFactory {

    public static Object getProxy(Class<?> clazz) {
        // 1、创建一个Cglib代理增强类
        Enhancer enhancer = new Enhancer();
        // 2、设置类加载器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 3、设置被代理类
        enhancer.setSuperclass(clazz);
        // 4、设置方法拦截器
        enhancer.setCallback(new DebugMethodIntercept());
        // 5、创建代理类
        return enhancer.create();
    }
}

代码测试

public static void main(String[] args) {
    // 3、Cglib动态代理
    cglibDynamicProxy();
}

/**
 * Cglib动态代理测试
 */
public static void cglibDynamicProxy() {
    CglibSmsService cglibSmsService = (CglibSmsService) CglibProxyFactory.getProxy(CglibSmsService.class);
    cglibSmsService.send("Cglib动态代理--发短信给小马");
}

代码执行输出内容

----调用方法之前可以添加操作:send
短信发送内容:Cglib动态代理--发短信给小马
调用方法之后可以添加操作----:send

3.3 JDK 动态代理和 CGLIB 动态代理的比较

  • JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 动态代理可以代理未实现接口的类。而且,CGLIB 动态代理是通过生成一个被代理的子类来拦截被代理类的方法调用,因此不能代理使用 final 修饰的类和方法。
  • 就效率而言,大多数情况下 JVM 动态代理的效率更高,随着 JDK 版本升级,这种优势就更明显。

4、静态代理和动态代理的比较

1、JVM 层面:静态代理是在编译器就已经将接口、实现类、代理类编译成实际的 .class 文件了;而动态代理是在运行期间动态生成类的字节码文件,并加载到 JVM 中去;
2、灵活性层面:动态代理更加灵活,不必须要实现接口,可以直接代理实现类或者未实现接口的类,并且不需要针对每个目标类都创建一个代理类;另外,静态代理,一旦接口新增了一个方法,实现类和代理类都必须要作出相应的修改,这是非常麻烦的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值