1 AOP概念和相关术语
Aspect Oriented Programming(AOP):面向切面编程,是面向对象编程(OOP)的一种补充,典型的就是Spring的事务管理。将比如,日志的记录,权限的校验,异常的处理这样非核心功能单独抽离出来,和核心功能解耦,横切在业务代码之上。
相关术语
Aspect
切面,在spring的aop里面,切面通常是一个类,对横切关注点的抽象
Join point
连接点,就是被拦截的点,简单来说,就是声明在什么时候,因为spring只支持方法的拦截,所以在spring中连接点可以理解为被拦截的方法
Pointcut
切入点,也可以叫切点,一般配合切点表达式定位到哪些方法,哪些类,什么地方,是对连接点拦截的定义,可以拦截一个连接点,也可以拦截多个连接点
Advice
通知,就是和Join point一起要做的事情,简单来说就是做什么事,就是拦截到连接点后,需要在这个连接点做什么事情
Weaving
织入,就是将切面中的通知应用到目标对象(Target Object)上,生成代理对象(AOP Proxy)的过程
2 基于XML配置
使用XML配置文件使用aop,需要加入一下xml约束
1 2 3
| <beans xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
|
注意:需要和之前的spring配置文件合在一起,需要bean的约束文件。
配置好以上的文件后,就可以在spring配置文件中使用aop了,所有的aop配置,都是在aop:config
标签下,首先需要把代理目标类和增强类在ioc中注册,然后在aop中配置bean。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?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" 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.xsd">
<bean id="coreService" class="com.moon.point.CoreService"/> <bean id="aspect" class="com.moon.aspect.MyAspect"/> <aop:config> <aop:pointcut id="point" expression="execution(public * com.moon..*.*(..))"/> <aop:aspect ref="aspect"> <aop:before pointcut-ref="point" method="logExecuteTime"/> </aop:aspect> </aop:config> </beans>
|
3 切点表达式
切点表达式,就是去匹配需要代理的类的方法,可以匹配多个或者单个方法,切点表达式书写在aop切点配置中
1 2
| <aop:pointcut id="point" expression="execution(public * com.moon..*.*(..))"/>
|
正常方法签名对应的切点表达式
方法签名 |
public void e() |
切点表达式 |
public void com.moon.point.CoreService.e(..) |
涉及到的通配符
常用的表达式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public * com.moon.point.CoreService.*(..)
* com.moon..*.*(..)
* com.moon..*.e(..)
|
4 连接点
连接点指的就是根据切点拦截到的具体方法,因为spring的aop是方法级别,所以这里的连接点全部指的是具体的方法。
如果我们想要获取到连接点的话,我们需要导入另外一个依赖-AspectJ
4.1 AspectJ
aspectJ全名是Eclipse AspectJ,它来自于Eclipse基金会,是一个完整的aop方案框架,它可以拦截到的连接点就不是单纯的方法级别了,它全部都可以拦截,他和spring的区别是:
spring是方法级别,aspectJ是全级别
spring是动态织入,aspectJ是静态织入
Spring使用JDK动态代理和Cglib动态代理,通过运行时在内存中生成零时的类文件来实现。
AspectJ则是通过它自己的编译器进行编译,在编译时直接修改了编的二进制class文件
spring性能慢,AspectJ性能快
导入AspectJ依赖
Maven
1 2 3 4 5
| <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency>
|
Gradle
1
| implementation group: 'org.aspectj', name: 'aspectjweaver', version: '1.9.6'
|
4.2 JoinPoint
依赖导好后,我们在自己的书写方法中就可以拿到连接点,JoinPoint连接点是所有切面方法都可以拿到的,在里面封装的我们方法的信息。
拿到连接点:
1
| public void before(JoinPoint joinPoint){...}
|
操作连接点:
1 2 3 4 5 6
| public void before(JoinPoint joinPoint){ System.out.println("目标对象为:"+joinPoint.getTarget()); System.out.println("目标方法签名为:"+joinPoint.getSignature()); Object[] args = joinPoint.getArgs(); System.out.println("目标方法参数个数为:"+args.length); }
|
连接点方法:
getSignature() |
获得目标对象的签名,就是当前拦截到的方法 |
getTarget() |
获得目标对象 |
getThis() |
获得正在执行的目标对象 |
getStaticPart() |
获得此连接点的静态部分 |
getSourceLocation() |
获得连接点对应的方法源地址,这里面的还包含的方法是使用不了的,Spring在重写时,直接抛出了不支持操作异常(UnsupportedOperationException) |
toLongString() |
获得连接点的扩展全命名 |
toShortString() |
获得连接点的缩写命名 |
getKind() |
返回该连接点的类型 |
getArgs() |
返回连接点的参数数组 |
4.3 ProceedingJoinPoint
该连接点是上面连接点的加强版,但是只有环绕通知才可以获取到,它主要是可以操作连接点方法的运行。
获得ProceedingJoinPoint
1
| public void before(ProceedingJoinPoint joinPoint){...}
|
操作连接点方法:proceed()
这也是这个连接点的核心,需要手动操作拦截的连接点方法,默认不执行
1 2 3 4 5 6 7 8 9 10
| public void before(ProceedingJoinPoint joinPoint){ try { Object proceed = joinPoint.proceed(); } catch (Throwable throwable) {
throw throwable; } }
|
5 通知的类型和使用
5.1 Before
前置通知,该通知是在连接点之前实现的,增强了连接点的前置方法,在前置方法中,我们可以拿到这个连接点,但是不可以对原来连接点方法的执行进行组织。
书写一个切面类,里面书写一个前置方法,在这个方法中我们拿到连接点
1 2 3 4 5 6
| public class MyAspect { public void before(JoinPoint joinPoint) throws Throwable { System.out.println("前置方法执行了,准备执行"+joinPoint.getSignature()); } }
|
配置配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?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" 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.xsd">
<bean id="Man" class="com.moon.point.Man"/> <bean id="aspect" class="com.moon.aspect.MyAspect"/> <aop:config>
<aop:pointcut id="point" expression="execution(public * com.moon..Man.*(..))"/>
<aop:aspect ref="aspect"> <aop:before method="before" pointcut-ref="point" /> </aop:aspect> </aop:config> </beans>
|
结果:

5.2 After Advice
after后置方法是分为三类,主要是针对方法执行后三种状态,分别可以获得结果,捕获异常,finally执行。
在AfterAdvice中,同样也可以获得连接点JoinPoint
5.2.1 After-finally
我们使用的afterAdvice其实就是After-finally,是放在finally块中,不管方法执行结果是什么,都会执行。
在After-finally中,同样也可以获得连接点JoinPoint
切面:
1 2 3 4 5 6 7 8 9 10 11
| public class MyAspect { public void before(JoinPoint joinPoint) throws Throwable { System.out.println("前置方法执行了,准备执行"+joinPoint.getSignature()); }
public void after(JoinPoint joinPoint) throws Throwable { System.out.println("后置方法执行了,执行完了"+joinPoint.getSignature()); } }
|
XML配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <?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" 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.xsd">
<bean id="Man" class="com.moon.point.Man"/> <bean id="aspect" class="com.moon.aspect.MyAspect"/> <aop:config>
<aop:pointcut id="point" expression="execution(public * com.moon..Man.*(..))"/>
<aop:aspect ref="aspect"> <aop:before method="before" pointcut-ref="point" /> <aop:after method="after" pointcut-ref="point" /> </aop:aspect> </aop:config> </beans>
|
结果:

5.2.2 After-returning
AfterReturning是放在连接点执行后面,可以获得连接点执行完返回的值,同样也可以拿到连接点JoinPoint
这里我们直接拿返回的值,就不拿连接点
切面:
1 2 3 4 5 6 7 8 9 10 11 12
| public class MyAspect { public void before(JoinPoint joinPoint) throws Throwable { System.out.println("前置方法执行了,准备执行"+joinPoint.getSignature()); }
public void afterReturning(Object result){ System.out.println("后置返回方法执行"); System.out.println("返回值为:"+result); } }
|
XML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <?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" 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.xsd">
<bean id="Man" class="com.moon.point.Man"/> <bean id="aspect" class="com.moon.aspect.MyAspect"/> <aop:config>
<aop:pointcut id="point" expression="execution(public * com.moon..Man.*(..))"/>
<aop:aspect ref="aspect"> <aop:before method="before" pointcut-ref="point" /> <aop:after-returning method="afterReturning" pointcut-ref="point" returning="result"/> </aop:aspect> </aop:config> </beans>
|
结果:
这里的eat方法没有设置返回值,所以返回值为null

5.2.3 After-throwing
后置异常通知,顾名思义,就是在连接点方法发生异常的时候,才会执行,其实这个通知是放在catch块里面的,用来捕获异常。
注意:
- 异常通知和返回结果通知是互斥的,俩者只有一个会被执行,如果出现异常就拿不到返回值,拿到返回值就说明没有异常。
- 正常的后置通知,是放在finally块中,所以一定会被执行。
切面:
1 2 3 4 5 6 7 8 9 10 11 12
| public class MyAspect { public void before(JoinPoint joinPoint) throws Throwable { System.out.println("前置方法执行了,准备执行"+joinPoint.getSignature()); }
public void afterThrowing(Exception e){ System.out.println("后置异常方法执行"); System.out.println("异常信息为:"+e.getMessage()); } }
|
XML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <?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" 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.xsd">
<bean id="Man" class="com.moon.point.Man"/> <bean id="aspect" class="com.moon.aspect.MyAspect"/> <aop:config>
<aop:pointcut id="point" expression="execution(public * com.moon..Man.*(..))"/>
<aop:aspect ref="aspect"> <aop:before method="before" pointcut-ref="point" />
<aop:after-throwing method="afterThrowing" pointcut-ref="point" throwing="e"/> </aop:aspect> </aop:config> </beans>
|
这里对man类,代理的目标类进行修改,抛一个异常
1 2 3 4 5 6 7
| public class Man implements Person{ @Override public void eat() { System.out.println("吃饭"); throw new RuntimeException("它没异常,我制造异常,哎,就是玩"); } }
|
结果:

5.3 Around Advice
AroundAdvice,环绕通知,该通知的特点是可以自定义代理,很类似一个代理类,把连接点方法直接给我们,我们可以选择执行或者不执行,默认是不执行的,这里我们拿的连接点就需要是ProceedingJoinPoint,该连接点仅支持环绕通知。
切面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class MyAspect { public Object around(ProceedingJoinPoint joinPoint)throws Throwable{ try { System.out.println("前置通知"); Object proceed = joinPoint.proceed(); System.out.println("返回值是"+proceed+",返回值后置通知"); return proceed; } catch (Throwable throwable) { System.out.println("异常通知:异常信息为:"+throwable.getMessage()); throw throwable; } finally { System.out.println("后置通知"); System.out.println(""); } } }
|
XML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?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" 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.xsd">
<bean id="Man" class="com.moon.point.Man"/> <bean id="aspect" class="com.moon.aspect.MyAspect"/> <aop:config>
<aop:pointcut id="point" expression="execution(public * com.moon..Man.*(..))"/>
<aop:aspect ref="aspect"> <aop:around method="around" pointcut-ref="point"/> </aop:aspect> </aop:config> </beans>
|
结果:

5.4 执行顺序

6 基于注解的AOP配置
6.1 XML配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="Man" class="com.moon.point.Man"/>
<context:component-scan base-package="com.moon"/> <aop:aspectj-autoproxy/> </beans>
|
6.2 前后置通知
使用java类来配置一个切面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| @Component
@Aspect public class AnnotationAspect {
@Pointcut("execution(* com.moon.point.Man.*(..))") private void pointCut(){}
@Before("pointCut()") public void beforeAdvice(){ System.out.println("This is beforeAdvice!"); }
@After("pointCut()") public void afterAdvice(){ System.out.println("This is afterAdvice!"); }
@AfterReturning(value = "pointCut()",returning = "result") public void afterReturning(Object result){ System.out.println("This is afterReturning! Result is "+result); }
@AfterThrowing(value = "pointCut()",throwing = "e") public void afterThrowing(Exception e){ System.out.println("This is afterReturning! Exception is "+e.getMessage()); } }
|
结果:

6.3 环绕通知
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| @Component
@Aspect public class AnnotationAspect {
@Pointcut("execution(* com.moon.point.Man.*(..))") private void pointCut(){}
@Around("pointCut()") @Around("pointCut()") public Object aroundAdvice(ProceedingJoinPoint point) throws Throwable { try { System.out.println("This is around-beforeAdvice"); Object proceed = point.proceed(); System.out.println("This is around-afterReturningAdvice! result is " + proceed); return proceed; } catch (Throwable e) { System.out.println("This is around-afterThrowingAdvice! Exception Message is " + e.getMessage()); throw e; } finally { System.out.println("This is around-afterFinallyAdvice!"); } } }
|
结果:
