亮点:直接在方法上添加一行注解,就可以实现统计方法的执行时间,另外根据注解参数中属性来控制是否进行方法入参的校验
知识点:java注解+AOP+java 反射机制
特别注意点:如果要进行方法入参的校验,返回参数类定义中必须要有 A(Stirng a,String b)的构造方法,否则会报错,如果方法没有返回参数 请修改红色部分,为防止隐私我已经将所有类中包路径去除,使用时请自行修改。
步骤一:创建注解类
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 计算方法调用时间注解
* name是业务名称描述,比如:调用下单接口
* isCheckParams 是否校验接口入参,如果是会调用BeanValidator.validate(obj);进行参数的校验
* Created by yunpeng.zhao on 2017/8/15.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface TimeDiff {
/**
* 业务名称描述
* @return
*/
String name() default "";
/**
* 是否对请求参数进行校验
* @return
*/
boolean isCheckParams() default false;
}
步骤二:定义aop实现类
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Date;
/**
* 计算方法调用时间切面
* @author yunpeng.zhao
* @version $Id TimeDiffAop.java, v 0.1 2017-08-15 上午11:25 yp-tc-m-2651 Exp $$
*/
@Aspect
@Component
public class TimeDiffAop {
/**
* 记录耗时变量
*/
ThreadLocal<Long> time = new ThreadLocal<Long>();
private static final Logger LOGGER = LoggerFactory.getLogger(TimeDiffAop.class);
/**
* 在方法前记录时间并根据注解,是否校验参数
* @param joinPoint
* @return
*/
@Before("@annotation(com.yeepay.g3.core.bc.trade.aop.TimeDiff)")
public void beforeMethod(JoinPoint joinPoint){
}
/**
* 方法执行前后拦截
* @param pjp
* @return
*/
@Around("@annotation(TimeDiff)")
public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
time.set(System.currentTimeMillis());
//获取TimeDiff注解中是否需要校验参数
try {
Method method = getObjMethod(pjp);
String name = method.getAnnotation(TimeDiff.class).name();
LOGGER.info("{}开始执行,开始时间:{}",name,DateUtils.getTimeStampStr(new Date(time.get())));
boolean isCheckParams = method.getAnnotation(TimeDiff.class).isCheckParams();
if (isCheckParams){
Type type = method.getGenericReturnType();
Object[] args = pjp.getArgs();
LOGGER.info("{}请求参数:{}",name, JSONUtils.toJsonString(args));
for (Object o : args) {
try {
BeanValidator.validate(o);
} catch (IllegalArgumentException e) {
LOGGER.error("参数验证错误", e);
return getResponseObj(type, BCErrorInfoConstants.PARAM_VALIDATE_ERROR, e.getMessage());
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return pjp.proceed();
}
private Method getObjMethod(JoinPoint joinPoint) throws ClassNotFoundException, NoSuchMethodException {
String targetName = joinPoint.getTarget().getClass().getName();
Class targetClass = Class.forName(targetName);
MethodSignature ms= (MethodSignature)joinPoint.getSignature();
String methodName = ms.getMethod().getName();
Class<?>[] par=ms.getParameterTypes();
return targetClass.getMethod(methodName,par);
}
/*此处根据方法的返回类型进行拼装返回,所以在返回参数中必须有构造函数
public Object getResponseObj(Type type,String retCode,String retMsg){
try {
if (type != null){
String returnName = type.toString().replace("class ","");
LOGGER.info("返回类型是:{}",returnName);
Class cls = Class.forName(returnName);
Constructor con = cls.getConstructor(String.class, String.class);
Object responseObj = con.newInstance(retCode, retMsg);
return responseObj;
}
return null;
} catch (Exception e) {
throw BCException.PAY_PARAM_CVRT_ERROR;
}
}
/**
* 在方法后打印总耗时
* @param joinPoint
*/
@After("@annotation(com.yeepay.g3.core.bc.trade.aop.TimeDiff)")
public void afterMethod(JoinPoint joinPoint){
try {
Method method = getObjMethod(joinPoint);
String name = method.getAnnotation(TimeDiff.class).name();
LOGGER.info("{}执行结束,结束时间:{},总耗时:{}ms",name, DateUtils.getLongDateStr(),System.currentTimeMillis()-time.get());
} catch (Exception e) {
LOGGER.error("打印总耗时出现异常:{}",e);
}
}
}
步骤三 在spring配置文件中开启aop 并能扫描到切面类
<aop:aspectj-autoproxy proxy-target-class="true"/>
<context:component-scan base-package="com.yeepay.g3.core.bc.trade" />
步骤四:在方法上添加这个牛逼的注解就可以了
@Service
public class CompensateOrderFacadeImpl implements CompensateOrderFacade{
private static final Logger LOGGER = LoggerFactory.getLogger(CompensateOrderFacadeImpl.class);
@Autowired
private CompensateOrderBiz compensateOrderBiz;
@TimeDiff(name = "赔付反查接口",isCheckParams = true)
@Override
public CompensateQueryResponseDTO queryOrgOrder(CompensateQueryRequestDTO query) {
CompensateQueryResponseDTO response = new CompensateQueryResponseDTO(BCException.FAIL.getDefineCode(),BCException.FAIL.getMessage());
try {
//1.反查流程
response = compensateOrderBiz.queryOrgOrder(query);
} catch (BCException e) {
LOGGER.error("赔付反查接口参数校验异常:{}",e);
response.setRetCode(e.getDefineCode());
response.setRetMsg(e.getMessage());
} catch (Exception e) {
LOGGER.error("赔付反查接口出现系统异常:{}",e);
response.setRetCode(BCErrorInfoConstants.TRADE_COMPENSATE_QUERY_ERROR);
response.setRetMsg(e.getMessage());
}
return response;
}
@TimeDiff(name = "赔付接口",isCheckParams = true)
@Override
public CompensateOrderResponseDTO createCompensateOrder(CompensateOrderDTO order) {
CompensateOrderResponseDTO responseDTO = new CompensateOrderResponseDTO();
return null;
}
}
步骤五;参数校验类
import java.util.Locale;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.springframework.context.i18n.LocaleContextHolder;
public class BeanValidator {
private static Validator validator;
static {
ValidatorFactory validatorFactory = Validation
.buildDefaultValidatorFactory();
validator = validatorFactory.getValidator();
}
public static String getMergedMessage(Set set) {
StringBuilder sb = new StringBuilder("");
for (Object obj : set) {
if (obj instanceof ConstraintViolation) {
ConstraintViolation constraintViolation = (ConstraintViolation) obj;
sb.append(constraintViolation.getPropertyPath());
sb.append(" ");
sb.append(constraintViolation.getMessage());
sb.append("; ");
}
}
return sb.toString();
}
/**
* 根据Bean中的注解配置验证Bean的参数合法性
*
* @param <E>
* @param obj
* 待验证对象
*/
@SuppressWarnings("unchecked")
public static <E> void validate(Object obj) {
Set<ConstraintViolation<E>> set = validator.validate((E) obj);
if (set.size() != 0) {
throw new IllegalArgumentException("验证参数合法性时出现异常["
+ getMergedMessage(set)
+ "]");
}
}
}
大功告成:看效果图
2017-08-16 09:28:52,768 - com.yeepay.g3.core.bc.trade.aop.TimeDiffAop -10764 [main] INFO - 赔付反查接口开始执行,开始时间:2017-08-16 09:28:52
2017-08-16 09:28:52,775 - com.yeepay.g3.core.bc.trade.aop.TimeDiffAop -10771 [main] INFO - 赔付反查接口请求参数:[{"customerRequestId":"2017081000004","customerNumber":"10040007799"}]
2017-08-16 09:28:52,795 - org.hibernate.validator.util.Version -10791 [main] INFO - Hibernate Validator 4.2.0.Final
2017-08-16 09:28:53,005 - com.yeepay.g3.core.bc.trade.aop.TimeDiffAop -11001 [main] INFO - 赔付反查接口执行结束,结束时间:2017-08-16 09:28:53,总耗时:244ms
相关参考文章:
http://www.cnblogs.com/peida/archive/2013/04/24/3036689.html 注解相关
http://www.xdemo.org/springmvc-aop-annotation/ AOP相关
http://blog.csdn.net/meiyang1990/article/details/50562046 获取method方法
http://blog.csdn.net/shenyunsese/article/details/51133065 获取注解属性,此处主要是直接通过 下面代码 无法获取到annotation,怀疑跟JDK版本有关
MethodSignature ms=(MethodSignature) joinPoint.getSignature();
Method method=ms.getMethod();
method.getAnnotation(Log.
class
).name()