妙用自定义注解,一行代码搞定大功能(文末赠书)

333次阅读  |  发布于2年以前

1.简介

在使用spring完成项目的时候需要完成记录日志,开始以为Spring 的AOP功能,就可以轻松解决,半个小时都不用,可是经过一番了解过后,发现一般的日志记录,只能记录一些简单的操作,例如表名、表名称等记录不到。

这个时侯就用到了自定义注解,把想要记录的内容放在注解中,通过切入点来获取到注解参数,然后将参数插入数据库记录

2.Spring AOP

2.1.关于Spring AOP的一些术语

通知类型

2.2.Spring AOP配置有两种风格:

3.首先自定义注解

定义一个日志描述和一个表名这里根据需要自定义注解

package com.ywj.log;

import java.lang.annotation.*;

/**
 * ClassName Crmlog
 * AOP日志记录 自定义注解类
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemCrmlog {
    /**
     * 日志描述
     * 对于什么表格进行了什么操作
     */
    String description()  default "";

    /**
     * 操作了的表名
     * @return
     */
    String  tableName() default "";
}

3.1.定义切面类,从切入点获取注解信息保存到数据库

对于一些可能碰到的问题我在方法的注释里都有解决办法,大家注意一下,这里我对于方法报错也有处理方法

这里是对于切面类里使用到的两个类解释:

AspectJ使用org.aspectj.lang.JoinPoint接口表示目标类连接点对象,如果是环绕增强时,使用org.aspectj.lang.ProceedingJoinPoint表示连接点对象,该类是JoinPoint的子接口。任何一个增强方法都可以通过将第一个入参声明为JoinPoint访问到连接点上下文的信息。我们先来了解一下这两个接口的主要方法:

1)JoinPoint
2)ProceedingJoinPoint

ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法:

package com.ywj.log;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.ywj.log.biz.Sys_logBiz;
import com.ywj.log.dao.Sys_logDao;
import com.ywj.login.biz.Sys_UserBiz;
import com.ywj.login.dao.Sys_UserDao;
import com.ywj.login.dao.Sys_righDao;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
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.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;


/**
 * @ClassName SystemLogAspect
 * @Author Administrator
 * @Describe  定义切入面类
 */
@Aspect
@Component
public class SystemLogAspect {


    /**
     * 注解Pointcut切入点
     * 定义出一个或一组方法,当执行这些方法时可产生通知
     * 指向你的切面类方法
     * 由于这里使用了自定义注解所以指向你的自定义注解
     */
    @Pointcut("@annotation(com.ywj.log.SystemCrmlog)")
    public void crmAspect() {
    }


    /**
     *抛出异常后通知(@AfterThrowing):方法抛出异常退出时执行的通知
     * 注意在这里不能使用ProceedingJoinPoint
     * 不然会报错ProceedingJoinPoint is only supported for around advice
     * throwing注解为错误信息
     * @param joinPoint
     * @param ex
     */
    @AfterThrowing(value="crmAspect()", throwing="ex")
    public void afterThrowingMethod(JoinPoint joinPoint, Exception ex) throws Exception {
        HttpServletRequest httpServletRequest = getHttpServletRequest();
        //获取管理员用户信息\
        WebUtil webUtil = new WebUtil();
        Map<String, Object> user = webUtil.getUser(httpServletRequest);
        CrmLogMessage log=new CrmLogMessage();
        //获取需要的信息
        String context=getServiceMthodDescription(joinPoint);
        String usr_name="";
        String rolename="";
        if(user!=null){
            usr_name = user.get("usr_name").toString();
            rolename=user.get("rolename").toString();
        }
        //管理员姓名
        log.setUserName(usr_name);
        //角色名
        log.setUserRole(rolename);
        //日志信息
        log.setContent(usr_name+context);
        //设置参数集合
        log.setRemarks(getServiceMthodParams(joinPoint));
        //设置表名
        log.setTableName(getServiceMthodTableName(joinPoint));
        //操作时间
        SimpleDateFormat sif=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        log.setDateTime(sif.format(new Date()));
        //设置ip地址
        log.setIp(httpServletRequest.getRemoteAddr());
        //设置请求地址
        log.setRequestUrl(httpServletRequest.getRequestURI());
        //执行结果
        log.setResult("执行失败");
        //错误信息
        log.setExString(ex.getMessage());
        //将数据保存到数据库
        Sys_logDao sysLogDao=new Sys_logDao();
        sysLogDao.addSys_log(log);
    }


    /**
     * 返回后通知(@AfterReturning):在某连接点(joinpoint)
     * 正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回
     * 方法执行完毕之后
     * 注意在这里不能使用ProceedingJoinPoint
     * 不然会报错ProceedingJoinPoint is only supported for around advice
     * crmAspect()指向需要控制的方法
     *  returning  注解返回值
     * @param joinPoint
     * @param returnValue  返回值
     * @throws Exception
     */
    @AfterReturning(value = "crmAspect()",returning = "returnValue")
    public  void doCrmLog(JoinPoint joinPoint,Object returnValue) throws Exception {
        HttpServletRequest httpServletRequest = getHttpServletRequest();
        //获取管理员用户信息
        WebUtil webUtil = new WebUtil();
        Map<String, Object> user = webUtil.getUser(httpServletRequest);
        CrmLogMessage log=new CrmLogMessage();
        String context=getServiceMthodDescription(joinPoint);

        String usr_name="";
        String rolename="";
        if(user!=null){
         usr_name = user.get("usr_name").toString();
         rolename=user.get("rolename").toString();
        }
        //管理员姓名
        log.setUserName(usr_name);
        //角色名
        log.setUserRole(rolename);
        //日志信息
        log.setContent(usr_name+context);
        //设置参数集合
        log.setRemarks(getServiceMthodParams(joinPoint));
        //设置表名
        log.setTableName(getServiceMthodTableName(joinPoint));
        //操作时间
        SimpleDateFormat sif=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        log.setDateTime(sif.format(new Date()));
        //设置ip地址
        log.setIp(httpServletRequest.getRemoteAddr());
        //设置请求地址
        log.setRequestUrl(httpServletRequest.getRequestURI());
        if(returnValue!=null){
           if(returnValue instanceof List){
               List ls= (List) returnValue;
               if(ls.size()>0){
                   log.setResult("执行成功");
               }else{
                   log.setResult("执行成功");
               }
           }else if(returnValue instanceof Boolean){
               Boolean falg= (Boolean) returnValue;
               if(falg){
                   log.setResult("执行成功");
               }else{
                   log.setResult("执行失败");
               }
           }else if(returnValue instanceof Integer){
               Integer i= (Integer) returnValue;
               if(i>0){
                   log.setResult("执行成功");
               }else{
                   log.setResult("执行失败");
               }
           }else{
               log.setResult("执行成功");
           }

        }
        //将数据保存到数据库
        Sys_logDao sysLogDao=new Sys_logDao();
        sysLogDao.addSys_log(log);
    }


    /**
     *获取自定义注解里的日志描述
     * @param joinPoint
     * @return 返回注解里面的日志描述
     * @throws Exception
     */
    private String getServiceMthodDescription(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();
        String description = "";
        for(Method method : methods) {
            //判断方法名是不是一样
            if(method.getName().equals(methodName)) {
                //对比参数数组的长度
                Class[] clazzs = method.getParameterTypes();
                if(clazzs.length == arguments.length) {
                    //获取注解里的日志信息
                    description = method.getAnnotation(SystemCrmlog.class).description();
                    break;
                }
            }
        }
        return description;
    }

    /**
     *获取自定义注解里的表名
     * @param joinPoint
     * @return 返回注解里的表名字
     * @throws Exception
     */
    private String getServiceMthodTableName(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();
        //表名
        String tableName = "";
        for (Method method : methods) {
            //判断方法名是不是一样
            if (method.getName().equals(methodName)) {
                //对比参数数组的长度
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    //获取注解里的表名
                    tableName = method.getAnnotation(SystemCrmlog.class).tableName();
                    break;
                }
            }
        }
        return tableName;
    }

    /**
     * 获取json格式的参数用于存储到数据库中
     * @param joinPoint
     * @return
     * @throws Exception
     */
    private String getServiceMthodParams(JoinPoint joinPoint)
            throws Exception {
        Object[] arguments = joinPoint.getArgs();
        ObjectMapper om=new ObjectMapper();
        return om.writeValueAsString(arguments);
    }

    /**
     * 获取当前的request
     * 这里如果报空指针异常是因为单独使用spring获取request
     * 需要在配置文件里添加监听
     * <listener>
     * <listener-class>
     * org.springframework.web.context.request.RequestContextListener
     * </listener-class>
     * </listener>
     * @return
     */
    public HttpServletRequest getHttpServletRequest(){
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes)ra;
        HttpServletRequest request = sra.getRequest();
        return request;
    }

}

每个切面传递的数据的都不一样,最终决定,获取切面的所有参数,转成json字符串,保存到数据库中。

相关类:

日志信息类

package com.ywj.log;

/**
 * @ClassName CrmLogMessage
 * @Author Administrator
 * @Describe 数据库日志类
 */
public class CrmLogMessage {
    private Integer logid;//日志id
    private String UserName;//管理员姓名
    private String UserRole;//管理员角色
    private String Content;//日志描述
    private String Remarks;//参数集合
    private String TableName;//表格名称
    private String DateTime;//操作时间
    private String resultValue;//返回值
    private String ip;//ip地址
    private String  requestUrl;//请求地址
    private String result;//操作结果
    private  String ExString;//错误信息

    public CrmLogMessage() {
    }

    @Override
    public String toString() {
        return "CrmLogMessage{" +
                "logid=" + logid +
                ", UserName='" + UserName + '\'' +
                ", UserRole='" + UserRole + '\'' +
                ", Content='" + Content + '\'' +
                ", Remarks='" + Remarks + '\'' +
                ", TableName='" + TableName + '\'' +
                ", DateTime='" + DateTime + '\'' +
                ", resultValue='" + resultValue + '\'' +
                ", ip='" + ip + '\'' +
                ", requestUrl='" + requestUrl + '\'' +
                ", result='" + result + '\'' +
                ", ExString='" + ExString + '\'' +
                '}';
    }

    public CrmLogMessage(Integer logid, String userName, String userRole, String content, String remarks, String tableName, String dateTime, String resultValue, String ip, String requestUrl, String result, String exString) {
        this.logid = logid;
        UserName = userName;
        UserRole = userRole;
        Content = content;
        Remarks = remarks;
        TableName = tableName;
        DateTime = dateTime;
        this.resultValue = resultValue;
        this.ip = ip;
        this.requestUrl = requestUrl;
        this.result = result;
        ExString = exString;
    }

    public String getExString() {
        return ExString;
    }

    public void setExString(String exString) {
        ExString = exString;
    }

    public Integer getLogid() {
        return logid;
    }

    public void setLogid(Integer logid) {
        this.logid = logid;
    }

    public String getUserName() {
        return UserName;
    }

    public void setUserName(String userName) {
        UserName = userName;
    }

    public String getUserRole() {
        return UserRole;
    }

    public void setUserRole(String userRole) {
        UserRole = userRole;
    }

    public String getContent() {
        return Content;
    }

    public void setContent(String content) {
        Content = content;
    }

    public String getRemarks() {
        return Remarks;
    }

    public void setRemarks(String remarks) {
        Remarks = remarks;
    }

    public String getTableName() {
        return TableName;
    }

    public void setTableName(String tableName) {
        TableName = tableName;
    }

    public String getDateTime() {
        return DateTime;
    }

    public void setDateTime(String dateTime) {
        DateTime = dateTime;
    }

    public String getResultValue() {
        return resultValue;
    }

    public void setResultValue(String resultValue) {
        this.resultValue = resultValue;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public String getRequestUrl() {
        return requestUrl;
    }

    public void setRequestUrl(String requestUrl) {
        this.requestUrl = requestUrl;
    }

    public String getResult() {
        return result;
    }

    public void setResult(String result) {
        this.result = result;
    }
}

用来获取登录用户信息的帮助类:

package com.ywj.log;

import com.base.web.BaseAction;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * @ClassName WebUtil
 * @Author Administrator
 * @Describe  日志帮助类 用来获取session中的用户信息来存入数据库
 */
public class WebUtil  {


    /**
     * 从session中获取到用户对象
     * @return
     */
    public Map<String, Object>   getUser(HttpServletRequest request){
        Map<String, Object> attribute=null;
        if(request!=null){
        Object user = request.getSession().getAttribute(Constans.USER_KEY);
      attribute = (Map<String, Object>) user;}
    return attribute;
}

}

在你的spring-context.xml中配置

  <!-- 启动对@AspectJ注解的支持  -->
    <aop:aspectj-autoproxy proxy-target-class="true" />
    <!-- 自动扫描包路径  -->
 <!--你需要刚才的切面类的包路径-->
    <context:component-scan base-package="com.ywj.log" />
   <!--你需要注解方法的包路径-->
    <context:component-scan base-package="com.*.*.biz.impl" />

然后在你需要记录的方法上加上注解

@SystemCrmlog(description = "进行了登录操作",tableName =Constans.USER_TABLENAME)

效果这里表名使用了常量类 对于一些表的信息可以写一个常量类 然后执行登录操作数据库记录为:

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8