AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。其与设计模式完成的任务差不多,是提供另一种角度来思考程序的结构,来弥补面向对象编程的不足,这个可能是面试中经常提到的问题,同时它也是Spring框架中一个重大的特性,对于我们开发中最常见的可能就是日志记录,事务处理,异常处理等等。。。
本文将给大家介绍如何在SpringBoot中使用以及AOP的相关知识点
一、AOP知识点
1、原理
AOP是通过动态代理实现的,动态代理又分为两个部分:JDK动态代理和CGLIB动态代理,AOP功能的使用还是比较简单的,把相关bean注入到Spring容器中,编写好相应的Aspect类即可,以下两点需要记住:
1、AOP基于
动态代理模式
2、AOP是方法级别
的
对代理模式不熟悉的可以参考我的这篇文章:JAVA代理模式
2、AOP术语
- 切面(Aspect)
一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式或者基于
@Aspect
注解的方式来实现
- 连接点(JoinPoint)
在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在SpringAOP中,一个连接点总是表示一个方法的执行
- 通知(Advice)
在切面的某个特定的连接点上执行的动作。其中包括了
Around
、Before
和After
等不同类型的通知。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链
- 切入点(PointCut)
匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时),切入点表达式如何和连接点匹配是AOP的核心:Spring
缺省使用AspectJ切入点语法
- 引入(Intorduction)
用来给一个类型声明额外的方法或属性(也被称为连接类型声明)。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用引入来使一个bean实现接口,以便简化缓存机制
- 目标对象(Target Object)
被一个或者多个切面所通知的对象。也被称做被通知对象。既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理对象
- AOP代理(Aop proxy)
AOP框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理
- 植入(weaving)
把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入
二、常用切入点指示符
1、execution表达式
用于匹配方法执行的连接点,属于方法级别
语法:execution(修饰符 返回值类型 方法名(参数)异常)
语法参数 | 描述 |
---|---|
修饰符 | 可选,如public,protected,写在返回值前,任意修饰符填* 号就可以 |
返回值类型 |
必选 ,可以使用* 来代表任意返回值 |
方法名 |
必选 ,可以用* 来代表任意方法 |
参数 |
() 代表是没有参数,(..) 代表是匹配任意数量,任意类型的参数,当然也可以指定类型的参数进行匹配,如要接受一个String类型的参数,则(java.lang.String) , 任意数量的String类型参数:(java.lang.String..) 等等。。。 |
异常 | 可选,语法:throws 异常 ,异常是完整带包名,可以是多个,用逗号分隔 |
符号:
符号 | 描述 |
---|---|
* | 匹配任意字符 |
.. | 匹配多个包或者多个参数 |
+ | 表示类及其子类 |
条件符:
符号 | 描述 |
---|---|
&&、and | 与 |
|| | 或 |
! | 非 |
案例:
-
拦截com.gj.web包下的所有子包里的任意类的任意方法
execution(* com.gj.web..*.*(..))
-
拦截com.gj.web.api.Test2Controller下的任意方法
execution(* com.gj.web.api.Test2Controller.*(..))
-
拦截任何修饰符为public的方法
execution(public * * (..))
-
拦截com.gj.web下的所有子包里的以ok开头的方法
execution(* com.gj.web..*.ok*(..))
更多用法大家可以根据语法自行设计,本文不在进行举例
2、@annotation
根据所应用的注解对方法进行过滤
语法:@annotation(注解全路径)
实例:
对用了com.gj.annotations.Test注解的所有方法进行拦截@annotation(com.gj.annotations.Test)
3、Within
根据类型(比如接口、类名或者包名过滤方法)进行拦截
语法:within(typeName)
示例:
-
对com.gj.web下的所有子包的所有方法进行拦截
within(com.gj.web..*)
更多用法可以根据语法自行设计
4、@Within
用于匹配所有持有指定注解类型内的方法,与within是有区别的,within是用于匹配指定类型内的方法执行,而@within是指定注解类型内的方法
5、bean
Spring AOP扩展的,AspectJ没有对于它的指示符,用于匹配特定名称的bean对象的执行方法
语法:bean(beanName)
示例:bean(testController)
三、AOP通知
在切面类中需要定义切面方法用于响应响应的目标方法,切面方法即为通知方法,通知方法需要用注解标识,AspectJ支持5种类型的通知注解
注解 | 描述 |
---|---|
@Before | 前置通知, 在方法执行之前执行 |
@After | 后置通知, 在方法执行之后执行 |
@AfterReturn | 返回通知, 在方法返回结果之后执行 |
@AfterThrowing | 异常通知, 在方法抛出异常之后 |
@Around | 环绕通知,围绕方法的执行 |
1、@Before
示例:
@Before("testCut()")
public void cutProcess(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("注解方式AOP开始拦截, 当前拦截的方法名: " + method.getName());
}
2、@After
示例:
@After("testCut()")
public void after(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("注解方式AOP执行的方法 :"+method.getName()+" 执行完了");
}
3、@AfterReturn
其中value
表示切点方法,returning
表示返回的结果放到result这个变量中
示例:
/**
* returning属性指定连接点方法返回的结果放置在result变量中
* @param joinPoint 连接点
* @param result 返回结果
*/
@AfterReturning(value = "testCut()",returning = "result")
public void afterReturn(JoinPoint joinPoint, Object result) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("注解方式AOP拦截的方法执行成功, 进入返回通知拦截, 方法名为: "+method.getName()+", 返回结果为: "+result.toString());
}
4、@AfterThrowing
其中value
表示切点方法,throwing
表示异常放到e这个变量
示例:
@AfterThrowing(value = "testCut()", throwing = "e")
public void afterThrow(JoinPoint joinPoint, Exception e) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("注解方式AOP进入方法异常拦截, 方法名为: " + method.getName() + ", 异常信息为: " + e.getMessage());
}
5、Around
示例:
@Around("testCut()")
public Object testCutAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("注解方式AOP拦截开始进入环绕通知.......");
Object proceed = joinPoint.proceed();
System.out.println("准备退出环绕......");
return proceed;
}
四、SpringBoot中使用
1、导入依赖
common和swagger是附加的,读者不用必须的哈
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.gjing</groupId>
<artifactId>tools-common</artifactId>
<version>1.0.4</version>
</dependency>
<dependency>
<groupId>cn.gjing</groupId>
<artifactId>tools-starter-swagger</artifactId>
<version>1.0.9</version>
</dependency>
2、创建注解
注解用于进行AOP拦截
/**
* @author Gjing
**/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}
2、创建一个接口
/**
* @author Gjing
**/
@RestController
@RequestMapping("test1")
public class TestController {
@GetMapping("/ok")
@ApiOperation(value = "测试1", httpMethod = "GET")
@ApiImplicitParam(name = "id", value = "id值", dataType = "int", paramType = "query")
@Test
@NotNull
public String test2(Integer id) {
return "ok";
}
}
3、创建切面类
/**
* @author Gjing
**/
@Aspect
@Component
public class TestAspect {
@Pointcut("@annotation(com.gj.annotations.Test)")
public void testCut() {
}
@Before("testCut()")
public void cutProcess(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("注解方式AOP开始拦截, 当前拦截的方法名: " + method.getName());
}
@After("testCut()")
public void after(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("注解方式AOP执行的方法 :"+method.getName()+" 执行完了");
}
@Around("testCut()")
public Object testCutAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("注解方式AOP拦截开始进入环绕通知.......");
Object proceed = joinPoint.proceed();
System.out.println("准备退出环绕......");
return proceed;
}
/**
* returning属性指定连接点方法返回的结果放置在result变量中
* @param joinPoint 连接点
* @param result 返回结果
*/
@AfterReturning(value = "testCut()",returning = "result")
public void afterReturn(JoinPoint joinPoint, Object result) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("注解方式AOP拦截的方法执行成功, 进入返回通知拦截, 方法名为: "+method.getName()+", 返回结果为: "+result.toString());
}
@AfterThrowing(value = "testCut()", throwing = "e")
public void afterThrow(JoinPoint joinPoint, Exception e) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
System.out.println("注解方式AOP进入方法异常拦截, 方法名为: " + method.getName() + ", 异常信息为: " + e.getMessage());
}
}
4、启动运行
-
正常情况:
-
异常情况:
本文到此就结束了,篇幅过长,如果有哪里有些错的地方欢迎各位评论区留言,项目Demo源码地址:SpringBoot-Demo