前面已经介绍过aop的多种应用场景,今天研究了一下使用aop记录日志,使用的是后置通知。具体的实现如下:
自定义注解,用来记录用户的操作和一些基本信息
package com.soecode.lyf.log.annatation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Create by wws on 2019/6/3
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface LogRequire {
/**
* 操作模块
*/
String operationModel() default "";
/**
* 操作功能
*/
String operationFunction()default "";
/**
* 操作说明
*/
String operationExplain ()default "";
}
增加一个获取参数里面的用户信息注解,注意这个值一般是从session里面获取的,从controller里面传过来,这里为了简单从页面传来一个假的参数。
package com.soecode.lyf.log.annatation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Description
* @Author DJZ-WWS
* @Date 2019/6/4 8:36
*/
@Target({ ElementType.TYPE, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface LogUser {
}
日志的切面实现
package com.soecode.lyf.log.aspect;
import com.mchange.v1.util.ArrayUtils;
import com.soecode.lyf.datasource.DataSourceAspect;
import com.soecode.lyf.log.annatation.LogRequire;
import com.soecode.lyf.log.annatation.LogUser;
import com.soecode.lyf.log.pojo.LogInfo;
import com.soecode.lyf.log.service.LogService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.text.MessageFormat;
import java.util.Date;
import java.util.Enumeration;
/**
* @Description 日志切面
* @Author DJZ-WWS
* @Date 2019/6/3 14:04
*/
@Aspect
@Component
public class SysLogAspect {
static Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);
@Autowired
private LogService logService;
@Pointcut("@annotation(com.soecode.lyf.log.annatation.LogRequire)")
public void log() {
}
@After("log()")
public void doAfter(JoinPoint joinPoint) throws UnknownHostException {
System.out.println("=====SysLogAspect后置通知开始=====,开始记录日志信息");
LogInfo logInfo = new LogInfo();
//记录基本信息
Class> target = joinPoint.getTarget().getClass();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Parameter[] parameters = method.getParameters();
//记录用户信息 获取参数里面加LogUser注解的值,表示用户信息
//获取参数上所有的注解
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
//判断注解LogUser是否存在
for (Annotation[] paraeterAnnotation : parameterAnnotations) {
//获取注解的索引
int paramIndex = ArrayUtils.indexOf(parameterAnnotations, paraeterAnnotation);
//获取注解标记的值
for (Annotation annotation : paraeterAnnotation) {
if (annotation instanceof LogUser) {
Object[] args = joinPoint.getArgs();
Object value = args[paramIndex];
logInfo.setAccountInfo(value.toString());
}
}
}
//获取注解上的值
LogRequire logRequire = null;
logRequire = this.getLogRequire(target, method);
//从接口初始化
if (logRequire == null) {
for (Class> clazz : target.getInterfaces()) {
logRequire = getLogRequire(clazz, method);
if (logRequire != null) {
break;//从某个接口中一旦发现注解,不再循环
}
}
}
if (logRequire != null && (!StringUtils.isEmpty(logRequire.operationExplain())) && (!StringUtils.isEmpty(logRequire.operationFunction())) && (!StringUtils.isEmpty(logRequire.operationModel()))) {
////调用日志处理类去处理我们的日志
logInfo.setOperationModel(logRequire.operationModel());
logInfo.setOperationFunction(logRequire.operationFunction());
logInfo.setOperationExplain(logRequire.operationExplain());
logInfo.setIp(this.getIp());
logInfo.setOperationTime(new Date());
logService.saveLog(logInfo);
}
}
/**
* 获取方法或类的注解对象DataSource
*
* @param target
* @param method
* @return
*/
private LogRequire getLogRequire(Class> target, Method method) {
try {
//1.优先方法注解
Class>[] types = method.getParameterTypes();
Method m = target.getMethod(method.getName(), types);
if (m != null && m.isAnnotationPresent(LogRequire.class)) {
return m.getAnnotation(LogRequire.class);
}
//2.其次类注解
if (target.isAnnotationPresent(LogRequire.class)) {
return target.getAnnotation(LogRequire.class);
}
} catch (Exception e) {
e.printStackTrace();
logger.error(MessageFormat.format("通过注解切换数据源时发生异常[class={0},method={1}]:"
, target.getName(), method.getName()), e);
}
return null;
}
private String getIp() throws UnknownHostException {
try {
InetAddress candidateAddress = null;
// 遍历所有的网络接口
for (Enumeration ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements(); ) {
NetworkInterface iface = (NetworkInterface) ifaces.nextElement();
// 在所有的接口下再遍历IP
for (Enumeration inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); ) {
InetAddress inetAddr = (InetAddress) inetAddrs.nextElement();
if (!inetAddr.isLoopbackAddress()) {// 排除loopback类型地址
if (inetAddr.isSiteLocalAddress()) {
// 如果是site-local地址,就是它了
return inetAddr.getHostAddress();
} else if (candidateAddress == null) {
// site-local类型的地址未被发现,先记录候选地址
candidateAddress = inetAddr;
}
}
}
}
if (candidateAddress != null) {
return candidateAddress.getHostAddress();
}
// 如果没有发现 non-loopback地址.只能用最次选的方案
InetAddress jdkSuppliedAddress = InetAddress.getLocalHost();
if (jdkSuppliedAddress == null) {
throw new UnknownHostException("The JDK InetAddress.getLocalHost() method unexpectedly returned null.");
}
return jdkSuppliedAddress.getHostAddress();
} catch (Exception e) {
UnknownHostException unknownHostException = new UnknownHostException(
"Failed to determine LAN address: " + e);
unknownHostException.initCause(e);
throw unknownHostException;
}
}
}
业务层的应用:
package com.soecode.lyf.bookuserservice.service.impl;
import com.soecode.lyf.bookuserservice.dao.BookDao;
import com.soecode.lyf.bookuserservice.pojo.Book;
import com.soecode.lyf.bookuserservice.service.BookService;
import com.soecode.lyf.datasource.DataSource;
import com.soecode.lyf.log.annatation.LogRequire;
import com.soecode.lyf.log.annatation.LogUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BookServiceImpl implements BookService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
// 注入Service依赖
@Autowired
private BookDao bookDao;
@Override
@DataSource("dataSource1")
@LogRequire(operationModel = "book",operationFunction = "根据书的id查询book",operationExplain = "查询操作")
public Book getById(long bookId,@LogUser String userName) {
return bookDao.queryById(bookId);
}
@Override
@DataSource("dataSource1")
@LogRequire(operationModel = "books",operationFunction = "查询所有的书",operationExplain = "查询所有的书")
public List getList() {
return bookDao.queryAll(0, 1000);
}
}
日志基本信息bean
package com.soecode.lyf.log.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* @Description
* @Author DJZ-WWS
* @Date 2019/6/3 13:47
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LogInfo {
/**
* 操作模块
*/
private String operationModel;
/**
* 操作功能
*/
private String operationFunction;
/**
* 操作说明
*/
private String operationExplain;
/**
* 账号信息
*/
private String accountInfo;
/**
* ip归属地
*/
private String ip;
/**
* 操作时间
*/
private Date operationTime;
}
记录日志的服务
package com.soecode.lyf.log.service.impl;
import com.soecode.lyf.log.pojo.LogInfo;
import com.soecode.lyf.log.service.LogService;
import org.springframework.stereotype.Service;
/**
* @Description
* @Author DJZ-WWS
* @Date 2019/6/3 14:09
*/
@Service
public class LogServiceImpl implements LogService {
@Override
public void saveLog(LogInfo logInfo) {
System.out.println("执行保存操作,保存的日志信息为"+logInfo.toString());
}
}
业务层注解的应用
日志结构图
这样就实现了aop对日志的操作。
结果如下:
这里的没用真正的实现保存,伪保存。
日志一般我们不会记录在数据库,数据量大的时候数据库压力大,可以采用mongodb记录日志。
另外记录日志可以采用异步的方式实现日志的记录。
关于异步可以采用MQ或者使用线程的方式实现日志的记录。