什么是面向切面编程
切面能过帮助我们模块化横切关注点(影响应用多处的功能)
每个模块的核心功能都是为特定业务领域提供服务,但是这些模块都需要类似的辅助功能(例如: 安全和事务管理)
AOP术语
通知(Advice):
- 定义了切面是什么以及何时使用
- 通知类型:
- 前置通知: 在目标方法调用之前,调用通知功能
- 后置通知: 在目标方法完成之后,调用通知功能
- 返回通知: 在目标方法成功执行之后,调用通知功能
- 异常通知: 在目标方法抛出异常后,调用通知功能
- 环绕通知: 在目标方法调用之前和调用之后,调用通知功能
连接点(Join point):
- 是在应用执行过程中能够插入切面的一个点,切面可以利用这些点插入到应用的正常流程中,并添加新的行为
切点(Pointcut):
- 匹配通知所有要织入的一个或多个链接点,定义了何处执行行为
切面(Aspect):
- 横切关注点可以被模块化为特殊的类,这些类被称为切面(aspect)
- 切面是通知和切面点的结合,通知和切点共同定义了切面的全部内容—它是什么,在何时和何处完成其功能
引入(Introduction):
- 向现有的类添加新方法或属性
织入(Weaving):
- 把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中
- 在目标对象的生命周期有多个点可以进行织入:
- 编译期: 切面在目标类编译时被织入,需要特殊的编译器
- 类加载期: 切面在目标类加载到JVM时织入,需要特殊的类加载器
- 运行期: 切面在应用运行的某个时刻被织入,AOP容器会动态创建代理对象
通过切点来选择链接点
Spring支持AspectJ切点指示器的一部分
编写切点
定义接口
// 代表现场表演 public interface Performance { public void perform(); }
使用
execution()
指示器execution(* concert.Performance.perform(..))
使用
within()
指示器限制切点范围execution(* concert.Performance.perform(..) && within(concert.*))
- 操作符:
&&
: and||
: or!
: not
- 操作符:
在切点中选择bean
Spring中有
bean()
指示器,通过bean的ID来标识beanexecution(* concert.Performance.perform() and bean('example'))
使用注解创建切面
定义切面
示例: 定义观众类
@Aspect public class Audience { // 表演前手机静音 @Before("execution(** concert.Performance.perform(..))") public void silenceCellPhones() { System.out.println("Silencing cell phones"); } // 表演前入座 @Before("execution(** concert.Performance.perform(..))") public void takeSeats() { System.out.println("Taking seats"); } // 表演后鼓掌 @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"); } }
@Aspect
表示该类为一个切面- 注解及对应通知方式:
@Before
: 前置通知@After
: 后置通知@AfterReturning
: 返回通知@AfterThrowing
: 异常通知@Around
: 环绕通知
使用
@Pointcut
注解来定义可重用的切点@Aspect public class Audience { // 定义命名的切点 @Pointcut("execution(** concert.Performance.perform(..))") public void performance() {} @Before("performance()") public void silenceCellPhones() { System.out.println("Silencing cell phones"); } @Before("performance()") public void takeSeats() { System.out.println("Taking seats"); } @AfterReturning("performance()") public void applause() { System.out.println("CLAP CLAP CLAP"); } @AfterThrowing("performance()") public void demandRefund() { System.out.println("Demanding a refund"); } }
performance()
方法的实际内容并不重要,只是作为一个标识- 除了注解和没有实际操做的
performance()
,Audience
类依然是一个POJO。能够正常调用其方法,可以独立的进行单元测试,也能够装配为bean 将切面装配为bean时,需要在JavaConfig类中启用自动代理功能
@Configuration @EnableAspectJAutoProxy @ComponentScan public class ConcertConfig { @Bean public Audience audience() { return new Audience(); } }
或者通过XML配置文件启动
<!-- 需要在beans标签中声明aop命名空间 --> <context:component-scan base-package="concert"/> <aop:aspectj-autoproxy/> <!-- 启动AspecJ自动代理 --> <bean class="concert.Audience"/> <!-- 声明Audience bean -->
AspectJ自动代理会为使用
@Aspect
注解的bean创建一个代理
创建环绕通知
- 环绕通知是最强大的通知类型,能够让编写的逻辑将被通知的目标完全包装起来
示例:
@Aspect public class Audience { @Pointcut("execution(** concert.Performance.perform(..))") public void performance() {} @Around("performance()") public void watchPerformance(ProceedingJoinPoint jp) { try { System.out.println("Silence cell phones"); System.out.println("Taking seats"); jp.proceed(); System.out.println("CLAP CLAP CLAP"); } catch (Throwable e) { System.out.println("Demanding a refund"); } } }
- 需要接受
ProceedingJoinPoint
对象作为参数,用于调用被通知的方法 - 当将控制权交给被通知的方法时,需要调用
ProceedingJoinPoint
的proceed()
方法
- 需要接受
处理通知中的参数
示例: 实现CD播放器的切面,来记录每首歌的播放次数
@Aspect public class SongCounter { private Map<Integer, Integer> songCounts = new HashMap<Integer, Integer>(); // 第一个Integer表示歌曲编号,第二个表示播放次数 @Pointcut("execution(* cdsystem.CompactDisc.playSong(int)) && args(songNumber)") public void songPlayed(int songNumber) {} @Before("songPlayed(songNumber)") public void countSong(int songNumber) { int currentCount = getPlayedCount(songNumber); songCounts.put(songNumber, currentCount + 1); } public void getPlayCount(int songNumber) { return songCounts.containsKey(songNumber) ? songCounts.get(songNumber) : 0; } }
args(songNumber)
限定符用于传递参数,表明传递给playSong
中的int
参数也会到通知中- 切点中
args(parameter)
需要和通知注解中的songPlayed(parameter)
一致
通过注解引入新功能
通过AOP为bean引入新的方法,代理拦截调用并委托给实现该方法的其他对象
实际上,一个bean的实现被拆分到了多个类中
示例:
创建包含新功能的接口
public interface Encoreable { public void performEncore(); }
- 创建实现了接口的对象
创建切面:
@Aspect public class EncoreableIntroducer { @DeclareParents(value="concert.Performance+", defaultImpl=DefaultEncoreable.class) public static Encoreable encoreable; }
- 此切面没有提供通知
- 通过
@DeclareParents
注解将Encoreable
接口引入到Performance
bean中 @DeclareParents
:
value
属性指定要引入该接口的bean类型defaultImpl
: 指定实现了引入功能的类@DeclareParents
标注的静态属性指明了要引入的接口
- 将切面声明为bean:
@Bean public EncoreableIntroducer encoreableIntroducer() { return new EncoreableIntroducer(); }
在XML中声明切面
通常而言,基于注解的配置优于基于Java的配置优于基于XML的配置
AOP配置元素能够以非侵入性的方式声明
无注解的Audience类
public class Audience { public void silenceCellPhones() { System.out.println("Silencing cell phones"); } public void takeSeats() { System.out.println("Taking seats"); } public void applause() { System.out.println("CLAP CLAP CLAP"); } public void demandRefund() { System.out.println("Demanding a refund"); } }
声明前置和后置通知
示例
<aop:config> <!-- 声明切面 --> <aop:aspect ref="audience"> <!-- 引用audience bean --> <aop:before pointcut="execution(**concert.Performance.perform(..))" method="slienceCellPhones"/> <aop:before pointcut="execution(**concert.Performance.perform(..))" method="takeSeats"/> <aop:after-returning pointcut="execution(**concert.Performance.perform(..))" method="applause"/> <aop:after-throwing pointcut="execution(**concert.Performance.perform(..))" method="demandRefund"/> </aop:aspect> </aop:config>
<aop:pointcut>
定义切点<aop:config> <!-- 声明切面 --> <aop:aspect ref="audience"> <!-- 引用audience bean --> <!-- 定义切点 --> <aop:pointuct id="performance" expression="execution(**concert.Performance.perform(..))"/> <aop:before point-ref="performance" method="slienceCellPhones"/> <aop:before point-ref="performance" method="takeSeats"/> <aop:after-returning point-ref="performance" method="applause"/> <aop:after-throwing point-ref="performance" method="demandRefund"/> </aop:aspect> </aop:config>
point-ref="pointcutID"
引用切点- 为了使
<aop:pointcut>
能够在多个切面中使用,可将其放在<aop:config>
元素内
声明环绕通知
Java代码:
public class Audience { public void watchPerformance(ProceedingJoinPoint jp) { try { System.out.println("Silence cell phones"); System.out.println("Taking seats"); jp.proceed(); System.out.println("CLAP CLAP CLAP"); } catch (Throwable e) { System.out.println("Demanding a "); } } }
XMl配置:
<aop:config> <aop:aspect ref="audience"> <aop:pointcut id="performance" expression="execution(** concert.Performance.perform(..))"/> <!-- 声明环绕通知 --> <aop:around pointcut-ref="performance" method="watchPerformance"/> </aop:aspect> </aop:config>
为通知传递参数
Java代码
public class SongCounter { private Map<Integer, Integer> songCounts = new HashMap<Integer, Integer>(); // 第一个Integer表示歌曲编号,第二个表示播放次数 public void countSong(int songNumber) { int currentCount = getPlayedCount(songNumber); songCounts.put(songNumber, currentCount + 1); } public void getPlayCount(int songNumber) { return songCounts.containsKey(songNumber) ? songCounts.get(songNumber) : 0; } }
XML配置
<bean id="songCounter" class="cdsystem.SongCounter"/> <bean id="cd" class="cdsystem.CompactDisc"/> <aop:config> <aop:aspect ref="songCounter"> <aop:pointcut id="songPlayed" expression="execution(* cdsystem.CompactDisc.playSong(int)) and args(songNumber)"/> <aop:before pointcut-ref="songPlaye" method="songTrack"/> </aop:aspect> </aop:config>
通过切面引入新功能
XML配置
<aop:config> <aop:aspect> <aop:declare-parents types-matching="concert.Performance+" implement-interface="concert.Encoreable" default-impl="concert.DefaultEncoreable"/> </aop:aspect> </aop:config>
- 配型匹配
Performance
接口的bean在父类结构中会增加Encoreable
接口 有两种标识引入接口的实现
default-impl
: 如上所示delegate-ref
: 将实现类声明为bean,再通过此标签引用<aop:aspect> <aop:declare-parents types-matching="concert.Performance+" implement-interface="concert.Encoreable" delegate-ref="encoreableDelegate"/> </aop:aspect>
<bean id="encoreableDelegate" class="concert.DefaultEncoreable"/>
- 配型匹配
注入AspectJ切面
目前水平太低也用不到,暂时略过QAQ