spring AOP(一)中介绍了AOP的基本概念和几个术语,现在学习一下在XML中如何配置AOP。 
在XML中AOP的配置元素有以下几种:

AOP配置元素 描述
<aop:config> 顶层的AOP配置元素,大多数的<aop:*>元素必须包含在<aop:config>元素内
<aop:aspect> 定义切面
<aop:aspect-autoproxy> 启用@AspectJ注解驱动的切面
<aop:pointcut> 定义切点
<aop:advisor> 定义AOP通知器
<aop:before> 定义AOP前置通知
<aop:after> 定义AOP后置通知(不管被通知的方法是否执行成功)
<aop:after-returning> 定义成功返回后的通知
<aop:after-throwing> 定义抛出异常后的通知
<aop:around> 定义AOP环绕通知
<aop:declare-parents> 为被通知的对象引入额外的接口,并透明地实现

总之,个人理解AOP就是将切面的功能(通知),通过切点织入程序的执行过程中。下面是AOP术语的配置和关系图:

下面通过一个具体的栗子来学习,这个例子描述的是在一个正在表演的节目(相当于应用中一个正常的业务功能)中加入观众们的反响(相当于切面),要求在表演前观众们入座、手机关机或静音,表演中记录一下表演所用时间,表演后观众们喝彩或不满。

表演类接口

package com.springinaction.aop;

public interface Performer {
public void perform();
}

表演类的一个实现:话剧演出

package com.springinaction.aop;

public class Drama implements Performer {

    @Override
public void perform() {
for (int i = 0; i < 1000; i++) {
System.out.println("话剧正在进行中——");
}
}
}

观众类作为切面

package com.springinaction.aop;

public class Audience {
public void takeSeats() {
// 节目开始之前
System.out.println("演出前——观众开始入座");
} public void turnOffCellPhones() {
// 节目开始之前
System.out.println("演出前——观众关机或静音");
} public void applaud() {
// 节目成功结束之后
System.out.println("成功演出很成功——观众鼓掌:啪啪啪");
} public void demandRefund() {
// 节目表演失败之后
System.out.println("节目演出很失败——切!一点都不好看,我们要求退钱!");
}
}

1 准备工作

  • 引入以下几个AOP包: 
    aopalliance-1.0.jar 
    aspectjrt-1.7.4.jar 
    aspectjweaver-1.7.4.jar

  • 在XML文件中加入包含关于Spring AOP命名空间的内容

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> </beans>

2 定义切面

在XML中通过元素<aop:config>开启AOP配置,使用<aop:aspect>配置切面,如下:

     <aop:config>
<aop:aspect ref="audience">
</aop:aspect>
</aop:config>

ref里面对应audience是切面bean的ID,也就是观众类对应的bean。观众类的bean和话剧表演的bean分别如下:

    <bean id="audience" class="com.springinaction.aop.Audience" />
<bean id="drama" class="com.springinaction.aop.Drama" />

3 定义切点

3.1 AspectJ切点表达式语言

Spring AOP中使用的是AspectJ的切点表达式语言来定义的切点,前面讲过Spring AOP是基于代理的,而AspectJ的某些切点表达式是与基于代理的AOP无关的,因此Spring AOP支持的AspectJ切点指示器仅有有限的几种,如下表所示:

AspectJ指示器 描述
execution{} 匹配到的连接点的执行方法
如:execution(* com.springinaction.aop.Performer.perform(..))
表达式表示当Performer的perform()方法执行时会触发通知, * 表明我们不关心方法返回值的类型, (..) 标识切点选择任意的paly()方法,无论该方法的参数是什么
args() 限制连接点匹配参数为指定类型的执行方法
如:execution(* com.springinaction.aop.Thinker.thinkOfSomething(String)) and args(thoughts)
注意到 thinkOfSomething(String) 有参数类型String,后面跟了一个 args(thoughts) ,这个是 thinkOfSomething() 方法要传给通知的参数,两个AspectJ知识器用and相连接,同样也可以使用or和not,当然我们可以使用&&、 || 和 ! 来代替and、or和not
@args() 限制连接点匹配参数由制定注解标注的执行方法
this() 限制连接点匹配AOP代理的Bean引用为指定类型的类
target() 限制连接点匹配目标对象为制定类型的类
within() 限制连接点匹配特定的类型
如:execution(* com.springinaction.aop.Performer.perform(..)) && within(com.springinaction.aop.*)
within(com.springinaction.aop.*)表示com.springinaction.aop包下任意类的方法被调用时
@within() 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里)
@annotation 限制匹配带有指定注解连接点
bean() 限制连接点只匹配特定的bean
如:execution(* com.springinaction.aop.Performer.perform(..)) adn bean(drama)
其中drama是一个类的beanID,这个bean是Performer接口的一个实现。加上了bean(drama)以后就只能匹配这个bean了

上面的所有指示器中,execution是主要使用的,是必须的;其他的指示器都是限制所匹配的切点。另外,当Spring尝试使用除了上面的AspectJ的其他指示器时,将会抛出IllegalArgumentException异常。

3.2 在XML中配置切点

使用元素<aop:pointcut>来配置切点,<aop:pointcut>既可以放在<aop:aspect>元素的作用域内,也可以放在<aop:config>元素的作用域下,对应的切点的作用范围就不同了。具体配置如下:

            <aop:pointcut
id="performance"
expression="execution(* com.springinaction.aop.Performer.perform(..))" />

id标识这个切点的id,expression里面就是上面讲到的AspectJ切点表达式语言。

4 定义通知

4.1 声明前置和后置通知

分别使用<aop:before><aop:after-returning>以及<aop:after-throwing>元素来声明前置通知、返回结果后置通知和抛出异常后置通知,注意这几个元素是放在<aop:aspect>元素的作用域内。具体配置如下:

            <aop:before pointcut-ref="performance" method="takeSeats"/>
<aop:before pointcut-ref="performance" method="turnOffCellPhones"/>
<aop:after-returning pointcut-ref="performance" method="applaud"/>
<aop:after-throwing pointcut-ref="performance" method="demandRefund"/>

该切面应用了4个不同的通知。两个<aop:before>元素定义了匹配切点的方法执行之前调用前置通知方法——audience Bean的takeSeats()和turnOffCellPhones()方法(由method属性所声明)。<aop:after-returning>元素定义了一个返回后(after-returning)通知,在切点所匹配的方法调用之后再执行applaud()方法。同样,<aop:after-throwing>元素定义了抛出后通知,如果所匹配的方式执行时抛出任何异常,都将调用demandRefund()方法。下图展示了通知逻辑如何编织到业务逻辑中。

下面我们用测试类来测试AOP实现

package com.springinaction.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; public class AudienceTest {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"com/springinaction/aop/aop.xml");
Performer drama = (Performer)ctx.getBean("drama");
drama.perform();
}
}

结果

演出前——观众开始入座
演出前——观众关机或静音
话剧正在进行中——
......(重复一千次)
成功演出很成功——观众鼓掌:啪啪啪

4.2 声明环绕通知

前面的前置通知和后置通知分别发生在业务功能的前面和后面,如果想要实现跨越业务功能的整个事件段呢?例如表演节目除了进场关机和结束了鼓掌,我们还希望观众一直关注演出,报告演出的时间。如果通过前置和后置通知来实现的话,我们可以在一个成员变量中保存时间。但是由于Audience是单例,所以会存在线程安全问题。 
环绕通知的作用在这里就体现出来了,它可以在一个方法中实现整个逻辑,即将切点的方法内嵌到环绕通知的方法中去。

4.2.1 编写环绕通知方法

下面是我们在观众类Audience中新添加的一个watchPerformance()方法作为AOP环绕通知。

    public void watchPerformance(ProceedingJoinPoint joinpoint) {
try {
System.out.println("演出前——观众开始入座");
System.out.println("演出前——观众关机或静音");
long start = System.currentTimeMillis(); joinpoint.proceed(); // 执行被通知的方法 long end = System.currentTimeMillis(); System.out.println("成功演出很成功——观众鼓掌:啪啪啪");
System.out.println("演出持续了 " + (end - start) + " milliseconds");
} catch (Throwable e) {
System.out.println("节目演出很失败——切!一点都不好看,我们要求退钱!");
}
}

对于这个通知方法,注意它使用了ProceedingJoinPoint作为方法的入参。这个对象能让我们在通知里调用被通知方法。通知方法首先完成它需要做的事情,如果希望把控制权转给被通知的方法,我们可以调用ProceedingJoinPoint的proceed()方法。

4.2.2 在XML声明环绕通知

声明环绕通知与声明其他类型的通知没有太大的区别,我们需要做的仅仅是使用<aop:around>元素。

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

然后我们通过测试类测试一下

package com.springinaction.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; public class AudienceTest {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"com/springinaction/aop/aop.xml");
Performer drama = (Performer)ctx.getBean("drama");
drama.perform();
}
}

输出结果:

演出前——观众开始入座
演出前——观众关机或静音
话剧正在进行中——
......(重复1000次)
演出很成功——观众鼓掌:啪啪啪
演出持续了 42 milliseconds

4.3 为通知传递参数

上面的栗子通知和被通知的方法之间虽然实现了有了先后逻辑,但是并没有参数的传递,有时候我们需要校验传递给方法的参数值,这个时候为通知传递参数就非常有用了。 
还是通过一个实例来说明:某位魔术师自称自己继承了其师傅刘半仙的衣钵,可以未卜先知知道他人内心所想;有一个志愿者不服,说要让他猜猜自己到底想了什么。这里就将魔术师作为切面。

读心术接口

package com.springinaction.aop;

public interface MindReader {
void interceptThoughts(String thoughts); String getThoughts();
}

魔术师类实现读心术接口

package com.springinaction.aop;

public class Magician implements MindReader {
private String thoughts; @Override
public void interceptThoughts(String thoughts) {
System.out.println("让我猜猜你在想什么?");
this.thoughts = thoughts;
System.out.println(this.getThoughts());
} @Override
public String getThoughts() {
System.out.print("你在想:");
return thoughts;
} }

思考者接口

package com.springinaction.aop;

public interface Thinker {
void thinkOfSomething(String thoughts);
}

志愿者类实现思考者接口

package com.springinaction.aop;

public class Volunteer implements Thinker {
private String thoughts; @Override
public void thinkOfSomething(String thoughts) {
this.thoughts = thoughts;
} public String getThoughts() {
return thoughts;
}
}

XML配置

    <aop:config>
<aop:aspect ref="magician">
<aop:pointcut
id="thinker"
expression="execution(* com.springinaction.aop.Thinker.thinkOfSomething(String))
and args(thoughts)"/> <aop:before
pointcut-ref="thinker"
method="interceptThoughts"
arg-names="thoughts"/>
</aop:aspect>
</aop:config>

从XML配置中我们可以看出,实现被通知方法向通知传参的关键有两点:

  1. 切点元素<aop:pointcut>的expression属性中使用了AspectJ切点表达式语言中的args()指示器,在本例中是args(thoughts),传递的是thinkOfSomething(String thoughts)方法的参数。
  2. 通知元素<aop:before>中加入了 arg-names 属性,这个属性的值就是切点传过来的对应的参数thoughts。

测试类

package com.springinaction.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; public class MagicianTest { public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"com/springinaction/aop/aop.xml");
Thinker thinker = (Thinker) ctx.getBean("volunteer");
thinker.thinkOfSomething("你猜我在想什么?");
} }

输出结果

让我猜猜你在想什么?
你在想:你猜我在想什么?
  • 1
  • 2
  • 1
  • 2

5 定义引入

5.1 通过切面引入的原理

讲解引入之前先思考一下我们之前都做到了什么。我们已经通过AOP实现了为目标对象拥有的方法添加了新的功能,这是通过代理实现的,即切面为目标bean创建一个代理,通知发出的方法调用看似发往了目标bean,其实是被代理拦截了,即代理代替目标bean实现原有功能并添加了切面的新功能。 
现在来说引入,什么是引入呢?就是在不改变切入的目标类的基础上为其添加新方法或属性。我们都知道Java不是动态语言,一旦类编译完成了,我们就很难再为该类添加新的功能了,那么所谓的“添加新方法或属性”是不是扯淡呢?是也不是,因为我们无法真的实现可以虚拟一个嘛,但你不说我不说谁知道呢,这就又用到了我们牛逼轰轰的代理。如下图所示:

这里,可以让代理发布一个新的接口,当引入接口的方法被调用时,代理就将此调用委托给了实现新接口的某个其他对象,也就是Bean的实现被拆分到了多个类,而不仅仅是原来的目标切点对应的那一个类。但是在外面看来,所有的功能都是通过切点对应的目标类的Bean实现的。

5.2 XML配置引入

还是通过一个实例来说明:前面的例子中表演类是一个切点,这里我们加入一个新的辅助表演类,来引入我们想要添加给表演类的方法。

辅助表演接口

package com.springinaction.aop;

public interface AssistPerformer {
void assist();
}

助人为乐者实现辅助表演接口

package com.springinaction.aop;

public class GraciousAssist implements AssistPerformer {

    @Override
public void assist() {
System.out.println("GraciousAssist来助演了——");
} }

XML配置 
引入是通过<aop:declare-parents>元素来实现的,这个元素在<aop:aspect>元素的作用域内,它声明了此切面所通知的Bean在它的对象层次结构中拥有新的父类,也就是它的代理拥有了新的父类。具体配置如下:

            <aop:declare-parents
types-matching="com.springinaction.aop.Performer+"
implement-interface="com.springinaction.aop.AssistPerformer"
default-impl="com.springinaction.aop.GraciousAssist"/>

具体说明一下<aop:declare-parents>的属性:

  • types-matching:Performer接口所实现的子类,也就是对应的目标切点类。
  • implement-interface:新加入的接口类。
  • default-impl:新加入的接口的默认实现类。

除了使用default-impl来指定接口的实现外,还可以使用delegate-ref属性来标识:

            <aop:declare-parents
types-matching="com.springinaction.aop.Performer+"
implement-interface="com.springinaction.aop.AssistPerformer"
delegate-ref="gracious"/>

其中“gracious”是新添加的接口的实现类对应Bean的ID:

    <bean id="gracious" class="com.springinaction.aop.GraciousAssist" />

测试类

package com.springinaction.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; public class AudienceTest {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"com/springinaction/aop/aop.xml");
Performer drama = (Performer)ctx.getBean("drama");
drama.perform();
AssistPerformer ap = (AssistPerformer) ctx.getBean("drama");
ap.assist();
}
}

注意一点,我们要实现的是新加入的AssistPerformer 接口的assist()方法,但是我们是通过切点的BeanID,也就是”drama“得到AssistPerformer 的实现的一个实例,这就仿佛是”drama“对应的切点Bean实现了AssistPerformer 接口一样,这就是AOP引入的神奇之处,可以为目标类添加新的方法。

输出结果

演出前——观众开始入座
演出前——观众关机或静音
话剧正在进行中——
......(重复1000次)
演出很成功——观众鼓掌:啪啪啪
演出持续了 42 milliseconds
GraciousAssist来助演了——

Spring AOP-xml配置的更多相关文章

  1. spring 5.x 系列第3篇 —— spring AOP (xml配置方式)

    文章目录 一.说明 1.1 项目结构说明 1.2 依赖说明 二.spring aop 2.1 创建待切入接口及其实现类 2.2 创建自定义切面类 2.3 配置切面 2.4 测试切面 附: 关于切面表达 ...

  2. Spring AOP Xml配置过程及解释

    目录 Spring AOP(基于xml) 专业术语: 基于xml的声明式AspectJ 具体实践 Spring AOP(基于xml) 目前主流的AOP框架有两个,分别是Spring AOP和Aspec ...

  3. 基于注解的Spring AOP的配置和使用

    摘要: 基于注解的Spring AOP的配置和使用 AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向切面编程.可以通过预编译方式和运行期动态代理实现在不 ...

  4. spring+mybaits xml配置解析----转

    一.项目中spring+mybaits xml配置解析 一般我们会在datasource.xml中进行如下配置,但是其中每个配置项原理和用途是什么,并不是那么清楚,如果不清楚的话,在使用时候就很有可能 ...

  5. Spring AOP 不同配置方式产生的冲突问题

    Spring AOP的原理是 JDK 动态代理和CGLIB字节码增强技术,前者需要被代理类实现相应接口,也只有接口中的方法可以被JDK动态代理技术所处理:后者实际上是生成一个子类,来覆盖被代理类,那么 ...

  6. spring AOP为什么配置了没有效果?

     spring Aop的配置一定要配置在springmvc配置文件中         springMVC.xml 1 <!-- AOP 注解方式 :定义Aspect --> <!-- ...

  7. spring的xml配置声明以及相应的问题处理

    spring的xml配置声明:  xml配置声明 Code 问题处理 问题1 xml报错: cvc-elt.1: Cannot find the declaration of element 'bea ...

  8. spring中用xml配置构造注入的心得

    spring中用xml配置构造注入时,如果 <constructor-arg> 属性都是 ref ,则不用理会参数顺序 <constructor-arg ref="kill ...

  9. 基于XML配置的spring aop增强配置和使用

    在我的另一篇文章中(http://www.cnblogs.com/anivia/p/5687346.html),通过一个例子介绍了基于注解配置spring增强的方式,那么这篇文章,只是简单的说明,如何 ...

  10. Spring 使用AOP——xml配置

    目录 AOP介绍 Spring进行2种实现AOP的方式 导入jar包 基于schema-based方式实现AOP 创建前置通知 创建后置通知 修改Spring配置文件 基于schema-based方式 ...

随机推荐

  1. 你也可以当面霸-MVC的原理及特点

    MVC是面试中经常被问到问题,如果能把MVC的原理简单清楚的描述出来,肯定会在面试官的心目中加分. 如果在能画图的情况下,画出一张MVC的流程图,无疑能简化不少概念上的术语,如果不能也不要紧,只要把核 ...

  2. 洛谷—— P2895 [USACO08FEB]流星雨Meteor Shower

    P2895 [USACO08FEB]流星雨Meteor Shower 题目描述 Bessie hears that an extraordinary meteor shower is coming; ...

  3. CodeForces - 393E Yet Another Number Sequence

    Discription Everyone knows what the Fibonacci sequence is. This sequence can be defined by the recur ...

  4. JDBC工具类 访问数据库 增删改查CRUD的通用方法

    1 package com.zmh.util; 2 3 import java.sql.*; 4 import java.util.ArrayList; 5 import java.util.Hash ...

  5. 前端必备性能知识 - http2.0

    前端开发中,性能是一定绕不开的,今天就来说一下前后台通信中最重要的一个通道--HTTP2.0 最开始的通讯协议叫http1.0,作为始祖级的它,定义了最基本的数据结构,请求头和请求体,以及每一个字段的 ...

  6. 什么叫PV,UV,PR值

    1.PV PV(page view),即页面浏览量:用户每1次对网站中的每个网页访问均被记录1次.用户对同一页面的多次访问,访问量累计. 2.什么是UV uv(unique visitor),指访问某 ...

  7. 【spring mvc】后台的API,测试中,总提示接口实体的某一个字段不能为null,但是明明给值了还提示不能为空

    实体是这三个字段 接口的实现类Controller 前台测试给值 依旧报错 解决方法: 需要添加@RequestBody注解

  8. python 依照list中的dic的某key排序

    面试题之中的一个. s=[ {"name":"Axx","score":"90"}, {"name" ...

  9. sql导入数据库

    有问题可以尝试加上这句 DROP TABLE IF EXISTS `t_saler_login_log`;

  10. 以goroutine为例看协程的相关概念

    转自  http://wangzhezhe.github.io/blog/2016/02/17/golang-scheduler/ 基本上是网上相关文章的梳理,初衷主要是想了解下golang中的gor ...