SpringAOP的应用实践之日志管理

SpringAOP 日志管理

前言:日志在系统中是必不可上的部分,系统日志,操作日志,异常日志等等。这些日志功能在系统中是非常常见的,现在有成熟的日志框架使用,像log4j等等。日志信息是非功能性代码,适合采用SpringAOP进行分离,集中管理。不需要在原有的功能上进行修改,符合开闭原则。方便开发人员集中精力进行业务开发。利用SpringAOP实现业务操作日志管理,登陆日志管理,异常日志管理。
一、业务操作日志的实现
功能实现:在Service层切入,通过连接点获取调用的方法,通过自定义的注解,注明调用方法的类型和名称。针对用户增、删、改、查进行信息保存。
1.1 业务日志的表结构

/*
Navicat MySQL Data Transfer

Source Server         : yuanjun
Source Server Version : 50519
Source Host           : localhost:3306
Source Database       : first_db

Target Server Type    : MYSQL
Target Server Version : 50519
File Encoding         : 65001

Date: 2018-01-05 21:26:20
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for log
-- ----------------------------
DROP TABLE IF EXISTS `log`;
CREATE TABLE `log` (
  `logid` int(11) NOT NULL AUTO_INCREMENT,
  `ip` varchar(255) DEFAULT NULL COMMENT 'ip地址',
  `operateUserName` varchar(255) DEFAULT NULL COMMENT '操作人员',
  `operationName` varchar(255) DEFAULT NULL COMMENT '操作',
  `operationType` varchar(255) DEFAULT NULL COMMENT '操作类型',
  `operationDate` datetime DEFAULT NULL COMMENT '操作时间',
  `operationTime` int(11) DEFAULT NULL COMMENT '操作时长',
  `state` int(1) DEFAULT NULL COMMENT '操作状态',
  `description` varchar(255) DEFAULT NULL COMMENT '状态描述',
  PRIMARY KEY (`logid`)
) ENGINE=InnoDB AUTO_INCREMENT=59 DEFAULT CHARSET=utf8;

1.2 对应的实体bean

import java.util.Date;

/**
 * 
 * @ClassName:Log
 * @Description :操作日志
 * @author yuanjun
 * @date 2018-1-4
 */
public class Log {

    private int logid;

    private String ip;//操作人的ip

    private String operateUserName;//操作人

    private String operationName;//操作名

    private String operationType;//操作类型

    private Date  operationDate;//操作时间

    private long operationTime;//操作时长

    private int state;//操作状态

    private String description;//操作描述

    ...get set方法

}

采用的SSM框架进行的演示,需要进行日志信息的保存。由于log插入不是内容的重点,没有粘贴处对应的代码。读者可自己完成。
1.3 自定义注解,用于描述操作的类型和描述

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemServiceLog {
     /** 要执行的操作类型比如:add操作 **/  
     public String operationType() default "";  

     /** 要执行的具体操作比如:添加用户 **/  
     public String operationName() default "";
}

1.4 切面的具体操作

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;


import com.yuanjun.anno.SystemServiceLog;
import com.yuanjun.bean.ExceptionLog;
import com.yuanjun.bean.Log;
import com.yuanjun.log.service.ExceptionLogService;
import com.yuanjun.log.service.LogService;
import com.yuanjun.log.util.IpUtil;
/**
 * 日志切面类
 * @ClassName:LogAspect
 * @Description :TODO
 * @author yuanjun
 * @date 2018-1-5
 */
public class LogAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionAspect.class);

    @Autowired
    private LogService logService;

    @Autowired
    private ExceptionLogService exceptionLogService;
     //获取开始时间
    private long BEGIN_TIME ;

    //获取结束时间
    private long END_TIME;



    //定义本次log实体
    private Log log = new Log();

    /**
     * 前置通知
     */
    public void before() {
         BEGIN_TIME = new Date().getTime();
         System.out.println("开始");
    }
    /**
     * 后置通知
     * @param joinPoint
     * @throws Exception
     */
    public void after(JoinPoint joinPoint) throws Exception{
        END_TIME = new Date().getTime();
        System.out.println("结束");
    }
    /**
     * 返回值
     */
    public void afterReturn(){
       if(log.getState()==1||log.getState()==-1){
            log.setOperationTime((END_TIME-BEGIN_TIME));
            log.setOperationDate(new Date(BEGIN_TIME));
            System.out.println(">>>>>>>>>>存入到数据库");
            logService.insertLog(log);
        }else {
            System.out.println(">>>>>>>>不存入到数据库");
            logService.insertLog(log);
        }
    }

    public void doAfterThrow(Exception ex){
        System.out.println(ex);
        System.out.println("例外通知-----------------------------------");
    }

    /**
     * 
     * @param joinPoint
     * @return
     */
    public Object aroud(ProceedingJoinPoint joinPoint){
          //日志实体对象
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Object object = null;
        try {
            Map<String, Object> map = getServiceOperation(joinPoint);
            //获取操作类型
            String operationType = (String) map.get("operationType");
            //获取操作描述
            String operationName = (String) map.get("operationName");
            //获取操作的IP地址
            String ip = IpUtil.getRemortIP(request);
            log.setIp(ip);
            log.setOperationName(operationName);
            log.setOperationType(operationType);
            try {
                object = joinPoint.proceed();
                log.setDescription("执行成功");
                log.setState( 1);
            } catch (Throwable e) {
                // TODO Auto-generated catch block
                log.setDescription("执行失败");
                log.setState(-1);
                //异常日志处理
                //handleException(joinPoint, e);
            }

        } catch (Exception e) {
        }
        return object;
    }


    /**
     * 通过连接点获取注解的信息,即操作的类型与描述
     * @param joinPoint
     * @return
     * @throws Exception
     */
    public static Map<String,Object> getServiceOperation(JoinPoint joinPoint)    
            throws Exception {    
       String targetName = joinPoint.getTarget().getClass().getName();    
       String methodName = joinPoint.getSignature().getName();    
       Object[] arguments = joinPoint.getArgs();    
       Class targetClass = Class.forName(targetName);    
       Method[] methods = targetClass.getMethods();    
       Map<String,Object> map = new HashMap<String,Object>();
       String operationType = "";
       String operationName = "";
        for (Method method : methods) {    
            if (method.getName().equals(methodName)) {    
               Class[] clazzs = method.getParameterTypes();    
                if (clazzs.length == arguments.length) {    
                    operationType = method.getAnnotation(SystemServiceLog. class).operationType(); 
                    operationName = method.getAnnotation(SystemServiceLog. class).operationName();    
                    map.put("operationType", operationType);
                    map.put("operationName", operationName);
                    break;    
               }    
           }    
       }    
        return map; 
    }
}

获取远程IP地址

import javax.servlet.http.HttpServletRequest;

/**
 * 
 * @ClassName:IpUtil
 * @Description : 获取远程ip的工具类
 * @author yuanjun
 * @date 2018-1-5
 */
public class IpUtil {
     /**  
     * 获取远程访问主机ip地址  
     *   
     * 创建时间:2017年2月24日  
     *   
     * @author HY  
     * @param request  
     * @return  
     */  
    public static String getRemortIP(HttpServletRequest request) {  
        String ip = request.getHeader("x-forwarded-for");  
        if ((ip == null) || (ip.length() == 0) || ("unknown".equalsIgnoreCase(ip))) {  
            ip = request.getHeader("Proxy-Client-IP");  
        }  
        if ((ip == null) || (ip.length() == 0) || ("unknown".equalsIgnoreCase(ip))) {  
            ip = request.getHeader("WL-Proxy-Client-IP");  
        }  
        if ((ip == null) || (ip.length() == 0) || ("unknown".equalsIgnoreCase(ip))) {  
            ip = request.getRemoteAddr();  
        }  
        return ip;  
    }  
}

LogService 提供向数据库插入log信息方法
1.5 本次采用的xml的配置(注解配置同理)
由Spring来管理切面类

 <!--通知spring使用cglib而不是jdk的来生成代理方法 AOP可以拦截到Controller --> 
    <aop:aspectj-autoproxy proxy-target-class="true" />
    <!-- 配置service切面bean -->
    <bean class="com.yuanjun.aop.LogAspect" id="myAspect"></bean>

    <aop:config>
        <!-- 定义service切面 -->
        <aop:pointcut expression="execution(* com.yuanjun.service.*.*(..))" id="txpointcut"/>

        <!-- 数值越小,优先级越高 -->
        <aop:aspect ref="myAspect" order="-998">
            <!-- 前置通知 -->
            <aop:before method="before" pointcut-ref="txpointcut"/>
            <aop:around method="aroud" pointcut-ref="txpointcut"/>
            <!-- 后置通知 -->
            <aop:after method="after" pointcut-ref="txpointcut"/>
            <aop:after-returning method="afterReturn" pointcut-ref="txpointcut"/>
        </aop:aspect>
    </aop:config>

1.6 页面访问测试结果
这里没有对用户信息保存,实际操作中用户信息还是很好获取的,可在session获取,获取在请求参数中获取
控制台展示

数据库展示
二、异常日志
异常日志在时间中这个是挺需要的,产品上线运行后,一些异常会被记录到log文件,一些不被重视的异常不容易发现,需要到log文件中找。有了这个记录,方便查找,和维护系统
2.1 异常日志的对应的bean与表设计

package com.yuanjun.bean;

import java.util.Date;

/**
 * 
 * @ClassName:ExceptionLog
 * @Description :异常信息日志表
 * @author yuanjun
 * @date 2018-1-5
 */
public class ExceptionLog {

    private int id;

    private String ip;//请求的ip地址

    private String url;//请求的url

    private String args;//请求参数

    private String className;//发生异常的类名

    private String methodName;//执行的方法名

    private String exceptionType;//异常类型

    private Date  exceptionTime;//发生异常时间

    private String exceptionMsg;//异常信息

    private byte isView;//是否查看

    ...get Set方法
}
/*
Navicat MySQL Data Transfer

Source Server         : yuanjun
Source Server Version : 50519
Source Host           : localhost:3306
Source Database       : first_db

Target Server Type    : MYSQL
Target Server Version : 50519
File Encoding         : 65001

Date: 2018-01-05 21:57:01
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for exceptionlog
-- ----------------------------
DROP TABLE IF EXISTS `exceptionlog`;
CREATE TABLE `exceptionlog` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `ip` varchar(255) DEFAULT NULL,
  `url` varchar(255) DEFAULT NULL,
  `args` varchar(255) DEFAULT NULL,
  `className` varchar(255) DEFAULT NULL,
  `methodName` varchar(255) DEFAULT NULL,
  `exceptionType` varchar(255) DEFAULT NULL,
  `exceptionTime` datetime DEFAULT NULL,
  `exceptionMsg` varchar(2000) DEFAULT NULL,
  `isView` varchar(1) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

异常处理的思路:
在aroud中,执行代理方法,捕获object = joinPoint.proceed();中的异常信息,即方法执行过程中产生的异常。将异常在哪个类,调用哪个方法产生什么类型的异常记录下来,存入到数据库中。在之前的业务日志信息处理把异常处理加上即可

/**
     * 异常日志处理
     * @param joinPoint
     * @param ex
     */
    public  void  handleException(JoinPoint joinPoint,Throwable ex){
        LOGGER.info(">>>>>>系统异常,记录异常信息到数据库------start------"); 
        ExceptionLog log = new ExceptionLog();
        HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.
                getRequestAttributes()).getRequest(); 
        //获取请求的URL
        StringBuffer requestURL = request.getRequestURL();
        //获取参 数信息
        String queryString = request.getQueryString(); 
        //封装完整请求URL带参数
        if(queryString != null){  
            requestURL .append("?").append(queryString);  
        }
        String ip = IpUtil.getRemortIP(request);
        //代理类
        String className = joinPoint.getTarget().getClass().getName();
        //执行的方法
        String methodName = joinPoint.getSignature().getName();
        //
        Object[] argsList = joinPoint.getArgs();
        StringBuffer args = new StringBuffer();
        for (int i = 0; i < argsList.length; i++) {
            args.append(argsList[i]);
        }
        String exceptionType = ex.getClass().getSimpleName();
        Date exceptionTime = new Date();
        String exceptionMsg = ex.getMessage();
        byte isView = 0;
        log.setIp(ip);
        log.setUrl(requestURL.toString());
        log.setArgs(args.toString());
        log.setClassName(className);
        log.setMethodName(methodName);
        log.setExceptionType(exceptionType);
        log.setExceptionTime(exceptionTime);
        log.setExceptionMsg(exceptionMsg);
        log.setIsView(isView);
        System.out.println(log);
        exceptionLogService.saveExceptionLog(log);
        LOGGER.info(">>>>>>系统异常,记录异常信息到数据库------end------");  

    }

三、用户登录日志
3.1 实现分析
针对用户登入的请求进行切面,植入保存用户登录日志信息。也可在处理用户登录逻辑的时候进行的日志处理,为了统一管理和代码分离,选择SpringAOP统一处理。通过拦截Controller中请求来处理。在Springmvc中配置处理Controller拦截时,如果aop的配置与mvc的配置不在同一个xml文件中的时候,会出现请求拦截不到,由于不是一个上下文产生的对象,代理拦截不到,具体的原因了自行百度。
3.2 登录日志的bean与表结构

import java.util.Date;

/**
 * 
 * @ClassName:LoginLog
 * @Description :登陆日志
 * @author yuanjun
 * @date 2018-1-5
 */
public class LoginLog {
    private int id;

    private String loginName;//登陆名

    private String loginIp;//登陆ip

    private Date loginTime;//登陆时间

    private Date loginOutTime;//退出时间

    private String loginStatus;//登陆状态
    //get set方法
    }

表结构

/*
Navicat MySQL Data Transfer

Source Server         : yuanjun
Source Server Version : 50519
Source Host           : localhost:3306
Source Database       : first_db

Target Server Type    : MYSQL
Target Server Version : 50519
File Encoding         : 65001

Date: 2018-01-06 10:47:11
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for loginlog
-- ----------------------------
DROP TABLE IF EXISTS `loginlog`;
CREATE TABLE `loginlog` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `loginName` varchar(255) DEFAULT NULL,
  `loginIp` varchar(255) DEFAULT NULL,
  `loginTime` datetime DEFAULT NULL,
  `loginOutTime` datetime DEFAULT NULL,
  `loginStatus` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;

切面层代码演示

import java.util.Date;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.yuanjun.bean.ExceptionLog;
import com.yuanjun.bean.LoginLog;
import com.yuanjun.log.service.ExceptionLogService;
import com.yuanjun.log.service.LogService;
import com.yuanjun.log.service.LoginLogService;
import com.yuanjun.log.util.IpUtil;

/**
 * 
 * @ClassName:ControllerAspect
 * @Description :controller的切面控制
 * @author yuanjun
 * @date 2018-1-4
 */

public class ControllerAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionAspect.class);

    @Autowired
    private LoginLogService loginLogService;

    @Autowired
    private LogService logService;

    @Autowired
    private ExceptionLogService exceptionLogService;    


    //定义本次log实体
    private LoginLog log = new LoginLog();

    public void before(){
        System.out.println("controller前置通知");
    }

    public void afterReturn(){
            log.setLoginTime(new Date());
            loginLogService.saveLoginLog(log);
    }

    /**
     * 环绕处理
     * @param joinPoint
     * @return
     */
    public Object aroud(ProceedingJoinPoint joinPoint){
          //日志实体对象
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        //用户信息的获取,可从session中获取,如果只针对login方法的,可以从请求中获取
        String  userName = "张三";
        Object object = null;
        try {           
            String ip = IpUtil.getRemortIP(request);
            log.setLoginIp(ip);
            log.setLoginName(userName);
            try {
                object = joinPoint.proceed();
                //根据登陆逻辑的处理结果,来判断登陆操作是否成功
                if("main".equals(object)){
                    log.setLoginStatus("success");
                }else{
                    log.setLoginStatus("fail");
                }
            } catch (Throwable e) {
                log.setLoginStatus("fail");
                handleException(joinPoint, e);
            }

        } catch (Exception e) {
        }
        return object;
    }


    /**
     * 处理异常
     * @param joinPoint
     * @param ex
     */
    public  void  handleException(JoinPoint joinPoint,Throwable ex){
        LOGGER.info(">>>>>>系统异常,记录异常信息到数据库------start------"); 
        ExceptionLog log = new ExceptionLog();
        HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.
                getRequestAttributes()).getRequest(); 
        //获取请求的URL
        StringBuffer requestURL = request.getRequestURL();
        //获取参 数信息
        String queryString = request.getQueryString(); 
        //封装完整请求URL带参数
        if(queryString != null){  
            requestURL .append("?").append(queryString);  
        }
        String ip = IpUtil.getRemortIP(request);
        //代理类
        String className = joinPoint.getTarget().getClass().getName();
        //执行的方法
        String methodName = joinPoint.getSignature().getName();
        //
        Object[] argsList = joinPoint.getArgs();
        StringBuffer args = new StringBuffer();
        for (int i = 0; i < argsList.length; i++) {
            args.append(argsList[i]);
        }
        String exceptionType = ex.getClass().getSimpleName();
        Date exceptionTime = new Date();
        String exceptionMsg = ex.getMessage();
        byte isView = 0;
        log.setIp(ip);
        log.setUrl(requestURL.toString());
        log.setArgs(args.toString());
        log.setClassName(className);
        log.setMethodName(methodName);
        log.setExceptionType(exceptionType);
        log.setExceptionTime(exceptionTime);
        log.setExceptionMsg(exceptionMsg);
        log.setIsView(isView);
        System.out.println(log);
        exceptionLogService.saveExceptionLog(log);
        LOGGER.info(">>>>>>系统异常,记录异常信息到数据库------end------");  

    }
}

Spring中xml配置切面bean

<!-- 配置控制层异常处理 -->
    <bean class="com.yuanjun.aop.ExceptionAspect"/>

    <!-- 配置control切面bean -->
    <bean class="com.yuanjun.aop.ControllerAspect" id="controllerAspect"></bean>
    <aop:config>
        <!-- 定义controller切面 -->
        <aop:pointcut expression="execution(* com.yuanjun.control.LoginControl.*(..))" id="controlpointcut"/>
        <!-- 数值越小,优先级越高 -->
        <aop:aspect ref="controllerAspect" order="-999">
            <!-- 后置通知 -->
            <aop:before method="before" pointcut-ref="controlpointcut"/>
            <aop:around method="aroud" pointcut-ref="controlpointcut"/>

            <aop:after-returning method="afterReturn" pointcut-ref="controlpointcut"/>

        </aop:aspect>
    </aop:config>

由于演示采用的是SSM框架,springMVC的配置与aop的配置不在同一个xml中,出现AOP在Controller不起作用,解决办法,在springmvc中的xml文件添加,扫描的包为切面实现类所在的包

  <!-- 保证在同一个容器中 -->
    <aop:aspectj-autoproxy/>
    <context:component-scan base-package="com.yuanjun.aop"/>
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.yuanjun.bean.User;
import com.yuanjun.service.UserService;

@Controller
public class LoginControl {
    @Autowired
    private UserService userService;

    @RequestMapping("/login")
    public String login(String userName,String password){
        //简单的展示了登陆逻辑,根据自己项目的实际处理,这里登陆处理成功的标识,与
        //AOP的处理保持一致,主要是为了获取登陆日志中是否登陆成功的字段信息
        if("yuanjun".equals(userName)&&"123456".equals(password)){
            return "main";
        }
        return "error";

    }

}

通过SpringAOP拦截Serivce与controller完成登陆日志,用户操作日志与异常日志等功能。将烦锁的日志进行一个统一管理。不需要到处进行异常处理。通过这个实例Demo加深连接SpringAOP是如何使用以及好处。
源码下载

猜你喜欢

转载自blog.csdn.net/shenbug/article/details/78985609