Spring源码分析——AOP实现(1)

来源:互联网 发布:网站seo分析工具 编辑:程序博客网 时间:2024/06/10 23:56

文章简单介绍AOP的相关概念,并简要说明Spring中实现AOP的两种方式

1 什么是 AOP

Spring 有几个关键词, IoC 和 AOP 是 Top2, 由此可见 AOP 在 Spring 框架中的作用, Spring 事务管理就是用 AOP 实现

说 AOP 之前应该先提下 OOP, OOP (Object-Oriented Programming) 是面向对象编程, 它使用归纳法把具有共性的东西归类并使之模块化, 达到便于维护可扩展的目的, 可以对业务需求进行很好的分解使其模块化

AOP (Aspect-Oriented Programming)与 OOP 在概念上属于同一范畴的, Aspect 对于 AOP 相当于 Class 之对于 OOP, 它是面向切面的编程, 它可以简化系统需求和实现时间的对比关系, 是对 OOP 的一种补足

SpringAOP<em>aspect</em>relation

所以 AOP 和 OOP 一样, 是一种理念, 而实现这种理念就需要有具体的产品, 也就是实现, 比如 AspectJ: Java 语言的 AOP 实现, AspectC: C 语言的 AOP 实现...

Spring AOP 也是 一种 Java 语言的 AOP 实现, 但 Spring AOP 不是将所有的 AOP 需求都囊括在内, 而是以有限的 20% 的 AOP 支持, 来满足 80% 的 AOP 需求

2 AOP 的几个重要概念

主要介绍 5 个, 分别是 Joint pointPointcutAdviceAspect, 以及 Target

2.1 Joint point

point during the execution of a program, such as the execution of a method or the handling of an exception.

比如:方法调用、方法执行、字段设置/获取、异常处理执行、类初始化、甚至是 for 循环中的某个点

理论上, 程序执行过程中的任何时点都可以作为作为织入点, 而所有这些执行时点都是 Joint point

但 Spring AOP 目前仅支持方法执行 (method execution)

2.2 Pointcut

A predicate that matches join points.

描述某一类 Joint points, 比如定义了很多 Joint point, 对于 Spring AOP 来说就是匹配哪些方法的执行

描述方式:

  1. 直接指定 Jointpoint 所在的方法名, 功能比较单一, 通常只支持方法级别的 AOP 框架
  2. 正则表达式
  3. 特定的描述语言, 如 AspectJ 提供的 Pointcut 描述语言

2.3 Advice

Action taken at a particular join point. 即 Joinpoint 横且逻辑执行的时机

  1. before advice

    在 Jointpoint 指定位置之前执行, 对于 Spring AOP 来说, 就是在某个方法执行之前执行

  2. after advice

    在 Jointpoint 指定位置之后执行, 对于 Spring AOP 来说, 就是在某个方法执行之后执行

    又可分为三种, 分别是

    • after returning advice: 比如方法正常返回之后执行, 是正常返回而没有抛出异常
    • after advice: 比如方法返回之后执行, 无论是否抛出异常, 定义成 afer finally advice 更合适
    • after throwing advice: 比如当方法执行出现异常时执行
  3. around advice

    顾名思义, 是围绕这 Joinpoint 前后, 比如在方法执行前后都可以执行, 所以同时兼有 before advice 和 after advice 的功能

    Spring AOP 的 around advice 实现是利用 AOP Alliance 下的拦截器(Interceptor), 需要实现MethodInterceptor 接口

  4. introduction

    暂略

2.4 Aspect

A modularization of a concern that cuts across multiple classes.

是对系统中横且点逻辑进行模块化封装的 AOP 概念实体, 一般 Aspect 可以包含多个 Pointcut 以及相关 Advice 定义

对Spring AOP 底层来说, Aspect 的概念使用 Advisor 代替, 一个 Advisor 只有一个 Pointcut 和 相应的 Advice

Spring 2.0 后, 由于集成了 AspectJ, 可以使用 AspectJ 相关的概念实体, 但在底层实现上还是原来的 Spring AOP 自己的实现

2.5 Weaving

Linking aspects with other application types or objects to create an advised object.

将 AOP 织入到系统中, 是 AOP 和 OOP 连接共同合作的桥梁, 织入的方式可以有多种, compile time (AspectJ compiler), load time, runtime

Spring AOP 是运行时动态织入

2.6 AOP proxy

An object created by the AOP framework in order to implement the aspect contracts.

AOP 具体的织入实现使用代理实现的, Spring AOP 使用的代理有两种, 分别是 JDK 动态代理 和 CGLIB 代理

2.7 Target object

要织入横切逻辑的目标对象, 因为 Spring AOP 使用运行时代理实现, 所以这个对象始终是一个代理对象 (proxiedobject)

下面这个图描述了各个概念所处的场景

SpringAOP_weaving

2.8 一个 demo

FooAspect.java

SpringAOP<em>demo</em>aspect

Action.java/FooAction.java

public class FooAction implements Action {    @Override    public void action(String type) {        System.out.println("action type: " + type);    }}

使用 IoC 容器

SpringAOP<em>demo</em>xml.png

target

SpringAOP<em>demo</em>proxied_object

执行结果如下:

beforeAdviceaction type: fooafterReturningAdvice

3 Spring AOP 实现机制

Spring AOP 的实现归根到底是利用代理实现的, 有种设计模式叫代理模式, 对, 就是它

Spring AOP 使用的代理方式有两种, 一种是使用基于反射(reflection)机制的 JDK dynamic proxy, 另一种是使用基于动态字节码生成技术的 CGLIB库

JDK 动态代理只能针对接口代理, 对基于类的代理却无能为力, 在这种情况下, Spring AOP 使用 CGLIB 动态字节码生成进行类的代理

3.1 代理模式

定义: 为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用

在代理模式中, 通常涉及4中角色,

SpringAOP_ProxyPattern

注意: 代理模式中, 无论如何代理, 无论添加多少层代理逻辑, 最终需要调用目标对象上的同一方法来执行最初所定义的方法逻辑

这个问题会导致 Spring AOP 的一个实现缺陷, 后面对讲到

  • ISubject: 该接口是对被访问者的一个抽象, OOP 理念提倡这种抽象
  • ISubjectImpl: 被访问者的具体实现类, 代理对象的调用逻辑终会调用到这个实现类的某个方法
  • SubjectProxy: 被访问者的代理实现类, 该类持有一个 ISubject 接口的具体实例
  • Client: 访问者的抽象角色

注意:代理类 SubjectProxy 和 SubjectImpl 都实现了相同的接口 Isubject

当 Client 通过代理对象的 request() 方法请求服务的时候, SubjectProxy 将转发该请求给 SubjectImpl, 但 SubjectProxy 的作用不只局限于转发, 更多时候是对请求添加更多的访问限制或这做一些额外的动作, 而 Spring AOP 中的 Advice 就可以看作是这些额外的动作

3.2 JDK 动态代理

用法很简单, 只需实现接口 InvocationHandler 就可以了

RequestInvocationHandler.java

public class RequestInvocationHandler implements InvocationHandler {    private Object target;    public RequestInvocationHandler(Object target) {        this.target = target;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args)      throws Throwable {        System.out.println("before request"); // before action        Object result = method.invoke(target, args);        System.out.println("after request"); // after action        return result;    }}

使用

ISubject subject = (ISubject) Proxy.newProxyInstance(        TestAop.class.getClassLoader(),        new Class[] {ISubject.class},        new RequestInvocationHandler(new SubjectImpl())        );subject.request();ISubject requestable = (ISubject) Proxy.newProxyInstance(        TestAop.class.getClassLoader(),        new Class[] {ISubject.class},        new RequestInvocationHandler(new RequestableImpl()));requestable.request();

JDK 动态代理的详细信息可以参考 一书

如果目标对象实现了接口, 则 Spring AOP 默认用 JDK 动态代理, 如果目标对象没有实现任何接口, Spring AOP 会尝试使用 CGLIB 的开源动态字节码生成类库, 如果 classpath 中未引入此库, 则会报错

3.3 动态字节码生成

动态字节码生成技术扩展对象行为到原理是, 对目标对象进行继承扩展, 为其生成相应的子类, 而子类可以通过重写来扩展父类的行为

Requestable.class/RequestInvocationHandler.class

public class Requestable {    public void request() {        System.out.println("request...");    }}public class RequestInvocationHandler implements InvocationHandler {    private Object target;    public RequestInvocationHandler(Object target) {        this.target = target;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args)            throws Throwable {        System.out.println("before request");        Object result = method.invoke(target, args);        System.out.println("after request");        return result;    }}

使用:

Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Requestable.class);enhancer.setCallback(new RequestCtrlCallback());Requestable req = (Requestable) enhancer.create();req.request();

实现 net.sf.cglib.proxy.MethodInterceptor 接口即可

CGLIB 可以在系统运行期间动态的为目标对象生成相应的扩展子类, 它唯一的限制是无法对 final 方法进行重写

1 0
原创粉丝点击