Spring4 AOP详解

第一章Spring 快速入门并没有对Spring4 的 AOP 做太多的描述,是因为AOP切面编程概念不好理解。所以这章主要从三个方面详解AOP:AOP简介(了解),基于注解的AOP编程(重点)和基于xml的AOP编程。


AOP简介

什么是AOP

AOP(Aspect Oriented Programming)面向切面编程,是对传统的OOP(ObjectOriented Programming)面向对象编程的补充。

AOP的作用

如果A,B,C三个方法都要在执行前做验证操作,执行后做日志打印操作。肿么办?

排版好丑。。。。。。

AOP专业术语

** 切面(Aspect) ** : A,B,C,方法执行前都要调用的验证逻辑和执行后都要调用的日志逻辑,这两层业务逻辑就是切面。

** 通知(Advice) ** : 有五种通知,执行前,执行后,执行成功后,执行抛出异常后,环绕通知。就是切面执行的方法。

** 目标(Target) ** : 被通知的对象,这里就是A,B,C三个方法。

** 连接点(Joinpoint) **:连接点是一个应用执行过程中能够插入一个切面的点。

** 切点(pointcut) **:每个类都拥有多个连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点

打个比方:一天,三位侠客(被通知的对象Target)来我府上做客,被大门(切面Aspect)拦住,门前有五个保安(负责通知的Advice),因为其中一位侠客会降龙十八掌(满足被通知的一个条件Joinpoint),其中一位保安告知他:"你可以进去了"。另外两个侠客因为武艺超群(满足被通知的统一标准poincut)也都进去了。


基于注解的AOP编程

基于注解的编程,需要依赖AspectJ框架(java中最流行的aop框架)。

第一步:导入AspectJ的jar包,该框架只有Spring 2.0以上才支持。

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>

第二步:核心文件applicationContext.xml,里面需要配置自动扫描包(用于IOC注解)和配置启用AspectJ注解

<?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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!-- 自动扫描的包 -->
<context:component-scan base-package="com.itdragon.spring.*" ></context:component-scan> <!-- 使 AspectJ 的注解起作用 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>

第三步:切面类,该类有什么特点?首先它必须是IOC的bean,还要声明它是AspectJ切面,最后还可以定义切面的优先级Order(非必填)

通知有五种注解

** @Before ** :前置通知的注解,在目标方法执行前调用

** @After **:后置通知的注解, 在目标方法执行后调用,即使程序抛出异常都会调用

** @AfterReturning **:返回通知的注解, 在目标方法成功执行后调用,如果程序出错则不会调用

** @AfterThrowing **:异常通知的注解, 在目标方法出现指定异常时调用

** @Around **:环绕通知的注解,很强大(相当于前四个通知的组合),但用的不多,

还有为了简化开发的重用切入点@Pointcut,以及抽象表达"*"

public interface Calculator {  

    public int add(int a, int b);
public int division(int a, int b); }
import org.springframework.stereotype.Repository;
@Repository("calculator")
public class CalculatorImp implements Calculator { @Override
public int add(int a, int b) {
System.out.println("add 方法执行了 ----> " + (a + b));
return (a + b);
} @Override
public int division(int a, int b) {
System.out.println("division 方法执行了 ----> " + (a / b));
return (a / b);
} }
import java.util.Arrays;
import java.util.List;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; /**
* @Order(n) : 切面的优先级,n越小,级别越高
* @Aspect:声明该类是一个切面
* @Component:切面必须是 IOC 中的 bean
*/
@Order(2)
@Aspect
@Component
public class LoggerAspect { /**
* 前置通知的注解,在目标方法执行前调用
* execution最基础的表达式语法。
* 注意点:
* 1. 方法里面不能有行参,及add(int a, int b) 这是会报错的。
* 2. int(方法的返回值),add(方法名) 可以用 * 抽象化。甚至可以将类名抽象,指定该包下的类。
* 3. (int, int) 可以用(..)代替,表示匹配任意数量的参数
* 4. 被通知的对象(Target),建议加上包的路径
*/
@Before("execution(int com.atguigu.spring.my.aop.CalculatorImp.add(int , int))")
public void beforeAdvice(JoinPoint joinPoint) {
/**
* 连接点 joinPoint:add方法就是连接点
* getName获取的是方法名,是英文的,可以通过国际化转换对应的中文比较好。
*/
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("@Before 前置通知 : 方法名 【 " + methodName + " 】and args are " + args);
} /**
* 后置通知的注解, 在目标方法执行后调用,即使是程序出错都会调用
* 这里将 方法的返回值 和 CalculatorImp类下所有的方法,以及方法的形参 都抽象了
*/
@After("execution(* com.atguigu.spring.my.aop.CalculatorImp.*(..))")
public void afterAdvice(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("@After 后置通知 : 方法名 【 " + methodName + " 】and args are " + args);
} /**
* 重用切入点定义:声明切入点表达式。该方法里面不建议添加其他代码
*/
@Pointcut("execution(* com.atguigu.spring.my.aop.CalculatorImp.*(..))")
public void declareExecutionExpression(){} /**
* 返回通知的注解, 在目标方法成功执行后调用,如果程序出错则不会调用
* returning="result" 和 形参 result 保持一致 ,获取函数的返回值
*/
@AfterReturning(value="declareExecutionExpression()", returning="result")
public void afterRunningAdvice(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("@AfterReturning 返回通知 : 方法名 【 " + methodName + " 】and args are " + args + " , result is " + result);
} /**
* 异常通知的注解, 在目标方法出现指定异常时调用
* throwing="exception" 和 形参 exception 保持一致 , 且目标方法出了Exception(可以是其他异常)异常才会调用。
*/
@AfterThrowing(value="declareExecutionExpression()", throwing="exception")
public void afterThrowingAdvice(JoinPoint joinPoint, Exception exception) {
String methodName = joinPoint.getSignature().getName();
System.out.println("@AfterThrowing 异常通知 : 方法名 【 " + methodName + " 】and exception is " + exception);
}
/**
* 公用多个切面
*/
@AfterReturning("execution(* com.xxx.xx.baserver.operation.service.WorkOrderService.createWorkOrder(..))" +
" || execution(* com.xxx.xx.baserver.operation.service.WorkOrderService.updateWorkOrder(..))")
}
import java.util.Arrays;
import java.util.List;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; @Order(1)
@Aspect
@Component
public class AroundAspect { /**
* 环绕通知,很强大,但用的不多。 用环绕通知测试Order的优先级看的不明显(这里是笔者的失误)
* 环绕通知需要用ProceedingJoinPoint 类型的参数
*/
@Around("execution(* com.atguigu.spring.my.aop.CalculatorImp.*(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) {
Object result = null;
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
try {
System.out.println("@Around 前置通知 : 方法名 【 " + methodName + " 】and args are " + args);
result = joinPoint.proceed();
System.out.println("@Around 返回通知 : 方法名 【 " + methodName + " 】and args are " + args + " , result is " + result);
} catch (Throwable e) {
e.printStackTrace();
System.out.println("@Around 异常通知 : 方法名 【 " + methodName + " 】and exception is " + e);
}
System.out.println("@Around 后置通知 : 方法名 【 " + methodName + " 】and args are " + args); return result;
} }
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main { public static void main(String[] args) { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Calculator calculator = (Calculator) ctx.getBean("calculator"); calculator.add(11, 12);
calculator.division(21, 3); // 测试时,将被除数换成0,可以测试@AfterReturning , @After 和 @AfterThrowing ctx.close();
} }

第四步:执行看结果。这里没有做环绕通知的打印。将被除数设置为零,可以测试 返回通知,后置通知 和 异常通知。

@Before 前置通知 : 方法名 【 add 】and args are [11, 12]
add 方法执行了 ----> 23
@After 后置通知 : 方法名 【 add 】and args are [11, 12]
@AfterReturning 返回通知 : 方法名 【 add 】and args are [11, 12] , result is 23
division 方法执行了 ----> 7
@After 后置通知 : 方法名 【 division 】and args are [21, 3]
@AfterReturning 返回通知 : 方法名 【 division 】and args are [21, 3] , result is 7

很简单对吧,用到的注解其实并不是很多。

以上代码有一个不足之处,就是测试Order优先级的时候,效果不明显。AroundAspect的优先级高于LoggerAspect,从打印的日志中发现,只有AroundAspect的前置通知在LoggerAspect前面打印,其他通知均在后面。

因为博客和课堂不同,如果把每个知识点都单独写出来,篇幅可能太长。笔者尽可能将所有知识点都写在一起,学A知识的同时将B,C,D的知识一起学习。但难免会有一些不听话的知识点。所以请各位读者见谅。


基于xml的AOP编程

上一篇文章讲到了基于xml的IOC设置bean,篇幅较长,内容较复杂。但配置AOP不同,它简单了很多。

第一步:核心文件applicationContext.xml,

首先是配置三个bean,方便是两个切面类,和一个方法类。

然后配置AOP,

aop:config:注明开始配置AOP了,

aop:pointcut:配置切点重用表达式,expression的值是具体的表达式,id 该aop:pointcut的唯一标识,

aop:aspect:配置切面,ref的值引用相关切面类的bean,order设置优先级(也可以不设置)。

五种通知的配置:aop:before,aop:after,aop:after-returning,aop:after-throwing,aop:around。method的值就是对应的方法,poincut-ref的值要引用 aop:pointcut 的id。其中有两个比较特殊:aop:after-returning 要多配置一个returning,其中returning的值要和对应方法的形参保持一致。同理aop:after-throwing 也要多配置一个throwing,其中throwing的值也要和对应方法的形参保持一致。不然执行程序会报错。

<?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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <bean id="calculator" class="com.atguigu.spring.my.xml.CalculatorImp"></bean>
<bean id="loggerAspect" class="com.atguigu.spring.my.xml.LoggerAspect"></bean>
<bean id="aroundAspect" class="com.atguigu.spring.my.xml.AroundAspect"></bean> <!-- AOP配置 -->
<aop:config>
<!-- 配置切点表达式 类似注解的重用表达式-->
<aop:pointcut expression="execution(* com.atguigu.spring.my.xml.CalculatorImp.*(..))"
id="pointcut"/>
<!-- 配置切面及通知 method的值就是 loggerAspect类中的值-->
<aop:aspect ref="loggerAspect" order="2">
<aop:before method="beforeAdvice" pointcut-ref="pointcut"/>
<aop:after method="afterAdvice" pointcut-ref="pointcut"/>
<aop:after-returning method="afterRunningAdvice" pointcut-ref="pointcut" returning="result"/>
<aop:after-throwing method="afterThrowingAdvice" pointcut-ref="pointcut" throwing="exception"/>
</aop:aspect>
<aop:aspect ref="aroundAspect" order="1">
<!-- <aop:around method="aroundAdvice" pointcut-ref="pointcut"/> -->
</aop:aspect>
</aop:config> </beans>

第二步:下面几个类,就是脱去了所有注解的外衣,采用通过配置的xml,实现AOP编程。

public interface Calculator {  

    public int add(int a, int b);
public int division(int a, int b); }
public class CalculatorImp implements Calculator {  

    @Override
public int add(int a, int b) {
System.out.println("add 方法执行了 ----> " + (a + b));
return (a + b);
} @Override
public int division(int a, int b) {
System.out.println("division 方法执行了 ----> " + (a / b));
return (a / b);
} }
import java.util.Arrays;
import java.util.List;
import org.aspectj.lang.JoinPoint; public class LoggerAspect { public void beforeAdvice(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("Before 前置通知 : 方法名 【 " + methodName + " 】and args are " + args);
} public void afterAdvice(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("After 后置通知 : 方法名 【 " + methodName + " 】and args are " + args);
} public void afterRunningAdvice(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("AfterReturning 返回通知 : 方法名 【 " + methodName + " 】and args are " + args + " , result is " + result);
} public void afterThrowingAdvice(JoinPoint joinPoint, Exception exception) {
String methodName = joinPoint.getSignature().getName();
System.out.println("AfterThrowing 异常通知 : 方法名 【 " + methodName + " 】and exception is " + exception);
} }
import java.util.Arrays;
import java.util.List;
import org.aspectj.lang.ProceedingJoinPoint; public class AroundAspect { public Object aroundAdvice(ProceedingJoinPoint joinPoint) {
Object result = null;
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
try {
System.out.println("@Around 前置通知 : 方法名 【 " + methodName + " 】and args are " + args);
result = joinPoint.proceed();
System.out.println("@Around 返回通知 : 方法名 【 " + methodName + " 】and args are " + args + " , result is " + result);
} catch (Throwable e) {
e.printStackTrace();
System.out.println("@Around 异常通知 : 方法名 【 " + methodName + " 】and exception is " + e);
}
System.out.println("@Around 后置通知 : 方法名 【 " + methodName + " 】and args are " + args); return result;
} }
import org.springframework.context.support.ClassPathXmlApplicationContext;  

public class Main {  

    public static void main(String[] args) {  

        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Calculator calculator = (Calculator) ctx.getBean("calculator"); calculator.add(11, 12);
calculator.division(21, 3); // 测试时,将被除数换成0,可以测试AfterReturning ,After 和 AfterThrowing ctx.close();
} }
Before 前置通知 : 方法名 【 add 】and args are [11, 12]
add 方法执行了 ----> 23
After 后置通知 : 方法名 【 add 】and args are [11, 12]
AfterReturning 返回通知 : 方法名 【 add 】and args are [11, 12] , result is 23
Before 前置通知 : 方法名 【 division 】and args are [21, 3]
division 方法执行了 ----> 7
After 后置通知 : 方法名 【 division 】and args are [21, 3]
AfterReturning 返回通知 : 方法名 【 division 】and args are [21, 3] , result is 7

到这里,基于xml文件的AOP编程也讲完了。4不4很简单。

Spring4 AOP详解的更多相关文章

  1. spring4配置文件详解

    转自: spring4配置文件详解 一.配置数据源 基本的加载properties配置文件 <context:property-placeholder location="classp ...

  2. Spring4 JDBC详解

    Spring4 JDBC详解 在之前的Spring4 IOC详解 的文章中,并没有介绍使用外部属性的知识点.现在利用配置c3p0连接池的契机来一起学习.本章内容主要有两个部分:配置c3p0(重点)和 ...

  3. Spring4 IOC详解

    Spring4 IOC详解 上一章对Spring做一个快速入门的教程,其中只是简单的提到了IOC的特性.本章便对Spring的IOC进行一个详解.主要从三个方面开始:基于xml文件的Bean配置,基于 ...

  4. 【转载】Spring AOP详解 、 JDK动态代理、CGLib动态代理

    Spring AOP详解 . JDK动态代理.CGLib动态代理  原文地址:https://www.cnblogs.com/kukudelaomao/p/5897893.html AOP是Aspec ...

  5. [Spring学习笔记 5 ] Spring AOP 详解1

    知识点回顾:一.IOC容器---DI依赖注入:setter注入(属性注入)/构造子注入/字段注入(注解 )/接口注入 out Spring IOC容器的使用: A.完全使用XML文件来配置容器所要管理 ...

  6. AOP 详解

    1. 需求:统计方法执行的性能情况(来源:<精通Spring 4.x>) // 性能监视类 PerformanceMonitor package com.noodles.proxy; pu ...

  7. Spring AOP详解(转载)所需要的包

    上一篇文章中,<Spring Aop详解(转载)>里的代码都可以运行,只是包比较多,中间缺少了几个相应的包,根据报错,几经百度搜索,终于补全了所有包. 截图如下: 在主测试类里面,有人怀疑 ...

  8. Spring AOP详解及简单应用

    Spring AOP详解   一.前言 在以前的项目中,很少去关注spring aop的具体实现与理论,只是简单了解了一下什么是aop具体怎么用,看到了一篇博文写得还不错,就转载来学习一下,博文地址: ...

  9. 转:Spring AOP详解

    转:Spring AOP详解 一.前言 在以前的项目中,很少去关注spring aop的具体实现与理论,只是简单了解了一下什么是aop具体怎么用,看到了一篇博文写得还不错,就转载来学习一下,博文地址: ...

随机推荐

  1. CentOS 6.5 + Nginx 1.8.0 + PHP 5.6(with PHP-FPM) 负载均衡源码安装

    CentOS 6.5 + Nginx 1.8.0 + PHP 5.6(with PHP-FPM) 负载均衡源码安装 http://www.cnblogs.com/ppoo24/p/4918288.ht ...

  2. C++向量(08)

    在数组生存期内,数组的大小是不会改变的.向量是一维数组的类版本,它与数组相似,其中的元素项总是连续存储的,但它和数组不同的是:向量中存储元素的多少可以在运行中根据需要动态地增长或缩小.向量是类模板,具 ...

  3. LeetCode 259. 3Sum Smaller (三数之和较小值) $

    Given an array of n integers nums and a target, find the number of index triplets i, j, k with 0 < ...

  4. OpenCV4Android背景建模(MOG、MOG2)

    本文为作者原创,转载请注明出处(http://www.cnblogs.com/mar-q/)by 负赑屃     很久以前的笔记了,分享给大家吧...OpenCV4Android中用于背景建模的类主要 ...

  5. ajax+分页

    <!DOCTYPE html> <html> <head lang="zh-cn"> <meta charset="UTF-8& ...

  6. CSS选择器汇总

    id选择器 #id 类选择器 .class 标签选择器 div, h1, p 相邻选择器 h1 + p 子选择器 ul > li 后代选择器 li a 通配符选择器 * 属性选择器 a[rel= ...

  7. 在你的网站实现qq登陆(php)

    这个qq的oauth2.1有个坑,坑了我半天,后来查了不少资料总算弄通了,现在把详细步骤记录下来. 步骤一.登陆http://connect.qq.com/     步骤二.创建应用.我创建的是wen ...

  8. 关于NIM博弈结论的证明

    关于NIM博弈结论的证明 NIM博弈:有k(k>=1)堆数量不一定的物品(石子或豆粒…)两人轮流取,每次只能从一堆中取若干数量(小于等于这堆物品的数量)的物品,判定胜负的条件就是,最后一次取得人 ...

  9. 理解Java包

    本质上,包是一个唯一命名的类的集合,将类集合到包里面的主要原因,是为了当在应用程序中使用预先编写的类时避免与类自身可能引起的明明冲突.用于包中的类名不会妨碍另一个包或程序中的类名,因为此时,包中的类名 ...

  10. 【DG】利用闪回数据库(flashback)修复Failover后的DG环境

    利用闪回数据库(flashback)修复Failover后的DG环境 1.1  BLOG文档结构图 1.2  前言部分 1.2.1  导读和注意事项 各位技术爱好者,看完本文后,你可以掌握如下的技能, ...