代理模式是什么?
使用代理对象来代替对真实对象的访问,在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
将目标对象注入进代理类,在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做额外处理。
代理模式有静态代理和动态代理两种实现方式,我们 先来看一下静态代理模式的实现。
1. 静态代理
静态代理的实现步骤
(1)定义一个接口及其实现类
(2)创建一个类同样实现该接口
(3)将被代理对象注入代理类,在代理类的方法中调用被代理对象的方法
//定义接口
public interface SmsService {
String send(String message);
}
//被代理类
public class SmsServiceImpl implements SmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
//代理类
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;
}
}
//实际使用
public class Main {
public static void main(String[] args) {
创建被代理对象
SmsService smsService = new SmsServiceImpl();
//创建代理类对象
SmsProxy smsProxy = new SmsProxy(smsService);
//通过代理类调用方法
smsProxy.send("java");
}
}
从代码中可以看出,静态代理对被代理类的每个方法都要手动增强,非常不灵活。并且每个被代理类都需要单独的一个代理类。
2.动态代理
Java中动态代理的方式有多种,比如JDK动态代理和CGLIB动态代理。
2.1 JDK动态代理
在 Java 动态代理机制中 InvocationHandler
接口和 Proxy
类是核心。
2.1.1 JDK 动态代理类使用步骤
1. 定义一个接口及其实现类
2. 自定义 InvocationHandler
并重写invoke
方法,在 invoke
方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
3. 通过 Proxy类的newProxyInstance
方法创建代理对象;
//定义接口
public interface SmsService {
String send(String message);
}
//定义被代理类
public class SmsServiceImpl implements SmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
//定义JDK动态代理类
public class DebugInvocationHandler implements InvocationHandler {
//被代理类对象
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
}
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;
}
}
//获取代理对象的工厂类
public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标类的类加载器
target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个
new DebugInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler
);
}
}
//实际使用
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");
从代码中可以看出通过使用JDK动态代理,我们不需要针对每个目标类都单独创建一个代理类。
2.2 CGLIB 动态代理机制
CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。
在 CGLIB 动态代理机中 MethodInterceptor
接口和 Enhancer
类是核心。
2.2.1 CGLIB 动态代理类使用步骤
(1)定义一个被代理类(不再需要定义接口)
(2)自定义 MethodInterceptor
并重写 intercept
方法,intercept
用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke
方法类似;
(3)通过 Enhancer
类的 create()
创建代理类
//定义一个被代理类
public class AliSmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}
//定义MethodInterceptor(方法拦截器)
public class DebugMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//调用方法之前操作
System.out.println("before method " + method.getName());
//调用被代理类的方法
Object object = methodProxy.invokeSuper(obj, args);
//调用方法之后操作
System.out.println("after method " + method.getName());
return object;
}
}
//获取代理类
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();
}
}
//实际使用
AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");
3.总结
1. JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。
2. CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
3. JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。
4. 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
5. 动态代理更加灵活,不需要必须实现接口,可以直接代理实现类(CGLIB动态代理),并且可以不需要针对每个目标类都创建一个代理类(JDK动态代理)。