Spring之Aop切面---日志收集(环绕处理、前置处理方式)--使用/教程/实例

这篇具有很好参考价值的文章主要介绍了Spring之Aop切面---日志收集(环绕处理、前置处理方式)--使用/教程/实例。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

简介

本文章介绍采用两种不同方式处理----系统登录、系统退出登录两种场景日志。

  • 环绕处理系统登录日志
  • 前置处理系统退出登录日志

系统登录日志类LoginLogEntity .java

package com.fy.test.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;

import java.io.Serializable;
import java.time.LocalDateTime;

import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;

/**
 * @ClassName: LoginLogEntity 
 * @Description: 
 * @Author fy
 * @Date 2023/07/10 9:00
 */
@Data
@Accessors(chain = true)
@TableName("t_login_log")
public class LoginLogEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private String id;

    /**
     * 操作系统
     */
    private String opOs;

    /**
     * 浏览器类型
     */
    private String opBrowser;

    /**
     * 登录IP地址
     */
    private String opIp;

    /**
     * 登录时间
     */
    private LocalDateTime opDate;

    /**
     * 登录用户ID
     */
    private String userId;

    /**
     * 登录用户名称
     */
    private String userName;

    /**
     * 错误类型
     */
    private String exCode;

    /**
     * 错误信息
     */
    private String exMsg;

    /**
     * 登录状态
     */
    private boolean status;

    /**
     * 描述
     */
    private String desc;
}

一、环绕处理方式

1、自定义注解类LoginLogAop.class

package com.fy.test.log.annotation;

import java.lang.annotation.*;

/**
 * @ClassName: LoginLogAop
 * @Description: 
 * @Author fy
 * @Date 2023/07/10 9:05
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoginLogAop {

    /**
     * 描述
     */
    String desc() default "";

}

2、切面处理类LogoutLogAspect.java

package com.fy.test.log.aspect;

import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.fy.test.log.annotation.LoginLogAop;
import com.fy.test.utils.RequestHolder;
import com.fy.test.utils.SecurityUtil;
import com.fy.test.service.dto.LoginLogDto;
import com.fy.test.service.feign.LogServiceFeign;
import com.fy.test.service.vo.UserVo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName: LoginLogAspect
 * @Description:
 * @Author fy
 * @Date 2023/07/10 9:05
 */
@Slf4j
@Aspect
public class LoginLogAspect {

    @Autowired
    private LogServiceFeign logServiceFeign;

    /**
     * 配置织入点
     */
    @Pointcut("@annotation(com.fy.test.common.log.annotation.LoginLogAop)")
    public void logPointCut() {
    }

    /**
     * 通知方法会将目标方法封装起来
     * 注意:环绕方式选择ProceedingJoinPoint
     * Proceedingjoinpoint 继承了JoinPoint,在JoinPoint的基础上暴露出 proceed(), 这个方法是AOP代理链执行的方法。
     * JoinPoint仅能获取相关参数,无法执行连接点。
     * 暴露出proceed()这个方法,就能支持 aop:around 这种切面(而其他的几种切面只需要用到JoinPoint,这跟切面类型有关),
     * 就能控制走代理链还是走自己拦截的其他逻辑。  
     * 
     * @param joinPoint 切点
     */
    @Around(value = "logPointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = joinPoint.proceed();
        LoginLogDto logDto = getLog();
        logDto.setStatus(true);
        handleLog(joinPoint, logDto);
        return result;
    }

    /**
     * 通知方法会在目标方法抛出异常后执行
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        LoginLogDto logDto = getLog();
        logDto.setExCode(e.getClass().getSimpleName()).setExMsg(e.getMessage());
        logDto.setStatus(false);
        handleLog(joinPoint, logDto);
    }

    private LoginLogDto getLog() {

        HttpServletRequest request = RequestHolder.getHttpServletRequest();
        UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));

        LoginLogDto loginLog = new LoginLogDto();
        loginLog.setOpIp(ServletUtil.getClientIP(request))
                .setOpOs(userAgent.getOs().getName())
                .setOpBrowser(userAgent.getBrowser().getName())
                .setUserId(SecurityUtil.getUserId())
                .setUserName(SecurityUtil.getUserName())
                .setOpDate(LocalDateTime.now());

        return loginLog;
    }

    protected void handleLog(final JoinPoint joinPoint, LoginLogDto loginLogDto) {
        // 获得注解
        LoginLogAop logAop = getAnnotationLog(joinPoint);
        if (null == logAop) {
            return;
        }

        loginLogDto.setDescription(logAop.description());

        Map<String, Object> requestParams = getRequestParams(joinPoint);
        if (requestParams.containsKey("userVo")) {
            UserVo userVo = JSONObject.parseObject(JSON.toJSONString(requestParams.get("userVo")), UserVo.class);
            if (null != userVo && StringUtils.isBlank(loginLogDto.getUserName())) {
                loginLogDto.setUserName(userVo.getUsername());
            }
        }

        // 保存数据库
        logServiceFeign.saveLoginLog(loginLogDto);
    }

    /**
     * 是否存在注解,如果存在就获取
     */
    private LoginLogAop getAnnotationLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(LoginLogAop.class);
        }
        return null;
    }

    /**
     * 获取入参
     */
    private Map<String, Object> getRequestParams(JoinPoint joinPoint) {
        Map<String, Object> requestParams = new HashMap<>();
        // 参数名
        String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        // 参数值
        Object[] paramValues = joinPoint.getArgs();
        for (int i = 0; i < paramNames.length; i++) {
            Object value = paramValues[i];
            // 如果是文件对象
            if (value instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) value;
                // 获取文件名
                value = file.getOriginalFilename();
            }
            requestParams.put(paramNames[i], value);
        }
        return requestParams;
    }
}

二、前置处理方式:

1、自定义注解类LogoutLogAop.class

package com.fy.test.log.annotation;

import java.lang.annotation.*;

/**
 * @ClassName: LogoutLogAop
 * @Description: 
 * @Author fy
 * @Date 2023/07/10 9:10
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogoutLogAop {

    /**
     * 描述
     */
    String desc() default "";

}

2、切面处理类LogoutLogAspect.java

package com.fy.test.log.aspect;

import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.fy.test.log.annotation.LoginLogAop;
import com.fy.test.log.annotation.LogoutLogAop;
import com.fy.test.utils.RequestHolder;
import com.fy.test.utils.SecurityUtil;
import com.fy.test.service.dto.LoginLogDto;
import com.fy.test.service.feign.LogServiceFeign;
import com.fy.test.service.vo.UserVo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName: LogoutLogAspect
 * @Description:
 * @Author fy
 * @Date 2023/07/10 9:10
 */
@Slf4j
@Aspect
public class LogoutLogAspect {

    @Autowired
    private LogServiceFeign logServiceFeign;

    /**
     * 配置织入点
     */
    @Pointcut("@annotation(com.fy.test.log.annotation.LogoutLogAop)")
    public void logPointCut() {
    }

    /**
     * 通知方法会将目标方法封装起来
     *
     * @param joinPoint 切点
     */
    @Before("logPointCut()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        try {
            System.out.println("==============前置处理开始==============");
            LoginLogDto logDto = getLog();
            logDto.setStatus(true);
            handleLog(joinPoint, logDto);
        } catch (Exception e) {
            //记录本地异常日志
            log.error("==前置通知异常==");
            log.error("异常信息:{}", e.getMessage());
        }

    }

    /**
     * 通知方法会在目标方法抛出异常后执行
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        LoginLogDto logDto = getLog();
        logDto.setExCode(e.getClass().getSimpleName()).setExMsg(e.getMessage());
        logDto.setStatus(false);
        handleLog(joinPoint, logDto);
    }

    private LoginLogDto getLog() {
        HttpServletRequest request = RequestHolder.getHttpServletRequest();
        UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));

        LoginLogDto loginLogDto = new LoginLogDto();
        loginLogDto.setOpIp(ServletUtil.getClientIP(request))
                .setOpOs(userAgent.getOs().getName())
                .setOpBrowser(userAgent.getBrowser().getName())
                .setUserId(SecurityUtil.getUserId())
                .setUserName(SecurityUtil.getUserName())
                .setOpDate(LocalDateTime.now());

        return loginLogDto;
    }

    protected void handleLog(final JoinPoint joinPoint, LoginLogDto loginLogDto) {
        // 获得注解
        LogoutLogAop logAop = getAnnotationLog(joinPoint);
        if (null == logAop) {
            return;
        }

        loginLogDto.setDesc(logAop.desc());
        // 保存数据库
        logServiceFeign.saveLoginLog(loginLogDto);
    }

    /**
     * 是否存在注解,如果存在就获取
     */
    private LogoutLogAop getAnnotationLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(LogoutLogAop.class);
        }
        return null;
    }

    /**
     * 获取入参
     */
    private Map<String, Object> getRequestParams(JoinPoint joinPoint) {
        Map<String, Object> requestParams = new HashMap<>();
        // 参数名
        String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        // 参数值
        Object[] paramValues = joinPoint.getArgs();
        for (int i = 0; i < paramNames.length; i++) {
            Object value = paramValues[i];
            // 如果是文件对象
            if (value instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) value;
                // 获取文件名
                value = file.getOriginalFilename();
            }
            requestParams.put(paramNames[i], value);
        }
        return requestParams;
    }
}

三、Proceedingjoinpoint简述

Proceedingjoinpoint 继承了JoinPoint,在JoinPoint的基础上暴露出 proceed(), 这个方法是AOP代理链执行的方法。

JoinPoint仅能获取相关参数,无法执行连接点。暴露出proceed()这个方法,就能支持 aop:around 这种切面(而其他的几种切面只需要用到JoinPoint,这跟切面类型有关),就能控制走代理链还是走自己拦截的其他逻辑。

import org.aspectj.lang.reflect.SourceLocation;  
public interface JoinPoint {  
   String toString();         //连接点所在位置的相关信息  
   String toShortString();    //连接点所在位置的简短相关信息  
   String toLongString();     //连接点所在位置的全部相关信息  
   Object getThis();          //返回AOP代理对象,也就是com.sun.proxy.$Proxy18
   Object getTarget();        //返回目标对象,一般我们都需要它或者(也就是定义方法的接口或类,为什么会是接口呢?
                              //这主要是在目标对象本身是动态代理的情况下,例如Mapper。所以返回的是定义方法的对象如
                              //aoptest.daoimpl.GoodDaoImpl或com.b.base.BaseMapper<T, E, PK>)
   Object[] getArgs();        //返回被通知方法参数列表  
   Signature getSignature();  //返回当前连接点签名。其getName()方法返回方法的FQN,如void aoptest.dao.GoodDao.delete()
                              //或com.b.base.BaseMapper.insert(T)(需要注意的是,很多时候我们定义了子类继承父类的时候,
                              //我们希望拿到基于子类的FQN,无法直接拿到,要依赖于
                              //AopUtils.getTargetClass(point.getTarget())获取原始代理对象,下面会详细讲解)
   SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置  
   String getKind();           //连接点类型  
   StaticPart getStaticPart(); //返回连接点静态部分  
  }  
 
 public interface ProceedingJoinPoint extends JoinPoint {  
       public Object proceed() throws Throwable;  
       public Object proceed(Object[] args) throws Throwable;  
 }

JoinPoint.StaticPart:提供访问连接点的静态部分,如被通知方法签名、连接点类型等等。文章来源地址https://www.toymoban.com/news/detail-640509.html

public interface StaticPart {  
   Signature getSignature();    //返回当前连接点签名  
   String getKind();            //连接点类型  
   int getId();                 //唯一标识  
   String toString();           //连接点所在位置的相关信息  
   String toShortString();      //连接点所在位置的简短相关信息  
   String toLongString();       //连接点所在位置的全部相关信息  
}

到了这里,关于Spring之Aop切面---日志收集(环绕处理、前置处理方式)--使用/教程/实例的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Spring面向切面编程(AOP)

    AOP(Aspect Oriented Programming),即面向切面编程,利用一种称为\\\"横切\\\"的技术,剖开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为\\\"Aspect\\\",即切面。所谓\\\"切面\\\",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装

    2024年02月07日
    浏览(49)
  • Spring AOP:面向切面编程

    在软件开发中,我们经常会遇到一些横切关注点(cross-cutting concerns),如日志记录、事务管理和安全性等。这些关注点不属于特定的模块或类,而是横跨整个应用程序。传统的面向对象编程方法往往会将这些关注点与核心业务逻辑混杂在一起,导致代码的可读性和可维护性下

    2024年02月09日
    浏览(47)
  • Java注解方式实现aop,切点切面实战

    注解方式实现aop我们主要分为如下几个步骤(有更好的方法的话,欢迎交流): 1.在切面类(为切点服务的类)前用@Aspect注释修饰,声明为一个切面类。 2.用@Pointcut注释声明一个切点,目的是为了告诉切面,谁是它的服务对象。(此注释修饰的方法的方法体为空,不需要写功

    2024年02月12日
    浏览(37)
  • spring之面向切面:AOP(2)

    学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。各位小伙伴,如果您: 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持,想组团高效学习… 想写博客但无从下手,急需写作干货注入能量… 热爱写作,愿意让自己成为更好

    2024年02月04日
    浏览(47)
  • 注解 + spring aop切面+ SpringEl

    2024年02月09日
    浏览(48)
  • Spring-AOP(面向切面)

    功能接口 实现类 在含有日志输出的实现类中可以了解到:与核心业务功能没有关系的日志输出加杂在模块中,对核心业务功能有干扰。 思路:解耦 , 将附加功能从业务功能模块中抽取出来 概念 二十三种设计模式中的一种,属于结构型模式,它的作用就是通过提供一个代理

    2024年02月16日
    浏览(43)
  • javaee spring 测试aop 切面

    spring配置文件

    2024年02月09日
    浏览(39)
  • Spring-aop面向切面

    1、理解必要的专业术语  先看看上面图,这是我的个人理解。(画的丑,主打真实)         1)Advice,通知/增强:类方法中提出来的共性功能(大白话就是提出来的重复代码)         2)Pointcut,切入点/切点:通知返回的方法         3)连接点:方法         4)织入:

    2023年04月15日
    浏览(95)
  • Spring02-Spring注解的使用、基于注解的IOC、纯注解配置、整合Junit、AOP入门、基于配置文件的AOP、切入点表达式、基于配置的文件环绕通知

    学习基于注解的 IOC 配置,即注解配置 和 XML 配置要实现的功能都是一样的,都是要降低程序间的耦合。只是配置的形式不一样。 关于实际的开发中到底使用xml还是注解,每家公司有着不同的使用习惯 , 所以这两种配置方式我们都需要掌握。 把 Spring 的 xml 配置内容改为使用

    2024年02月03日
    浏览(75)
  • 26.Spring-AOP(切面编程)

    目录 一、Spring-AOP。 (1)AOP的简介。  (2)AOP的底层实现-动态代理。  (2.1)JDK的动态代理。  (2.2)cglib的动态代理。  (3)AOP的相关概念。  (4)xml配置——AOP的快速入门。 (5) xml配置AOP详解。 (5.1)切点表达式的写法。  (5.2)通知的类型。 (5.3)切点表达式的

    2023年04月24日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包