方法的增强——代理模式
1. 定义
代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。
代理对象会表现出和目标对象一样的行为,同时对目标对象进行增强,完成了一些无法完成的业务。
现实的例子:
-
律师。没有完善的法律知识,依然可以进行维权。
-
代购。无需护照,无需出国,通过朋友直接买到国外的商品。
2. 设计
代理模式两大角色:
- 目标对象。
- 代理对象。
类图如下:
代理模式-类图根据业务模型,代理可以进行分类。
根据我的理解,从对目标对象侵入程度的影响,可以分成两大类:
- 强权代理。会干涉目标对象的执行。比如权限控制。
- 佛系代理。不影响目标对象的执行。比如日志记录,方法耗时统计。
代理模式的实现,有个关键的地方就是 代理与目标需要有一致的行为。
Java 有这几种方式:
- 静态代理。设计接口,代理与目标实现接口。
- JDL 动态代理。在内存中使用反射工具创建代理对象。也需要接口
- CGLIB 动态代理。使用 ASM 工具直接进行字节码修改,创建代理对象。不需要接口。但属于继承关系,所以无法代理 final 修饰的目标类。
2.1. 静态代理
目标对象创建接口:
public interface ISubject {
void request();
}
实现类:
public class RealSubject implements ISubject {
public void request() {
System.out.println("Hello world!");
}
}
代理对象也实现该接口,并且持有目标对象的引用:
public class Proxy implements ISubject {
public ISubject target;
public Proxy(ISubject target) {
this.target = target;
}
public void request() {
System.out.println("begin");
target.request();
System.out.println("end");
}
public static ISubject wrap(ISubject target) {
return new Proxy(target);
}
}
使用者面向接口编程:
public class TestStaticProxy {
public static void main(String[] args) {
ISubject subject = new RealSubject();
System.out.println("代理前:");
subject.request();
System.out.println("代理后:");
subject = Proxy.wrap(subject);
subject.request();
}
}
确实实现了代理。
静态代理实现简单,但有以下弊端:
- 必须实现同一个接口。
- 目标类型多样,代理类数量会膨胀。
2.2. JDK 动态代理
无需静态创建代理类,解决了静态代理类膨胀的问题。
使用 JDK reflect 包的工具,核心类有:
- Proxy
- InvocationHandler
两种的关系为:把目标类的接口传递给 Proxy 生成代理对象,在调用接口的方法时,会触发 InvocationHandler 回调,在回调中实现代理业务。
还是上面的例子,这里代理对象实现 InvocationHandler:
public class JDKProxy implements InvocationHandler {
public Object target;
public JDKProxy(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("begin");
Object result = method.invoke(target, args);
System.out.println("end");
return result;
}
@SuppressWarnings("unchecked")
public static <T> T wrap(Object target) {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new JDKProxy(target));
}
}
使用:
public class TestJDKProxy {
public static void main(String[] args) {
ISubject subject = new RealSubject();
System.out.println("代理前:");
subject.request();
System.out.println("代理后:");
subject = JDKProxy.wrap(subject);
subject.request();
}
}
因为动态去创建代理对象,所以 SimpleProxy 可以应用到任务实现接口的目标类型中,而不仅仅是 ISubject。
JDK 动态代理虽强大,但也存在几个问题:
- 反射消耗性能。
- 目标类必须实现接口。
假如我们的目标类就没有实现接口,该怎么做?
2.3. Cglib 动态代理
静态代理和 JDK 动态代理都需要实现接口,但 Cglib 不需要。
Cglib 的底层使用了 ASM 修改字节码,动态创建代理类并加载。
虽然没有实现同一套接口,但与目标类属于继承关系,所以 final 类型的类是无法代理的。
核心类:
- Enhance,生产代理对象。
- MethodInterceptor,方法拦截器,执行到对应方法会触发拦截器。
使用方式和 JDK 动态代理类似:
public class CglibProxy implements MethodInterceptor {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("begin");
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("end");
return result;
}
@SuppressWarnings("unchecked")
public static <T> T wrap(Class<T> targetCls) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetCls);
enhancer.setCallback(new CglibProxy());
return (T) enhancer.create();
}
}
及时目标对象没有实现接口,也可以进行代理。
3. 应用
在实际开发中,代理模式有着很广的应用,也是很多框架的基础。
具体的应用有:
-
远程访问:客户端要调用远程对象时可以使用,比如 RPC 调用。
-
权限控制:方法执行权限的控制。
-
数据校验:输入参数的校验。
-
缓存设计:调用频繁或耗性能的操作加入缓存。
-
统计设计:对一些关键方法进行埋点,加入统计信息。
-
日志设计:对方法调用加入一些日志记录。
-
实现 AOP 编程。
3.1. Spring:AopProxy
AOP 编程,在我理解就是把通用的规则、规律或者公共行为统一在统一的地方搞定,然后通知到各个要处理的分类对象中(对象没有继承关系)。这样就可以实现非侵入式代码,而且便于维护和修改规则代码。
Spring AOP 编程中,来指定切入点(point cut),获取连接点(join point),编写通知(advice),内聚到一个切面类(Apect)中并使用 @Aspect 注解。
框架会去识别到这个切面类。
然后 Bean 的创建后,内部会使用 AopProxy,结合我们定义好的规则,创建代理对象,找到每个代理对象的连接点,织入增强代码。
AopProxy 的定义如下:
public interface AopProxy {
/**
* Create a new proxy object.
* <p>Uses the AopProxy's default class loader (if necessary for proxy creation):
* usually, the thread context class loader.
* @return the new proxy object (never {@code null})
* @see Thread#getContextClassLoader()
*/
Object getProxy();
/**
* Create a new proxy object.
* <p>Uses the given class loader (if necessary for proxy creation).
* {@code null} will simply be passed down and thus lead to the low-level
* proxy facility's default, which is usually different from the default chosen
* by the AopProxy implementation's {@link #getProxy()} method.
* @param classLoader the class loader to create the proxy with
* (or {@code null} for the low-level proxy facility's default)
* @return the new proxy object (never {@code null})
*/
Object getProxy(ClassLoader classLoader);
}
AopProxy 的具体实现有:
- JdkDynamicAopProxy,使用 JDK 动态代理。
- CglibAopProxy,使用 Cglib 动态代理。
4. 特点
4.1. 优势
- 解耦:降低系统耦合。
- 易修改:代理类独立变化,不影响目标类。
- 非侵入式 代码。
4.2. 缺点
- 性能损耗:增加了代理的业务逻辑,目标对象方法处理变慢。
- 增加复杂度:有些代理实现复杂。
4.3. 注意事项
- 代理类的设计要注意性能问题。