最近在项目中要求把后台的一些关键操作记录下来,想了好半天能想到的也就那两三种方式,要么就是写一个拦截器,然后再web.xml里面进行配置,要么就是就是在每个需要记录操作日志的代码里面进行拦截,最后我选择了第三种,也就是基于AOP的拦截,用这种方式,只需要在需记录操作的接口方法上,添加上自定义注解就好了。其实在项目开发里面真正用到AOP感觉不是很多,我也一样很少就没有怎么用到。刚好这次碰见了,就记录下来,也算是自己学习一次。
我在做的项目是基于SSH(Spring,Spring MVC,Hibernate)框架。
`@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@Entity
@Table(name = "表名")
public class LogModel extends IdEntity {
//用户
private User user;
//IP
private String remoteAddr;
//异常
private String exception;
//日志标题
private String title;
//请求地址
private String requestUri;
//日志类型
private String type;
//日志记录描述
private String description;
以上是我所用到的实体类
2. 关于实体类的Service层,Dao层,Impl等等我就不再这里过多叙述,无非就是一些与数据库操作挂钩的一些代码。
3. 自定义一个切面注解
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MethodLog {
/**
* 该注解作用于方法上时需要备注信息
*/
String remarks() default "";
}
4. 接下来就是重点了,切面类的具体实现。
@Component
@Aspect
public class SystemLogAspect {
//这段代码调用了org.slf4j.LoggerFactory line:280
private static final Logger logger = LoggerFactory.getLogger(SystemLogAspect.class);
private static final ThreadLocal beginTimeThreadLocal =
new NamedThreadLocal("ThreadLocal beginTime");
private static final ThreadLocal logThreadLocal =
new NamedThreadLocal("ThreadLocal log");
private static final ThreadLocal currentUser = new NamedThreadLocal("ThreadLocal user");
@Autowired
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
/**
* Controller层切点 注解拦截
*/
@Pointcut("@annotation(com.shopping.logrecord.MethodLog)")
public void controllerAspect() {
}
/**
* 用于拦截Controller层记录用户的操作的开始时间
*
* @param joinPoint 切点
* @throws InterruptedException
*/
@Before("controllerAspect()")
public void doBefore(JoinPoint joinPoint) throws InterruptedException {
Date beginTime = new Date();
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
beginTimeThreadLocal.set(beginTime);//线程绑定变量(该数据只有当前请求的线程可见)
if (logger.isDebugEnabled()) {//这里日志级别为debug
logger.debug("开始计时: {} URI: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
.format(beginTime), request.getRequestURI());
}
//读取session中的用户
HttpSession session = request.getSession();
User user = (User) session.getAttribute("user");
System.out.println(user);
currentUser.set(user);
}
/**
* 用于拦截Controller层记录用户的操作
*
* @param joinPoint 切点
*/
@SuppressWarnings("unchecked")
@After("controllerAspect()")
public void doAfter(JoinPoint joinPoint)throws Exception {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
User user = currentUser.get();
if (user != null) {
String title = "";
String type = "info"; //日志类型(info:入库,error:错误)
String remoteAddr = SystemLogAspect.getIp();//请求的IP
String requestUri = request.getRequestURI();//请求的Uri
try {
title = getControllerMethodDescription2(joinPoint);
} catch (Exception e) {
e.printStackTrace();
}
LogModel logModel = new LogModel();
logModel.setTitle(title);
logModel.setType(type);
logModel.setRemoteAddr(remoteAddr);
logModel.setRequestUri(requestUri);
logModel.setException("无异常");
logModel.setUserId(user.getId());
User users = userService.getObjById(user.getId());
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format = simpleDateFormat.format(date);
logModel.setAddTime(date);
logModel.setUserName(users.getUserName());
//通过线程池来执行日志保存
threadPoolTaskExecutor.execute(new SaveLogThread(logModel, logModelService));
logThreadLocal.set(logModel);
}
}
/**
* 异常通知 记录操作报错日志
* @param joinPoint
* @param e
*/
@AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
LogModel logModel = logThreadLocal.get();
logModel.setType("error");
logModel.setException(e.toString());
new UpdateLogThread(logModel, logModelService).start();
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
* @param joinPoint 切点
* @return 方法描述
*/
public static String getControllerMethodDescription2(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
MethodLog controllerLog = method
.getAnnotation(MethodLog.class);
String discription = controllerLog.remarks();
return discription;
}
/**
* 获取请求ip
*/
public static String getIp()throws Exception {
InetAddress ia=null;
ia=ia.getLocalHost();
String localip=ia.getHostAddress();
return localip;
}
/**
* 保存日志线程
*
* @author lin.r.x
*
*/
private static class SaveLogThread implements Runnable {
private LogModel logModel;
private LogModelService logModelService;
public SaveLogThread(LogModel logModel, LogModelService logModelService) {
this.logModel = logModel;
this.logModelService = logModelService;
}
@Override
public void run() {
logModelService.save(logModel);
}
}
/**
* 日志更新线程
*
* @author lin.r.x
*
*/
private static class UpdateLogThread extends Thread {
private LogModel logModel;
private LogModelService logModelService;
public UpdateLogThread(LogModel logModel, LogModelService logModelService) {
super(UpdateLogThread.class.getSimpleName());
this.logModel = logModel;
this.logModelService = logModelService;
}
@Override
public void run() {
this.logModelService.update(logModel);
}
}
}
接下来我们需要做一步操作,那就是在XML中开启对AOP的支持
<aop:aspectj-autoproxy/>
//放置切面实现类的包路径
<context:component-scan base-package="com.shopping.logrecord.logss"/>
//日志表的service的包路径
<context:component-scan base-package="com.shopping.logrecord.logservicess"/>
<bean id="threadPoolTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="5" />
<property name="maxPoolSize" value="10" />
bean>
OKAY,到这我们就基本算是配置完成了。
接下来,加入我们需要执行编辑操作
在编辑接口方法上添加自定义的注解
okay,完成,**注意**remarks的值。
看一下表数据
大功告成!