Spring之旅第七站:面向切面编程(AOP)

面向切面的Spring

本章主要内容:java

  • 面向切面编程的基本原理
  • 经过POJO建立切面
  • 使用@Aspect注解
  • 为AspectJ切面注入依赖。

说明

若是你有幸能看到。git

  • 一、本文参考了《Spring 实战》重点内容,参考了GitHub上的代码
  • 二、本文只为记录做为之后参考,要想真正领悟Spring的强大,请看原书。
  • 三、在一次佩服老外,国外翻译过来的书,在GiuHub上大都有实例。看书的时候,跟着敲一遍,效果很好。
  • 四、代码和笔记在这里GitHub,对你有帮助的话,欢迎点赞。
  • 五、每一个人的学习方式不同,找到合适本身的就行。2018,加油。
  • 六、问候了下Java 8 In Action 的做者Mario Fusco,竟然回复了。
  • 七、Spring In Action 、Spring Boot In Action的做者Craig Walls老忙了,没理睬。
  • 八、知其然,也要知其因此然。

谈一些我的感觉github

  • 一、赶快学习Spring吧,Spring MVC 、Spring Boot 、微服务。
  • 二、重点中的重点,学习JDK 8 Lambda,Stream,Spring 5 最低要求JDK1.8.
  • 三、还有Netty、放弃SH吧,否则你会落伍的。
  • 四、多看一些国外翻译过来的书,例如 Xxx In Action 系列。权威指南系列。用Kindle~

软件系统中的一些功能就像咱们家里的电表同样。则核心功能须要用到应用程序的多个地方。可是咱们又不想在每一个点都明确调用它。日志、安全、事务管理的确很重要。但它们是否为应用对象主动参与的行为呢?若是让应用对象只关注与本身所针对的业务领域问题,而其余方面的问题由其余应用对象来处理,这样不更好吗?express

在软件开发中,散布于应用中多出功能被称为横切关注点(crosscutting concern)。一般来说横切关注点从概念上是与应用的业务逻辑分离的。但每每是耦合在一块儿的,把这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所要解决的问题。编程

依赖注入(DI)管理咱们的应用对象,DI有助于应用对象之间解耦。而AOP能够实现横切关注点与它们所影响的对象之间的耦合。安全

4.1 什么是面向切面编程

切面可以帮咱们模块化横切关注点。简而言之,横切关注点能够被描述为影响应用多处的功能。例如 安全,事务、日志等功能。app

若是要重用对象的话,最多见的面向对象技术是继承、委托、组合。可是,若是整个应用中都使用相同的基类,继承每每会致使一个脆弱的对象体系。而使用委托可能须要委托对象进行复杂的调用。框架

切面提供了取代继承和委托的另外一种可选方案。在使用面向切面编程时,咱们仍然在一个地方定义通知功能,而无需修改受影响的类。横切关注点能够被模块化为特殊的类,这些类被称为切面(aspect). 这样作带来两个好处:每一个关注点都集中到一个地方,而不是分散到多处代码中:其次,服务模块更简洁,由于它只包含了主要关注点(核心功能)的代码。而次要关注的代码被移到切面中了。编程语言

4.1.1 定义AOP术语

描述切面的经常使用术语有:通知(advice)、切点(pointcut)、(链接点)。ide

通知(advice)

通知定义了切面是什么以及什么时候使用。除了描述切面要完成的工做外,通知还解决了什么时候执行这个工做问题。它应该在某个方法被调用以前?以后?以前和以后都调用?仍是只在方法抛出异常时调用?

Spring切面能够应用5中类型的通知:

  • 前置通知(Before):在目标方法被调用以前调用通知功能。
  • 后置通知(After):在目标方法完成以后调用通知
  • 返回通知(After-returning):在目标方法成功执行以后调用通知
  • 异常通知(After-throwing):在目标方法抛出异常后调用通知
  • 环绕通知(Around):在被通知方法调用以前和调用以后执行自定义的行为

链接点

咱们的应用可能有数以千计的时机应用通知,这些时机被称为链接点。链接点是在应用执行过程当中可以插入的一个点。这个点能够是调用方法时,抛出异常时,甚至修改一个字段时。切面能够利用这些点插入到应用的正常流程之中,并添加新的行为。

切点 若是说通知定义了切面的的“什么”和“什么时候”,那么切点定义了“何处”。切点的定义会匹配通知所要织入的一个或多个链接点。

切面 切面是通知和切点的结合。通知和切点经过定义了切面的所有 内容——他是什么,在何时和在哪里完成其功能。

引入 引入容许咱们向现有的类添加新的方法或者属性。

织入

织入是把切面应用到目标对象并建立新的代理对象的过程。切面在指定的链接点被织入到目标对象。在目标对象的生命周期里有多个点能够进行织入:

  • 编译器:切面在目标类编译时被织入。Aspect的织入编译器就是以这种方式织入切面的。
  • 类加载器:切面在目标类加载到JVM时被织入。须要特殊的类加载(Classloader),它能够在目标类被引入以前加强该目标类的字节码(CGlib)
  • 运行期:切面在应用运行时的某个时刻被织入。AOP会为目标对象建立一个代理对象

通知包含了须要用于多个应用对象的横切关注点。链接点是程序执行过程当中可以应用通知的全部点。切点定义了通知被应用的具体位置(在哪些链接点),其中关键是切点定义了哪些链接点会获得通知。

4.1.2 Spring对AOP的支持

并非全部的AOP框架都是相同的,他们在链接点模型上可能有强弱之分。有些容许在字段修饰符级别的通知,而另外一些只支持与方法调用相关的链接点。它们织入切面的方式和时机也有所不一样。可是,不管如何,建立切点来定义切面所织入的链接点是AOP的基本功能。

Spring提供了4种类型的AOP支持:

  • 基于代理的经典Spring AOP
  • 纯POJO切面
  • @AspectJ注解驱动的切面
  • 注入式AspectJ切面

前三种都是Spirng AOP实现的变体,Spring AOP构建在动态代理基础上。所以,Spring对AOP的支持局限于方法拦截。

引入了简单的声明式AOP与基于注解的AOP以后,Spring经典的看起来就显得很是笨拙和过于复杂话,直接使用ProxyFactory bean 会让人感受厌烦。

借助于Spring的aop命名空间,咱们能够将纯POJO转为切面。

Spring借鉴了AspectJ的切面,以提供注解驱动的AOP。本质上,它依然是Spring基于代理的AOP,可是编程模型几乎与编写成熟的AspectJ注解切面彻底一致。这种AOP风格的好处在于可以不使用XML来完成功能。

Spring所建立的通知都是用标准的Java类编写的,定义通知所应用的切点一般会使用注解或在Spring配置文件里采用XML来编写

通知带代理类中包裹切面,Spring在运行时把切面织入到Spring所管理的bean中。代理类封装了目标类,并拦截被通知方法的调用。再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标bean方法以前,会执行切面逻辑。直到应用须要被代理bean时,Spring才会建立代理对象。若是使用ApplicationContext的话,在ApplicationContext从BeanFactory中加载全部的bean的时候,Spring才会建立被代理的对象。由于Spirng运行时才建立代理对象,因此咱们不须要特殊的编译器来织入Spring AOP的切面。

Spring基于动态代理,因此Spring只支持方法链接点。方便拦截能够知足大部分的需求。

4.2 经过切点来选择链接点

切点用于准肯定位应该在什么地方应用切面的通知。通知和切点是切面最基本的元素。

Spring仅支持AspectJ切点指示器的一个子集。Spring是基于代理的,而某些切点表达式是基于代理的AOP无关的。

Spring支持的指示器,只有execution指示器是实际执行匹配的,而其余的指示器都是用来限制匹配的。这说明execution指示器是咱们在编写切点定义时最主要的指示器。

4.2.1编写切点

为了阐述Spring中的切面, 咱们须要有个主题来定义切面的切点。

package com.guo.cocert;
public interface Performance {
  public void perform();
}
execution(* concert.Performance.perform(..))

咱们使用execution()指示器选择Performance的perform()方法,方法表达式以"*"号开始,代表了咱们不关心方法返回值的类型。而后指明了全限定类名和方法名,对于方法参数列表,咱们使用了两个点号(..)代表切点要选择任意的perform()方法,不管该方法的入参是什么。

如今假设咱们须要配置的切点仅匹配concert包,可使用within()指示器

execution(* concert.Performance.perform(..)) && within(concert.*)

由于“&”在XMl中有特殊的含义,因此在Spring和XML配置中,描述切点时,可使用and代替“&&”。

4.2.2 在切点中选择bean

Spring引入了一个新的bean()指示器,它容许咱们在切点表达式中使用bean的ID来标识bean。bean()使用bean ID 或 bean 名称做为参数来限制切点只匹配特定的bean。

execution(* concert.Performance.perform(..)) and bean("woodsotck")

也能够这样

execution(* concert.Performance.perform(..)) and !bean("woodsotck")

切面的通知会被编织到全部ID不为woodsotck的bean中。

4.3使用注解建立切面

使用注解来建立切面是AspectJ 5所引入的关键特性。

4.3.1 定义切面

若是一场演出没有观众的话,那不能称之为演出。

@AspectJ
public class Audience {

}

Audience类使用@AspectJ注解进行了标注。该注解代表Audience不只仅是一个POJO,仍是一个切面。Audience类中的方法都是使用注解来定义切面的具体行为。

@AspectJ
public class Audience {
  @Pointcut("execution(* * concern.Performance.perform(..))")
  public void performance() {};
}

在Autience中,performance()方法使用了@Pointcut注解。为@Pointcut注解设置的值是一个切点表达式,就像以前在通知注解上所设置的那样。

须要注意的是,除了注解和没有实际操做的performa()方法,Audience类依然是一个POJO,咱们可以像使用其余的Java类那样调用它的方法,它的方法也能独立的进行单元测试。与其余Java类没有什么区别。

像其余的Java类同样,它能够装配为Spring中的bean

@Bean
public Audience audience() {
  return new Audience();
}

若是你就此止步的话,Audience只会是Spring容器中的一个bean。即使使用了AspectJ注解,但它并不会被视为切面,这些注解不会解析,也不会建立将其转化为切面的代理。

若是你使用JavaConfig的话,能够在配置类的级别上经过使用EnableAspectJ-AutoProxy注解启用自动代理功能。

@Configuration
@EnableAspectJAutoProxy             //启用AspectJ自动代理
@ComponentScan
public class ConcertConfig {
  @Bean
  public Audience autidence() {     //声明Audience bean
    return new Audience();
  }
}

假如你在Spring中使用XMl来装配bean的话,那么须要使用Spring aop命名空间中的<aop:aspect-autoproxy>元素

<?xml version="1.0" encoding="UTF-8"?>

、、、、、、、、、、、、、、、、、、、、、、、、

<context:component-scan base-package="com.guo.concert"/>
<aop:aspect-autoproxy/>
<bean class="com.guo.concert.Audience"/>

无论你使用JavaConfig仍是XML,AspecJ自动代理都会使用@Aspect注解的bean建立一个代理。这个代理会围绕着全部该切面的切点所匹配的bean。

咱们须要记住的是,Spring的AspectJ自动代理仅仅使用@AspectJ做为建立切面的指导,切面依然是基于代理的。本质上它依然是Spring基于代理的切面。

4.3.2 建立环绕通知

环绕通知是最为强大的通知类型,它可以让你编写的逻辑将被通知的目标方法安全包装起来,实际上就像在一个通知方法中同时编写前置通知和后置通知。

@AspectJ
public class Audience {
  @Pointcut("execution(* * concern.Performance.perform(..))")
  public void performance() {};
  @Around
  public void xx(Xxx jp) {
    .......
    jp.proced()
  }
}

在这里,@Around注解,代表这个xx()方法会做为performance()切点的环绕通知。

这个通知所达到的效果与以前的前置通知和后置通知是同样的。

须要注意的是,别忘记调用proceed()方法,若是不调用这个方法,那么你的通知实际上会阻塞对被通知方法的调用,有意思的是,你能够不调用proceed方法,从而阻塞对被通知方法的反问,

4.3.4 经过注解引入新功能

一些编程语言,例如:Ruby和Groovy,有开放来的理念,它们能够不直接使用修改对象或类的定义就可以为对象或类增长新的方法。不过Java并非动态语言,一旦编译完成了,就很难在为该类添加新的功能了。

若是切面可以为现有的方法增长额外的功能,为何不恩那个为一个对象增长新的方法呢?利用引入AOP的概念,切面能够为Spring bean 添加新的方法。

在Spring中,注解和自动代理提供了一种便利的方式来建立切面,它很是简单,而且只设计最少的Spring配置,可是,面向注解的切面有一个明显的不足点:你必须可以为通知类添加注解,为了作到这一点,必需要有源码。

4.4 在XML中声明切面

以前,有这样一条原则:那就是基于注解的配置要优于Java的配置,基于Java的配置要优于XMl的配置,可是,若是你须要声明切面,可是又不能为通知类添加注解的时候 ,那么就必须转向XML配置了。

在Spring的aop命名空间中,提供了多个元素用来在XML中声明切面,

  • <aop:advisor> :定义AOP通知器
  • <aop:after> :定义AOP后置通知
  • <aop:after-returning> :定义AOP返回通知
  • <aop:after-throwing> :定义AOP异常通知
  • <aop:around> :定义AOP环绕通知
  • <aop:aspect> :定义一个切面
  • <aop:aspectj-autoproxy> :启用@AspectJ注解
  • <aop:before> :定义一个AOP前置通知
  • <aop:poiontcut> :定义一个切点

4.4.1 声明前置通知和后置通知

咱们会使用Spring aop命名空间中的一些元素,将没有注解的Aurience类转为切面

<aop:config>
    <aop:aspect ref="audience">       <!--引用audience Bean-->

        <aop:before pointcut="execution(* * concert.Performance.perform(..))" method="silenceCellIphones"/>

        <aop:before pointcut="execution(* * concert.Performance.perform(..))" method="takeSeats"/>

        <aop:after-returning pointcut="execution(* * concert.Performance.perform(..))" method="applause"/>

        <aop:after-throwing pointcut="execution(* * concert.Performance.perform(..))" method="demandRefund"/>

    </aop:aspect>
</aop:config>

第一须要注意的就是大多数AOP配置元素必须在<aop:config>元素的上下文中使用。

在全部的通知元素中,pointcut属性定义了通知所应用的切点,它的值是使用AspectJ切点表达式语法所定义的切点。

在基于Aspectj注解的通知中,当发如今这些类型的重复时,使用@Pointcut注解来消除这些重复的内容。

以下的XMl配置展现了如何将通用的切点表达式抽取到一个切点声明中,这样,这个声明就能在全部的通知元素中使用了

<aop:config>
    <aop:aspect ref="audience">       <!--引用audience Bean-->
        <aop:pointcut id="performance" expression="execution(* * concert.Performance.perform(..))"  />

        <aop:before pointcut="" method="silenceCellIphones"/>

        <aop:before pointcut-ref="performance" method="takeSeats"/>

        <aop:after-returning pointcut-ref="performance" method="applause"/>

        <aop:after-throwing pointcut-ref="performance" method="demandRefund"/>

    </aop:aspect>
</aop:config>

如今的切点是一个地方定义的,而且被多个通知元素所引用,<aop:pointcut>元素定义了一个id为performance的切点,同时修改全部的通知元素,用pointcut0ref来引用这个命名切点。

4.4.2 声明环绕通知

相比于前置通知和后置通知,环绕通知在这点上有明显的优点。使用环绕通知,咱们能够完成前置通知和后置通知所实现的相同功能,并且只须要在一个方法中实现。由于整个通知逻辑都是在一个方法中实现的。

<aop:config>
    <aop:aspect ref="audience">       <!--引用audience Bean-->
        <aop:pointcut id="performance" expression="execution(* * concert.Performance.perform(..))"  />

        <aop:around pointcut-ref="performance" method="watchPerformance"/>

    </aop:aspect>
</aop:config>

像其余通知的XML元素同样,<aop:around>指定了一个切点和一个通知方法的名字。

4.4.3 为通知传递参数

区别在于切点表达式中包含了一个参数,这个参数传递到通知方法中。还有区别就是这里使用了and关键字

4.4.4 经过切面引入新的功能

借助于AspectJ的@DeclareParents注解为被通知的方法引入新的方法。可是AOP引入并非Aspectj特有的。使用Spring aop命名空间中的<aop:declare-parents>元素,咱们能够实现相同的功能

<aop:config>
     <aop:aspect ref="audience">       <!--引用audience Bean-->
        <aop:declare-parents types-matching="concert.Performance"
                             implement-interface="concert.Encoreable"
                             default-impl="concert.DefaoultEncoreable"

     </aop:aspect>
 </aop:config>

4.5 注入AspectJ切面

虽然Spring AOP可以知足许多应用的切面需求,可是与AspectJ相比,Spring AOP是一个功能比较弱的AOP解决方案,ASpect提供了Spring AOP 所不能支持的许多类型的切点。

Spring不能像以前那样使用<bean>声明来建立一个实例----它已经在运行时由AspectJ建立完成了,Spring须要经过工厂方法获取切面的引用。而后像<bean>元素规定的那样在该对象上执行依赖注入

4.6 小节(重点中的重点)

AOP是面向对象编程的一个强大补充,经过AspectJ,咱们如今能够把以前分散在应用各处的行为放入可重用的模块中。咱们显示地声明在何处如何应用该行为。这样有效减小了代码冗余,并让咱们的类关注自身的主要功能。

Spring提供了一个AOP框架,让咱们把切面插入到方法执行的周围。如今咱们已经学会了如何把通知织入前置,后置和环绕方法的调用中,以及为处理异常增长自定义行为。

关于在Spirng应用中如何使用切面 ,咱们能够有多种选择。经过使用@AspectJ注解和简化的配置命名空间,在Spring中装配通知和切点变得很是简单

最后,当Spring不能知足需求时,咱们必须转向更为强大的AspectJ。对于这些场景,咱们了解了如何使用Spring为AspectJ切面注入依赖。

此时此刻,咱们已经覆盖了Spring框架的基础知识,了解到如何配置Spring容器以及如何为Spring管理的对象应用切面,这些技术为建立高内聚,低耦合的应用奠基了坚实的基础。

从下一章开始,首先看到的是如何使用Spring构建Web应用。。

期待......

相关文章
相关标签/搜索