1.AOP的概念:
在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想(范式)就是面向切面的编程。
一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。
这里我们重点区分的一下两个流行的框架:Spring AOP和AspectJ。
两者最大区别在于—Spring AOP的运行时增强,而AspectJ是编译时增强。曾经以为AspectJ是Spring AOP一部分,是因为Spring AOP使用了AspectJ的Annotation。
Spring默认不支持@Aspect风格的切面声明,通过如下配置开启@Aspect支持:
<aop:aspectj-autoproxy/>
下面我们举的例子的AOP都是AspectJ风格的aop。
术语
切面 | 切点和通知的集合,通知和切点共同定义了切面的功能 |
---|---|
通知(advice) | 描述了切面所要完成的工作以及何时需要执行该工作。 |
连接点 | 描述了切面所要完成的工作以及何处需要执行该工作。 |
切点 | 定义通知被应用的位置(在哪些连接点)。 |
织入 | 将切面应用到目标对象来创建的代理对象过程。 |
spring aop通知(advice)分成五类:
- 前置通知Before advice:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。
- 正常返回通知After returning advice:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。
- 异常返回通知After throwing advice:在连接点抛出异常后执行。
- 后置通知After (finally) advice:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。
- 环绕通知Around advice:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。
五种通知的执行顺序
-
在方法执行正常时:
- 环绕通知
@Around
- 前置通知
@Before
- 方法执行
- 环绕通知
@Around
- 后置通知
@After
- 正常返回通知
@AfterReturning
- 环绕通知
-
在方法执行抛出异常时:
这个时候顺序会根据环绕通知的不同而发生变化:
- 环绕通知捕获异常并不抛出:
- 环绕通知
@Around
- 前置通知
@Before
- 方法执行
- 环绕通知
@Around
- 后置通知
@After
- 正常返回通知
@AfterReturning
- 环绕通知
- 环绕通知捕获异常并抛出:
- 环绕通知
@Around
- 前置通知
@Before
- 方法执行
- 环绕通知
@Around
- 后置通知
@After
- 异常返回通知
@AfterThrowing
- 环绕通知
- 环绕通知捕获异常并不抛出:
2.简单举例:
1.导入jar包。aspectj-weaver…
2.在xml中加入命名空间 xmlns:aop=”http://www.springframework.org/schema/aop”
3.基于注解的方式
① 在配置文件中加入
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
在本例中,就是为CalculateImpl生成代理
② 把横切关注点的代码抽象到切面类中
Ⅰ. 切面是IOC容器中的Bean,即加入@Component注解
Ⅱ. 加入@Aspect注解
③ 在类中声明各种通知
Ⅰ. 声明一个方法
Ⅱ . 在方法前加入@ Before,After,AfterReturning,AfterThrowing,Around 注解
Ⅲ. 用AspectJ表达式作为注解值
④. 如果访问方法的细节(如函数名,参数等),在通知方法中加入类型为JoinPoint的参数
我们可以把切面看成是我们抽象出来的一个类(比如日志类),这个类包含的方法就是我们要插入到业务逻辑中的代码段;切点本身是一个空的方法体,要切入的位置信息用@Pointcut标识出来,见下例。
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.spring.aop.impl"></context:component-scan>
<!--自动为匹配的类生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
package com.spring.aop.impl;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* Created by kaixin on 2018/8/23.
*/
@Component
@Aspect
public class LogAspect {
@Pointcut( "execution(* com.spring.aop.impl..*.*(int ,int ) )")
public void init(){
}
@Before( value = "init()")
public void beforeMethod(JoinPoint joinPoint) {
String methodname=joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("计算方法"+methodname+ " 计算参数 "+args );
}
@After( value = "init()")
public void after() {
System.out.println("计算后执行...");
}
}
public interface Calculate {
int add(int i, int j);
int sub(int i, int j);
int multiply(int i, int j);
int divide(int i, int j);
}
import org.springframework.stereotype.Component;
/**
* Created by kaixin on 2018/8/23.
*/
@Component
public class CalculateImpl implements Calculate {
@Override
public int add(int i, int j) {
return i+j;
}
@Override
public int sub(int i, int j) {
return i-j;
}
@Override
public int multiply(int i, int j) {
return i*j;
}
@Override
public int divide(int i, int j) {
return i/j;
}
}
public class Main {
public static void main(String[] args) {
ApplicationContext ac =new ClassPathXmlApplicationContext("application-aop.xml");
Calculate ccl = (Calculate) ac.getBean("calculateImpl");
System.out.println(ccl.divide(1,4));
}
}
上述代码定义了一个名为“LogAspect”的切点,它在@pointcut的注释中用切片表达式标识了切点的位置:
execution() | 表达式的主体; |
---|---|
第一个”*“符号 | 表示返回值的类型任意; |
com.spring.aop.impl | AOP所切的服务的包名 |
包名后面的”..“ | 表示当前包及子包 |
第二个”*“ | 表示类名,*即所有类。此处可以自定义 |
*(..) | 表示带任意参数的任意函数名的方法 |
如果含有多个切面,比如再添加一个关于验证的切面,可以用@Order(1)括号里的为数字,数字越小,优先级越高。
3.Execution表达式
用来找出合适插入代码片段的连接点;Spring只支持方法级别的连接点。Spring缺少对字段连接点的支持,无法让我们创建细粒度的通知,例如拦截对象字段的修改。所以切点表达式作用的最小力度也就是方法了。
execution( // ? 表示可以省略
modifiers-pattern? //方法的修饰符
ret-type-pattern //方法的返回值
declaring-type-pattern? //方法所在的类的路径
name-pattern(param-pattern) //方法名和参数
throws-pattern? //抛出的异常
)
可以使用“”和“..”通配符,其中“”表示任意类型的参数,而“..”表示任意类型参数且参数个数不限。
在切点中引用Bean:
@Pointcut( excecution(* com.company.service.WebService.updateWeb(..)) and bean('BaiduWeb') )
这表示将切面应用于WebService的updateWeb方法上,但是仅限于ID为”BaiduWeb”的Bean。
若想排除ID为”BaiduWeb”的Bean,只需在and 前加上 ’ ! ’ 。
其它切点描述符:
within
//匹配WebService类下的所有方法
@Pointcut("within(com.company.service.WebService)")
public void match(){ }
//匹配Service包及其子包下的所有类的方法
@Pointcut("within(com.company.service..*)")
public void matchPackage(){ }
this 和 target
Spring AOP是一个基于代理的系统,它区分代理对象本身(绑定到’this’)和代理后面的目标对象(绑定到’target’)。
//匹配DemoDAO接口的AOP代理对象方法
@Pointcut("this(com.company.DemoDao)")
//匹配DemoDAO接口的AOP目标对象方法
@Pointcut("this(com.company.DemoDao) ")
args参数匹配
//匹配任何只有一个Long参数的方法
@Pointcut("within(com.company..*) && args(Long)")
//匹配第一个参数为Long型的方法
@Pointcut("within(com.company..*) && args(Long,..)")
4.基于xml的方式配置AOP
<!-- 配置AOP -->
<aop:config>
<!-- 配置切点表达式 -->
<aop:pointcut id="pointcutExpression"
expression="execution(* com.sqp.spring.aop.dao.MyCalculator.*(..))"/>
<!-- 配置loggingAspect切面 -->
<aop:aspect id="loggingAspect" ref="loggingAspectXml" order="1">
<!-- 前置通知 -->
<aop:before method="beforeMethod" pointcut-ref="pointcutExpression"/>
<!-- 返回通知 -->
<aop:after-returning method="afterReturningMethod"
pointcut-ref="pointcutExpression" returning="result"/>
<!-- 后置通知 -->
<aop:after method="afterMethod" pointcut-ref="pointcutExpression"/>
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowingMethod"
pointcut-ref="pointcutExpression" throwing="ex"/>
<!-- 环绕通知 -->
<aop:around method="aroundMethod" pointcut-ref="pointcutExpression"/>
</aop:aspect>
</aop:config>