一直以来都知道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,查看日志

 

结果和期望中的一样.

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据