在日常开发过程中,需要记录日志,但是每次都要写日志收集的代码就会很繁琐,那么如何优雅的实现日志收集呢?利用自定义注解可以很好的实现,下面就来说一说如何利用切面实现日志的收集
1、先自定义一个切点
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/** @Author: best_liu
* @Description:
* @Date: 11:21 2022/11/23
* @Param
* @return
**/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface RequiredLog {
String methodType() default "";
}
2、再实现一个切面类,再该类里面写日志收集的方法
import com.mes.material.domain.MaterialEamLog;
import com.mes.material.service.IMaterialEamLogService;
import lombok.extern.slf4j.Slf4j;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
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 java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: best_liu
* @Description:
* @Date Create in 9:11 2022/11/23
* @Modified By:
*/
@Aspect
@Component
@Slf4j
public class SysLogAspect {
@Autowired
private IMaterialEamLogService materialEamLogService;
private static Object result = "";
/**
* @Pointcut 注解用于描述或定义一个切入点
* 切入点的定义需要遵循spring中指定的表达式规范
例如:("bean(sysMenuServiceImpl)")为切入点表达式
的一种定义方式。
*/
@Pointcut("@annotation(com.mes.material.annotation.log.RequiredLog)")
public void logPointCut() {}
/**
* @Around 注解描述的方法为一个环绕通知方法,
* 在此方法中可以添加扩展业务逻辑,可以调用下一个
切面对象或目标方法
* @param jp 连接点(此连接点只应用@Around描述的方法)
* @return
* @throws Throwable
*/
@Around("logPointCut()")
public Object aroundAdvice(ProceedingJoinPoint jp)
throws Throwable{
long start=System.currentTimeMillis();
log.info("start:"+start);
try {
//调用下一个切面或目标方法
result = jp.proceed();
}finally {
long end=System.currentTimeMillis();
log.info("end:"+end);
//记录日志(用户行为信息)
saveLog(jp,result,String.valueOf(end-start));
}
return result;
}
@AfterThrowing(value = "logPointCut()",throwing = "t")
public void afterThrowing(Throwable t) {
result = t;
}
//日志记录
private void saveLog(ProceedingJoinPoint jp,Object result,String time) {
//1.获取用户行为日志(ip,username,operation,method,params,time,createdTime)
//获取类的字节码对象,通过字节码对象获取方法信息
Class<?> targetCls=jp.getTarget().getClass();
//获取方法签名(通过此签名获取目标方法信息)
MethodSignature ms=(MethodSignature)jp.getSignature();
//获取目标方法名(目标类型+方法名)
String targetClsName=targetCls.getName();
String targetObjectMethodName=targetClsName+"."+ms.getName();
//获取请求参数
String targetMethodParams= Arrays.toString(jp.getArgs());
String url = "";
String ipAddr = "";
if(RequestContextHolder.getRequestAttributes() != null){
//获取请求url
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
url = request.getRequestURL().toString();
//ipAddr = request.getRemoteHost();
ipAddr = getIpAddress(request);
}
//2.封装用户行为日志(SysLog)
//插入日志--start
MaterialEamLog log = new MaterialEamLog();
log.setReqParam(targetMethodParams);
log.setMethod(targetObjectMethodName);
//获取注解中的参数
Map<String, Object> annotationArgs = this.getAnnotationArgs(jp);
log.setMethodType(String.valueOf(annotationArgs.get("methodType")));
log.setResult(String.valueOf(result));
log.setUrl(url);
log.setIp(ipAddr);
log.setTime(time);
//3.调用业务层对象方法(saveObject)将日志写入到数据库
materialEamLogService.insert(log);
}
/**
* 获取锁参数
*
* @param proceeding
* @return
*/
private Map<String, Object> getAnnotationArgs(ProceedingJoinPoint proceeding) {
Class target = proceeding.getTarget().getClass();
Method[] methods = target.getMethods();
String methodName = proceeding.getSignature().getName();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Map<String, Object> result = new HashMap<String, Object>();
RequiredLog requiredLog = method.getAnnotation(RequiredLog.class);
result.put("methodType", requiredLog.methodType());
return result;
}
}
return null;
}
/**
* 获取用户真实IP地址,不使用request.getRemoteAddr()的原因是有可能用户使用了代理软件方式避免真实IP地址,
* 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值
*
* @return ip
*/
private String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
log.info("x-forwarded-for ip: " + ip);
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
if( ip.indexOf(",")!=-1 ){
ip = ip.split(",")[0];
}
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
log.info("Proxy-Client-IP ip: " + ip);
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
log.info("WL-Proxy-Client-IP ip: " + ip);
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
log.info("HTTP_CLIENT_IP ip: " + ip);
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
log.info("HTTP_X_FORWARDED_FOR ip: " + ip);
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
log.info("X-Real-IP ip: " + ip);
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
log.info("getRemoteAddr ip: " + ip);
}
log.info("获取客户端ip: " + ip);
return ip;
}
}
这样一个自定义注解就完成了,在需要收集日志的地方用@RequiredLog(methodType="")注解即可
@Service
public class MesProvideRomaServiceImpl implements MesProvideRomaService {
@Override
@RequiredLog(methodType="2")
public AjaxResult materialInfoRomaToMes(MaterialRequestVo materialRequestVo) throws IllegalAccessException, IntrospectionException, InstantiationException {
}
}