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

  1、Spring借助AspectJ的切点表达式语言来定义Spring切面

  在Spring中,要使用AspectJ的切点表达式语言来定义切点。

  重要的一点是,Spring仅支持AspectJ切点指示器的一个子集,当尝试使用AspectJ其他指示器时,会抛出异常

  arg()        限制连接点匹配参数为指定类型的执行方法

  @args()     限制连接点匹配参数为指定注解标注的执行方法

 execution()      用于匹配是连接点的执行方法

  this()       限制连接点匹配AOP代理的bean引用为指定类型的类

  target      限制连接点匹配目标对象为指定类型的类

  @target()     限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解

  within()      限制连接点匹配指定的类型

  @within     限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义

            在由指定的注解所注解的类中)

  @annotation   限定匹配带有指定注解的连接点

  2、编写切点——切点表达式

package concert
public interface Performance{
public void perform();
}

  下面编写Performance的perform()方法的触发的通知,这个切点表达式能够设置当perform()方法执行时触发通知的调用:

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

  其中方法表达式从“ * ”号开始,表示我们不关心方法返回值的类型;

  然后,指定全限定类名和方法名;

  方法参数列表中,用两个点号(..)表明切点要选择任意的perform()方法,而不关心该方法的入参是什么。

  假设,我们要配置的切点仅匹配concert包,可以利用within()指示器:

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

  这里我们使用了“&&”操作,在其他切点表达式中我们也可以使用“||”、“!”操作。(or、not)

  3、在切点中选择bean

  Spring引入了一个新的bean()指示器,它允许我们在切点表达式中使用bean的ID or bean名称作为参数来限制切点,让它只匹配特定的bean。

 execution( *  concert.Performance.perform(..) ) and bean('woodstock')

  这时,切面的通知会被织入到ID为woodstock的bean中。

  还可以如下使用,切面的通知会被织入到所有ID不为woodstock的bean中:

 execution( *  concert.Performance.perform(..) ) and !bean('woodstock')

  4、使用AspectJ注解创建切面

  使用注解创建切面是AspectJ 5引入的关键特性,之前需要学习一种Java语言的扩展。

  首先确定,上面定义的Performance接口,是切面中切点的目标对象,接下来我们定义一个Audience类,我们把观众作为演出的切面。

@Aspect
public class Audience { @Before("execution(* concert.Performance.perform(..))")
public void silenceCellPhones(){
System.out.println("Silencing cell phones.");
} @Before("execution(* concert.Performance.perform(..))")
public void takeSeats(){
System.out.println("Taking seats.");
} @AfterReturning("execution(* concert.Performance.perform(..))")
public void applause(){
System.out.println("CLAP!!~~CLAP!!~~");
} @AfterThrowing("execution(* concert.Performance.perform(..))")
public void demandRefund(){
System.out.println("Demanding a refund.");
}
}

  这四个方法定义了一个观众在观看演出时可能会做的事情:

  演出之前,观众就坐—将手机调至静音状态;

  演出精彩—观众鼓掌;

  演出很烂—观众要求退款。

  (通知方法中的注解都给定了一个切点表达式作为它的值)

  5、AspectJ提供的注解(声明通知方法)

  @After          通知方法在目标方法返回or抛出异常后执行;

  @AfterReturning       通知方法在目标方法返回后调用;

  @AfterThrowing      通知方法在目标方法抛出异常后调用;

  @Around         通知方法将目标方法封装起来;

  @Before          通知方法在目标方法调用之前执行;

  6、使用@Pointcut注解在一个@AspectJ切面内定义可以重用的切点。

  在上一个切面Performance类中,注解中的切点表达式我们重复使用了四次,下面使用@Pointcut进行简化:

@Aspect
public class Audience {
@Pointcut("execution(* concert.Performance.perform(..))")
public void performance() {} @Before("performance()")
public void silenceCellPhones(){
System.out.println("Silencing cell phones.");
} @Before("performance()")
public void takeSeats(){
System.out.println("Taking seats.");
} @AfterReturning("performance()")
public void applause(){
System.out.println("CLAP!!~~CLAP!!~~");
} @AfterThrowing("performance()")
public void demandRefund(){
System.out.println("Demanding a refund.");
}
}

  这个Audience类中的performance()方法的实际内容并不重要,在这里它实际上应该是空的,因为该方法本身只是一个标识,供@Pointcut注解依附。

  

  到此为止,我们创建了切面吗?并没有,目前,Audience只会是Spring容器中的一个bean。(@AspectJ会自动创建为bean?)

  这里即使我们使用了AspeJ注解,但它不会被是为切面,这些注解不会解析,也不会创建将其转换为切面的代理。

  7、使用JavaConfig,XML开启自动代理

  JavaConfig:

  可以在配置类(使用JavaConfig有一个配置类(@Configuration)来开启各项功能)上使用@EnableAspeJAutoProxy启用自动代理功能;

@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class ConcertConfig{
@Bean
public Audience audience{
return new Audience();
}
}

  XML:

  使用Spring的aop命名空间中的<aop:aspectj-autoproxy>元素,开启自动代理。(先声明Spring的AOP命名空间)

  不管是哪种方式,AspeJ自动代理都会为使用@Aspect所注解的bean创建一个代理,这个代理会围绕所有该切面所匹配的bean。

  8、创建环绕通知@Around

  环绕通知是最为强大的通知类型,可以使自己编写的逻辑将被通知的目标方法完全包装起来。

  下面用一个环绕通知来代替之前的多个前置和后置通知:

@Aspect
public class AudienceAround {
@Pointcut("execution(* concert.Performance.perform(..))")
public void performance() {
} @Around("performance()")
public void watchPerformance(ProceedingJoinPoint jp) {
try {
System.out.println("Silencing cell phone");
System.out.println("Taking seats");
jp.proceed();
System.out.println("CLAP~~CLAP~");
}catch(Throwable e){
System.out.println("Demanding a refund!");
}
}
}

  这个位于一个方法中的通知所达到的效果,与之前的几个前置和后置通知是一样的。

  注意到,这个watchPerformance方法接受了一个ProceedingJoinPoint类型的参数,jp对象必须要有,因为通过它的proceed()方法来调用被通知(增强)的方法。也就是说,这个通知阻塞了被通知的方法,我们必须手动调用。

  9、若被通知的方法含有参数,切面能访问和使用被传递给被通知的方法的参数吗?

   也就是说,我们的增强逻辑中,需要利用被增强的方法中的参数完成一些功能。例如:

@Aspect
public class TrackCounter{
@Pointcut("execution(* soundsystem.CompactDisc.playTrack(int))"&&"args(trackNumber)")
public void trackPlayed(int trackNumber) {} @Before("trackPlayed(trackNumber)")
public void countTrack(int trackNumber){...} ...
}

  这个切面中,使用@Pointcut注解定义命名的切点中声明了要提供给通知方法的参数。切点表达式中"args(trackNumber)"限定符表明了传递给playTrack()方法的int型参数也会传递到通知中去,参数的名称trackNumber也与切点方法签名中的参数相匹配。

  下面的前置通知方法的注解@Before中与countTrack方法中的入参均有trackNumber,这样就完成了从命名切点到通知方法的参数转移。

  形象点来说,我们靠AspectJ注解作为载体,把被通知方法中的参数转义到通知中。

  目前为止,我们所使用的切面中,所包装的都是被通知对象的已有方法,这仅仅是切面能实现的功能之一。

  10、如何通过编写切面,为被通知的对象(不是方法)引入全新的功能?

  Java不是动态语言,一旦类编译完成了,我们就很难再为该类添加新功能。

  我们之前做的都是为对象拥有的方法添加新功能,那我们为什么不能为对象增加新的方法呢?

  使用Spring AOP,我们可以为bean引入新的方法,代理拦截调用并委托给实现该方法的其他对象。

    11、通过@DeclareParens注解实现为对象引入新方法

  现在我们来为之前的Performance接口的所有实现引入新接口Encoreable(观众要求返场表演):

package concert;

public interface Encoreable {
void performEncore();
}

  借助于AOP的引入功能,我们可以不必在设计上妥协或者侵入性地改变现有的实现。

  为了实现该功能,创建一个新的切面:

@Aspect
public class EncoreableIntroducer {
@DeclareParents(value="concert.Performance+",defaultImpl = DefaultEncoreable.class)
public static Encoreable encoreable;
}

  这个切面并没有提供前置、后置或环绕通知,而是通过@DeclareParens注解,将Encoreable接口引入到Performance bean中:

  • value属性指定了那种类型的bean要引入该接口。这里是指定为所有实现Performance的类型,“+”表示是Performance的所有子类型,而不是本身。
  • defaultImpl属性指定了为引入功能提供实现的类。在这里,指定DefaultEncoreable提供实现。
  • @DeclareParens注解所标注的静态属性表明了要引入的接口,这里我们引入Encoreable接口。

  和其他切面一样,我们需要在Spring应用中将EncoreableIntroducer声明为一个bean。然后Spring的自动代理机制会获取到它的声明,当Spring发现一个bean使用@AspectJ注解时,Spring就会创建一个代理,单后将调用委托给被代理的bean或被引入的实现,这取决于调用的方法属于被代理的bean还是属于被引入的接口。

  Spring的注解和自动代理提供了一种便利的方式创建切面,但是面向注解的切面声明有一个明显的劣势:你必须能够为通知类添加注解。为了做到这一点,必须要有源码!

  若没有源码or不想将AspectJ注解放入你的代码中,可以使用Spring XML配置文件声明切面。

Spring实战(十)Spring AOP应用——为方法引入新功能、为对象引入新方法的更多相关文章

  1. 【Spring实战】Spring容器初始化完成后执行初始化数据方法

    一.背景知识及需求 在做WEB项目时,经常在项目第一次启动时利用WEB容器的监听.Servlet加载初始化等切入点为数据库准备数据,这些初始化数据是系统开始运行前必须的数据,例如权限组.系统选项.默认 ...

  2. 【Spring实战】Spring注解配置工作原理源码解析

    一.背景知识 在[Spring实战]Spring容器初始化完成后执行初始化数据方法一文中说要分析其实现原理,于是就从源码中寻找答案,看源码容易跑偏,因此应当有个主线,或者带着问题.目标去看,这样才能最 ...

  3. 【转】【Spring实战】Spring注解配置工作原理源码解析

    一.背景知识 在[Spring实战]Spring容器初始化完成后执行初始化数据方法一文中说要分析其实现原理,于是就从源码中寻找答案,看源码容易跑偏,因此应当有个主线,或者带着问题.目标去看,这样才能最 ...

  4. 【Spring实战】—— 10 AOP针对参数的通知

    通过前面的学习,可以了解到 Spring的AOP可以很方便的监控到方法级别的执行 ,针对于某个方法实现通知响应. 那么对于方法的参数如何呢? 比如我们有一个方法,每次传入了一个字符串,我想要知道每次传 ...

  5. Spring 学习十五 AOP

    http://www.hongyanliren.com/2014m12/22797.html 1: 通知(advice): 就是你想要的功能,也就是安全.事物.日子等.先定义好,在想用的地方用一下.包 ...

  6. (一)《Spring实战》——Spring核心

    <Spring实战>(第4版) 第一章:Spring之旅 1. 简化Java开发 为了降低Java开发的复杂性,Spring采取了以下4种关键策略: 基于POJO的轻量级和最小侵入性编程: ...

  7. 【Spring实战】—— 9 AOP环绕通知

    假如有这么一个场景,需要统计某个方法执行的时间,如何做呢? 典型的会想到在方法执行前记录时间,方法执行后再次记录,得出运行的时间. 如果采用Spring的AOP,仅仅使用前置和后置方法是无法做到的,因 ...

  8. (二)《Spring实战》——Spring核心

    第二章:装配Bean 在Spring中,对象无需自己查找或创建与其所关联的其他对象.相反,容器负责把需要相互协作的对象引用赋予各个对象.例如,一个订单管理组件需要信用卡认证组件,但它不需要自己创建信用 ...

  9. spring MVC(十)---spring MVC整合mybatis

    spring mvc可以通过整合hibernate来实现与数据库的数据交互,也可以通过mybatis来实现,这篇文章是总结一下怎么在springmvc中整合mybatis. 首先mybatis需要用到 ...

  10. Spring学习(十)-----Spring依赖检查

    在Spring中,可以使用依赖检查功能,以确保所要求的属性可设置或者注入. 依赖检查模式 4个依赖检查支持的模式: none – 没有依赖检查,这是默认的模式. simple – 如果基本类型(int ...

随机推荐

  1. vmware 中Linux系统怎么连接外网?

    VMware虚拟机有三种网络模式,分别是Bridged(桥接模式).NAT(网络地址转换模式).Host-only(主机模式). VMware workstation安装好之后会多出两个网络连接,分别 ...

  2. Nginx反向代理设置header

    特别提示:本人博客部分有参考网络其他博客,但均是本人亲手编写过并验证通过.如发现博客有错误,请及时提出以免误导其他人,谢谢!欢迎转载,但记得标明文章出处:http://www.cnblogs.com/ ...

  3. docker save/load以及export/import使用测试

    对于有些环境需要离线安装的情况,docker以及docker容器都需要能够支持离线安装,对于docker离线安装,比较简单,按照https://www.cnblogs.com/qq931399960/ ...

  4. ubuntu19.04 安装workbench

    1.首先下载安装这两个包: https://packages.ubuntu.com/cosmic/amd64/libssl1.0.0/download https://packages.ubuntu. ...

  5. 通过一个name获取tbody下的其他name的值

    <tbody id="add_enterGoods_table">  <tr> <td align="center">< ...

  6. SAN LAN MAN WAN的区别

    主要是范围不同 SAN: System Area NetworkLAN: Local Area NetworkMAN: Metropolitan Area NetworkWAN: Wide Area ...

  7. 实验一 绘制任意斜率的直线段 | 使用VS2017工具

    这世界上有很多坑,注定有些坑是要填的.下面我就用VS2017使用MFC对这个课堂实验进行填坑. 一.实验目的 (1)掌握任意斜率直线段的重点 Bresenham 扫描转换算法: (2)掌握 Cline ...

  8. IDEA使用git

    本文转自:http://www.cnblogs.com/java-maowei/p/5950930.html 一.安装git 下载地址:  https://git-scm.com/download/w ...

  9. python中pip的安装与更新

    python -m pip install --upgrade pip --force-reinstall

  10. Jenkins - Tips

    01 - RPM包安装方式 默认路径 配置文件:/etc/sysconfig/jenkins 日志文件:/var/log/jenkins/jenkins.log 执行文件:/usr/lib/jenki ...