因为要写一个需求,就是统计接口的访问次数,还希望这些接口的访问时间,以及是否访问成功。
所有我简单得的设计了一下,不想引入更多的技术,所以我选择打印日志的方式(主流的方式是使用ELK,这里不展开了),并且把日志打印到文件,用于分析。
在网上看了蛮多文章,感觉都很乱,所以自己整理一下。我不会大幅度的介绍AOP是什么,以及里边的概念有什么,一切以栗子,需求为主导来展开。最后给一个完整的例子,前边我会分析,什么时候用到了AOP中的哪些概念。
想要在springboot中使用AOP,在POM中引入依赖
org.springframework.boot
spring-boot-starter-aop
明白我想对什么进行处理,或者说增强
举个栗子,我想对controller进行拦击。因为我想知道这个接口被调用了多少次。
所以现在明确了,我关注的点(切入点)应该是 controller包下的所有的方法。
如何告诉框架,我关注的是这些方法呢?
定义一个AOP的类如下,里边的@Pointcut("within(com.angus.controller..*)"),就是说,我要切controller包中所有的方法
/**
* @author Gjing
**/
@Aspect
@Component
public class TestAspect {
@Pointcut("within(com.angus.controller..*)")
public void testCut() {
}
}
除了上边的栗子,我们往往会有不一样的需求,比方说,我只对某个方法。有耐心的话,就看看下边的更多的切入想要的方法的技巧。 切入的点的方式有五种:
1、execution表达式
用于匹配方法执行的连接点,属于方法级别
语法:execution(修饰符 返回值类型 方法名(参数)异常)
*
号就可以返回值类型必选
,可以使用*
来代表任意返回值方法名必选
,可以用*
来代表任意方法参数()
代表是没有参数,(..)
代表是匹配任意数量,任意类型的参数,当然也可以指定类型的参数进行匹配,如要接受一个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对象的执行方法
切面通知
在上边的代码中,其实就包含了切面。然后@Before @After @AfterReturn @AfterThrowing @Around这些定义的,其实就是我要做什么操作。
注解描述@Before前置通知, 在方法执行之前执行@After后置通知, 在方法执行之后执行,比如要在调用完成以后,记录日志,这里有一个坑,需要注意。after是不管方法是否执行成功还是失败,还是在执行过程中出异常,它都会做的一个操作。 @AfterReturn返回通知, 在方法返回结果之后执行,比如要在调用完成以后,记录日志。我觉得这个更好一点,对于记录日志。这个是返回结果后记录日志的,花费的时间不会到接口响应中。这个可以理解是,执行成功以后执行的操作。@AfterThrowing异常通知, 在方法抛出异常之后 ,这里比如要事务回滚@Around环绕通知,围绕方法的执行 ,比方要统计接口的处理时间经验:
我举一个例子,比方说我上边的需求是想要统计接口的访问次数,以及正确访问的和访问过程中出异常的。基于这个需求,那么应该这样理解,访问正常的,记录一条访问正常的日志,状态码为200,访问异常的记录一条异常的日志,状态码为500,并记录异常的原因。 我一开始想使用after这个通知,我在after之后打印一条正确访问的日志,但是我发现,我的程序出问题了,它也会执行这个操作,也会打印一条执行成功的日志。所以说 after其实是不管如何都会执行的操作,可以理解为 finally。那如何想要实现我的需求,就要使用 @AfterReturn 和 @AfterThrowing 来配合使用了,@AfterReturn可以保证只在我程序执行成功的时候做打印日志的操作,而@AfterThrowing 可以保证在程序发成错误的时候,做打印错误日志的操作。
定义增强方法的通知,如下:
/**
* @author Gjing
**/
@Aspect
@Component
public class TestAspect {
@Pointcut("within(com.angus.controller..*)")
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());
}
}
再给出一个使用注解的方式,来标记切入点的完整案例
需要注意,这个方式需要自定义注解
1、导入依赖
common和swagger是附加的,读者不用必须的哈
org.springframework.boot
spring-boot-starter-web
cn.gjing
tools-common
1.0.4
cn.gjing
tools-starter-swagger
1.0.9
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、启动运行
- 正常情况:
- 异常情况: