##Spring in Action4-AOP Spring in Action4的读书笔记之AOP:
在我们编写程序的过程中,如果要重用通用功能的话,在 Java中最常见的技术继承(inheritance) 或委托 (delegation) ,但是, 如果在整个应用中都使用相同的基类, 继承往往会导致一个脆弱的对象体系; 而使用委托可能需要对委托对象进行复杂的调用。
切面提供了取代继承和委托的另一种可选方案, 而且在很多场景下更清晰简洁。
在使用面向切面编程时, 我们仍然需要在一个地方定义这些通用的功能, 但是可以通过声明的方式定义这个功能要以何种方式在何处应用, 而无需修改受影响的类。 横切关注点可以被模块化为特殊的类, 这些类被称为**切面(aspect) **。
这样做有两个好处:
- 现在每个关注点都集中于一个地方, 而不是分散到多处代码中,便于修改。
- 服务模块更简洁, 因为它们只包含主要核心功能的代码, 而次要关注点的代码被转移到切面中了。
####AOP术语
- 通知(
advice) 、 - 切点(
pointcut) - 连接点(
join point)
#####通知(advice): 切面的工作被称为通知。通知定义了切面是什么以及何时使用,它应该应用在某个方法被调用之前? 之后? 之前和之后都调用? 还是只在方法抛出异常时调用?
#####Spring切面可以应用5种类型的通知:
- 前置通知(Before) : 在目标方法被调用之前调用通知功能;
- 后置通知(After) : 在目标方法完成之后调用通知, 此时不会关心方法的输出是什么;
- 返回通知(After-returning) : 在目标方法成功执行之后调用通知;
- 异常通知(After-throwing) : 在目标方法抛出异常后调用通知;
- 环绕通知(Around) : 通知包裹了被通知的方法, 在被通知的方法调用之前和调用之后 执行自定义的行为。
#####连接点(Join point): 程序可能有数以千计的时机应用通知。 这些时机被称为连接点。 连接点是在应用执行过程中能够插入切面的一个点。 这个点可以是调用方法时、 抛出异常时、 甚至修改一个字段时。 切面代码可以利用这些点插入到应用的正常流程之中, 并添加新的行为。
切点(pointcut) :
切点表示了通知在哪里执行,切点有助于缩小切面所通知的连接点的范围。
切点的定义会匹配通知所要织入的一个或多个连接点。 我们通常使用明确的类和方法名称, 或是利用正则表达式定义所匹配的类和方法名称来指定这些切点
如:
@Before("execution(**chapter03.annotation_aop.Performance.perform())")
#####切面(AspectJ): 切面是通知和切点的结合。 通知和切点共同定义了切面的全部内容——它是什么, 在何时和何处完成其功能。
<br> 如下定义了一个切面:
[@Aspect](https://my.oschina.net/aspect)
public class Audience {
/**
* 方法执行之前执行
*/
@Before("execution(**chapter03.annotation_aop.Performance.perform())")
public void silenceCellPhones(){
System.out.println("表演之前,手机调成静音");
}
/**
* 方法执行后执行
*/
@AfterReturning("execution(**chapter03.annotation_aop.Performance.perform())")
public void applause(){
System.out.println("表演很精彩,鼓掌");
}
/**
* 方法抛出异常执行
*/
@AfterThrowing("execution(**chapter03.annotation_aop.Performance.perform())")
public void demandRefund(){
System.out.println("表演失败,要求退款");
}
}
<br> ######Spring在运行时通知对象
通过在代理类中包裹切面, Spring在运行期把切面织入到Spring管理的bean中。 代理类封装了目标类, 并拦截被通知方法的调用, 再把调用转发给真正的目标bean。
当代理拦截到方法调用时, 在调用目标bean方法之前, 会执行切面逻辑。
因为Spring基于动态代理来实现AOP功能, 所以Spring只支持方法连接点,但是方法拦截可以满足绝大部分的需求。 如果需要方法拦截之外的连接点拦截功能, 那么我们可以利用Aspect来补充Spring AOP的功能
<br> ####编写切点 比如有如下一个接口,编写一个切面通知,在接口方法调用的时候触发:
package chapter03.annotation_aop;
public interface Performance {
void perform();
}
那么我们的切点表达式可以这样写:
@Pointcut("execution(* chapter03.annotation_aop.Performance.perform(..))")
public void perform(){
}
其中 ***** 表示该接口下任意返回值的方法, .. 表示方法的任意参数
还可以使用指示器来更加详细的进行匹配,有以下几种指示器:
| AspectJ指示器 | 描述 | 例子 | 说明 |
|---|---|---|---|
| arg() | 限制连接点匹配参数为指定类型,args属于动态切入点,这种切入点开销非常大,非特殊情况最好不要使用; | @Pointcut("execution(* CompactDisc.playTrack(int)) && args(number)")public void trackPlay(int number){} | 匹配playTrack()方法且参数为int类型 |
| @args() | 匹配当前执行的方法传入的参数持有指定注解的执行 | @args(Anno)) | 方法参数标有Anno注解 |
| this() | 匹配当前AOP代理对象类型的执行方法 | this(service.IPointcutService) | 当前AOP对象实现了 IPointcutService接口的任何方法 |
| target() | 匹配当前目标对象类型的执行方法 | target(service.IPointcutService) | 当前目标对象(非AOP对象)实现了 IPointcutService接口的任何方法 |
| @target() | 匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解 | @target (Secure) | 任何目标对象持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用 |
| within() | 匹配指定类型内的方法执行 | within(cn.javass..*) | cn.javass包及子包下的任何方法执行 |
| @within() | 匹配所有持有指定注解类型内的方法 | @within(Secure) | 任何目标对象持有Secure注解的类方法;必须是在目标对象上声明这个注解,在接口上声明的对它不起作用 |
| @annotation | 匹配当前执行方法持有指定注解的方法 | @annotation(Secure ) | 当前执行方法上持有注解Secure将被匹配 |
| bean() | 匹配特定名称的Bean对象的执行方法 | bean(*Service) | 匹配所有以Service命名(id或name)结尾的Bean |
<br><br> ####Spring使用注解实现AOP:
首先定义一个接口,通知作用在该接口中的方法,当该接口下的方法被调用的时候就会触发通知。
package chapter03.annotation_aop;
public interface Performance {
void perform();
}
然后写一个该接口的实现类:
package chapter03.annotation_aop;
@Component(value = "performance")
public class PerformanceImpl implements Performance {
@Override
public void perform() {
System.out.println("舞蹈表演中......");
}
}
使用@Component(value = "performance")把该类注入到Spring中,bean的名字为:performance。
<br> 接下来编写**切面**:
@Aspect
public class Audience {
/**
* 方法执行之前执行
*/
@Before("execution(* chapter03.annotation_aop.Performance.perform())")
public void silenceCellPhones(){
System.out.println("表演之前,手机调成静音");
}
/**
* 方法执行之前执行
*/
@Before("execution(* chapter03.annotation_aop.Performance.perform())")
public void takeSeats(){
System.out.println("表演之前,坐好");
}
/**
* 方法执行后执行
*/
@AfterReturning("execution(* chapter03.annotation_aop.Performance.perform())")
public void applause(){
System.out.println("表演很精彩,鼓掌");
}
/**
* 方法抛出异常执行
*/
@AfterThrowing("execution(* chapter03.annotation_aop.Performance.perform())")
public void demandRefund(){
System.out.println("表演失败,要求退款");
}
}
@Aspect表示该类是一个切面。@Before标注的通知方法在目标方法执行之前执行。@AfterReturning标注的通知方法在目标方法成功执行后执行。@AfterThrowing标注的通知方法在目标方法出现异常的时候执行
提示:在每个通知方法中,都要写一遍切点表达式:
execution(* chapter03.annotation_aop.Performance.perform())"
可以使用 @Pointcut 来编写一个可重用的切点表达式:
@Aspect
public class Audience2 {
//定义可重用的切点表达式
@Pointcut("execution(* chapter03.annotation_aop.Performance.perform(..))")
public void perform(){
}
/**
* 方法执行之前执行
*/
@Before("perform()")
public void silenceCellPhones(){
System.out.println("表演之前,手机调成静音");
}
/**
* 方法执行之前执行
*/
@Before("perform()")
public void takeSeats(){
System.out.println("表演之前,坐好");
}
/**
* 方法执行后执行
*/
@AfterReturning("perform())")
public void applause(){
System.out.println("表演很精彩,鼓掌");
}
/**
* 方法抛出异常执行
*/
@AfterThrowing("perform()")
public void demandRefund(){
System.out.println("表演失败,要求退款");
}
}
<br> 我们定义了切点之后,它只是一个POJO,并没有特别之处,我们还要把它们注入到Spring中,
下面编写一个配置文件:
@Configuration
@EnableAspectJAutoProxy//启动 AspectJ 自动代理
@ComponentScan
public class Config {
// 注入 bean
@Bean
public Audience2 audience(){
return new Audience2();
}
}
<br> 1. `@Configuration`:标识这是一个配置文件。 2. `@EnableAspectJAutoProxy` : 启动 AspectJ 自动代理。 3. `@ComponentScan`:启动注解扫描。
测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Config.class)
public class TestAop {
@Autowired
private Performance performance;
@Test
public void testAdvice(){
performance.perform();
}
}
@RunWith(SpringJUnit4ClassRunner.class):自动创建 Spring 上下文@ContextConfiguration(classes = Config.class): 加载配置文件
结果:
表演之前,手机调成静音 表演之前,坐好 舞蹈表演中...... 表演很精彩,鼓掌
####环绕通知: 环绕通知 @Around():
这个环绕通知所达到的效果与之前的前置通知和后置通知是一样的。 但是, 现在它们 位于同一个方法中, 不像之前那样分散在四个不同的通知方法里面。
使用 **ProceedingJoinPoint**当作方法的参数
@Aspect
public class AroundAdvice {
//定义可重用切点
@Pointcut("execution(* chapter03.annotation_aop.Performance.perform(..))")
public void perform(){
}
@Around("perform()")
public void around(ProceedingJoinPoint joinPoint){
try {
System.out.println("表演之前,手机调成静音");
System.out.println("表演之前,坐好");
joinPoint.proceed();
System.out.println("表演很精彩,鼓掌");
} catch (Throwable throwable) {
System.out.println("表演失败,要求退款");
throwable.printStackTrace();
}
}
}
####带参数的情况:
接口:
package chapter03.annotation_aop.aop_args;
public interface CompactDisc {
void playTrack();
void playTrack(int i);
}
接口的实现:
@Component(value = "compactDisc")
public class CompactDiscImpl implements CompactDisc {
@Override
public void playTrack() {
System.out.println("playTrack with no args........");
}
@Override
public void playTrack(int i) {
System.out.println("playTrack with args : " + i);
}
}
编写切面:
@Aspect
public class AvdiceWithArgs {
@Pointcut("execution(* chapter03.annotation_aop.aop_args.CompactDisc.playTrack(int)) && args(number)")
public void trackPlay(int number){
}
@Before("trackPlay(num)")
public void trackPlayBefore(int num){
System.out.println("表演之前......" + num);
}
@After("trackPlay(num)")
public void trackPlayAfter(int num){
System.out.println("表演之后......" + num);
}
}
args(number):: 它表明传递给 playTrack()方法的int类型参数也会传递到通知中去
@Configuration
@EnableAspectJAutoProxy//启动 AspectJ 自动代理
@ComponentScan
public class Config {
@Bean
public AvdiceWithArgs avdiceWithArgs(){
return new AvdiceWithArgs();
}
}
测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Config.class)
public class TestAop {
@Autowired
private CompactDisc compactDisc;
@Test
public void testAdviceWithArgs(){
compactDisc.playTrack(100);
}
}
结果:
表演之前......100 playTrack with args : 100 表演之后......100
<BR><BR> ####Spring使用XML实现AOP
定义切面:只是一个POJO。
public class Audience {
public void silenceCellPhones(){
System.out.println("表演之前,手机调成静音");
}
public void takeSeats(){
System.out.println("表演之前,坐好");
}
public void applause(){
System.out.println("表演很精彩,鼓掌");
}
public void demandRefund(){
System.out.println("表演失败,要求退款");
}
}
定义环绕通知:
public class AroundAdvice {
public void aroundPerform(ProceedingJoinPoint joinPoint){
try {
System.out.println("表演之前,手机调成静音");
System.out.println("表演之前,坐好");
joinPoint.proceed();
System.out.println("表演很精彩,鼓掌");
} catch (Throwable throwable) {
System.out.println("表演失败,要求退款");
throwable.printStackTrace();
}
}
}
在 XML 文件中配置通知:
大多数的AOP配置元素必须在**<aop:config>元素的上下文内使用。这条规则有几种例外场景, 但是把bean声明为一个切面时,我们总是从<aop:config>**元素开始配置的。
在**<aop:config>**元素内, 我们可以声明一个或多个通知器、 切面或者切点。
使用**<aop:aspect>*元素声明了一个简单的切面, ref元素引用了一个POJO bean, 该bean实现了切面的功能
使用 <aop:pointcut> 定义了可重用的切点表达式, 供后面的通知使用而不用重新定义一遍
**<aop:pointcut>**元素所定义的切点可以被同一个<aop:aspect>元素之内的所 有通知元素引用。 如果想让定义的切点能够在多个切面使用,我们可以把<aop:pointcut>元素放在<aop:config>元素的范围内
启动 AspectJ 自动代理
<aop:aspectj-autoproxy/>
配置切面的 bean
<bean id="audience" class="chapter03.annotation_xml.Audience"/>
配置环绕通知的 bean
<aop:config>
<!-- 引用配置的切面 bean-->
<aop:aspect ref="audience">
<!-- 定义可重用的切点表达式-->
<aop:pointcut id="pc" expression="execution(* chapter03.annotation_xml.Performance.perform(..))"/>
<!-- 前置通知-->
<aop:before pointcut-ref="pc" method="silenceCellPhones"/>
<aop:before pointcut-ref="pc" method="takeSeats"/>
<!-- 后置通知 -->
<aop:after-returning pointcut-ref="pc" method="applause"/>
<!-- 异常通知-->
<aop:after-throwing pointcut-ref="pc" method="demandRefund"/>
</aop:aspect>
<!-- 配置环绕通知 -->
<aop:aspect ref="aroundAdvice">
<aop:pointcut id="apc" expression="execution(* chapter03.annotation_xml.Performance.perform(..))"/>
<aop:around pointcut-ref="apc" method="aroundPerform"/>
</aop:aspect>
<!--配置带参数的通知-->
<aop:aspect ref="adviceWithArgs">
<aop:pointcut id="bpc" expression="execution(* chapter03.annotation_xml.aop_args.CompactDisc.playTrack(int)) and args(num)"/>
<aop:before pointcut-ref="bpc" method="trackPlayBefore"/>
<aop:after pointcut-ref="bpc" method="trackPlayAfter"/>
</aop:aspect>
</aop:config>
测试:
首先加载配置文件:
@Configuration
@ImportResource("classpath:applicationContext.xml")
public class Config {
}
编写测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Config.class)
public class TestAop {
@Autowired
private Performance performance;
@Test
public void test(){
performance.perform();
}
}
结果:
表演之前,手机调成静音 表演之前,坐好 表演之前,手机调成静音 表演之前,坐好 舞蹈表演中...... 表演很精彩,鼓掌 表演很精彩,鼓掌
因为配置了两个通知,所以出现上述效果。
结束!!!!
引用:
详细参考:
http://sishuok.com/forum/posts/list/281.html


3252

被折叠的 条评论
为什么被折叠?



