利用SpringMVC的AOP来实现后台系统的操作日志记录

最近在项目中要求把后台的一些关键操作记录下来,想了好半天能想到的也就那两三种方式,要么就是写一个拦截器,然后再web.xml里面进行配置,要么就是就是在每个需要记录操作日志的代码里面进行拦截,最后我选择了第三种,也就是基于AOP的拦截,用这种方式,只需要在需记录操作的接口方法上,添加上自定义注解就好了。其实在项目开发里面真正用到AOP感觉不是很多,我也一样很少就没有怎么用到。刚好这次碰见了,就记录下来,也算是自己学习一次。
我在做的项目是基于SSH(Spring,Spring MVC,Hibernate)框架。

  1. 首先我们先想想我们做的功能到底是什么,我们要记录操作,我们首先就应该有一张记录操作记录的表
  `@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的值。
看一下表数据
这里写图片描述
大功告成!

你可能感兴趣的:(成长)