Spring实战(十)Spring AOP应用——为方法引入新功能、为对象引入新方法
切面最基本的元素是通知和切点,切点用于准确定位应该在什么地方应用切面的通知。
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应用——为方法引入新功能、为对象引入新方法的更多相关文章
- 【Spring实战】Spring容器初始化完成后执行初始化数据方法
一.背景知识及需求 在做WEB项目时,经常在项目第一次启动时利用WEB容器的监听.Servlet加载初始化等切入点为数据库准备数据,这些初始化数据是系统开始运行前必须的数据,例如权限组.系统选项.默认 ...
- 【Spring实战】Spring注解配置工作原理源码解析
一.背景知识 在[Spring实战]Spring容器初始化完成后执行初始化数据方法一文中说要分析其实现原理,于是就从源码中寻找答案,看源码容易跑偏,因此应当有个主线,或者带着问题.目标去看,这样才能最 ...
- 【转】【Spring实战】Spring注解配置工作原理源码解析
一.背景知识 在[Spring实战]Spring容器初始化完成后执行初始化数据方法一文中说要分析其实现原理,于是就从源码中寻找答案,看源码容易跑偏,因此应当有个主线,或者带着问题.目标去看,这样才能最 ...
- 【Spring实战】—— 10 AOP针对参数的通知
通过前面的学习,可以了解到 Spring的AOP可以很方便的监控到方法级别的执行 ,针对于某个方法实现通知响应. 那么对于方法的参数如何呢? 比如我们有一个方法,每次传入了一个字符串,我想要知道每次传 ...
- Spring 学习十五 AOP
http://www.hongyanliren.com/2014m12/22797.html 1: 通知(advice): 就是你想要的功能,也就是安全.事物.日子等.先定义好,在想用的地方用一下.包 ...
- (一)《Spring实战》——Spring核心
<Spring实战>(第4版) 第一章:Spring之旅 1. 简化Java开发 为了降低Java开发的复杂性,Spring采取了以下4种关键策略: 基于POJO的轻量级和最小侵入性编程: ...
- 【Spring实战】—— 9 AOP环绕通知
假如有这么一个场景,需要统计某个方法执行的时间,如何做呢? 典型的会想到在方法执行前记录时间,方法执行后再次记录,得出运行的时间. 如果采用Spring的AOP,仅仅使用前置和后置方法是无法做到的,因 ...
- (二)《Spring实战》——Spring核心
第二章:装配Bean 在Spring中,对象无需自己查找或创建与其所关联的其他对象.相反,容器负责把需要相互协作的对象引用赋予各个对象.例如,一个订单管理组件需要信用卡认证组件,但它不需要自己创建信用 ...
- spring MVC(十)---spring MVC整合mybatis
spring mvc可以通过整合hibernate来实现与数据库的数据交互,也可以通过mybatis来实现,这篇文章是总结一下怎么在springmvc中整合mybatis. 首先mybatis需要用到 ...
- Spring学习(十)-----Spring依赖检查
在Spring中,可以使用依赖检查功能,以确保所要求的属性可设置或者注入. 依赖检查模式 4个依赖检查支持的模式: none – 没有依赖检查,这是默认的模式. simple – 如果基本类型(int ...
随机推荐
- vmware 中Linux系统怎么连接外网?
VMware虚拟机有三种网络模式,分别是Bridged(桥接模式).NAT(网络地址转换模式).Host-only(主机模式). VMware workstation安装好之后会多出两个网络连接,分别 ...
- Nginx反向代理设置header
特别提示:本人博客部分有参考网络其他博客,但均是本人亲手编写过并验证通过.如发现博客有错误,请及时提出以免误导其他人,谢谢!欢迎转载,但记得标明文章出处:http://www.cnblogs.com/ ...
- docker save/load以及export/import使用测试
对于有些环境需要离线安装的情况,docker以及docker容器都需要能够支持离线安装,对于docker离线安装,比较简单,按照https://www.cnblogs.com/qq931399960/ ...
- ubuntu19.04 安装workbench
1.首先下载安装这两个包: https://packages.ubuntu.com/cosmic/amd64/libssl1.0.0/download https://packages.ubuntu. ...
- 通过一个name获取tbody下的其他name的值
<tbody id="add_enterGoods_table"> <tr> <td align="center">< ...
- SAN LAN MAN WAN的区别
主要是范围不同 SAN: System Area NetworkLAN: Local Area NetworkMAN: Metropolitan Area NetworkWAN: Wide Area ...
- 实验一 绘制任意斜率的直线段 | 使用VS2017工具
这世界上有很多坑,注定有些坑是要填的.下面我就用VS2017使用MFC对这个课堂实验进行填坑. 一.实验目的 (1)掌握任意斜率直线段的重点 Bresenham 扫描转换算法: (2)掌握 Cline ...
- IDEA使用git
本文转自:http://www.cnblogs.com/java-maowei/p/5950930.html 一.安装git 下载地址: https://git-scm.com/download/w ...
- python中pip的安装与更新
python -m pip install --upgrade pip --force-reinstall
- Jenkins - Tips
01 - RPM包安装方式 默认路径 配置文件:/etc/sysconfig/jenkins 日志文件:/var/log/jenkins/jenkins.log 执行文件:/usr/lib/jenki ...