通過AOP+注解實現日志打印


選擇spring的AOP還是AspectJ?spring確實有自己的AOP。功能已經基本夠用了,除非你的要在接口上動態代理或者方法攔截精確到getter和setter,一般不使用。

②在使用AOP的時候,你是用xml還是注解的方式(@Aspect)?
1)如果使用xml方式,不需要任何額外的jar包。
2)如果使用@Aspect方式,你就可以在類上直接一個@Aspect就搞定,不用費事在xml里配了。但是這需要額外的jar包( aspectjweaver.jar)。因為spring直接使用AspectJ的注解功能,注意只是使用了它 的注解功能而已。並不是核心功能 !!!

如果要實現基於接口的動態代理使用full aspectJ
如果用full AspectJ。比如說Load-Time Weaving的方式 還 需要額外的jar包 spring-instrument.jar
當然,無論是使用spring aop還是 aspectj都需要aspectjweaver.jar spring-aop.jar這兩個jar包。

1. AOP:Aspect Oriented Programming(面向切面編程)
2. 利用動態代理實現面向切面編程(底層原理是動態代理這你理解的沒錯)
3. Spring實現動態代理配置是有兩種配置文件:
    1、xml文件方式;
    2、annotation方式(使用AspectJ類庫實現的。)
4. aspectJ類庫,AspectJ是一個專門用來實現動態代理(AOP編程)的類庫,AspectJ是面向切面編程的框架,Spring使用就是這個類庫實現動態代理的
5. aspectj的專業術語:
1、JoinPoint連接點(切入點)
2、PointCut切入點,當需要定義一個切入點時,則需要使用這個
3、Aspect切面
4、Advice切入點的邏輯
5、Target被代理對象
6、Weave織入 

@EnableAspectJAutoProxy:

表示開啟AOP代理自動配置,如果配@EnableAspectJAutoProxy表示使用cglib進行代理對象的生成;設置@EnableAspectJAutoProxy(exposeProxy=true)表示通過aop框架暴露該代理對象,aopContext能夠訪問.

從@EnableAspectJAutoProxy的定義可以看得出,它引入AspectJAutoProxyRegister.class對象,該對象是基於注解@EnableAspectJAutoProxy注冊一個AnnotationAwareAspectJAutoProxyCreator,該對象通過調用AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);注冊一個aop代理對象生成器。

SpringBoot為我們提供了一個 spring-boot-starter-aop 自動配置模塊。

spring-boot-starter-aop 自動配置行為由兩部分內容組成:

  1. 位於 spring-boot-autoconfigure的org.springframework.boot.autoconfigure.aop.AopAutoConfiguration 提供 @Configuration 配置類和相應的配置項。
  2. spring-boot-starter-aop 模塊自身提供了針對 spring-aop、aspectjrt 和 aspectjweaver 的依賴。

一般情況下,只要項目依賴中加入了 spring-boot-starter-aop,其實就會自動觸發 AOP 的關聯行為,包括構建相應的 AutoProxyCreator,將橫切關注點織入(Weave)相應的目標對象等,不過 AopAutoConfiguration 依然為我們提供了可憐的兩個配置項,用來有限地干預 AOP 相關配置:

  • spring.aop.auto=true
  • spring.aop.proxy-target-class=false

用戶可以選擇關閉自動的 aop 配置(spring.aop.auto=false),或者啟用針對 class 而不是 interface 級別的 aop 代理(aop proxy)。

 <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

注:

AOP依賴包后,不需要去做其他配置。AOP的默認配置屬性中,spring.aop.auto屬性默認是開啟的,也就是說只要引入了AOP依賴后,默認已經增加了@EnableAspectJAutoProxy,不需要再在程序主類中增加@EnableAspectJAutoProxy了。

代碼實現:

日志注解:

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

/**
 * @author zhao
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    String value() default "";
}

定義切面:

package com.could.demo.aop.log.aspect;

import com.could.demo.aop.log.entity.Log;
import com.could.demo.aop.log.service.LogService;
import com.could.demo.aop.log.utils.RequestHolder;
import com.could.demo.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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

/**
 * @author zhao
 * @date 2019-03-27
 */
@Component
@Aspect
@Slf4j//如果不想每次都寫private  final Logger logger = LoggerFactory.getLogger(當前類名.class); 可以用注解@Slf4j; 安裝Lombok插件和配置lombok依賴;
public class LogAspect {

    @Autowired
    LogService logService;

    private ThreadLocal<Long> currentTime = new ThreadLocal<>();

    /**
     * 配置切入點,通用的攔截切面
     */
    @Pointcut("@annotation(com.could.demo.aop.log.Log)")
    public void logPointcut() {
        // 該方法無方法體,主要為了讓同類中其他方法使用此切入點
    }

    /**
     * 配置環繞通知,使用在方法logPointcut()上注冊的切入點
     *
     * @param joinPoint join point for advice
     */
    @Around("logPointcut()")
    public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("@Slf4j注解測試");//2020-03-27 17:05:58.787  INFO 9636 --- [nio-8085-exec-2] com.could.demo.aop.log.aspect.LogAspect  : @Slf4j注解測試
        currentTime.set(System.currentTimeMillis());
        joinPoint.proceed();
        Log log1 = new Log(1, System.currentTimeMillis() - currentTime.get());
        HttpServletRequest request = RequestHolder.getHttpServletRequest();
        //getRequestURL() 請求的是不帶參數的完整路徑 :http://localhost:8083/Aop/getUserDetails/45
        logService.save(getUsername(), request.getRequestURL().toString(), joinPoint, log1);
        currentTime.remove();
    }

    /**
     * 配置異常通知
     *
     * @param joinPoint join point for advice
     * @param e exception
     */
    @AfterThrowing(pointcut = "logPointcut()", throwing = "e")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
        Log logError = new Log(0,System.currentTimeMillis() - currentTime.get());
        currentTime.remove();
        logError.setExceptionDetail(Arrays.toString(e.getStackTrace()).getBytes());
        HttpServletRequest request = RequestHolder.getHttpServletRequest();
        logService.save(getUsername(),request.getRequestURL().toString(),(ProceedingJoinPoint)joinPoint, logError);
    }

    /**
     * 獲取的的當前登錄用戶名
     * @return
     */
    public String getUsername() {
        /**
         * 獲取Security 中的用戶名
         * obj = getUserDetails();
         * return new JSONObject(obj).get("username", String.class);
         */
         /**
         * 獲取session中的用戶名
         */
        Object user1 = RequestHolder.getHttpServletRequest().getSession().getAttribute("user");
        if(user1!=null){
            User user = (User)user1 ;
            return user.getName();
        }
       return "";
    }
}

全局獲取HttpServletRequest

/**
 * 獲取 HttpServletRequest
 */
public class RequestHolder {

    public static HttpServletRequest getHttpServletRequest() {
        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
    }
}

controller及@Log使用,只需在要存儲日志信息的請求上添加@Log注解即可

package com.could.demo.aop.log.controller;


import com.could.demo.aop.log.Log;
import com.could.demo.entity.User;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;


@RestController
public class LogController {
   @Log("獲取用戶詳情")
    @GetMapping(value = "/getUserDetails/{ss}")
    public String userDetails(@PathVariable("ss") String ss) {
        return" success"+ss;
    }

   @Log("用戶登錄")
    @GetMapping(value = "/login")
    public void login(HttpServletRequest request ) {
        User user = new User();
        user.setName("aop打印日志測試號");
        request.getSession().setAttribute("user",user);

    }

    @Log("測試")
    @GetMapping(value = "/test")
    public String test( ) {
       return "ok";

    }
}

dao

package com.could.demo.aop.log.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.could.demo.aop.log.entity.Log;



public interface LogDao extends BaseMapper<Log> {
}

實體:

package com.could.demo.aop.log.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;

/**
 * 系統日志類
 */
@Data
@TableName("log")
@NoArgsConstructor
public class Log  implements Serializable {

    private static final long serialVersionUID = 7365297754812218451L;
    /**
     * 主鍵
     * @TableId中可以決定主鍵的類型,不寫會采取默認值,默認值可以在yml中配置
     * AUTO: 數據庫ID自增
     * INPUT: 用戶輸入ID
     * ID_WORKER: 全局唯一ID,Long類型的主鍵
     * ID_WORKER_STR: 字符串全局唯一ID
     * UUID: 全局唯一ID,UUID類型的主鍵
     * NONE: 該類型為未設置主鍵類型
     */
//    @TableId(type = IdType.ID_WORKER)
//    private Long id;
    @TableId(type = IdType.ID_WORKER_STR)
    private String id;

    /** 操作用戶 */
    private String username;

    /** 描述 */
    private String description;

    /** 方法名 */
    private String method;

    /** 參數 */
    private String params;

    /** 日志類型
     * 1 :info
     * 0:錯誤日志
     * */
    private Integer logType;

    /** 請求全路徑(不帶參數)*/
    private String requestAddress;

    /** 請求耗時單位毫秒 */
    private Long time;

    /** 異常詳細  */
    private byte[] exceptionDetail;

    /** 創建日期 */
    private Date createTime=new Date();

    /**
     * @param logType  日志類型
     * @param time   請求時間
     */
    public Log(Integer logType, Long time) {
        this.logType = logType;
        this.time = time;
    }
}

service 

@Async異步處理
package com.could.demo.aop.log.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.could.demo.aop.log.entity.Log;
import org.aspectj.lang.ProceedingJoinPoint;


public interface LogService extends IService<Log> {
@Async
void save(String username, String requestAddress, ProceedingJoinPoint joinPoint, Log log); }

service接口實現

package com.could.demo.aop.log.service.impl;


import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.could.demo.aop.log.dao.LogDao;
import com.could.demo.aop.log.entity.Log;
import com.could.demo.aop.log.service.LogService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Method;

@Service
@Transactional
public class LogServiceImpl  extends ServiceImpl<LogDao, Log> implements  LogService {
    @Override
    public void save(String username, String requestAddress, ProceedingJoinPoint joinPoint, Log log){

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        /** 獲取激活切面的(類)方法*/
        Method method = signature.getMethod();
        /** 獲取注解*/
        com.could.demo.aop.log.Log aopLog = method.getAnnotation(com.could.demo.aop.log.Log.class);
        /** 獲取方法完整類路徑名
         *  signature.getName() 獲取的是方法名
         * */
        String methodName = joinPoint.getTarget().getClass().getName()+"."+signature.getName()+"()";
        StringBuilder params = new StringBuilder("{");
        //獲取參數值
        Object[] argValues = joinPoint.getArgs();
        //獲取參數名稱
        String[] argNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames();
        if(argValues != null){
            for (int i = 0; i < argValues.length; i++) {
                params.append(" ").append(argNames[i]).append(": ").append(argValues[i]);
            }
        }
        // 獲取注解@Log的描述也就是value() 值
        if (log != null) {
            log.setDescription(aopLog.value());
        }
        assert log != null; //斷言log不能為null否則程序拋出AssertionError,並終止執行。
        /** 設置請求路徑 */
        log.setRequestAddress(requestAddress);
        /** 設置請求方法名 */
        log.setMethod(methodName);
        /** 設置當前登錄的用戶名 */
        log.setUsername(username);
        /** 設置當前請求的參數和值*/
        log.setParams(params.toString() + " }");
        this.save(log);
    }



}

啟動類:

package com.could.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@MapperScan(basePackages = {"com.could.demo.aop.log.dao"}) //掃描DAO
@ComponentScan("com.could.demo.aop.log")
//@EnableAspectJAutoProxy
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

http://127.0.0.1:8085/login 或 http://127.0.0.1:8085/test數據庫就會存儲對應的請求日志信息

sql文件:

/*
 Navicat Premium Data Transfer

 Source Server         : saas-test
 Source Server Type    : MySQL
 Source Server Version : 50729
 Source Host           : localhost:3306
 Source Schema         : plus

 Target Server Type    : MySQL
 Target Server Version : 50729
 File Encoding         : 65001

 Date: 27/03/2020 19:01:49
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for log
-- ----------------------------
DROP TABLE IF EXISTS `log`;
CREATE TABLE `log`  (
  `id` varchar(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主鍵',
  `username` varchar(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作用戶名',
  `description` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述',
  `method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '方法名',
  `params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '參數',
  `log_type` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '日志類型',
  `request_address` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '請求全路徑(不帶參數的)',
  `time` bigint(20) NULL DEFAULT NULL COMMENT '請求耗時',
  `exception_detail` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '異常詳細',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '創建時間',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of log
-- ----------------------------
INSERT INTO `log` VALUES ('1243487804686430210', NULL, '測試', 'com.could.demo.aop.log.controller.LogController.test()', '{ }', '1', 'http://127.0.0.1:8085/test', 8, NULL, '2020-03-27 10:39:30');
INSERT INTO `log` VALUES ('1243489076491608066', 'aop打印日志測試號', '用戶登錄', 'com.could.demo.aop.log.controller.LogController.login()', '{ request: org.apache.catalina.connector.RequestFacade@283e7ec5 }', '1', 'http://127.0.0.1:8085/login', 16, NULL, '2020-03-27 10:44:33');

SET FOREIGN_KEY_CHECKS = 1;

application.yml

# 配置端口
server:
  port: 8085
spring:
  # 配置數據源
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/plus?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
# mybatis-plus相關配置
mybatis-plus:
  # xml掃描,多個目錄用逗號或者分號分隔(告訴 Mapper 所對應的 XML 文件位置)
#  mapper-locations: classpath:mapper/*.xml
  # 以下配置均有默認值,可以不設置
  global-config:
    db-config:
      #主鍵類型 AUTO:"數據庫ID自增" INPUT:"用戶輸入ID",ID_WORKER:"全局唯一ID (數字類型唯一ID)", UUID:"全局唯一ID UUID";
      id-type: ID_WORKER
      #字段策略 IGNORED:"忽略判斷"  NOT_NULL:"非 NULL 判斷")  NOT_EMPTY:"非空判斷"
      field-strategy: NOT_EMPTY
      #數據庫類型
      db-type: MYSQL
  configuration:
    # 是否開啟自動駝峰命名規則映射:從數據庫列名到Java屬性駝峰命名的類似映射
    map-underscore-to-camel-case: true
    # 返回map時true:當查詢數據為空時字段返回為null,false:不加這個查詢數據為空時,字段將被隱藏
    call-setters-on-nulls: true
    # 這個配置會將執行的sql打印出來,在開發或測試的時候可以用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

總Maven環境:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.could.demo</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>學習</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!--log start-->
        <!-- Commons Lang,這是Java實用程序類的軟件包,用於Java.lang層次結構中的類-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>
        <dependency>
            <groupId>org.lionsoul</groupId>
            <artifactId>ip2region</artifactId>
            <version>1.7.2</version>
        </dependency>
        <!--log end-->
        <!--aop注解-->
        <!--引入AOP依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>org.aspectj</groupId>-->
<!--            <artifactId>aspectjweaver</artifactId>-->
<!--            <version>1.9.2</version>-->
<!--        </dependency>-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- mybatisPlus 核心庫 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.0</version>
        </dependency>
        <!-- 引入阿里數據庫連接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.6</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

持久層框架用的是mybatis-plus

    

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM