使用AOP拦截Throwable,自定义一些信息输出
20190717:大改LogAopUtil文件和ErrorAopLog(调用的LogAopUtil方法返回类型由StringBuffer改为StringBuilder.)
LogAopUtil修改根据request请求类型是GET还是POST来分不同方式获取请求的参数.(因为原有网上抄袭的方法,会遇到尝试用FASTJSON输出不是序列化的request报错问题)
修改POST时获取请求的输出内容不是标准JSON格式,把参数名和值放入Map,交给FastJson格式化输出
20190625:修改ErrorAopLog.java文件,移除logger.error("异常基本信息为:{} ", e.fillInStackTrace().toString());这行代码导致输出的报错信息会有缺失.
相关知识点链接
https://blog.csdn.net/yangkai_hudong/article/details/18409007
https://blog.csdn.net/amanicspater/article/details/72721996
具体配置可以参考之前博文内容
Log4j无缝升级到Log4j2+Slf4j
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.7.1version>
dependency>
找到项目的springMVC的xml配置文件.修改代理模式.
<aop:aspectj-autoproxy proxy-target-class="true" />
LogAopUtil 为了方便输出方法的参数和对应值
package cn.com.example.util;
import com.alibaba.fastjson.JSON;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* 获取AOP代理的方法的参数名称和参数值工具类
* 修改尝试获取Request的参数内容
* @author shengquan
*/
public class LogAopUtil {
private static final Logger LOGGER = getLogger(LogAopUtil.class);
private LogAopUtil(){
}
public static StringBuilder getNameAndArgs(
Class<?> cls, String clazzName, String methodName, Object[] args) throws NotFoundException {
Map<String, Object> nameAndArgs = new HashMap<String, Object>();
ClassPool pool = ClassPool.getDefault();
ClassClassPath classPath = new ClassClassPath(cls);
pool.insertClassPath(classPath);
CtClass cc = pool.get(clazzName);
CtMethod cm = cc.getDeclaredMethod(methodName);
MethodInfo methodInfo = cm.getMethodInfo();
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
LocalVariableAttribute attr =
(LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
Object valueObj;
boolean isGetMethod = false;
boolean isPostMethod = false;
if (attr != null) {
int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
for (int i = 0; i < cm.getParameterTypes().length; i++) {
valueObj = args[i];
// 对于 ServletRequest ServletResponse MultipartFile 无法直接JsonToString()会抛出错误需要特殊处理
if (!(valueObj instanceof ServletRequest
|| valueObj instanceof ServletResponse
|| valueObj instanceof MultipartFile)) {
nameAndArgs.put(attr.variableName(i + pos), args[i]);
} else {
if (valueObj instanceof ServletRequest) {
//GET方法的参数获取方式 放入nameAndArgs
if ("GET".equals(((HttpServletRequest) valueObj).getMethod())) {
isGetMethod = true;
} else {
//如果是POST方法的参数获取方式,不需要使用nameAndArgs,靠后面的方式去获取参数.
if ("POST".equals(((HttpServletRequest) valueObj).getMethod())) {
isPostMethod = true;
break;
}
}
}
}
}
}
StringBuilder sb = new StringBuilder();
//判定 HttpServletRequest 请求方法是GET时
if (isGetMethod) {
try {
sb.append(JSON.toJSONString(nameAndArgs));
} catch (IllegalStateException ie) {
LOGGER.error(ie.getMessage(), ie);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
} else {
//判定 HttpServletRequest 请求方法是POST时
if (isPostMethod && args != null) {
ServletRequest servletRequest;
for (Object object : args) {
if (object instanceof ServletRequest) {
servletRequest = (ServletRequest) object;
Enumeration<String> enumeration = servletRequest.getParameterNames();
Map<String, Object> modelMap = new HashMap<>();
while (enumeration.hasMoreElements()) {
String parameterName = enumeration.nextElement();
try {
modelMap.put(
parameterName,
((ServletRequest) object).getParameter(parameterName));
} catch (IllegalStateException ie) {
LOGGER.error(ie.getMessage(), ie);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
sb.append(JSONObject.toJSONString(modelMap, SerializerFeature.WriteMapNullValue));
}
}
}
}
return sb;
}
}
切面拦截有两种实现方式,一种是xml配置文件,另一种是注解方式.
大家根据需求选择一种来使用即可.
在刚才SpringMVC的xml文件添加的内容下面可以继续添加切面配置
<aop:config>
<aop:aspect id="logAspect" ref="errorAopLog ">
<aop:pointcut expression="execution(* cn.com.example.*.controller.*.*(..))" id="logPointCut"/>
<aop:around method="around" pointcut-ref="logPointCut"/>
aop:aspect>
aop:config>
execution(* cn.com.example.*.controller.*.*(..))
<aop:around method="around" pointcut-ref="logPointCut"/>
**method=“around”**指向<aop:aspect id="logAspect" ref="errorAopLog ">
中ref="errorAopLog "对应的springbean中的方法名.<aop:pointcut expression="execution(* cn.com.example.*.controller.*.*(..))" id="logPointCut"/>
<aop:pointcut expression="execution(* cn.com.example.*.controller.*.*(..))" id="logPointCut"/>
<aop:around method="around" pointcut-ref="logPointCut"/>
创建java类文件ErrorAopLog
package cn.com.example;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Arrays;
import static org.slf4j.LoggerFactory.getLogger;
@Aspect
@Component
public class ErrorAopLog {
private static final Logger logger = getLogger(ErrorAopLog.class);
// private static final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);
String logStr = null;
// //前置通知
// public void before(JoinPoint jp){
// System.out.println("==========aaaaaaaaaaaaaa");
// logStr=jp.getTarget().getClass().getName()+"类的"
// +jp.getSignature().getName()+"方法开始执行******Start******";
// logger.info(logStr);
// }
// //最终通知
// public void after(JoinPoint jp){
// logStr=jp.getTarget().getClass().getName()+"类的"
// +jp.getSignature().getName()+"方法执行结束******End******";
// logger.info(logStr);
// }
// //异常抛出后通知
// public void afterThrowing(JoinPoint call){
// String className = call.getTarget().getClass().getName();
// String methodName = call.getSignature().getName();
// System.out.println(className+"."+methodName+"()方法抛出了异常...");
// }
// //后置通知
// public void afterReturn(JoinPoint call){
// String className = call.getTarget().getClass().getName();
// String methodName = call.getSignature().getName();
// System.out.println(className+"."+methodName+"()方法正常执行结束...");
// }
// 用来做环绕通知的方法可以第一个参数定义为org.aspectj.lang.ProceedingJoinPoint类型
public Object around(ProceedingJoinPoint call) throws Throwable {
Object result = null;
// 取得类名和方法名
// String className = call.getTarget().getClass().getName();
// String methodName = call.getSignature().getName();
// 相当于前置通知
// logStr=className+"类的"+methodName+"方法开始执行******Start******";
// logger.info(logStr);
try {
result = call.proceed();
// 相当于后置通知
// logStr=className+"."+methodName+"()方法正常执行结束...";
// logger.info(logStr);
} catch (Throwable e) {
// 相当于异常抛出后通知
// StackTraceElement stackTraceElement= e.getStackTrace()[
// e.getStackTrace().length-1];
StackTraceElement stackTraceElement = e.getStackTrace()[0];
String className = stackTraceElement.getClassName();
String methodName = stackTraceElement.getMethodName();
Object[] args = call.getArgs();
logger.error(
"----------------------------------------------------------------------------------");
HttpServletRequest request =
((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
if (request != null) {
HttpSession session = request.getSession();
if (session != null) {
// 读取session中的用户
EnterpriseLogin user = (EnterpriseLogin) session.getAttribute(EnterpriseLogin.USER);
if (user != null) {
logger.error(
"用户id:{},用户登录名:,企业id:{}",
user.getId(),
user.getLoginName(),
user.getEnterprise().getindustry_id());
}
}
// 获取Ip 来源IpAddressUtil:https://blog.csdn.net/qq_19167629/article/details/85280943
String ipAddress = IpAddressUtil.getIpAdrress(request);
if (StringUtils.isNotBlank(ipAddress)) {
logger.error("访问的IP:{}", ipAddress);
}
}
logger.error("执行{}类的{}()方法的{}行", className, methodName, stackTraceElement.getLineNumber());
// logger.error("异常基本信息为:{} ", e.fillInStackTrace().toString());
// logger.error("===参数信息为:{} ",args);
// logger.error("===异常信息e.getStackTrace()为:{} ",e.getStackTrace());
logger.error("请求类方法:{}", call.getSignature());
logger.error("请求类方法参数:{}", Arrays.toString(call.getArgs()));
String classType = call.getTarget().getClass().getName();
Class<?> clazz = Class.forName(classType);
String clazzName = clazz.getName();
String methodName2 = call.getSignature().getName(); // 获取方法名称
// 获取参数名称和值
StringBuilder sb = LogAopUtil.getNameAndArgs(this.getClass(), clazzName, methodName2, args);
logger.error("请求类方法参数名称和值:" + sb);
logger.error("异常详细信息:", e);
// 下面这种输出e的 不需要占位符 {},属于logger本身输出方法error(String str,Throwable throwable)
// logger.error("===异常信息e为:{} ",e);
throw e;
} finally {
// 相当于最终通知
// logStr=className+"类的" +methodName+"方法执行结束******End******";
// logger.info(logStr);
}
return result;
}
}
备注:
就一个java文件
package cn.com.example.analysis.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.springframework.stereotype.Component;
import static org.slf4j.LoggerFactory.getLogger;
@Aspect
@Component
public class ErrorAopLog {
private static final Logger logger = getLogger(ErrorAopLog.class);
String logStr=null;
/**
* 切点 与xml配置的语法一致,自行根据需求调整适合自己的项目结构
*/
@Pointcut(value="execution(* cn.com.example.*.controller.*.*(..))")
public void pointCutOne(){
}
//@Around对应xml里的around
@Around(value = "pointCutOne()")
public Object around(ProceedingJoinPoint call) throws Throwable {
//TODO:与上面类似省略
}
@AfterThrowing(pointcut="pointCutOne()",throwing="e")
public void afterThrowable(Throwable e) throws Throwable{
//TODO:抛出异常后想做的事情
}
}
在项目里随便写了一个报错代码
int i=1/0;
输出日志如下
2019-05-10 16:39:48.548 [ERROR] cn.com.example.util.ErrorAopLog .around(ErrorAopLog .java:90) - ----------------------------------------------------------------------------------
2019-05-10 16:39:48.549 [ERROR] cn.com.example.util.ErrorAopLog .around(ErrorAopLog .java:113) - 访问的IP:127.0.0.1
2019-05-10 16:39:48.549 [ERROR] cn.com.example.util.ErrorAopLog .around(ErrorAopLog .java:117) - 执行cn.com.example.login.service.impl.LoginServiceImpl类的findEnLogin()方法的33行
2019-05-10 16:39:48.549 [ERROR] cn.com.example.util.ErrorAopLog .around(ErrorAopLog .java:118) - 异常基本信息为:java.lang.ArithmeticException: / by zero
2019-05-10 16:39:48.549 [ERROR] cn.com.example.util.ErrorAopLog .around(ErrorAopLog .java:121) - 请求类方法:String cn.com.example.login.controller.LoginController.LoginAction(HttpServletRequest,HttpServletResponse)
2019-05-10 16:39:48.549 [ERROR] cn.com.example.util.ErrorAopLog .around(ErrorAopLog .java:122) - 请求类方法参数:[cn.com.example.util.ParameterRequestWrapper@5e4f2dd1, org.apache.catalina.connector.ResponseFacade@606e938c]
2019-05-10 16:39:48.550 [ERROR] cn.com.example.util.ErrorAopLog .around(ErrorAopLog .java:130) - 请求类方法参数名称和值:loginName:"保密";password:"保密";checkcode:"5f90";
2019-05-10 16:39:48.550 [ERROR] cn.com.example.util.ErrorAopLog .around(ErrorAopLog .java:131) - 异常详细信息:
java.lang.ArithmeticException: / by zero
at cn.com.example.util.ErrorAopLog .around(ErrorAopLog .java:118) [classes/:?]
at sun.reflect.GeneratedMethodAccessor189.invoke(Unknown Source) ~[?:?]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_181]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_181]
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:629) [spring-aop-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:618) [spring-aop-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70) [spring-aop-4.3.3.RELEASE.jar:4.3.3.RELEASE]
看上面测试的日志结果不难看出,抛出了异常,但是并不是原有的异常输出内容,被抛出位置是在AOP的java方法类上抛出.还没有解决希望抛出在原始的那层异常处抛出.