前言

前面已经讲解了bean的装配技术,接着学习Spring中另外一个核心概念:切面

面向切面

面向切面编程

切面能够帮助模块化横切关注点横切关注点可以被描述为影响应用的功能,如为业务添加安全和事务管理等。

AOP(Aspect Orient Programming)

  • 通知,通知定义切面何时被使用,Spring切面可以应用5种类型的通知。

    • 前置通知(Before),在目标方法被调用之前调用通知功能。
    • 后置通知(After),在目标方法完成之后调用通知,并不关心方法的输出。
    • 返回通知(AfterReturning),在目标方法成功执行之后调用通知。
    • 异常通知(AfterThrowing),在目标方法抛出异常后调用通知。
    • 环形通知(Around),通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
  • 连接点,在应用执行过程中能够插入切面的一个点。
  • 切点,匹配通知所要织入的一个或多个连接点。
  • 切面,通知和切点的结合。
  • 引入,允许向现有类添加新方法或属性。
  • 织入,把切面应用到目标对象并创建新的代理对象的过程,切面可以在指定的连接点被织入到目标对象中,在目标对象的生命周期中有多个点可以进行织入。
    • 编译期,在目标类编译时被织入,需要特殊的编译器支持。
    • 类加载器,切面在目标类加载到JVM时被织入,需要特殊类加载器。
    • 运行期,在应用运行的某个时刻被织入,AOP容器会为目标对象动态创建代理对象,这也是Spring AOP的织入方式。

Spring AOP

SpringAOP的支持在很多方面借鉴了AspectJ项目,提供如下四种支持。

  • 基于代理的经典Spring AOP
  • POJO切面。
  • @AspectJ注解的切面。
  • 注入式AspectJ切面(适用于Spring各版本)。

Spring AOP构建在动态代理基础上,只能局限于对方法拦截;Spring在运行时通知对象(通过在代理类中包裹切面,在运行期把切面织入到Spring管理的bean中,代理类封装了目标类,并拦截被通知方法的调用,执行切面逻辑,再把调用转发给真正的目标bean);Spring只支持方法级别的连接点(基于动态代理决定)。

通过切点选择连接点

编写切点

首先先定义一个方法


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

然后使用切点表达式设置当perform方法执行时触发通知的调用execution(* ch4.Performance.perform(..)),*表示并不关心返回值,然后指定具体的方法名,方法中的..表示切点要选择任意的perform方法。还可使用&&、and、||、or对切点进行限定。

切点中选择bean

切点表达式中可使用beanID来标识bean,如下切点表达式execution(* ch4.Performance.perform(..)) && bean(musicPerformance),表示限定beanIDmusicPerformance时调用通知,其中musicPerformancePerformance的一个子类实现。

使用注解创建切面

定义切面

定义一个切面如下。


package ch4; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component; @Aspect
@Component
public class Audience {
@Before("execution(* ch4.Performance.perform(..))")
public void silenceCellPhones() {
System.out.println("Silencing cell phones");
} @Before("execution(* ch4.Performance.perform(..))")
public void takeSeats() {
System.out.println("Taking seats");
} @AfterReturning("execution(* ch4.Performance.perform(..))")
public void applause() {
System.out.println("CLAP CLAP CLAP CLAP");
} @AfterThrowing("execution(* ch4.Performance.perform(..))")
public void demandRefund() {
System.out.println("Demanding a refund");
}
}

可以看到配合注解和切点表达式可以使得在执行perform方法之前、之后完成指定动作,当然,对于每个方法都使用了execution切点表达式,可以进一步进行精简。


package ch4; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component; @Aspect
@Component
public class Audience {
@Pointcut("execution(* ch4.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 CLAP");
} @AfterThrowing("performance()")
public void demandRefund() {
System.out.println("Demanding a refund");
}
}

可以看到使用@Pointcut定义切点,然后在其他方法中直接使用注解和切点方法即可,不需要再繁琐的使用execution切点表达式。

启动代理功能

在定义了注解后,需要启动,否则无法识别,启动方法分为在JavaConfig中显式配置和XML注解。

  • JavaConfig显式配置

package ch4; 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
@EnableAspectJAutoProxy
@ComponentScan
public class ConcertConfig { @Bean
public Audience audience() {
return new Audience();
}
}
  • XML配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <aop:aspectj-autoproxy/>
<context:component-scan base-package="ch4"/> </beans>

创建环绕通知

将被通知的目标方法完全包装起来,就像在一个通知方法中同时编写前置通知和后置通知。


package ch4; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component; @Aspect
@Component
public class Audience {
@Pointcut("execution(* ch4.Performance.perform(..)) && ! bean(musicPerformance)")
public void performance() { } @Around("performance()")
public void watchPerformance(ProceedingJoinPoint jp) {
try {
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
jp.proceed();
System.out.println("CLAP CLAP CLAP CLAP");
} catch (Throwable e) {
System.out.println("Demanding a refund");
}
}
}

使用Around注解表示环绕通知,注意需要调用proceed()方法来调用实际的通知方法。

处理通知中的参数

perform方法中添加int number参数表示有多少观众,使用如下切点表达式execution(\* ch4.Performance.perform(int)) && args(number),表示需要匹配perform(int)型方法并且通知方法的参数名为number

  • MusicPerformance如下

package ch4; import org.springframework.stereotype.Service; @Service
public class MusicPerformance implements Performance {
public void perform(int number) {
System.out.println("perform music, and the audience number is " + number);
}
}
  • Audience如下

package ch4; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component; @Aspect
@Component
public class Audience {
@Pointcut("execution(* ch4.Performance.perform(int)) && args(number)")
public void performance(int number) { } @Before("performance(int)")
public void silenceCellPhones() {
System.out.println("Silencing cell phones");
} @Before("performance(int)")
public void takeSeats() {
System.out.println("Taking seats");
} @AfterReturning("performance(int)")
public void applause() {
System.out.println("CLAP CLAP CLAP CLAP");
} @AfterThrowing("performance(int)")
public void demandRefund() {
System.out.println("Demanding a refund");
} @Around("performance(int)")
public void watchPerformance(ProceedingJoinPoint jp) {
try {
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
jp.proceed();
System.out.println("CLAP CLAP CLAP CLAP");
} catch (Throwable e) {
System.out.println("Demanding a refund");
}
}
}
  • 测试AOPTest如下

package ch4; import org.junit.Test;
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; import static org.junit.Assert.assertNotNull;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath*:spring-learning.xml")
public class AOPTest {
@Autowired
private Performance performance; @Test
public void notNull() {
assertNotNull(performance);
performance.perform(100); System.out.println("++++++++++++++++++");
performance.perform(999);
System.out.println("++++++++++++++++++");
}
}

运行结果:

Silencing cell phones

Taking seats

Taking seats

Silencing cell phones

perform music, and the audience number is 100

CLAP CLAP CLAP CLAP

CLAP CLAP CLAP CLAP

++++++++++++++++++

Silencing cell phones

Taking seats

Taking seats

Silencing cell phones

perform music, and the audience number is 999

CLAP CLAP CLAP CLAP

CLAP CLAP CLAP CLAP

++++++++++++++++++

在XML中声明切面

除了使用注解方式声明切面外,还可通过XML方式声明切面。

前置通知和后置通知


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <aop:aspectj-autoproxy/>
<context:component-scan base-package="ch4"/> <aop:config>
<aop:aspect ref="audience">
<aop:before
pointcut="execution(* ch4.Performance.perform(..))"
method="silenceCellPhones" /> <aop:before
pointcut="execution(* ch4.Performance.perform(..))"
method="takeSeats" /> <aop:after-returning
pointcut="execution(* ch4.Performance.perform(..))"
method="applause" /> <aop:after-throwing
pointcut="execution(* ch4.Performance.perform(..))"
method="demandRefund" /> </aop:aspect>
</aop:config> </beans>

Audience注解删除后运行单元测试可得出正确结果;当然上述XML也有点复杂,可进一步简化。


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <aop:aspectj-autoproxy/>
<context:component-scan base-package="ch4"/> <aop:config>
<aop:aspect ref="audience">
<aop:pointcut
id="performance"
expression="execution(* ch4.Performance.perform(..))" /> <aop:before
pointcut-ref="performance"
method="silenceCellPhones" /> <aop:before
pointcut-ref="performance"
method="takeSeats" /> <aop:after-returning
pointcut-ref="performance"
method="applause" /> <aop:after-throwing
pointcut-ref="performance"
method="demandRefund" /> </aop:aspect>
</aop:config> </beans>

声明环绕通知

XML如下


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <aop:aspectj-autoproxy/>
<context:component-scan base-package="ch4"/> <aop:config>
<aop:aspect ref="audience">
<aop:pointcut
id="performance"
expression="execution(* ch4.Performance.perform(..))"/>
<aop:around
pointcut-ref="performance"
method="watchPerformance" />
</aop:aspect>
</aop:config> </beans>

运行单元测试,可得正确结果。

为通知传递参数

XML文件如下


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <aop:aspectj-autoproxy/>
<context:component-scan base-package="ch4"/> <aop:config>
<aop:aspect ref="audience">
<aop:pointcut
id="performance"
expression="execution(* ch4.Performance.perform(int)) and args(int)" />
<aop:before
pointcut-ref="performance"
method="silenceCellPhones" /> <aop:before
pointcut-ref="performance"
method="takeSeats" /> <aop:after-returning
pointcut-ref="performance"
method="applause" /> <aop:after-throwing
pointcut-ref="performance"
method="demandRefund" />
</aop:aspect>
</aop:config> </beans>

运行单元测试,可得正确结果。

总结

AOPSpring的核心概念,通过AOP,我们可以把切面插入到方法执行的周围,通过本篇博文可以大致了解AOP的使用方法。源码已经上传至github,欢迎fork and star

【Spring】面向切面之AOP的更多相关文章

  1. Spring面向切面之AOP深入探讨

    Spring之AOP深入探讨 刚接触AOP之前我已经找了网上各种博客论坛上的关于AOP的文章利于我理解因为听好多人说AOP很复杂,很深奥当我接触之后发现根本不是那么的难于理解.它只是一个基于OOP技术 ...

  2. Spring面向切面编程(AOP)

    1 spring容器中bean特性 Spring容器的javabean对象默认是单例的. 通过在xml文件中,配置可以使用某些对象为多列. Spring容器中的javabean对象默认是立即加载(立即 ...

  3. Spring面向切面编程(AOP,Aspect Oriented Programming)

    AOP为Aspect Oriented Programming的缩写,意为:面向切面编程(也叫面向方面),可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术. ...

  4. spring 面向切面(AOP)

    AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. AOP与OOP是面向不同领域的两种设计思想. ...

  5. Spring面向切面编程AOP(around)实战

    spring aop的环绕通知around功能强大,我们这里就不细说,直接上代码,看着注释就能明白 需要的可以点击下载源码 1.如果使用注解的方式则需要先创建个注解类 package com.mb.a ...

  6. Spring面向切面编程(AOP)方式二

    使用注解进行实现:减少xml文件的配置. 1 建立切面类 不需要实现任何特定接口,按照需要自己定义通知. package org.guangsoft.utils; import java.util.D ...

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

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

  8. Spring学习手札(二)面向切面编程AOP

    AOP理解 Aspect Oriented Program面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. 但是,这种说法有些片面,因为在软件工程中,AOP的价值体现的并 ...

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

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

随机推荐

  1. windows server 2012 + sql server 2008 r2安装

    windows server 2012 r2  里面安装 sql server 2008 r2 问题总结 前提是 windows server 2012 r2 已经安装完成  ,(仅仅是安装完成 啥服 ...

  2. 如何快速的理解JavaScript闭包?

    先看问题 1. 在js中的作用域是什么? 作用域就是作用范围,作用空间.作用域分为全局作用域和局部作用域.(这个东西大家都明白) 如果把局部作用域比作一个国家,那么全局作用域就是地球,地球上除了那个国 ...

  3. .net中html转pdf

    最近研究了2天如何把html转为pdf保存的方法,网上找了好多解决方案.总结下来也就是2种 一种是使用js的插件 jspdf  这款插件我试了下,很强大  使用起来也很简单,唯一 一点不好的就是转成p ...

  4. 红黑树的插入Java实现

    package practice; public class TestMain { public static void main(String[] args) { int[] ao = {5, 1, ...

  5. CountDownLatch与CyclicBarrier

    对于AbstractQueuedSynchronizer衍生出来的并发工具类,这一篇再介绍俩. 场景1:有4个大文件的数据需要统计,最终将所有的统计结果进行加工,得到最后的分析数据.为了加速处理过程, ...

  6. Error Handling in ASP.NET Core

    Error Handling in ASP.NET Core 前言  在程序中,经常需要处理比如 404,500 ,502等错误,如果直接返回错误的调用堆栈的具体信息,显然大部分的用户看到是一脸懵逼的 ...

  7. zookeeper原理介绍

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt354 ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,它 ...

  8. nhibernate教程(4)--条件查询(Criteria Query)

    NHibernate之旅(4):探索查询之条件查询(Criteria Query) 2008-10-16 18:20 by 李永京, 44341 阅读, 43 评论, 收藏,  编辑 本节内容 NHi ...

  9. C 语言 define 变参__VA_ARGS__使用

    在C语言的标准库中,printf.scanf.sscanf.sprintf.sscanf这些标准库的输入输出函数,参数都是可变的.在调试程序时,我们可能希望定义一个参数可变的输出函数来记录日志,那么用 ...

  10. 【Alpha阶段】第四次 Scrum Meeting

    每日任务 1.本次会议为第 四次 Meeting会议: 2.本次会议在上午09:35,大课间休息时间在禹州楼召开,召开本次会议为20分钟,分析完善接下来的任务: 一.今日站立式会议照片 二.每个人的工 ...