spring的依赖注入看完了,接下来是spring中与DI一并重要的AOP了,开始吧,GO。

在软件开发中,散布于应用中多处的功能被称为横切发关注点,通常来讲,这些横切关注点从概念上市与应用的业务逻辑相分离的(但是往往会直接嵌入到应用的业务逻辑中)。这些横切关注点与业务逻辑想分离正是面向切面编程所要解决的问题。

一、什么是面向切面编程

如果要重用通用功能的话,最常用的面向对象技术是继承(inheritance)和委托(delegation)。但是,如果咋整个应用中都使用相同基类,继承往往会导致一个脆弱的对象体系;而委托可能需要对委托对象进行复杂的调用。

切面提供了取代继承和委托的另一种可选方案,而且在很多场景下更清晰简洁。在使用面向切面编程的时候,我们仍然在一个地方定义通用功能,但是可以通过声明的方式定义这个功能要以何种方式在何处应用,二无需修改受影响的类。横切关注点可以被模块化为特殊的类,这些类被称为切面(aspect)。这样做有两个好处:首先,现在每个关注点都集中于一个地方,而不是分散到多处代码中,其次,服务模块更简洁因为它们只包含主要关注点(或核心功能)的代码,而次要关注点的代码被转移到切面中了。

1、AOP中的术语

(1)通知(Advice) 通知定义了切面是什么以及何时使用。

类比于:抄表员登记用电量并回去向电力公司报告,抄表员的主要工作是记录电量

spring切面可以应用5种类型的通知:

1)前置通知(Before):在目标方法被调用钱调用通知功能

2)后置通知(After):在目标方法完成之后调用通知,此时不关心方法的输出是什么

3)返回通知(After-returning):在目标方法成功执行之后调用通知

4)异常通知(After-throwing):在目标方法抛出异常之后调用通知

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

(2)连接点(Join point)

类比于:抄表员也许能够读取各种类型的设备,但是为了完成工作 ,他的目标应该是房屋内所安装的电表

我们的应用可能也有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点

(3)切点(Pointcut) 定义了切面的“何处”,在什么地方使用

类比于:电力公司为每一个抄表员都分别指定了某一块区域而定住户

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

(4)切面(Aspect)

类比于:抄表员知道自己要做的事情(报告电表量)和从哪些住户收集信息。因此,他能玩成工作了

切面是通知和切点的结合。通知和切点定义了切面的全部内容分--它是什么,在何时何处完成去功能

(5)引入(Introduction)

引入允许我们向现有的类添加新方法和属性

(6)织入(Wearing)

织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入:

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

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

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

2、spring对AOP的支持 创建切点来定义切面所织入的连接点是AOP框架的基本功能

spring提供了四种类型的AOP支持:

1)基于代理的经典的spring AOP

2)纯POJO切面

3)@AspectJ 注解驱动的切面

4)注入式AspectJ 切面(适用于spring 的各种版本)

前三种都是spring AOP实现的变体,spring AOP构建在动态代理基础之上,因此,spring对AOP 的支持局限于方法拦截

spring AOP 框架的一些关键知识:

1)spring所创建的通知都是用标准的Java类编写的。定义通知所应用的切点通常会使用注解或在spring配置文件里采用XML来编写

2)通过在代理类中包裹切面,spring在运行时期把切面织入到spring管理的bean中,直到应用需要被代理的bean时,spring才会创建代理对象

3)因为spring基于动态代理,所以spring只支持方法级别的连接点

 二、通过切点来选择连接点

spring借助AspectJ 的切点表达式语言来定义spring切面

AspectJ指示器 描述
arg() 限制连接点匹配参数为指定类型的执行方法
@args() 限制连接点 匹配参数由指定注解标注的执行方法
execution() 用于匹配是连接点的执行方法
this() 限制连接点匹配AOP代理的bean引用为指定的类型的bean
target 限制连接点匹配目标对象为指定类型的类
@target() 限制连接点匹配待定的执行对象,这些对象对应的类要具有指定类型的注解
within() 限制连接点匹配指定的类型
@within() 限制连接点匹配指定注解标注的类型(当使用spring AOP时,方法定义在由指定的注解所标注的类里)
@annotation 限制匹配带有指定注解的连接点

在spring尝试使用AspectJ 其他指示器的时候,将会跑出IllgalArgumentException异常。execution指示器是我们编写切点定义时最主要的指示器

1、编写切点

spring切面的讲解,需要有个主题来定义切面的切点。定义了一个Performance接口

package concert;
public interface Performance {
public void perform();
}

execution(* concert.Performance.perform(...))

注:表达式从* 号开始,表明了我们不关心方法的返回值类型,然后我们指定了全限定类名和方法名。对于参数列表,我们使用(..)表明切点要选择任意的perform()方法,无论给方法的入参是什么。

execution(* concert.Performance.perform(...)) && within(concert.*)

注:&&是execution()指示器和within()指示器连接在一起形成与(and)关系,within()指示器来限定匹配,当concert包下任意类的方法被调用的时候

2、在切点中选择bean

bean()指示器,使用bean ID 和bean名称作为参数来限制切点只匹配特定的bean

execution(* concert.Performance.perform(...)) and bean('woodstock')

注:限定的bean的ID 为woodstock,切面的通知会编织到ID为woodstock的bean中

execution(* concert.Performance.perform(...)) !and bean('woodstock')

注:切面的通知会编织到ID不为woodstock的bean中

三、使用注解创建切面

1、定义切面

//Audience类;观看演出的切面
package concert;
import org.aspectj.lang.annotation.AtferReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; //@Aspect注解表明该类不仅是一个POJO,还是一个切面
@Aspect
public class Audience { //表演之前
@Befor("execution(** concert.Performance.perform(..))")
public void sienceCellPhones(){
System.out.println("Silencing cell phone");
} //表演之前
@Befor("execution(** concert.Performance.perform(..))")
public void takeSeats(){
System.out.println("Taking setas");
} //表演之后
@AfterReturning("execution(** concert.Performance.perform(..))")
public void applause(){
System.out.println("CLAP CLAP CLAP !!!");
} //表演失败之后
@AfterThrowing("execution(** concert.Performance.perform(..))")
public void demandRefund(){
System.out.println("Demanding a refund");
}
}

spring中使用AspectJ注解来声明通知方法

1)@After:通知方法会在目标方法返回或抛出异常后调用

2)@AfterBeturning:通知方法会在目标方法返回后调用

3)@AfterThrowing:通知方法会在目标方法抛出异常后调用

4)@Around:通知方法将目标方法封装起来

5)@Before:通知方法会在目标方法调用之前执行

//Audience类;观看演出的切面
package concert;
import org.aspectj.lang.annotation.AtferReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; //@Aspect注解表明该类不仅是一个POJO,还是一个切面
@Aspect
public class Audience { //定义命名的切点
//该方法本身只是一个标识,供@Pointcut注解依附
@Pointcut("execution(** concert.Performance.perform(..))")
public void performance(){} //表演之前
@Befor("performance()")
public void sienceCellPhones(){
System.out.println("Silencing cell phone");
} //表演之前
@Befor("performance()")
public void takeSeats(){
System.out.println("Taking setas");
} //表演之后
@AfterReturning("performance()")
public void applause(){
System.out.println("CLAP CLAP CLAP !!!");
} //表演失败之后
@AfterThrowing("performance()")
public void demandRefund(){
System.out.println("Demanding a refund");
}
}

这样的话只是编写了切面,Audience只是spring容器中的一个bean,即使使用了AspectJ 注解,并不会视为切面,这些注解不会解析,也不会创建将其转化为切面的代理,接下来:

package concert;

import org.springframework,context.annotation.Bean;
import org.springframework,context.annotation.ComponentScan;
import org.springframework,context.annotation.Configuration;
import org.springframework,context.annotation.EnableAspectJAutoProxy; @Configuration
//启动AspectJ自动代理
@EnableAspectJAutoProxy
@ComponentScan
public class ConectConfig() { @Bean
public Audience audience(){
return new Audience();
}
} //XML中配置
<context:component-scan base-package="concert" />
//启用AspectJ自动代理
<aop:aspectj-autoproxy>
//声明Audience bean
<bean class="concert.Audience" />

注意:spring的AspectJ自动代理仅仅是使用@AspectJ作为创建切面的指导,切面依然是基于代理的。。。

2、创建环绕通知(环绕通知是最为强大的通知类型)

package concert;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut; @Aspect
public class Audience { //定义命名的切点
@Pointcut("execution(** concert.Performance.perform(..))")
public void performance() {} @Around
public void watchPerformance(ProceedingJoinPoint jp){
try{
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
jp.proceed();
}catch(Throwable e){
System.out.println("Demanding a refund");
}
}
}

3、处理通知中的参数

直接上代码吧,这个是BlackDisc类。

//使用参数化的通知来记录磁道播放的次数
package soundsystem;
import java.util.HashMap;
import java.util.Map;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut; @Aspect
public class TrackCounter {
private Map<Integer,Integer> trackCounts = new HashMap<Integer,Integer>();
@Pointcut(execution(* soundsystem.CompactDisc.playTrack(int)) && args(trackNumber))
public void trackPlayed(int trackNumber) {} @Before("trackPlayed(trackNumber)")
public void countTrack(int trackNumber){
int currentCount = getPlayCount(trackNumber);
trackCounts.put(trackNumber, currentCount +1);
} public int getPlayCount(int trackNumber){
return trackCounts.countainsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
}
}

execution(* soundsystem.CompactDisc.playTrack(int)) && args(trackNumber)
  在切点表达式中声明参数,这个参数传入到通知方法中。需要关注的是args(trackNumber)限定符,它表明传递给palyTrack()方法的int类型参数也会传递到通知中去,参数的名称trackNumber也与切点方法签名中的参数相匹配,这个参数会传递到通知方法中,这个通知方法是通过@Befor注解和命名切点trackPlayed(trackNumber)定义的。切点定义中的参数与切点方法中的参数名称是一样的。这样就完成了从命名切点到通知方法的参数转移。

package soundsystem;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration
//启用AspectJ自动代理
@EnableAspectJAutoProxy
public class TrackCountConfig { @Bean
public CompactDisc sgtPeppers(){
BlankDisc cd = new BlankDisc();
cd.setTitle("蓝莲花");
cd.setArtist("许巍");
List<String> tracks = new ArrayList<String>();
tracks.add("那一年");
tracks.add("空谷幽兰");
tracks.add("爱情");
tracks.add("此时此刻"); cd.setTrack(tracks);
return cd;
} @Bean
public TrackCounter trackCounter(){
return new TrackCounter();
}
} //这里写一个测试的类
package soundsystem;
import static org.junit.Assert.*;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.springJunit4ClassRunner; @RunWith(springJunit4ClassRunner.class)
@ContextConfiguration(classes=TrackCounterConfig.class)
public class TrackCounterTest { @Rule
public final StandardOutputStreamLog log = new StandardOutputStreamLog(); @Autowired
private CompactDisc cd; @Autowired
private TrackCounter counter; @Test
public void testTrackCounter() {
//播放一些磁道
cd.palyTrack(1);
cd.palyTrack(2);
cd.palyTrack(3);
cd.palyTrack(3);
cd.palyTrack(3);
cd.palyTrack(3);
cd.palyTrack(7);
cd.palyTrack(7); //断言期望的
assertEquals(1, counter.getPlayCount(1));
assertEquals(1, counter.getPlayCount(2));
assertEquals(4, counter.getPlayCount(3));
assertEquals(0, counter.getPlayCount(4)); assertEquals(0, counter.getPlayCount(5));
assertEquals(0, counter.getPlayCount(6));
assertEquals(2, counter.getPlayCount(7));
}
}

4、通过注解引入新功能

利用被称为引入的AOP概念,切面可以为spring bean添加新方法

package concert;

public interface Encoreable {
void performEncore();
} package concert; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents; @Aspect
public class EncoreableIntroducer { @DeclareParents(value="concert.Performance" defaultImpl=DefaultEncoreable.class)
public static Encoreable encoreable;
}

spring的自动代理机制将会获取到它的声明,当spring发现一个bean使用了@Aspect注解时,spring就会创建一个代理,然后将调用委托给代理bean或被引入的实现,这取决于调用方法属于被代理的bean还是属于被引入的接口。

五、面向切面的spring(1)的更多相关文章

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

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

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

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

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

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

  4. 面向切面的Spring

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

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

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

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

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

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

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

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

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

  9. Spring AOP 面向切面的Spring

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

随机推荐

  1. bzoj3620

    KMP 我似乎复杂度写的不对... 因为位置相同只算一次,后缀数组什么的都不管用了,我们就暴力kmp,但是我写的是暴力跳...竟然过了...我写bzoj3670才发现... #include<c ...

  2. 关于netty的多个handler链式模式

    1. 老规矩, 引入我们喜闻乐见的maven依赖 <dependency> <groupId>io.netty</groupId> <artifactId&g ...

  3. 美化console.log的文本(转载)

    原文地址:http://www.css88.com/archives/5260 JavaScript Console 那些少人所知的特性 console.log("%c css88.com& ...

  4. 解决Linux与Windows压缩解压中文文件名乱码(转载)

    转自:http://crazyfeng.com/linux-windows-compress-chinese-filename.html 由于Linux与Windows编码问题,使用Zip Tar 压 ...

  5. 洛谷P4206 [NOI2005]聪聪与可可(期望dp+最短路)

    传送门 首先,猫的走位太飘了……只能预处理…… 先对每一个点跑一遍dijkstra跑出最短路,然后再预处理出$nxt[i][j]$表示当猫在$i$老鼠在$j$时猫下一步会走到哪里 然后考虑dp,设$d ...

  6. 「vijos」lxhgww的奇思妙想(长链剖分)

    传送门 长链剖分的板子(又是乱搞优化暴力) 对于每一个点,我们定义它深度最深的子节点为它的重儿子(为什么不叫长儿子……),他们之间的连边为重边 然后长链剖分有几个性质 1.总链长为$O(n)$ 2.一 ...

  7. 初学者的疑惑,到底什么是javaBean?

    JavaBeans是Java中一种特殊的类,可以将多个对象封装到一个对象(bean)中.特点是可序列化,提供无参构造器,提供getter方法和setter方法访问对象的属性.名称中的"Bea ...

  8. 【react-native】持续踩坑总结

    陆陆续续的已经接触了RN快3个月,整体的感受...感觉在调试兼容andorid问题的时候就像回到了IE时代. 本来想按自己踩坑的路径持续更新一些记录,但是,现实是坑太多,还是统一写一篇汇总一下吧(鉴于 ...

  9. 人工智能-深度学习(2)TensorFlow安装及基本使用(学习笔记)

    一.TensorFlow 简介 TensorFlow 是 Google 开源的一款人工智能学习系统.为什么叫这个名字呢? Tensor 的意思是张量,代表 N 维数组:Flow 的意思是流,代表基于数 ...

  10. 转 Docker Swarm vs Kubernetes

    容器化已经改变我们部署软件和微服务开发的方式.如果你刚听说容器, 这篇博客帮你入门. 什么是容器编排 容器能够把服务打包成基本单元,你可以把它部署到任何地方:本地机器.测试环境或者生产系统.但是在生产 ...