一、什么是aop?
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是Spring框架中的一个重要内容,它通过对既有程序定义一个切入点,然后在其前后甚至异常处切入不同的执行内容,比如常见的有:打开数据库连接/关闭数据库连接、打开事务/关闭事务、记录日志等。基于AOP不会破坏原来程序逻辑,因此它可以很好的对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
二、aop中的术语
1.连接点(Joinpoint)
程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。一个类或一段程序代码拥有一些具有边界性质的特定点,这些点中的特定点就称为“连接点”。Spring仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入通知。连接点由两个信息确定:第一是用方法表示的程序执行点;第二是用相对点表示的方位。
2.切点(Pointcut)
每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点,即连接点是程序类中客观存在的事物。AOP通过“切点”定位特定的连接点。连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。在Spring中,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件,Spring AOP的规则解析引擎负责切点所设定的查询条件,找到对应的连接点。其实确切地说,不能称之为查询连接点,因为连接点是方法执行前、执行后等包括方位信息的具体程序执行点,而切点只定位到某个方法上,所以如果希望定位到具体连接点上,还需要提供方位信息。
3.通知(Advice)
通知是织入到目标类连接点上的一段程序代码,在Spring中,通知除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点方位信息和切点信息,我们就可以找到特定的连接点。
通知有以下几种:
前置通知(Before advice):在切入点匹配的方法执行之前运行。使用@Before注解来声明
返回后通知(After returning advice):在切入点匹配的方法返回的时候执行。使用@AfterReturning注解来声明
抛出后通知(After throwing advice):在切入点匹配的方法执行时抛出异常的时候运行。使用 @AfterThrowing 注解来声明
后置通知(After(finally)advice):不论切入点匹配的方法是正常结束的,还是抛出异常结束的,在它结束后(finally)后通知(After(finally)advic)都会运行。使用@After注解来声明。这个通知必须做好处理正常返回和异常返回两种情况。通常用来释放资源。
环绕通知(Around Advice):环绕通知既在切入点匹配的方法执行之前又在执行之后运行。并且,它可以决定这个方法在什么时候执行,如何执行,甚至是否执行。在环绕通知中,除了可以自由添加需要的横切功能以外,还需要负责主动调用连接点(通过 proceed)来执行激活连接点的程序。请尽量使用最简单的满足你需求的通知。(比如如果前置通知也可以适用的情况下,就不要使用环绕通知)。环绕通知使用 @Around 注解来声明。而且该通知对应的方法的第一个参数必须是 ProceedingJoinPoint 类型 。在通知体内(即通知的具体方法内),调用 ProceedingJoinPoint 的 proceed() 方法来执行连接点方法 。
4.目标对象(Target Object)
通知逻辑的织入目标类。如果没有AOP,目标业务类需要自己实现所有逻辑,而在AOP的帮助下,目标业务类只实现那些非横切逻辑的程序逻辑,而性能监视和事务管理等这些横切逻辑则可以使用AOP动态织入到特定的连接点上。
5.引入(Introduction)
引入是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。
6.织入(Weaving)
织入是将通知添加到目标类具体连接点上的过程。AOP像一台织布机,将目标类、通知或引入通过AOP这台织布机天衣无缝地编织到一起。根据不同的实现技术,AOP有三种织入的方式:
- 编译期织入,这要求使用特殊的Java编译器。
- 类装载期织入,这要求使用特殊的类装载器。
- 动态代理织入,在运行期为目标类添加增强生成子类的方式。
Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
7.代理(Proxy)
一个类被AOP织入通知后,就产出了一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类,所以我们可以采用调用原类相同的方式调用代理类。
8.切面(Aspect)
切面由切点和通知(引入)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。
三、实践
1.添加maven依赖
首先做的就是添加aop依赖,默认已经使用了spring-boot-web依赖,这边就不加了。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.基础配置
当我们增加了依赖之后,需要怎么使用呢?在spring中我们可以通过@EnableAspectJAutoProxy来启用,那么我们在spring boot中的启动类上是否需要添加该注解呢?答案是否定的。
在spring boot中可以想到是否能够在配置文件中配置某个属性来开启aop功能。
我们来看下在application.yml文件中如何配置。
从图上我们可以看到,在spring boot中默认给我们开启了aop功能,也就等于自动帮我们加上了@EnableAspectJAutoProxy。
spring aop默认采用Java的动态代理,如果想使用cglib则需要将上图第二个配置项修改为true。
3.cglib与Java动态代理区别
cglib封装了asm,可以在运行期动态生成新的class。
cglib用于AOP,jdk中的proxy必须基于接口,cglib却没有这个限制。
原理区别:
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
4.方法使用简介
execution:使用“execution(方法表达式)”匹配方法执行;
within:使用“within(类型表达式)”匹配指定类型内的方法执行;
this:使用“this(类型全限定名)”匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口方法也可以匹配;注意this中使用的表达式必须是类型全限定名,不支持通配符;
target:使用“target(类型全限定名)”匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;注意target中使用的表达式必须是类型全限定名,不支持通配符;
args:使用“args(参数类型列表)”匹配当前执行的方法传入的参数为指定类型的执行方法;注意是匹配传入的参数类型,不是匹配方法签名的参数类型;参数类型列表中的参数必须是类型全限定名,通配符不支持;args属于动态切入点,这种切入点开销非常大,非特殊情况最好不要使用;
@within:使用“@within(注解类型)”匹配所以持有指定注解类型内的方法;注解类型也必须是全限定类型名;
@target:使用“@target(注解类型)”匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;注解类型也必须是全限定类型名;
@args:使用“@args(注解列表)”匹配当前执行的方法传入的参数持有指定注解的执行;注解类型也必须是全限定类型名;
@annotation:使用“@annotation(注解类型)”匹配当前执行方法持有指定注解的方法;注解类型也必须是全限定类型名;
bean:使用“bean(Bean id或名字通配符)”匹配特定名称的Bean对象的执行方法;Spring ASP扩展的,在AspectJ中无相应概念;
reference pointcut:表示引用其他命名切入点,只有@ApectJ风格支持,Schema风格不支持,如下所示:
5.编写切面类
下面我们来实现一个aop的小例子,让我们直接上代码来看一下:
@Aspect
@Component
public class AopPractice {
private static final Logger logger = LoggerFactory.getLogger(AopPractice.class);
/**
* 基本语法格式为:
* execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
* 除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。
* 第一个*代表任意修饰符,如:public
* 第二个*代表任意返回类型,如:void
* 第一个..代表restController包以及子包
* 第三个*代表任意类
* 第四个*代表任意方法
* (..)代表任意入参
* *:匹配任何数量字符;
* ..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
* +:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。如java.lang.Number+ 匹配java.lang包下的任何Number的自类型;
如匹配java.lang.Integer,也匹配java.math.BigInteger
*/
@Pointcut("execution(public * com.god.restController..*.*(..))")
public void webLog(){}
@Pointcut("execution(public String com.god.restController.UserController.helloWorld())")
public void webLog2(){}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) {
logger.info("进入doBefore方法");
logger.info(LocalDateTime.now().toString());
}
@AfterReturning("webLog()")
public void afterReturning() {
// logger.info("返回结果:" + o.toString());
logger.info(Thread.currentThread().getName() + " afterReturning");
logger.info(LocalDateTime.now().toString());
}
@AfterReturning(value = "webLog2()", argNames = "value", returning = "value")
public void afterReturning2(String value) {
logger.info(Thread.currentThread().getName() + " afterReturning2");
logger.info(LocalDateTime.now().toString());
}
}
上面定义了一个切面类,并交由spring容器管理,然后定义了两个不同的切点,在第一个切点上标注了基础语法,接着定义了一个前置通知,两个后置通知。让我们运行一下看一下效果:
2018-01-05 09:54:00.047 INFO 13856 --- [nio-8080-exec-1] c.g.a.AopPractice : 进入doBefore方法
2018-01-05 09:54:00.055 INFO 13856 --- [nio-8080-exec-1] c.g.a.AopPractice : 2018-01-05T09:54:00.055
2018-01-05 09:54:00.074 INFO 13856 --- [nio-8080-exec-1] c.g.a.AopPractice : http-nio-8080-exec-1 afterReturning
2018-01-05 09:54:00.074 INFO 13856 --- [nio-8080-exec-1] c.g.a.AopPractice : 2018-01-05T09:54:00.074
2018-01-05 09:54:00.076 INFO 13856 --- [nio-8080-exec-1] c.g.a.AopPractice : http-nio-8080-exec-1 afterReturning2
2018-01-05 09:54:00.076 INFO 13856 --- [nio-8080-exec-1] c.g.a.AopPractice : 2018-01-05T09:54:00.076
从日志输出可以看到aop为单线程执行,在这里我们没法通过@Order注解去控制两个切点或者两个通知的先后顺序,但是能控制两个切面的先后执行顺序。
结尾处推荐一篇文章:里面有详细讲到aop注解的一些用法和匹配表达式的一些用法。http://blog.youkuaiyun.com/zhengchao1991/article/details/53391244