一直以来都知道Spring支持一种叫做面向切面编程(AOP)的东西,但是一直都没有自己尝试使用过. 直到最近为了Debug方法,记录使用时间猛然发现AOP正好适合使用在这个场景下.为了灵活的使用AOP,我选择了使用注解来作为标记,当某个特定的注解被使用的时候将会自动触发这个切面.
1.注解的编写
package org.jzbk.rssplus.aspect.annotation; import java.lang.annotation.*; /** * Created by Kotarou on 2017/1/11. */ @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Timed { boolean displayArgs() default false; }
将注解设置为运行时RetentionPolicy.RUNTIME, 在编译时不会丢失这个注解信息.
设置注解主体为方法和类.
注解内部保存一个displayArgs的boolean变量,用于判断是否输出传入参数.
2. 编写AOP类
package org.jzbk.rssplus.aspect; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.jzbk.rssplus.aspect.annotation.Timed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * Created by Kotarou on 2017/1/11. */ @Aspect @Component public class TimedAOP { final private Logger logger = LoggerFactory.getLogger(getClass()); @Pointcut("@annotation(org.jzbk.rssplus.aspect.annotation.Timed) || @target(org.jzbk.rssplus.aspect.annotation.Timed)") public void annotationProcessor() { } @Pointcut("execution(public * org.jzbk.rssplus..*.*(..))") public void publicMethod() { } @Around(value = "publicMethod() && annotationProcessor()") public Object count(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { final String methodName = proceedingJoinPoint.getSignature().getName(); Long startTime = System.currentTimeMillis(); Object result = proceedingJoinPoint.proceed(); Long finishTime = System.currentTimeMillis(); Signature signature = proceedingJoinPoint.getSignature(); String[] packageName = signature.getDeclaringTypeName().split("\\."); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < packageName.length; ++i) { if (i < packageName.length - 1) { stringBuilder.append(packageName[i].substring(0, 1)); } else { stringBuilder.append(packageName[i]); } stringBuilder.append("."); } logger.info("Executing: " + stringBuilder + signature.getName() + " took: " + (finishTime - startTime) + " ms"); Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod(); if (method.getDeclaringClass().isInterface()) { method = proceedingJoinPoint.getTarget().getClass().getDeclaredMethod(methodName, method.getParameterTypes()); } // 方法上的注解优先级比类上的注解高,可以覆盖类上注解的值 Timed timed = null; if (method.isAnnotationPresent(Timed.class)) { //处理方法上的注解 timed = method.getAnnotation(Timed.class); if (timed.displayArgs()) { logArgs(proceedingJoinPoint.getArgs()); } } else { //处理类上面的注解 Object target = proceedingJoinPoint.getTarget(); if (target.getClass().isAnnotationPresent(Timed.class)) { timed = target.getClass().getAnnotation(Timed.class); if (timed.displayArgs()) { logArgs(proceedingJoinPoint.getArgs()); } } } return result; } private void logArgs(Object[] args) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < args.length; ++i) { stringBuilder.append("["); stringBuilder.append(i); stringBuilder.append("]: "); stringBuilder.append(args[i].toString()); if (i < args.length - 1) { stringBuilder.append(", "); } } if (!stringBuilder.toString().isEmpty()) logger.info("Argument List: " + stringBuilder); else logger.info("Argument List: Empty"); } }
AOP的切入点为使用了Timed的方法或者类.
方法上面的注解优先级比类上面的高,可以在方法上使用注解来覆盖掉类上注解的值.
演示:
在类上面增加注解,并设置displayArgs为true
在某个方式上覆盖注解冰将displayArgs设置为false
运行tomcat,查看日志
结果和期望中的一样.