第8章 Spring AOP概述及其实现机制

本章内容

  • Spring AOP概述
  • Spring AOP的实现机制

Spring AOP概述

SpringAOP 采用Java作为AOP的实现语言(AOL),较之像Aspect那种语言扩展型的AOP实现,SpringAOP可以更快捷地融入开发过程,学习曲线相对要平滑得多。而且,SpringAOP的设计哲学也是简单而强大。它不打算将所有的AOP需求全部囊括在内,而是要以有限的20%的AOP支持,来满足80%的AOP需求。如果觉得SpringAOP无法满足你所需要的那80%之外的需求,那么求助于Aspect好了,SpringAOP对AspectJ也提供了很好的集成。

SpringAOP的AOL语言为Java。所以,在Java语言的基础之上,SpringAOP对AOP的概念进行了适当的抽象和实现,使得每个AOP的概念都可以落到实处。而这些概念的抽象和实现,都是以我们所熟悉的Java语言的结构呈现在我们面前的。

SpringAOP从最初的框架发布以来,一直保持稳定的AOP模型。即使2.0引入了AspectJ的支持,但总的设计和实现依然延续最初的设想,也就是使用动态代理机制,构建基于Java语言的简单而强大的AOP框架。

Spring AOP的实现机制

Spring AOP属于第二代AOP,采用 动态代理机制字节码生成 技术实现。与最初的AspectJ采用编译器将横切逻辑织入目标对象不同,动态代理机制和字节码生成都是在运行期间为目标对象生成一个代理对象,而将横切逻辑织入到这个代理对象中, 系统最终使用的是织入了横切逻辑的代理对象,而不是真正的目标对象。

为了理解这种差别以及最终可以达到的效果,我们有必要先从动态代理机制的根源——代理模式(Proxy Pattern)开始说起…

设计模式之代理模式

房地产中介就是一种代理。代理处于访问者与被访问者之间,可以隔离这两者之间的直接交互,访问者与代理打交道就好像在跟被访问者在打交道一样,因为代理通常几乎会全权拥有被代理者的职能,代理能够处理的访问请求就不必要劳烦被访问者来处理了。从这个角度来说,代理可以减少被访问者的负担。另外,即使代理最终要将访问请求转发给真正的被访问者,它也可以在转发访问请求之前或者之后加入特定的逻辑,比如安全访问限制,或者,像房产中介那样抽取一定的中介费等。

在软件系统中,代理机制的实现有现成的设计模式支持,就叫 代理模式 。在代理模式中,通常涉及4种角色,如图8-2所示。

image-20220407184331185

  • ISubject。该接口是对 被访问者或者被访问资源的抽象 。在严格的设计模式中,这样的抽象接口是必须的,但往宽了说,某些场景下不使用类似的统一抽象接口也是可以的。
  • SubjectImpl。 被访问者或者被访问资源的具体实现类 。如果你要访问某位明星,那么SubjectImpl就是你想访问的明星;如果你要买房子,那么SubjectImpl就是房主…
  • SubjectProxy。被访问者或者被访问资源的 代理实现类 ,该类持有一个ISubject接口的具体实例。在这个场景中,我们要对SubjectImpl进行代理,那么subjectProxy现在持有的就是SubjectImpl的实例。
  • Client。代表 访问者 的抽象角色,Client将会访问ISubject类型的对象或者资源。在这个场景中,Client将会请求具体的subjectImpl实例,但Client无法直接请求其真正要访问的资源subjectImpl,而是必须要通过ISubject资源的访问代理类subjectProxy进行。

SubjectImpl和SubjectProxy都实现了相同的接口ISubject,而SubjectProxy内部持有subjectImpl的引用。当Client通过request()请求服务的时候,subjectProxy将转发该请求给SubjectImpl。从这个角度来说,SubjectProxy反而有多此一举之嫌了。 不过,SubjectProxy的作用不只局限于请求的转发,更多时候是对请求添加更多访问限制。

在将请求转发给被代理对象subjectImpl之前或者之后,都可以根据情况插入其他处理逻辑,或者,可以只在转发之后对SubjectImpl的request()方法返回结果进行覆盖,返回不同的值。甚至,可以不做请求转发,这样,就不会访问subjectImpl。

如果SubjectImpl是系统中的Joinpoint所在的对象,即 目标对象,那么就可以为这个目标对象创建一个代理对象,然后将横切逻辑添加到这个代理对象中。当系统使用这个代理对象运行的时候,原有逻辑的实现和横切逻辑就完全融合到一个系统中。如图8-4所示。

image-20220407185033056

SpringAOP本质上就是采用这种代理机制实现的,但是,具体实现细节上有所不同,让我们来看一下为什么。

假设要对系统中所有的request()方法进行拦截,我们希望在每天午夜0点到次日6点之间不允许调用它,那么,我们应该为SubjectImpl提供一个ServiceControlSubjectProxy,以添加这种横切逻辑。

这样就有了下方代码的ServiceControlSubjectProxy定义。

 
 
    public class ServiceControlSubjectProxy implements ISubject {
        private ISubject subject;
 
        public LogSubjectProxy(ISubject s) {
            this.subject = s;
        }
 
        public String request() {
            TimeOfDay startTime = new TimeOfDay(0, 0, 0);
            TimeOfDay endTime = new TimeOfDay(5, 59, 59);
            TimeOfDay currentTime = new TimeOfDay();
          	// 根据日期判断是否需要执行
            if (currentTime.isAfter(startTime) && currentTime.isBefore(endTime))
                return null;
            String originalResult = subject.request();
            return "Proxy: " + originalResult;
        }
    }
 
 

之后,我们使用该ServiceControlsubjectProxy代替SubjectImpl使用,如以下代码所示:

 
 
    ISubject target = new SubjectImpl();
    ISubject finalSubject = new ServiceControlSubjectProxy(target);
    finalSubject.request();
    //访问控制相关逻辑已经包含在请求的处理逻辑中

但是,系统中可不一定就ISubject的实现类有request()方法,IRequestable接口以及相应实现类可能也有request()方法,它们也是我们需要横切的关注点。

IRequestable接口:

 
 
    public interface IRequestable {
      void request();
    }
 

其对应实现类:

 
    public class RequestableImpl implements IRequestable {
      public void request() {
        System.out.println("request processed in RequestableImpl");
      }
    }

为了能够为IRequestable相应实现类也织入以上的横切逻辑,我们又得提供对应的代理对象,如下方代码所示。

 
 
    public class ServiceControlRequestableProxy implements IRequestable {
        private IRequestable requestable;
 
        public ServiceControlRequestableProxy(IRequestable requestable) {
            this.requestable = requestable;
        }
 
        public void request() {
            TimeOfDay startTime = new TimeOfDay(0, 0, 0);
            TimeOfDay endTime = new TimeOfDay(5, 59, 59);
            TimeOfDay currentTime = new TímeOfDay();
            // 根据事件判断是否需要执行
            if (currentTime.isAfter(startTime) && currentTime.isBefore(endTime)) {
                return;
            }
            requestable.request();
        }
    }

并且将该代理对象而不是目标对象绑定到系统中,如以下代码所示:

 
 
    IRequestable target = newRequestableImpl();
    IRequestableproxy = new ServiceControlRequestableProxy(target);
    proxy.request();
    // 横切逻辑现在已经织入到代理对象中
 

发现问题了没有?虽然Joinpoint相同(request()方法的执行),但是对应的目标对象类型是不一样的。针对不一样的目标对象类型,我们要为其单独实现一个代理对象。而实际上,这些代理对象所要添加的横切逻辑是一样的。

当系统中存在成百上千的符合Pointcut匹配条件的目标对象时并且这些目标对象的类型都不同,我们就要为这成百上千的目标对象创建成百上千的代理对象,所以要寻找其他方法,以避免这种窘境…