SysOperateLog
先看注解的定义:
@Documented
@Retention(RUNTIME)
@Target(METHOD)
public @interface SysOperateLog {
/**
* 操作类型,取值为 com.gosun.isap.operlog.api.OperateType
*
* @return 操作类型
*/
int operateType();
/**
* 业务类型,取值为com.gosun.isap.operlog.api.ServiceType
*
* @return 业务类型
*/
int serviceType();
/**
* 操作描述
*
* @return 操作描述信息
*/
String description();
}
一个目标为方法的运行时注解,有三个数据。
Around
通过Spring Aop切面实现日志的记录功能,在这里主要用到了Around这个注解,该注解的意思是在目标方法运行之前和运行之后增加某些功能,可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值。
在我们的项目中:
@Around("within(com.gosun.isap..*) && @annotation(rl)")
如果看不懂肯定就是因为within(com.gosun.isap..*) && @annotation(rl)
这句表达式。
下面就分析一下这句表达式的意思:
within的官方定义是:
within - limits matching to join points within certain types (simply the execution of a method declared within a matching type when using Spring AOP)
简单的说就是匹配某个类型。
com.gosun.isap..*
:
官方例子:within(com.xyz.service..*)
any join point (method execution only in Spring AOP) within the service package or a sub-package
和spring的component-scan类似,递归扫描下面的包。
@annotation的官方定义是:
@annotation - limits matching to join points where the subject of the join point (method being executed in Spring AOP) has the given annotation
简单的说就是只匹配拥有某个注解的方法。
上面两个条件用&&连接起来意思就很清楚了,切入com.gosun.isap
下的包,并且在类方法包含SysOperateLog注解的情况下,增加日志功能。
Spring
在spring配置文件中添加配置启用Aop功能
实现
在看看方法的具体实现:
@Around("within(com.gosun.isap..*) && @annotation(rl)")
public Object writeOperateLog(ProceedingJoinPoint jp, SysOperateLog rl) throws Throwable {
String className = jp.getTarget().getClass().toString();// 获取目标类名
className = className.substring(className.indexOf("com"));
String signature = jp.getSignature().toString();// 获取目标方法签名
String methodName = signature.substring(signature.lastIndexOf(".") + 1, signature.indexOf("("));
Object[] parames = jp.getArgs();// 获取目标方法体参数
String params = parseParames(parames); // 解析目标方法体的参数
int serviceType = rl.serviceType();
int operateType = rl.operateType();
String description = rl.description();
StringBuilder sbLogDetail = new StringBuilder();
sbLogDetail.append(description).append(", 参数为:").append(params);
Object object;
try {
object = jp.proceed();
if (object != null && object instanceof ResponseResult) {
int code = ((ResponseResult) object).getHead().getErrorCode();
if (code == ErrorCode.ERR_OK) {
success(serviceType, operateType, sbLogDetail.toString());
} else {
failed(serviceType, operateType, sbLogDetail.toString(),
((ResponseResult) object).getHead().getMessage());
}
} else {
success(serviceType, operateType, sbLogDetail.toString());
}
} catch (Throwable throwable) {
failed(serviceType, operateType, sbLogDetail.toString(), throwable.getMessage());
throw throwable;
}
return object;
}
private void success(int serviceType, int operateType, String description) {
try {
OperateLogWriter.success(serviceType, operateType, description);
} catch (Exception e) {
logger.error("Write operate log error ", e);
}
}
private void failed(int serviceType, int operateType, String description, String reason) {
try {
OperateLogWriter.fail(serviceType, operateType, description, reason);
} catch (Exception e) {
logger.error("Write operate log error ", e);
}
}
可以看到不管是成功还是失败,最终依旧调用了OperateLogWriter去写日志,但通过AOP的方式解耦了分散在业务逻辑中的写日志代码。
在这里object = jp.proceed();
才真正的执行了我们的方法,并通过返回的对象int code = ((ResponseResult) object).getHead().getErrorCode();
判断方法的成功与失败,然后写入日志。
int serviceType = rl.serviceType();
int operateType = rl.operateType();
String description = rl.description();
这三句话将注解中的内容提取出来写入日志中,因此这个注解的使用也非常简单:
@SysOperateLog(serviceType = ServiceType.CONFIG_RES, operateType = OperateType.CONFIG_ADD, description = "添加时间模板")
最终这些元数据都会写入到日志中。