目录
代理模式
代理模式的组成
代理模式的作用
静态代理
静态代理实现步骤:
静态代理的缺点
动态代理
动态代理的实现
JDK 动态代理(接口代理)
jdk动态代理核心
JDK 动态代理类实现步骤:
CGLIB动态代理
CGLIB动态代理的核心
CGLIB 动态代理类实现步骤
JDK 动态代理和 CGLIB 动态代理对⽐
定义:为其他对象提供⼀种代理以控制对这个对象的访问。在某些情况下,⼀个对 象不适合或者不能直接引⽤另⼀个对象,⽽代理对象可以在客户端和⽬标对象之间 起到中介的作⽤。
代理模式分为静态代理和动态代理。
代理模式一般由三个角色组成:
Subject(抽象角色):声明了真实主题和代理主题的共同接口。
Proxy(代理角色):具体的代理类,其内部持有对RealSubject的引用,因此具备完全的对RealSubject的代理权。客户端调用代理对象的方法,同时也调用被代理对象的方法,但是会在代理对象前后增加一些代码处理。
RealSubject(真实角色):实现了真实的业务操作。
增强就是扩展对象功能,这里就是在代理的基础上可以添加自己的代码。
隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
开闭原则:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。
静态代理中,我们对⽬标对象的每个⽅法的增强都是⼿动完成的,⾮常不灵活(⽐ 如接⼝⼀旦新增加⽅法,⽬标对象和代理对象都要进⾏修改)且麻烦(需要对每个 ⽬标类都单独写⼀个代理类)。在jvm上来说,静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。
这样的话,我们就可以通过代理类屏蔽对⽬标对象的访问,并且可以在⽬标⽅法执 ⾏前后做⼀些⾃⼰想做的事情。
定义接口(抽象接口)
public interface PayService {
void pay();
}
实现接⼝(实现类)
public class AliPayService implements PayService {
@Override
public void pay() {
System.out.println("ali pay...");
}
}
创建代理类并同样实现支付接口(代理类)
public class StaticProxy implements PayService{
private final PayService payService;
public StaticProxy(PayService payService) {
this.payService = payService;
}
@Override
public void pay() {
System.out.println("before...");
payService.pay();
System.out.println("after...");
}
}
使用
public class Main {
public static void main(String[] args) {
PayService service = new AliPayService();
PayService proxy = new StaticProxy(service);
proxy.pay();
}
}
上边的代码我们可以看出代理类和实现类都实现了抽象接口,这也是静态代理的一个前提,就是实现类和代理类要是实现同一个接口。
而且静态代理提前写好了,会占用内存,而且代理每个对象的每个增强都是自己手写的,代码量也越来越大,我们想要减少代码量并且想要在我们调用的时候,代理再去创建。因此,有了动态代理。
对比于静态代理来说,动态代理更加灵活。我们不需要针对每个⽬标类都单独创建 ⼀个代理类,并且也不需要我们必须实现接⼝,我们可以直接代理实现类( CGLIB 动态代理机制)。 从 JVM ⻆度来说,动态代理是在运⾏时动态⽣成类字节码,并加载到 JVM 中 的。
动态代理在我们日常开发中使用的相对较少,但是在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。
JDK 动态代理
CGLIB 动态代理等。
在 Java 动态代理机制中 InvocationHandler 接⼝和 Proxy 类是核⼼。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
invoke()方法有下面三个参数:
public static Object newProxyInstance(ClassLoader loader,
Class>[] interfaces,
InvocationHandler h)
这个方法一共有 3 个参数:
InvocationHandler
接口的对象;定义代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class JDKInvocationHandler implements InvocationHandler {
//⽬标对象即就是被代理对象
private Object target;
public JDKInvocationHandler(Object target) {
this.target = target;
}
//proxy代理对象
@Override
public Object invoke(Object proxy, Method method, Object[] args) throw
s Throwable {
//1.安全检查
System.out.println("安全检查");
//2.记录⽇志
System.out.println("记录⽇志");
//3.时间统计开始
System.out.println("记录开始时间");
//通过反射调⽤被代理类的⽅法
Object retVal = method.invoke(target, args);
//4.时间统计结束
System.out.println("记录结束时间");
return retVal;
}
}
创建一个代理对象并使用
public static void main(String[] args) {
PayService target= new AliPayService();
//创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建
PayService proxy = (PayService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{PayService.class},
new JDKInvocationHandler(target)
);
proxy.pay();
}
JDK 动态代理有⼀个最致命的问题是其只能代理实现了接⼝的类。 为了解决这个问题,我们可以⽤ CGLIB 动态代理机制来避免。cglib 是针对类来实现代理的,他的原理是对指定的目标类生成一个子类并通过回调的方式来实现增强,但因为采用的是继承,所以不能对 final 修饰的类进行代理。
CGLIB(Code Generation Library)是⼀个基于ASM的字节码⽣成库,它允许我们 在运⾏时对字节码进⾏修改和动态⽣成。CGLIB 通过继承⽅式实现代理。很多知名的开源框架都使⽤到了CGLIB, 例如 Spring 中的 AOP 模块中:如果⽬标对象 实现了接⼝,则默认采⽤ JDK 动态代理,否则采⽤ CGLIB 动态代理。
在 CGLIB 动态代理机制中 MethodInterceptor 接⼝和 Enhancer 类是核⼼。
你需要⾃定义 MethodInterceptor 并重写 intercept ⽅法,intercept ⽤于拦截增 强被代理类的⽅法。
public interface MethodInterceptor
extends Callback{
// 拦截被代理类中的⽅法
public Object intercept(Object obj, java.lang.reflect.Method method, Ob
ject[] args,MethodProxy proxy) throws Throwable;
}
intercept()方法有4个参数:
添加依赖
和JDK 动态代理不同, CGLIB(Code Generation Library) 实际是属于⼀个开源项 ⽬,如果你要使⽤它的话,需要⼿动添加相关依赖。
cglib
cglib
3.3.0
⾃定义 MethodInterceptor(⽅法拦截器)
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLIBInterceptor implements MethodInterceptor {
//被代理对象
private Object target;
public CGLIBInterceptor(Object target){
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] args, Method
Proxy methodProxy) throws Throwable {
//1.安全检查
System.out.println("安全检查");
//2.记录⽇志
System.out.println("记录⽇志");
//3.时间统计开始
System.out.println("记录开始时间");
//通过cglib的代理⽅法调⽤
Object retVal = methodProxy.invoke(target, args);
//4.时间统计结束
System.out.println("记录结束时间");
return retVal;
}
}
创建代理类并使用
import org.springframework.cglib.proxy.Enhancer;
public static void main(String[] args) {
PayService target= new AliPayService();
PayService proxy= (PayService) Enhancer.create(target.getClass(),new CGLIBInterceptor(target));
proxy.pay();
}
1. JDK 动态代理只能代理实现了接⼝的类或者直接代理接⼝,⽽ CGLIB 可以代 理未实现任何接⼝的类。
2. JDK 动态代理实现InvocationHandler,重写invoke方法,通过生成被代理类的子类来增强代码。CGLIB 动态代理是通过实现MethodInterceptor,重写intercept方法,⽣成⼀个被代理类的⼦类来拦截被代理类的⽅法调用,因此不能代理声明为 final。
性能: ⼤部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更 加明显。
补充一点
Spring代理选择
1. proxyTargetClass 为false, ⽬标实现了接⼝, ⽤jdk代理
2. proxyTargetClass 为false, ⽬标未实现接⼝, ⽤cglib代理
3. proxyTargetClass 为true, ⽤cglib代理