静态代理、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、灵活性层面:动态代理更加灵活,不必须要实现接口,可以直接代理实现类或者未实现接口的类,并且不需要针对每个目标类都创建一个代理类;另外,静态代理,一旦接口新增了一个方法,实现类和代理类都必须要作出相应的修改,这是非常麻烦的。