欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

springboot aop 自定义注解方式实现一套完善的日志记录(完整源码)

程序员文章站 2022-04-06 13:12:06
本文主要记录如何使用aop切面的方式来实现日志记录功能。 主要记录的信息有: 操作人,方法名,参数,运行时间,操作类型(增删改查),详细描述,返回值。 ......

版权声明:本文为博主原创文章,欢迎转载,转载请注明作者、原文超链接

一:功能简介

本文主要记录如何使用aop切面的方式来实现日志记录功能。

主要记录的信息有: 操作人,方法名,参数,运行时间,操作类型(增删改查),详细描述,返回值。

二:项目结构图

springboot aop 自定义注解方式实现一套完善的日志记录(完整源码)

三:代码实现

1.配置文件

这里只有两个配置:
1)server.port=11000,设置项目启动的端口号,防止被其他服务占用;
2)spring.aop.auto=true,开启spring的aop配置,简单明了,不需要多配置其他的配置或注解。
application.yml文件
server:
  port: 11000
spring:
  aop:
    auto: true #启动aop配置

 2.aop切点类

这个是最主要的类,可以使用自定义注解或针对包名实现aop增强。

1)这里实现了对自定义注解的环绕增强切点,对使用了自定义注解的方法进行aop切面处理;

2)对方法运行时间进行监控;

3)对方法名,参数名,参数值,对日志描述的优化处理;

在方法上增加@aspect 注解声明切面,使用@pointcut 注解定义切点,标记方法。

 使用切点增强的时机注解:@before,@around,@afterreturning,@afterthrowing,@after

package com.wwj.springboot.aop;

import com.alibaba.fastjson.json;
import com.wwj.springboot.annotation.operationlogdetail;
import com.wwj.springboot.model.operationlog;
import org.aspectj.lang.joinpoint;
import org.aspectj.lang.proceedingjoinpoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.methodsignature;
import org.springframework.stereotype.component;

import java.util.date;
import java.util.hashmap;
import java.util.map;
import java.util.uuid;

/**
 * created by intellij idea
 *
 * @author weiwenjun
 * @date 2018/9/12
 */
@aspect
@component
public class logaspect {

    /**
     * 此处的切点是注解的方式,也可以用包名的方式达到相同的效果
     * '@pointcut("execution(* com.wwj.springboot.service.impl.*.*(..))")'
     */
    @pointcut("@annotation(com.wwj.springboot.annotation.operationlogdetail)")
    public void operationlog(){}


    /**
     * 环绕增强,相当于methodinterceptor
     */
    @around("operationlog()")
    public object doaround(proceedingjoinpoint joinpoint) throws throwable {
        object res = null;
        long time = system.currenttimemillis();
        try {
            res =  joinpoint.proceed();
            time = system.currenttimemillis() - time;
            return res;
        } finally {
            try {
                //方法执行完成后增加日志
                addoperationlog(joinpoint,res,time);
            }catch (exception e){
                system.out.println("logaspect 操作失败:" + e.getmessage());
                e.printstacktrace();
            }
        }
    }

    private void addoperationlog(joinpoint joinpoint, object res, long time){
        methodsignature signature = (methodsignature)joinpoint.getsignature();
        operationlog operationlog = new operationlog();
        operationlog.setruntime(time);
        operationlog.setreturnvalue(json.tojsonstring(res));
        operationlog.setid(uuid.randomuuid().tostring());
        operationlog.setargs(json.tojsonstring(joinpoint.getargs()));
        operationlog.setcreatetime(new date());
        operationlog.setmethod(signature.getdeclaringtypename() + "." + signature.getname());
        operationlog.setuserid("#{currentuserid}");
        operationlog.setusername("#{currentusername}");
        operationlogdetail annotation = signature.getmethod().getannotation(operationlogdetail.class);
        if(annotation != null){
            operationlog.setlevel(annotation.level());
            operationlog.setdescribe(getdetail(((methodsignature)joinpoint.getsignature()).getparameternames(),joinpoint.getargs(),annotation));
            operationlog.setoperationtype(annotation.operationtype().getvalue());
            operationlog.setoperationunit(annotation.operationunit().getvalue());
        }
        //todo 这里保存日志
        system.out.println("记录日志:" + operationlog.tostring());
//        operationlogservice.insert(operationlog);
    }

    /**
     * 对当前登录用户和占位符处理
     * @param argnames 方法参数名称数组
     * @param args 方法参数数组
     * @param annotation 注解信息
     * @return 返回处理后的描述
     */
    private string getdetail(string[] argnames, object[] args, operationlogdetail annotation){

        map<object, object> map = new hashmap<>(4);
        for(int i = 0;i < argnames.length;i++){
            map.put(argnames[i],args[i]);
        }

        string detail = annotation.detail();
        try {
            detail = "'" + "#{currentusername}" + "'=》" + annotation.detail();
            for (map.entry<object, object> entry : map.entryset()) {
                object k = entry.getkey();
                object v = entry.getvalue();
                detail = detail.replace("{{" + k + "}}", json.tojsonstring(v));
            }
        }catch (exception e){
            e.printstacktrace();
        }
        return detail;
    }

    @before("operationlog()")
    public void dobeforeadvice(joinpoint joinpoint){
        system.out.println("进入方法前执行.....");

    }

    /**
     * 处理完请求,返回内容
     * @param ret
     */
    @afterreturning(returning = "ret", pointcut = "operationlog()")
    public void doafterreturning(object ret) {
        system.out.println("方法的返回值 : " + ret);
    }

    /**
     * 后置异常通知
     */
    @afterthrowing("operationlog()")
    public void throwss(joinpoint jp){
        system.out.println("方法异常时执行.....");
    }


    /**
     * 后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
     */
    @after("operationlog()")
    public void after(joinpoint jp){
        system.out.println("方法最后执行.....");
    }

}

3.自定义注解

package com.wwj.springboot.annotation;

import com.wwj.springboot.enums.operationtype;
import com.wwj.springboot.enums.operationunit;

import java.lang.annotation.*;

/**
 * created by intellij idea
 *
 * @author weiwenjun
 * @date 2018/9/12
 */
//@operationlogdetail(detail = "通过手机号[{{tel}}]获取用户名",level = 3,operationunit = operationunit.user,operationtype = operationtype.select)
@documented
@target({elementtype.method})
@retention(retentionpolicy.runtime)
public @interface operationlogdetail {

    /**
     * 方法描述,可使用占位符获取参数:{{tel}}
     */
    string detail() default "";

    /**
     * 日志等级:自己定,此处分为1-9
     */
    int level() default 0;

    /**
     * 操作类型(enum):主要是select,insert,update,delete
     */
    operationtype operationtype() default operationtype.unknown;

    /**
     * 被操作的对象(此处使用enum):可以是任何对象,如表名(user),或者是工具(redis)
     */
    operationunit operationunit() default operationunit.unknown;
}

4.注解用到的枚举类型

package com.wwj.springboot.enums;

/**
 * created by intellij idea
 *
 * @author weiwenjun
 * @date 2018/9/12
 */
public enum operationtype {
    /**
     * 操作类型
     */
    unknown("unknown"),
    delete("delete"),
    select("select"),
    update("update"),
    insert("insert");

    private string value;

    public string getvalue() {
        return value;
    }

    public void setvalue(string value) {
        this.value = value;
    }

    operationtype(string s) {
        this.value = s;
    }
}
package com.wwj.springboot.enums;

/**
 * created by intellij idea
 * 被操作的单元
 * @author weiwenjun
 * @date 2018/9/12
 */
public enum operationunit {
    /**
     * 被操作的单元
     */
    unknown("unknown"),
    user("user"),
    employee("employee"),
    redis("redis");

    private string value;

    operationunit(string value) {
        this.value = value;
    }

    public string getvalue() {
        return value;
    }

    public void setvalue(string value) {
        this.value = value;
    }
}

5.日志记录对象

package com.wwj.springboot.model;

import java.util.date;

/**
 * created by intellij idea
 *
 * @author weiwenjun
 * @date 2018/9/12
 */
public class operationlog {

    private string id;
    private date createtime;
    /**
     * 日志等级
     */
    private integer level;
    /**
     * 被操作的对象
     */
    private string operationunit;
    /**
     * 方法名
     */
    private string method;
    /**
     * 参数
     */
    private string args;
    /**
     * 操作人id
     */
    private string userid;
    /**
     * 操作人
     */
    private string username;
    /**
     * 日志描述
     */
    private string describe;
    /**
     * 操作类型
     */
    private string operationtype;
    /**
     * 方法运行时间
     */
    private long runtime;
    /**
     * 方法返回值
     */
    private string returnvalue;

    @override
    public string tostring() {
        return "operationlog{" +
                "id='" + id + '\'' +
                ", createtime=" + createtime +
                ", level=" + level +
                ", operationunit='" + operationunit + '\'' +
                ", method='" + method + '\'' +
                ", args='" + args + '\'' +
                ", userid='" + userid + '\'' +
                ", username='" + username + '\'' +
                ", describe='" + describe + '\'' +
                ", operationtype='" + operationtype + '\'' +
                ", runtime=" + runtime +
                ", returnvalue='" + returnvalue + '\'' +
                '}';
    }

    public long getruntime() {
        return runtime;
    }

    public void setruntime(long runtime) {
        this.runtime = runtime;
    }

    public string getreturnvalue() {
        return returnvalue;
    }

    public void setreturnvalue(string returnvalue) {
        this.returnvalue = returnvalue;
    }

    public string getid() {
        return id;
    }

    public void setid(string id) {
        this.id = id;
    }

    public date getcreatetime() {
        return createtime;
    }

    public void setcreatetime(date createtime) {
        this.createtime = createtime;
    }

    public integer getlevel() {
        return level;
    }

    public void setlevel(integer level) {
        this.level = level;
    }

    public string getoperationunit() {
        return operationunit;
    }

    public void setoperationunit(string operationunit) {
        this.operationunit = operationunit;
    }

    public string getmethod() {
        return method;
    }

    public void setmethod(string method) {
        this.method = method;
    }

    public string getargs() {
        return args;
    }

    public void setargs(string args) {
        this.args = args;
    }

    public string getuserid() {
        return userid;
    }

    public void setuserid(string userid) {
        this.userid = userid;
    }

    public string getusername() {
        return username;
    }

    public void setusername(string username) {
        this.username = username;
    }

    public string getdescribe() {
        return describe;
    }

    public void setdescribe(string describe) {
        this.describe = describe;
    }

    public string getoperationtype() {
        return operationtype;
    }

    public void setoperationtype(string operationtype) {
        this.operationtype = operationtype;
    }
}

6.springboot启动类

package com.wwj.springboot;

import org.springframework.boot.springapplication;
import org.springframework.boot.autoconfigure.springbootapplication;

/**
 * created by intellij idea
 *
 * @author weiwenjun
 * @date 2018/9/12
 */
@springbootapplication
public class aopapplication {

    public static void main(string[] args) {
        springapplication.run(aopapplication.class);
    }
}

7.controller类

package com.wwj.springboot.controller;

import com.wwj.springboot.service.userservice;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.stereotype.controller;
import org.springframework.web.bind.annotation.requestmapping;
import org.springframework.web.bind.annotation.requestparam;
import org.springframework.web.bind.annotation.responsebody;

/**
 * created by intellij idea
 *
 * @author weiwenjun
 * @date 2018/9/12
 */
@controller
@requestmapping("user")
public class usercontroller {

    @autowired
    private userservice userservice;

    /**
     * 访问路径 http://localhost:11000/user/findusernamebytel?tel=1234567
     * @param tel 手机号
     * @return username
     */
    @responsebody
    @requestmapping("/findusernamebytel")
    public string findusernamebytel(@requestparam("tel") string tel){
        return userservice.findusername(tel);
    }
}

8.service类

package com.wwj.springboot.service;

/**
 * created by intellij idea
 *
 * @author weiwenjun
 * @date 2018/9/13
 */
public interface userservice {

    /**
     * 获取用户信息
     * @return
     * @param tel
     */
    string findusername(string tel);
}

9.serviceimpl类

package com.wwj.springboot.service.impl;

import com.wwj.springboot.annotation.operationlogdetail;
import com.wwj.springboot.enums.operationtype;
import com.wwj.springboot.enums.operationunit;
import com.wwj.springboot.service.userservice;
import org.springframework.stereotype.service;

/**
 * created by intellij idea
 *
 * @author weiwenjun
 * @date 2018/9/13
 */
@service
public class userserviceimpl implements userservice {

    @operationlogdetail(detail = "通过手机号[{{tel}}]获取用户名",level = 3,operationunit = operationunit.user,operationtype = operationtype.select)
    @override
    public string findusername(string tel) {
        system.out.println("tel:" + tel);
        return "zhangsan";
    }
}

四:mavem依赖

本项目有两个pom文件,父类的pom文件主要作用是对子类pom文件依赖的版本号进行统一管理。

1.最外层的pom文件配置如下

<?xml version="1.0" encoding="utf-8"?>
<project xmlns="http://maven.apache.org/pom/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
         xsi:schemalocation="http://maven.apache.org/pom/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelversion>4.0.0</modelversion>

    <groupid>com.wwj.springboot</groupid>
    <artifactid>springboot</artifactid>
    <packaging>pom</packaging>
    <version>1.0-snapshot</version>
    <modules>
        <module>springboot-aop</module>
    </modules>

    <!-- inherit defaults from spring boot -->
    <parent>
        <groupid>org.springframework.boot</groupid>
        <artifactid>spring-boot-starter-parent</artifactid>
        <version>2.0.4.release</version>
    </parent>
    <properties>
        <fastjson.version>1.2.49</fastjson.version>
    </properties>

    <dependencymanagement>
        <dependencies>
            <dependency>
                <!-- import dependency management from spring boot -->
                <groupid>org.springframework.boot</groupid>
                <artifactid>spring-boot-dependencies</artifactid>
                <version>2.0.4.release</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupid>com.alibaba</groupid>
                <artifactid>fastjson</artifactid>
                <version>${fastjson.version}</version>
            </dependency>
        </dependencies>
    </dependencymanagement>
</project>

2.子pom文件配置如下

<?xml version="1.0" encoding="utf-8"?>
<project xmlns="http://maven.apache.org/pom/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
         xsi:schemalocation="http://maven.apache.org/pom/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactid>springboot</artifactid>
        <groupid>com.wwj.springboot</groupid>
        <version>1.0-snapshot</version>
    </parent>
    <modelversion>4.0.0</modelversion>

    <artifactid>springboot-aop</artifactid>

    <dependencies>
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-web</artifactid>
        </dependency>
        <!-- spring-boot aop依赖配置引入 -->
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-aop</artifactid>
        </dependency>
        <dependency>
            <groupid>com.alibaba</groupid>
            <artifactid>fastjson</artifactid>
        </dependency>
    </dependencies>

</project>

五:运行结果

进入方法前执行.....
tel:1234567
记录日志:operationlog{id='cd4b5ba7-7580-4989-a75e-51703f0dfbfc', createtime=fri sep 14 08:54:55 cst 2018, level=3, operationunit='user', method='com.wwj.springboot.service.impl.userserviceimpl.findusername', args='["1234567"]', userid='#{currentuserid}', username='#{currentusername}', describe=''#{currentusername}'=》通过手机号["1234567"]获取用户名', operationtype='select', runtime=4, returnvalue='"zhangsan"'}
方法最后执行.....
方法的返回值 : zhangsan

 

项目源码github地址:

 

感谢您的阅读,如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮。本文欢迎各位转载,但是转载文章之后必须在文章页面中给出作者和原文连接。