最近项目中关联方接口比较多,时不时的会出现一些接口不稳定,调用很耗时,这就需要将每个接口的耗时打印出来,以便于找关联方解决问题,feign接口在项目中没有具体的实现,所以不可能在接口的前后将耗时打印出来,在具体调用的地方打印的话需要添加的重复代码太多,不想写那么多的无用的代码,而且也影响项目的可读性,另外控制起来也不方便,生产上并不需要打印这些日志。
so,面向切面就闪亮登场了,先上代码:
@Aspect
@Service
public class PrintInterfaceTimeAspect {
private Logger logger = LoggerFactory.getLogger(PrintInterfaceTimeAspect.class);
public PrintInterfaceTimeAspect() {
}
//路径就自己定义咯
@Pointcut("execution(* com.syygl.test.service.sao..*(..))")
public void feignInterfacePointCut() {
}
@Around(value = "feignInterfacePointCut()")
public Object aroundFeignInterfaceMethod(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object obj = null;
try {
obj = joinPoint.proceed();
}
finally {
logger.debug("调用关联方的方法及耗时 method={} cost={}",
joinPoint.getSignature().getName(), System.currentTimeMillis() - start);
}
return obj;
}
}
将 @Pointcut中的路径改为自己接口的路径就可以将该路径下的接口中的所有的方法在被调用的时候将接口的耗时打印出来了,是不是很好用,接口和方法那里一行代码都没有改动。这就是面向切面编程的一个简单的应用。主要需要关注的是切点方法的定义和切点方法前后的调用,这就需要关注以下几个注解的使用。
1.@Pointcut的用法
以下给出几个基本的用法,有兴趣的可以深入研究下,这里就看切面定义的需要,当然切面也可以定义为注解,这也就是自定义注解的一个使用方式。
1)execution(* *(..))
//表示匹配所有方法
2)execution(public * com. savage.service.UserService.*(..))
//表示匹配com.savage.server.UserService中所有的公有方法
3)execution(* com.savage.server..*.*(..))
//表示匹配com.savage.server包及其子包下的所有方法
4)@annotation(com.syygl.test.annotation.TestAnnotation)
//在方法内部调用,注解切面会失效
//表示被该注解标识的方法就会成为这个切点
2.切点方法的使用
获取到切点后就需要写在该切点上需要完成的逻辑,上文的例子只是简单的打印方法调用前后的时间。在编写具体逻辑代码时常用的注解有@Around @AfterReturning @Before @AfterThrowing @After,使用起来也是比较方便,顾名思义。
/**
* 前置通知:目标方法执行之前执行以下方法体的内容
*/
@Before(value = "feignInterfacePointCut()")
public void beforeMethod(JoinPoint jp){
String methodName = jp.getSignature().getName();
System.out.println("【前置通知】the method 【" + methodName + "】 begins with " + Arrays.asList(jp.getArgs()));
}
/**
* 返回通知:目标方法正常执行完毕时执行以下代码
*/
@AfterReturning(value="feignInterfacePointCut()",returning="result")
public void afterReturningMethod(JoinPoint jp, Object result){
String methodName = jp.getSignature().getName();
System.out.println("【返回通知】the method 【" + methodName + "】 ends with 【" + result + "】");
}
/**
* 后置通知:目标方法执行之后执行以下方法体的内容,不管是否发生异常。
*/
@After("feignInterfacePointCut()")
public void afterMethod(JoinPoint jp){
System.out.println("【后置通知】this is a afterMethod advice...");
}
/**
* 异常通知:目标方法发生异常的时候执行以下代码
*/
@AfterThrowing(value="feignInterfacePointCut()",throwing="e")
public void afterThorwingMethod(JoinPoint jp, NullPointerException e){
String methodName = jp.getSignature().getName();
System.out.println("【异常通知】the method 【" + methodName + "】 occurs exception: " + e);
}
/**
* 环绕通知:目标方法执行前后分别执行一些代码,发生异常的时候执行另外一些代码
*/
@Around(value="feignInterfacePointCut()")
public Object aroundMethod(ProceedingJoinPoint jp){
String methodName = jp.getSignature().getName();
Object result = null;
try {
System.out.println("【环绕通知中的--->前置通知】:the method 【" + methodName + "】 begins with " + Arrays.asList(jp.getArgs()));
//执行目标方法
result = jp.proceed();
System.out.println("【环绕通知中的--->返回通知】:the method 【" + methodName + "】 ends with " + result);
} catch (Throwable e) {
System.out.println("【环绕通知中的--->异常通知】:the method 【" + methodName + "】 occurs exception " + e);
}
System.out.println("【环绕通知中的--->后置通知】:-----------------end.----------------------");
return result;
}
3.常用注解总结
@Aspect:作用是把当前类标识为一个切面供容器读取
@Pointcut:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。
@Around:环绕增强,相当于MethodInterceptor
@AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行
@Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有
@AfterThrowing:异常抛出增强,相当于ThrowsAdvice
@After: final增强,不管是抛出异常或者正常退出都会执行