开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第31天,点击查看活动详情
引言
在做应用的时候经常在文章、日常的开发中听到 Spring 的核心特色是 AOP 和 IOC,随着经验和知识的积累,所以今天就来刨根问底,去探究一下,Spring AOP 到底如何实现的,系列文章包含以下内容:
- Java 中代理模式——静态代理和动态代理
- JDK 动态代理和 CGLIB
- Spring AOP 源码分析
代理模式
静态代理
说起静态代理模式,我还能记得刚开始接触代码的时候读的《大话设计模式》,小菜和大鸟关于娇娇的恋爱故事所抽象出的设计模式。所以关于静态代理的设计模式,我想致敬经典,将原来的 C# 代码的代码模式用 Java 重写一下。
静态代理UML
静态代理逻辑实现
Subject 接口,定义了 RealSubject 和 Proxy 的公用接口,这样就在任何使用 RealSubject 的地方都可以使用 Proxy。
public interface Subject {
void request();
}
RealSubject 类,定义 Proxy 所代表的真实实体。
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("真实的用户请求");
}
}
Proxy 类,保存一个引用使得代理可以访问实体,并提供一个与 Subject 的接口相同的接口,这样代理就可以用来代替实体。
public class Proxy implements Subject {
RealSubject realSubject;
public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
System.out.println("代理准备执行请求");
realSubject.request();
System.out.println("代理已经执行请求");
}
}
结合 Spring 实现静态代理,采用注入的方式实现代理对象。
@Service
public class SpringStaticProxy {
@Resource
private Subject subject;
public String request() {
System.out.println("proxy static start");
String result = subject.request();
System.out.println("proxy static end");
return result;
}
}
JDK 动态代理
JDK 动态代理 两个重要类 Proxy and InvocationHandler
-
InvocationHandler:
给代理实例调用的 invocation handler (调用程序)提供的一个实现接口,每一个代理实例都有一个相关的 invocation handler(调用程序),当一个代理实例调用一个方法时(Runtime),这个方法会被编码并且被派遣到这个代理实例的 invocation handler 中的 invoke 方法。
/** * 处理代理实例的方法调用并且返回结果,当一代理实例,代理的对象方法被调用时, * 这个方法会在invocation handler(调用处理程序)上被调用, * * @param proxy 为代理实例(代理实例代理的对象方法被调用时) * * @param method 为Method实例(为运行时,代理实例所调用到代理对象所实现的接口方法) * * @param args 为Object数组(method所对应方法的参数值,当方法无参时可以为null) * * @return 为代理实例调用的方法的返回值 */ public Object invoke(Object proxy, Method method, Object[] args)throws Throwable; -
proxy:
/** * 返回特定接口的代理实例,该接口所有的方法调用,都会分发到特定的invocation handler * * @param loader classloader用来界定代理类 * @param interfaces 代理类实现的接口数组 * @param h invocation handler 转发函数调用 * @return 返回一个被特定classloader 定义,含有特定 invocation handler以及实现特定接口的代理实例 */ @CallerSensitive public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h);
JDK 动态用法:
public class RealSubjectDynamicProxy implements InvocationHandler {
//被代理对象
Subject realSubject = null;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//实现延迟加载
//体现出代理一个运行时实例化的特点
if (realSubject == null) {
realSubject = new RealSubject();
}
System.out.println("动态代理执行方法");
return method.invoke(realSubject, args);
}
public static Subject newProxyInstance() {
return (Subject) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
RealSubject.class.getInterfaces(), new RealSubjectDynamicProxy());
}
}
客户端实例:
@Test
public void test_proxy_method() {
Subject subject = RealSubjectDynamicProxy.newProxyInstance();
subject.request();
}
从上述示例中可以看出动态代理有很多好处:
- 动态代理不需要为真实主题写一个形式上完全一样的封装类,假如主题接口中有很多,为每一个代理方法写一个代理方法也很麻烦。如果接口变动,则真实主题和代理类都要修改,不利于系统维护。
- 使用一些动态代理的生成方法,可以在运行时制定代理类的执行逻辑,从而大大提升系统灵活性。
JDK 动态代理和 CGLIB
这一章节暂时还没深入学习研究,就先说说自己在开发中的感觉吧。
JDK 动态代理使用上相对简单,内置在 JDK 中不用引用第三方包,同样地,这也使它在实现复杂业务功能时候不够好用;CGLIB(Code Generation Library)字节码生成库,Spring 使用 CGLIB 来进行代理,相对 JDK 动态代理,功能要强大,性能会更好。
我们实际业务开发还选择了 Aspectj AOP 框架作为权限校验的检查,后续学习也会一起总结一下,各自的特点和使用场景的。
(未完,后续学习,将持续更新……)