一直以来都知道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,查看日志
结果和期望中的一样.