文章目录
如题
其实知道后也很简单,原理是因为 aop切面,环绕通知是最强大的,当你设置了对某个方法的环绕通知后,它内部会捕获这个方法所抛出的所有异常,然后你的全局自定义异常处理器当然就捉不到异常了(这个老六。。。)
解决
也很简单,既然我偷拿了异常,那我再扔地上不就行了?反正你也会去捡。。。
aop 切面日志自定义
背景:设备与系统交互,需求:捕获每次交互记录,传参/返回值情况,并存入数据库。
使用:Maven: org.aspectj:aspectjweaver:1.9.5 jar包
话不多说,直接上代码:
① 切面支持抽象类
package com.yunsystem.system.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
public abstract class AspectSupport {
Method resolveMethod(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
Class<?> targetClass = point.getTarget().getClass();
Method method = getDeclaredMethod(targetClass, signature.getName(),
signature.getMethod().getParameterTypes());
if (method == null) {
throw new IllegalStateException("无法解析目标方法: " + signature.getMethod().getName());
}
return method;
}
private Method getDeclaredMethod(Class<?> clazz, String name, Class<?>... parameterTypes) {
try {
return clazz.getDeclaredMethod(name, parameterTypes);
} catch (NoSuchMethodException e) {
Class<?> superClass = clazz.getSuperclass();
if (superClass != null) {
return getDeclaredMethod(superClass, name, parameterTypes);
}
}
return null;
}
}
② 继承实现自定义日志类
package com.yunsystem.system.aspect;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.yunsystem.common.annotation.ControllerEndPoint1;
import com.yunsystem.common.annotation.ControllerEndpoint;
import com.yunsystem.common.model.system.DeviceAccessLog;
import com.yunsystem.system.service.DeviceAccessLogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.UUID;
/**
* 设备访问系统后台切面日志
*/
@Slf4j
@Aspect
@Component
public class DeviceAccessControllerAspect extends AspectSupport{
private DeviceAccessLog accessLog = new DeviceAccessLog();
@Autowired
private DeviceAccessLogService deviceAccessLogService;
@Pointcut("@annotation(com.yunsystem.common.annotation.ControllerEndPoint1)") // 表示标注了该注解ControllerEndPoint1的所有方法
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
//增加返回值
Object proceed = null;
try {
// 执行方法
proceed = point.proceed();
} catch (Throwable e) {
log.error("目标方法执行返回值为:"+e.getMessage());
//e.printStackTrace();
//环绕通知最强大,包括了全局异常处理,所以使用了环绕通知,业务异常被捕获后,全局异常捕获不到,会失效。
// 此时只要重新抛出来,给全局异常自行捕获就好
throw e;
}
// 执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
// 保存日志
saveLog(point, time,proceed);
//关键,同时该参数作为入参存储在数据库中。
return proceed;
}
private void saveLog(ProceedingJoinPoint joinPoint, long time, Object proceed) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//获取注解
ControllerEndPoint1 controllerEndpoint = method.getAnnotation(ControllerEndPoint1.class);
if (controllerEndpoint != null) {
// 注解上的描述 上报类型,0 申请 1 上报
String operation = controllerEndpoint.operation();
accessLog.setAccess_type(Integer.parseInt(operation)); // 访问类型
}
// 请求的方法名
// String className = joinPoint.getTarget().getClass().getName();
// String methodName = signature.getName();
// accessLog.setMethod(className + "." + methodName + "()");
// 请求的方法参数值
Object[] args = joinPoint.getArgs();
// 请求的方法参数名称
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
String[] paramNames = u.getParameterNames(method);
// log.info("Request请求参数: "+ argString);
// log.info("Request请求参数名: "+ paramNames);
if (args != null && paramNames != null) { // 默认字段上传按顺序sn、型号、名称
if (args.length > 1) { // get请求参数
accessLog.setSn(args[0].toString());
accessLog.setType(args[1].toString());
accessLog.setName(args[2].toString());
} else { // post请求参数——json串
//String json1 = JSONObject.toJSONString(args);
String json2 = JSONObject.toJSONString(args[0]); // 转成json字符串
JSONObject jsonObject = JSONObject.parseObject(json2);
accessLog.setSn(jsonObject.getString("sn"));
accessLog.setDevice_type(jsonObject.getString("type"));
accessLog.setCustomer_name(jsonObject.getString("name"));
}
}
accessLog.setAccess_time(new Date()); // 访问时间
assert controllerEndpoint != null;
//查询返回值
log.info("target=" + joinPoint.getTarget());
log.info("kind=" + joinPoint.getKind());
log.info("proceed=" + proceed.toString()); //返回结果
if (controllerEndpoint.operation().equals("0")) { // 0对应申请激活码接口
accessLog.setInfo(proceed.toString()); // info保存get请求接口的返回数据
} else { // 否则对应上报接口
String json2 = JSONObject.toJSONString(args[0]); // 转成json字符串
accessLog.setInfo(proceed +json2); // 保存上报的数据和返回结果
}
// sysLog.setResp(proceed.toString());
// 随机生成id
String id = UUID.randomUUID().toString().substring(0, 32).replace("-","");
accessLog.setId(id);
// 保存系统日志
deviceAccessLogService.saveLog(accessLog);
}
}
③ 日志实体类
package com.yunsystem.common.model.system;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "device_access_log")
public class DeviceAccessLog {
@Id
private String id;
private String sn; // sn号
private String name; // 名称
private String type; // 型号
private Integer access_type; // 访问类型 0申请 1上报
private String info; // 上报内容详情
private Date access_time; // 访问时间
}
④ service 接口
package com.yunsystem.system.service;
import com.yunsystem.common.error.BusinessException;
import com.yunsystem.common.model.system.DeviceAccessLog;
import com.yunsystem.common.model.system.Log;
import com.yunsystem.common.vo.business.ActiveCodeInfoVO;
import com.yunsystem.common.vo.system.DeviceAccessLogVO;
import com.yunsystem.common.vo.system.PageVO;
import org.springframework.scheduling.annotation.Async;
public interface DeviceAccessLogService {
/**
* 日志列表
* @param pageNum
* @param pageSize
* @param deviceAccessLogVO
* @return
*/
PageVO<DeviceAccessLogVO> findDeviceAccessLogVOList(Integer pageNum, Integer pageSize, DeviceAccessLogVO deviceAccessLogVO);
/**
* 异步保存操作日志
*/
@Async("CodeAsyncThreadPool")
void saveLog(DeviceAccessLog accessLog);
/**
* 日志详情
* @param id
* @return
*/
DeviceAccessLogVO detail(String id) throws BusinessException;
/**
* 删除日志(一经使用不能删除)
* @param id
*/
void delete(String id) throws BusinessException;
}
⑤ serviceImpl即对上面接口方法的实现,细节略。
⑥ 最重要的自定义注解,用于标注在controller的方法上,异步记录日志
package com.yunsystem.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ControllerEndPoint1 {
String operation() default "";
String exceptionMessage() default "系统内部异常";
}
⑦ 然后看controller引用:
注意这里的operation的值是我用来区分这两个接口的,用来保存日志时做相应的不同处理。
over~