在软件开发中,发布于应用中多处的功能被称为横切关注点。通常,这些横切关注点从概念上是与应用的业务逻辑相分离的(但往往直接嵌入到应用的业务逻辑之中)。将横切关注点与业务逻辑相分离是AOP所要解决的。

一、AOP术语

1.通知(Advice):定义切面是什么以及何时使用。除了描述要完成的工作,还要解决何时执行工作。

5种类型的通知:

Before,在方法被调用之前调用通知

After,在方法完成之后调用通知,无论方法执行是否成功

After-returning,在方法成功执行之后调用通知

After-throwing,在方法抛出异常后调用通知

Around,通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为

2.连接点,是在应用执行过程中能够插入切面的一个点,切面可以利用这些点插入到应用的正常流程之中,并添加新的行为。

3.切点,切点的定义会匹配通知所要织入的一个或多个连接点。通常使用明确的类和方法名称来指定这些切点,或者利用正则表达式定义匹配的类和方法名称模式来指定这些切点。

4.切面,是通知和切点的结合,是什么,在何时和何处完成其功能。

5.引入,允许向现有的类添加新方法或属性,例如可以创建一个Auditable通知类,该类记录了对象最后一次修改时的状态,只需一种方法setLastModified(Date),和一个实例变量来保存这个状态。然后这个新方法和变量就可以被引入到现有的类中。从而在无需修改现有类的情况下,让它们具有新的行为和状态。

6.织入,将切面应用到目标对象来创建新的代理对象的过程,切面在指定的连接点被织入到目标对象中。在目标对象的声明周期有多个点可以进行织入。

编译期:在目标类编译时被织入,需要特殊编译器,AspectJ的织入编译器就是以这种方式织入切面的。

类加载期:在目标类加载到JVM时被织入,需要特殊的类加载器,可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5的LTW(load-time-weaving)支持以这种方式织入切面。

运行期:在应用运行的某个时刻被织入,一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象,Spring AOP以这种方式织入切面。

二、Spring对AOP的支持

提供4种各具特色的AOP支持:

1.基于代理的经典AOP

2.@AspectJ注解驱动的切面

3.纯POJO切面

4.注入式AspectJ切面(适合Spring各版本)

前3种都是Spring基于代理的AOP变体,因此,Spring对AOP的支持局限于方法拦截。如果需要构造器或属性拦截,那么应该考虑在AspectJ里实现切面,利用Spring的DI把Spring Bean注入到AspectJ切面中。

AOP框架的关键点:

1.Spring通知是Java编写的,可以使用与普通Java开发一样的IDE来开发切面,而且,定义通知所应用的切点通常在Spring配置文件里采用XML来编写。

2.Spring在运行期通知对象,通过在代理类中包裹切面,Spring在运行期将切面织入到Spring管理的Bean中。

代理类封装了目标类,并拦截被通知的方法的调用,再将调用转发给真正的目标Bean

当拦截到方法调用时,在调用目标Bean方法之前,代理会执行切面逻辑

直到应用需要被代理的Bean时,Spring才创建代理对象。如果使用ApplicationContext,在ApplicationContext从BeanFactory中加载所有Bean时,Spring创建被代理的对象。因为Spring运行时才创建代理对象,所以不需要特殊的编译器织入Spring AOP的切面。

3.Spring只支持方法连接点

二、使用切点选择连接点

切点用于准确定位应该在什么地方应用切面的通知。

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

Spring仅支持AspectJ切点指示器的一个子集。

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

@arg()

限制连接点匹配参数由指定注解标注的执行方法
execution() 用于匹配是链接点的执行方法
this() 限制链接点匹配AOP代理的Bean引用为指定类型的类
target() 限制链接点匹配目标对象为指定类型的类
@target() 限制连接点匹配待定的执行对象,这些对象对应的类要具备指定类型的注解
within() 限制连接点匹配指定的类型
@within() 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里)
@annotaion 限制匹配带有指定注解连接点

1.编写切点

该切点表达式指示选择Instrument的play()方法执行时触发通知。(..)标识其诶单选择任意的play()方法,不管方法的入参是什么。

指示器之间可以混合使用,用and、or、not(&&、||、!)连接。

2.使用Spring的bean指示器

Spring 2.5引入一个新的bean()指示器,允许在切点表达式中使用Bean的ID来标识Bean。

在执行Instrument的play()方法时应用通知,但限定Bean的ID为eddie。

三、在XML中声明切面

AOP配置元素

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

创建一个公众类:

import org.aspectj.lang.ProceedingJoinPoint;

public class Audience {
public void takeSeats() {
System.out.println("The audience is taking their seats.");
} public void turnOffPhones() {
System.out.println("The audience is turning off their cellphones.");
} public void applaud() {
System.out.println("CLAP CLAP CLAP CLAP CLAP...");
} public void demandRefund() {
System.out.println("Boo! We want out money back!");
} }

Audience只是一个有几个方法的简单Java类。把它注册为Spring应用上下文的一个Bean

<bean id="audience" class="cn.edu.stu.springidol.Audience" />

把audience Bean变成一个切面:

<aop:config>
<aop:aspect ref="audience">
<aop:pointcut expression="execution(* cn.edu.stu.springidol.Performer.perform(..))" id="performance"/>
<aop:before pointcut-ref="performance" method="takeSeats"/>
<aop:before pointcut-ref="performance" method="turnOffPhones"/>
<aop:after-returning pointcut-ref="performance" method="applaud"/>
<aop:after-throwing pointcut-ref="performance" method="demandRefund"/>
</aop:aspect>
</aop:config>

切点声明为当调用Performer的perform方法时,应用切面。

测试:

声明一个Instrumentalist Bean,它继承了Performer

<bean id="saxphone" class="cn.edu.stu.springidol.Saxphone" />

<bean id="kenny" class="cn.edu.stu.springidol.Instrumentalist">
<property name="song" value="Jingle Bells" />
<property name="instrument" ref="saxphone" />
</bean>

测试代码

ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-idol.xml");
Performer p = (Performer) ctx.getBean("kenny");
p.perform();

结果打印出

声明环绕通知:
前面的切面使用了前置通知和后置通知,但如果不使用成员变量存储信息,那么在前置通知和后置通知之间共享信息非常麻烦。

例如,希望Audience能够知道参赛者表演了多长时间,如果使用成员变量保存开始时间,以为Audience是单例,将存在线程安全问题。

使用环绕通知,因为整个通知逻辑在一个方法内实现,所以不需要使用成员变量。

public void watchPerformance(ProceedingJoinPoint jointpoint) {
try {
System.out.println("The audience is taking their seats.");
System.out.println("The audience is turning off their cellphone.");
long start = System.currentTimeMillis(); jointpoint.proceed();//执行被通知的方法 long end = System.currentTimeMillis();
System.out.println("CLAP CLAP CLAP CLAP CLAP");
System.out.println("The performance took " + (end - start) + " milliseconds.");
} catch(Throwable t) {
System.out.println("Boo! We want out money back!");
}
}
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut expression="execution(* cn.edu.stu.springidol.Performer.perform(..))" id="performance"/>
<aop:around pointcut-ref="performance" method="watchPerformance"/> </aop:aspect>
</aop:config>

测试代码一样,打印结果一样。

为通知传递参数:

有时候,通知不仅仅是对方法进行简单包装,还需要校验传递给方法的参数值,这时候为通知传递参数就非常有用了。

public interface MindReader {
void interceptThoughts(String thoughts);
String getThoughts();
} public class Magician implements MindReader {
private String thoughts;
@Override
public void interceptThoughts(String thoughts) {
System.out.println("Intercepting volunteer's thoughts");
this.thoughts = thoughts;
} @Override
public String getThoughts() {
return thoughts;
}
} public interface Thinker {
void thinkOfSomething(String thoughts);
} public class Volunteer implements Thinker {
private String thoughts;
@Override
public void thinkOfSomething(String thoughts) {
this.thoughts = thoughts;
} public String getThoughts() {
return thoughts;
}
}

MindReader可以截获Thinker的想法thoughts(一个String参数)<bean id="magician" class="cn.edu.stu.springidol.Magician" />

<bean id="magician" class="cn.edu.stu.springidol.Magician" />
<bean id="volunteer" class="cn.edu.stu.springidol.Volunteer" />
<aop:config>
<aop:aspect ref="magician">
<aop:pointcut expression="execution(* cn.edu.stu.springidol.Thinker.thinkOfSomething(String))
and args(thoughts)" id="thinking"/>
<aop:before pointcut-ref="thinking" method="interceptThoughts" arg-names="thoughts"/> </aop:aspect>
</aop:config>

切点标识了Thinker的thinkOfSomething方法,指定String参数,然后再args参数中标识了将thoughts作为参数,在<aop:before>元素引用了thoughts参数,标识该参数必须传递给Magician的interceptThoughts方法。

通过切面引入新功能:

切面只是实现了它们所包装Bean的相同接口的代理。如果代理还能发布新的接口,那么切面所通知的Bean看起来实现了新的接口,即便底层实现类并没有实现这些接口。

当引入接口的方法被调用时,代理将此调用委托给实现了新接口的某个其他对象。实际上,Bean的实现被拆分到多个类。

public interface Contestant {
void receiveAward();
} public class GraciousContestant implements Contestant { @Override
public void receiveAward() {
System.out.println("Receive Award"); } }

XML配置

<aop:config>
<aop:aspect>
<aop:declare-parents
types-matching="cn.edu.stu.springidol.Performer+"
implement-interface="cn.edu.stu.springidol.Contestant"
   default-impl="cn.edu.stu.springdiol.GraciousContestant"/>
</aop:aspect>
</aop:config>

四、注解切面

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut; @Aspect
public class Audience {
@Pointcut("execution(* cn.edu.stu.springidol.Performer.perform(..))")
public void performance() { } @Before("performance()")
public void takeSeats() {
System.out.println("The audience is taking their seats.");
} @Before("performance()")
public void turnOffPhones() {
System.out.println("The audience is turning off their cellphones.");
} @AfterReturning("performance()")
public void applaud() {
System.out.println("CLAP CLAP CLAP CLAP CLAP...");
} @AfterThrowing("performance()")
public void demandRefund() {
System.out.println("Boo! We want out money back!");
} }

除了注解和无操作的performance方法,Audience类在实现上没有任何改变。

让Spring将Audience应用为一个切面,需要在Spring上下文声明一个自动代理Bean,该Bean知道如何将@AspectJ注解所标注的Bean转变为代理通知。

aop提供的一个自定义的配置元素:

<aop:aspectj-autoproxy />

<aop:aspect>元素和@Aspect注解都是把一个POJO转变为一个切面的有效方式,但是<aop:aspect>相对@Aspect的优势是不需要实现切面功能的源码,通过@Aspect,我们必须标注类和方法,它需要源码,而<aop:aspect>可以引用任意一个Bean。

创建环绕通知:

@Around("performance()")
public void watchPerformance(ProceedingJoinPoint jointpoint) {
try {
System.out.println("The audience is taking their seats.");
System.out.println("The audience is turning off their cellphone.");
long start = System.currentTimeMillis(); jointpoint.proceed();//执行被通知的方法 long end = System.currentTimeMillis();
System.out.println("CLAP CLAP CLAP CLAP CLAP");
System.out.println("The performance took " + (end - start) + " milliseconds.");
} catch(Throwable t) {
System.out.println("Boo! We want out money back!");
}
}

传递参数给所标注的通知:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut; @Aspect
public class Magician implements MindReader {
private String thoughts; @Pointcut("execution(* cn.edu.stu.springidol.Thinker.thinkOfSomething(String)) && args(thoughts)")
public void thinking(String thoughts) { } @Override
@Before("thinking(thoughts)")
public void interceptThoughts(String thoughts) {
System.out.println("Intercepting volunteer's thoughts : " + thoughts);
this.thoughts = thoughts;
} @Override
public String getThoughts() {
return thoughts;
} }

标注引入:

@Aspect
public class ContestantIntroducer {
@DeclareParents(value = "cn.edu.stu.springidol.Performer+", defaultImpl = GraciousContestant.class)
public static Contestant contestant;
}

ContestantIntroducer是一个切面,它为Performer Bean引入Contestant接口,单没有delegate-ref的对应物,所以单靠@DeclareParents还不行,必须借助<aop:declare-parents>

五、注入AspectJ切面

AspectJ提供比Spring AOP更细粒度的通知,例如拦截对象字段的修改,支持构造器链接点。

创建一个新切面(这是一个aspect):

public aspect JudgeAspect {
public JudgeAspect() {
} pointcut performance() : execution(* perform(..)); after() returning() : performance() {
System.out.println(criticismEngine.getCriticism());
} private CriticismEngine criticismEngine;
public void setCriticismEngine(CriticismEngine criticismEngine) {
this.criticismEngine = criticismEngine;
}
}

JudgeAspect的职责是在表演结束后为表演发表评论,performance()切点匹配perform()方法,当它与after() returning()通知一起配合使用时,可以让该切面在表演结束时起作用。

并不是JudgeAspect本身发表评论,它与一个CriticismEngine对象相协作,调用该对象的getCriticism()方法发表一个评论,为了避免JudgeAspect和CriticismEngine产生耦合,通过setter依赖注入为JudgeAspect赋予CriticismEngine。

public interface CriticismEngine {
String getCriticism();
} public class CriticismEngineImpl implements CriticismEngine { @Override
public String getCriticism() {
int i = (int) (Math.random() * criticismPool.length);
return criticismPool[i];
} private String[] criticismPool;
public void setCriticismPool(String[] criticismPool) {
this.criticismPool = criticismPool;
}
}
      <bean id="criticismEngine" class="cn.edu.stu.springidol.CriticismEngineImpl">
<property name="criticisms">
<list>
<value>I'm not being rude, but that was appalling.</value>
<value>You may be the least talented person in this show.</value>
<value>Do every a favor and keep your day job.</value>
</list>
</property>
</bean> <bean class="cn.edu.stu.springidol.JudgeAspect" factory-method="aspectOf">
<property name="criticismEngine" ref="criticismEngine" />
</bean>

通常情况下,Spring Bean由Spring容器初始化,但是AspectJ切面是由AspectJ在运行期创建的。因为Spring无法负责创建JudgeAspect,就不能简单地将JudgeAspect声明为一个Bean,相反,需要一种方式为所有的AspectJ切面提供一个静态的aspectOf()方法,该方法返回切面的一个单例。必须使用factory-method来调用aspectOf()方法来代替调用JudgeAspect的构造器方法。

面向切面的Spring的更多相关文章

  1. Spring使用笔记(四) 面向切面的Spring

    面向切面的Spring 一.面向切面的概念 在软件开发中,散布于应用多处的功能被称为横切关注点(cross-cutting concern). 通常来讲这些横切关注带点从概念上来讲是与应用逻辑相分离的 ...

  2. Spring实战第四章学习笔记————面向切面的Spring

    Spring实战第四章学习笔记----面向切面的Spring 什么是面向切面的编程 我们把影响应用多处的功能描述为横切关注点.比如安全就是一个横切关注点,应用中许多方法都会涉及安全规则.而切面可以帮我 ...

  3. 五、面向切面的spring(1)

    spring的依赖注入看完了,接下来是spring中与DI一并重要的AOP了,开始吧,GO. 在软件开发中,散布于应用中多处的功能被称为横切发关注点,通常来讲,这些横切关注点从概念上市与应用的业务逻辑 ...

  4. Spring学习(四)--面向切面的Spring

    一.Spring--面向切面 在软件开发中,散布于应用中多处的功能被称为横切关注点(cross- cutting concern).通常来讲,这些横切关注点从概念上是与应用的业 务逻辑相分离的(但是往 ...

  5. 第04章-面向切面的Spring

    1. 什么是面向切面编程 AOP是什么 切面帮助我们模块化横切关注点. 横切关注点可被描述为影响应用[多处的]功能.如安全,应用许多方法会涉及安全规则. 继承与委托是最常见的实现重用 通用功能 的面向 ...

  6. Spring系列(四) 面向切面的Spring

    除了IOC外, AOP是Spring的另一个核心. Spring利用AOP解决应用横切关注点(cross-cutting concern)与业务逻辑的分离, 目的是解耦合. 横切关注点是指散布于代码多 ...

  7. Spring学习笔记(三):面向切面的Spring

    Spring之面向切面编程 一.理解何为面向切面编程 对于这个的理解,我觉得Spring实战中的例子讲得很明白: 假设我现在是一个小区用户,每个月小区都要收电费,这时候就会来人查看电表,算出来这个月电 ...

  8. Spring AOP 面向切面的Spring

    定义AOP术语 描述切面的常用术语有: 通知 (advice) 切点 (pointcut) 连接点 (joinpoint) 下图展示了这些概念是如何关联的 Spring 对AOP的支持 Spring提 ...

  9. 六、面向切面的spring(2)

    这个是承接五的,这部分主要的内容是在XML中声明切面. 一.在XML中声明切面 让我们先看一下spring中的AOP配置元素有哪些: AOP配置元素 用途 <aop:advisor> 定义 ...

随机推荐

  1. 【虚拟化实战】VM设计之一vCPU

    作者:范军 (Frank Fan) 新浪微博:@frankfan7 虚拟机需要多少个vCPU呢?是不是个数越多性能越好呢?这方面存在着很多误区.给VM配置CPU资源的时候,要精打细算才能最大可能的利用 ...

  2. mysql 重要维护工具 图解

        下载地址: http://maatkit.org/get/mk-query-digest更多信息: http://maatkit.org/ | http://code.google.com/p ...

  3. Ruby on Rails Tutorial 第四章 Rails背后的Ruby 之 其他数据类型(二)

    1.方法 定义如下所示: def string_message(str='') if str.empty? "It's an empty string!" else "T ...

  4. UNIX基础知识之出错处理

    当UNIX函数出错时,常常会返回一个负值,而且整型变量errno通常被设置为含有附加信息的一个值.例如,open函数如成功执行则返回一个非负文件描述符,如出错则返回-1.在open出错时,有大约15种 ...

  5. UNIX基础知识之文件和目录

    程序清单1-1 列出一个目录中的所有文件(ls命令的简要实现): [root@localhost unix_env_advance_prog]# cat prog1-.c #include " ...

  6. LeetCode: Palindrome Partition

    LeetCode: Palindrome Partition Given a string s, partition s such that every substring of the partit ...

  7. 小白日记21:kali渗透测试之提权(一)--本地提权

    本地提权 简单地说,本地提权漏洞就是说一个本来非常低权限.受限制的用户,可以提升到系统至高无上的权限.权限提升漏洞通常是一种"辅助"性质的漏洞,当黑客已经通过某种手段进入了目标机器 ...

  8. logstash jdbc 各种数据库配置

    MySQL数据库 Driver ="path/to/jdbc-drivers/mysql-connector-java-5.1.35-bin.jar"   //驱动程序Class ...

  9. 在Android中动画移动一个View的位置,采用Scroller类实现Android动画之 View移动

    在Android中动画移动一个View的位置,采用Scroller类实现 今天说最近自己遇到的一个问题,就是要用动画效果来移动一个VIew的位置. 这个具体的情况是,需要做一个SlidingMenu的 ...

  10. Mirco2440核心板设计思考

    1.核心板架构 注意的是:此处的RAM和ROM都是外置的 硬件框架 S3C2440+ SDRAM + NAND Flash + NOR Flash 也就是 CPU + RAM + ROM 2.S3C2 ...