第一章Spring 快速入门并无对Spring4 的 AOP 作太多的描述,是由于AOP切面编程概念很差理解。因此这章主要从三个方面详解AOP:AOP简介(了解),基于注解的AOP编程(重点)和基于xml的AOP编程。java
AOP(Aspect Oriented Programming)面向切面编程,是对传统的OOP(ObjectOriented Programming)面向对象编程的补充。spring
若是A,B,C三个方法都要在执行前作验证操做,执行后作日志打印操做。肿么办?express
排版好丑。。。。。。编程
切面(Aspect): A,B,C,方法执行前都要调用的验证逻辑和执行后都要调用的日志逻辑,这两层业务逻辑就是切面。
通知(Advice): 有五种通知,执行前,执行后,执行成功后,执行抛出异常后,环绕通知。就是切面执行的方法。
目标(Target): 被通知的对象,这里就是A,B,C三个方法。
链接点(Joinpoint):链接点是一个应用执行过程当中可以插入一个切面的点。
切点(pointcut):每一个类都拥有多个链接点,即链接点是程序类中客观存在的事务。AOP 经过切点定位到特定的链接点
打个比方:一天,三位侠客(被通知的对象Target)来我府上作客,被大门(切面Aspect)拦住,门前有五个保安(负责通知的Advice),由于其中一位侠客会降龙十八掌(知足被通知的一个条件Joinpoint),其中一位保安告知他:"你能够进去了"。另外两个侠客由于武艺超群(知足被通知的统一标准poincut)也都进去了。segmentfault
基于注解的编程,须要依赖AspectJ框架(java中最流行的aop框架)。
第一步:导入AspectJ的jar包,该框架只有Spring 2.0以上才支持。app
<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,以及抽象表达"*"ide
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); } }
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的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很简单。