《Spring实战 第三版》四
Posted ase265
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Spring实战 第三版》四相关的知识,希望对你有一定的参考价值。
《第四章 面向切面的Spring》
在软件开发中,分布于应用中多处的功能被称为横切关注点(cross-cutting concerns)
如安全和日志等功能即为横切关注点
通常,这些横切关注点从概念上是与应用的业务逻辑相分离的
但具体实现上往往是直接嵌入到应用的业务逻辑之中
将这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所解决的
什么是面向切面编程
切面可以帮助我们模块化横切关注点
横切关注点可以被模块化为特殊的类,这些类被称为切面
这样做有两个好处:首先,每个关注点现在都中集中于一处,而不是分散到多处代码中
其次,服务模块更简洁,因为它们只包含主要关注点(或核心功能)的代码
次要关注点的代码就被转移到切面中了
1.定义AOP术语
通知(Advice)
通知定义了切面是什么以及何时使用
Spring切面可以应用5种类型的通知
- Before 在方法调用之前调用通知
- After 在方法完成之后调用通知,不论方法执行是否成功
- After-returnning 在方法成功执行之后调用通知
- After-throwing 在方法抛出异常之后调用通知
- Around 通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
连接点(Joinpoint)
我们的应用可能需要对数以千计的时机应用通知,这些时间被称为连接点
连接点是在应用执行过程中能够插入切面地一个点
切点(PointCut)
如果通知定义了切面的“什么”和“何时”,那么切点就定义了何处
切点的定义会匹配通知所要织入的一个或多个连接点
我们通常使用明确的类和方法名来指定这些切点
或是利用正则表达式定义匹配的类和方法名称模式来指定这些切点
切面(Aspect)
切面是通知和切点的结合
通知和切点共同定义了关于切面的共同内容
引入
引入允许我们向现有的类添加新方法或属性
可以在无需修改现有的类的情况下,让他们具有新的行为和状态
织入
织入是将切面应用到目标对象来创建新的代理对象的过程
切面在指定的连接点被织入目标对象中
在目标对象的声明周期里有多个点可以进行织入
2.Spring对AOP的支持
并不是所有的AOP框架都是一样的,它们多多少少都是有一些分别
我们更关注于Spring AOP
Spring AOPt提供了4种各具特色的AOP支持:
- 基于代理的经典AOP
- @AspectJ注解驱动的切面
- 纯POJO切面
- 注入式AspectJ切面(适合Spring个版本)
Spring通知是Java编写的
Spring所创建的通知都是用标准的Java类编写的
定义通知所应用的切点通常在Spring配置文件里采用XML来编写的
Spring在运行期间通知对象
通过在代理类中包裹切面,Spring在运行期间将切面织入到Spring管理的Bean中
Spring只支持方法连接点
Spring基于动态代理
只支持方法连接点
使用切点连接选择点
在Spring AOP中,需要使用AspectJ的切点表达式语言来定义切点
关于Spring AOP的AspectJ切点,最重要的一点是Spring仅支持AspectJ切点指示器的一个子集
下面列出了Spring AOP所支持的AspectJ切点指示器
当Spring尝试使用AspectJ其他指示器时,将会抛出IllegalArgumentException异常
1编写切点
execution(*com.Instrument.play(..))
我们使用execution()指示器选择Instrument的play方法
方法表达式以*号开始,标识了我们不关心方法返回值的类型
接着我们指定了全限定类名和方法名
对于方法的参数列表,我们使用(..)标识切点选择任意的play方法
即无论该方法的入参是什么
现在假设我们需要配置切点仅匹配com包,在此场景下,可以使用within指示器来限制匹配
execution(*com.Instrument.play(..))&&within(com.*)
使用了&&操作符把execution把with指示器连接在一起
类似的可以使用||或者!操作符来表达或、非的关系
也可以使用and,or和not来替换上述操作符
2.使用Spring的bean()指示器
Spring 2.5还引入了一个新的bean指示器
该指示器允许我们在切点表达式中使用Bean的ID来表示Bean
execution(*com.Instrument.play(..))and bean(eddile)
在这里,我们希望在执行Instrument的play方法时应用通知,但限定Bean的ID为eddile
还可以使用非操作符指定除了指定ID的Bean意外的其他Bean应用通知
execution(*com.Instrument.play(..))and !bean(eddile)
在XML中声明切面
在Spring的AOP配置命名空间中,提供了声明式切面的选择
Spring的AOP配置元素简化了基于POJO切面的声明
1.声明前置和后置通知
假设我们用拥有一个观众类
public class Audience {
//表演之前
public void takeSeats(){
System.out.println("the audience is taking theirs seats.");
}
//表演之前
public void turnOffCellPhones(){
System.out.println("the audience is turning off their cellphones");
}
//表演之后
public void applaud(){
System.out.println("CLAP CLAP CLAP CLAP CLAP");
}
}
并将其在XML文件中进行声明
<bean id="audience" class="com.Audience"/>
现在,Audience其实已经具备了成为一个切面的所有条件
现在它只需要一点Spring的AOP魔法
其关于AOP的配置如下:
<aop:config>
<!--声明一个简单的切面-->
<aop:aspect ref="audience">
<!--定义了匹配切点的方法执行之前调用前置通知方法-->
<aop:before pointcut="execution(* com.Performer.perform(..))" method="takeSeats"/>
<aop:before pointcut="execution(* com.Performer.perform(..))" method="turnOffCellPhones"/>
<!--定义了匹配切点的方法执行之后调用后置通知方法-->
<aop:after pointcut="execution(* com.Performer.perform(..))" method="applaud"/>
</aop:aspect>
</aop:config>
显然,我们各个通知方法调用的切点是一样的
可以将切点独立出来声明,然后再引用切点
<aop:pointcut id="performer" expression="execution(* com.Performer.perform(..))"/>
然后再在通知里使用pointcut-ref来引用定义好的切点
2.声明环绕通知
假如我们需要观众报告每个参赛者的表演时间,那么可以使用环绕通知
先定义一个方法:
public void watchPerformance(ProceedingJoinPoint joinPoint){
try {
System.out.println("the audience is taking theirs seats.");
System.out.println("the audience is turning off their cellphones");
long start = System.currentTimeMillis();
joinPoint.proceed();
long end = System.currentTimeMillis();
System.out.println("CLAP CLAP CLAP CLAP CLAP");
System.out.println("the performer took"+(end-start)+"milliseconds");
}
catch (Throwable t){
System.out.println("we want our money back ");
}
}
然后就是使用<aop:around>元素来定义环绕通知即可
<aop:around pointcut-ref="performer" method="watchPerformance"/>
3.为通知传递参数
除了环绕通知,我们的通知都没有用到参数
为了给通知传递参数其实很简单
这里是通过配置实现将被通知的方法的的参数传递给通知
<aop:config>
<aop:aspect ref="magician">
<!--thoughts为参数的名字-->
<!--切点定义了一个带参数的方法 参数名为thoughts,类型为Sting-->
<aop:pointcut id="thinking" expression="execution(* com.Thinker.thinkOfSomething(String)) and args(thoughts)"/>
<!--前置通知通过arg-names将参数进行传递-->
<aop:before pointcut-ref="thinking" method="interceptThoughts" arg-names="thoughts"/>
</aop:aspect>
</aop:config>
4.通过切面引入新功能
一些编程语言,例如Ruby和Groovy,有开放类的理念,可以不用直接修改类或对象
就能够为类或对象增加新的方法
实际上,Java可以利用被称为引入的AOP概念,切面可以为Spring Bean添加新方法
举个例子,现在拥有某一接口的多个实现类,在此基础上我们需要在接口中再增加一个方法
一个方法是一一修改实现类,另一个方法是通过AOP来实现,不需要修改现有的实现
<aop:config>
<aop:aspect>
<aop:declare-parents types-matching="com.Performer+" implement-interface="com.Contestant" default-impl="com.GraciousContestant"/>
</aop:aspect>
<aop:declare-parents>声明了此切面所通知的所有Bean在它的对象层次结构中拥有新的父类型
类型匹配Performer接口的那些Bean将会实现Contestant接口
那么Contestant接口中方法的实现来自于何处
default-impl属性通过给定的全限定类名来显示指定Constestant接口的实现
或者使用delegate-ref属性给出引用的Bean的ID
在mian函数中测试
Performer performer = (Performer) ctx.getBean("duke");
performer.perform();
Contestant contestant = (Contestant)performer;
contestant.receiveAward();
发现,确实Performer接口既实现了自身的方法也实现了Contestant接口的方法
注解切面
使用注解来创建切面是AspectJ 5 所引入的关键特性
@Aspect
public class Audience {
//定义切点 匹配Performer的perform方法
@Pointcut(
"execution(* com.Performer.perform(..))"
)
public void performance(){}
//表演之前 前置通知
@Before("performance()")
public void takeSeats(){
System.out.println("the audience is taking theirs seats.");
}
//表演之前 前置通知
@Before("performance()")
public void turnOffCellPhones(){
System.out.println("the audience is turning off their cellphones");
}
//表演之后 后置通知
@After("performance()")
public void applaud(){
System.out.println("CLAP CLAP CLAP CLAP CLAP");
}
//环绕通知
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint joinPoint)
{
...
}
}
如上,就是使用注解实现的一个切面
切点的名称来自于注解所应用的方法名称,所以该切点的名称为performance
performance方法的实际内容并不重要
该方法只是一个标识,供@Pointcut注解依附
同时该类仍然是一个POJO类
最后让Spring将Audience应用为一个切面即可
为此,我们需要在Spring上下文中声明一个自动代理Bean
该Bean知道如何把@AspectJ注解所标注的Bean转化为代理通知
Spring在aop命名空间中提供了一个自定义的配置元素<aop:aspectj-autoproxy>
使用该元素后将在Spring上下文中创建一个AnnotationAwareProxyCreator类,它会自动代理一些Bean
这些Bean的方法需要与使用@AspectJ注解的Bean中所定义的切点相匹配
而这些切点又是使用@Pointcut注解定义出来的
总之,最后在配置文件XML中加上
<aop:aspectj-autoproxy/>
那么基于注解的AOP定义完成
传递参数给所标注的通知
@Aspect
public class Magician implements MindReader {
private String thoughts;
//声明参数化的切点
@Pointcut("execution(* com.Thinker.thinkOfSomething(String)) && args(thoughts)")
public void thinking(String thoughts){}
@Before("thinking(thoughts)")
//把参数传递给通知
public void interceptThoughts(String thoughts) {
System.out.println("Intercepting volunteer's thoughts: "+thoughts);
this.thoughts = thoughts;
}
public String getThoughts() {
return thoughts;
}
}
标注引入
使用注解来为已有的Bean引入接口
@Aspect
public class ContestantIntroducer {
@DeclareParents(value = "com.Performer+", defaultImpl = GraciousContestant.class)
public static Contestant contestant;
}
上面这段代码为Performer Bean引入了Contestant接口
value等同于type-matching属性
defaultImpl等同于default-impl
static属性则指定了将被引入的接口
以上是关于《Spring实战 第三版》四的主要内容,如果未能解决你的问题,请参考以下文章