Java 切面增强版

深入解析Java AOP:动态代理与Spring实战 面向切面编程(AOP)是Java开发中实现横切关注点分离的核心技术。其原理是通过动态代理机制,在不修改原有代码的情况下,将日志记录、权限校验等非业务逻辑横向织入程序流程。Spring框架基于JDK动态代理和CGLIB两种技术实现AOP,前者针对接口代理,后者处理类代理,在性能和应用场景上各有特点。在实际工程中,AOP广泛应用于性能监控、事务管理、安全控制等场景,能显著提升代码复用性和可维护性。通过合理使用切点表达式和通知类型,开发者可以构建高内聚低耦合的企业级应用。本文以咖啡机为喻,结合Spring AOP 阅读详情

前言

    众说周知,aop是oop思想的延续,是为了我们更好的程序的开发更便于我们对技术及代码的维护。 
 今天就利用aop来做一个日志的记录。废话不多说,上代码。

代码

package com.wind.sky.util;
import com.wind.sky.annotation.LoggerRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Component
@Aspect
public class LoggerRecordAop implements Serializable {

    private static final Logger logger = LoggerFactory.getLogger(LoggerRecordAop.class);

    private final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 规定日期格式
    
    private static final long serialVersionUID = 8147703597827139556L;

    @Resource
    private HttpServletRequest request;
    
    @Resource
    private HttpServletResponse response;

    @Resource
    private JdbcTemplate jdbcTemplate;
    
    @Resource
    private LoggerService  loggerService;
    
    private ThreadLocal<LoggerRecord> loggerRecordThread = new ThreadLocal<LoggerRecord>(); //防止多线程导致的日志被重新赋值

    LoggerRecord loggerRecord=new LoggerRecord();  ①

    @Pointcut(value= "execution (* com.XXX.XXX.controller.XXXController.*(..))")
   // @Pointcut("@annotation(LoggerAop)")
    public void recordApiLogger(){
     /* *
      *1.先定义一个切入点
      *  2.可以匹配注解
      *  3.可以用 execution 表达式
      * 注解的执行顺序
      * @before    @around    @after  ②
      * */

    }

    /**
    *@Description : 可以获取一些常用参数,非密切关联的 比如 ip,url ,interface_desc
    */
    @Before(value="recordApiLogger()")
    public void getRequestBaseInfo(){
        loggerRecordThread.set(loggerRecord);//
        String remoteAddr = request.getRemoteAddr();//获取请求地址
        String url = request.getRequestURL().toString();
        String headParam = request.getParameter("XXXXX"); //获取项目请求头的参数
    }

    
    @Around(value = "recordApiLogger()")
    public Object  saveRecordApiLogger(ProceedingJoinPoint joinPoint){
            long startTime = System.currentTimeMillis();
            Object retVal = joinPoint.proceed();
            long entTime = System.currentTimeMillis();
            String  consumeTime  = formatDuring(entTime - startTime);
            Object[] args = joinPoint.getArgs();//
            List<Object> validArgs =new ArrayList<>(); //可以用来记录的非接口类型参数
            for (Object arg: args){
                if(arg instanceof MultipartFile || arg instanceof HttpServletRequest || arg instanceof HttpServletResponse){
                    continue;
                }else{
                    validArgs.add(arg); 
                }
            }
         // validArgs 是入参,可以通过反射获取所需要的具体值
         //retVal 是返回的对象,响应的结果,可以强转拿到自己所需的参数
        for (int i = 0; i < validArgs.size(); i++) {
            if(validArgs.get(i) instanceof  Object){
                Class userCla = validArgs.get(i).getClass();
                // getFields():获得某个类的所有的公共(public)的字段,包括父类中的字段。
                // getDeclaredFields():获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段
                Field[] fs = userCla.getDeclaredFields();
                for (Field f : fs) {
                    f.setAccessible(true);//修改访问private修饰的权限
                    if ("XXXXXX".equals(f.getName())) {
                        try {
                            Object  value = f.get(args[i]); //获取当前属性的值
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }  
            }
         
        }
        return retVal; //必须要返回,不然所有的资源会被拦截
    }

    /**
    *@Description : @AfterThrowing 异常通知用来抓取异常,前提是异常被抛出来,如果Try catch  是进不来的  可以选择其他方式获取所需
    *@Param:
    *@return:
    */
    @AfterThrowing(value="recordApiLogger()",throwing = "e")
    public void  recordExceptionInfo(JoinPoint joinPoint,Throwable e){
        //错误信息匹配记录
        if(e instanceof MessageException){
            loggerRecord.setMessage(e.getMessage()); //失败提醒信息
        }else if (e instanceof NullPointerException ){
            loggerRecord.setMessage("空指针异常,详情查看error记录");
        }else if( e instanceof ArithmeticException){
            loggerRecord.setMessage("算术异常类异常,详情查看error记录");
        }else if(e instanceof ArrayIndexOutOfBoundsException ){
            loggerRecord.setMessage("索引越界异常,详情查看error记录");
        }else if( e instanceof ClassCastException){
            loggerRecord.setMessage("类强转异常,详情查看error记录");
        }else if (e instanceof NumberFormatException){
            loggerRecord.setMessage("数据格式转换异常,详情查看error记录");
        }else if(e instanceof SQLException){
            loggerRecord.setMessage("数据库SQL异常,详情查看error记录");
        }else if(e instanceof RuntimeException) {
            loggerRecord.setMessage(e.getMessage().substring(0, 300));
        }
        loggerRecord.setError(e.toString().length()<1000?e.toString():e.toString().substring(0,1001));//程序异常信息的打印
    }

     /**
    *@Description :@After 无论如何都会走进来,做一些必须在最后操作的逻辑
    */
    @After(value="recordApiLogger()")
    public void getResult(){
        LoggerRecord loggerRecord = loggerRecordThread.get();//先得到
        String createTime = df.format(new Date());
        try{
            //保存日志
            String  insertSql = " insert  table values() ";
            jdbcTemplate.update(insertSql,Object ... args); //方式1
            loggerService.save(loggerRecord); //方式2
         } catch (DataAccessException e) {
            logger.error("日志入库失败: {}",e.getCause());
         } finally {
            loggerRecordThread.remove();//一定要手动释放内存
         }

    }

    //请求耗时计算
    public static String formatDuring(long time) {
        long minutes = (time % (1000 * 60 * 60)) / (1000 * 60);
        long seconds = (time % (1000 * 60)) / 1000;
        long millisecond = time % 1000;
        return "共耗时"+time+"毫秒,转为时分秒为: "+ minutes + "分钟," + seconds + "秒," + millisecond + "毫秒 ";
    }

    class MessageException extends  RuntimeException{

    }

}


注意

① 处有线程安全问题
② 处顺序不对

处理参考方案

①处的线程安全问题
   A:如果本切面只用一个方法写到这个全局位置是没有问题的,而且也不需 ThreadLocal<LoggerRecord> loggerRecordThread 来进行线程隔离
   B:因为本切面有多个方法,并发情况下就会导致切面日志对象的数据不一致性,此时必须使用ThreadLocal来进行线程隔离
   C: 尽管用了ThreadLocal,这只是第一步,本切面这样就会导致数据不一致问题的出现,日志对象不应该是全局的,应该做为局部变量来使用,可以有两个方案解决:
      方案一:
            private ThreadLocal<LoggerRecord> loggerRecordThread = new ThreadLocal<LoggerRecord>(); //防止多线程导致的日志被重新赋值
    @Around(value = "recordApiLogger()")
    public Object  saveRecordApiLogger(ProceedingJoinPoint joinPoint){
          //  LoggerRecord loggerRecord=new LoggerRecord();  ①
            LoggerRecord loggerRecord=new LoggerRecord(); //①位置改为局部
            getBaseReqInfo(loggerRecord); //前置拦截作为一个私有方法
            long startTime = System.currentTimeMillis();
            Object retVal = joinPoint.proceed();
            long entTime = System.currentTimeMillis();
            String  consumeTime  = formatDuring(entTime - startTime);
            Object[] args = joinPoint.getArgs();//
            List<Object> validArgs =new ArrayList<>(); //可以用来记录的非接口类型参数
            ...
            }

  //抽取一个私有方法
 private LoggerRecord void getBaseReqInfo(LoggerRecord loggerRecord) {
            String remoteAddr = request.getRemoteAddr();//获取请求地址
            String url = request.getRequestURL().toString();
            String headParam = request.getParameter("XXXXX"); //获取项目请求头的参数
            loggerRecord.setRemoteAddr(remoteAddr);
            loggerRecord.setUrl(url);
            loggerRecord.setHeadParam(headParam);
            loggerRecordThread.set(loggerRecord);
        }

方案二:
   /**
    *@Description : 源代码不变的情况下,二次赋值ThreadLocal
    */
    @Before(value="recordApiLogger()")
    public void getRequestBaseInfo(){
      //  loggerRecordThread.set(loggerRecord);//
       if(null==loggerRecordThread.get()){
               loggerRecordThread.set(new LoggerRecord());
               loggerRecord=loggerRecordThread.get();
           }  
        String remoteAddr = request.getRemoteAddr();//获取请求地址
        String url = request.getRequestURL().toString();
        String headParam = request.getParameter("XXXXX"); //获取项目请求头的参数
    }

对于②处的顺序问题
  A: 这个自己调试一下就会真相大白
  B: @Around(value = "recordApiLogger()") 环绕通知是最先进来的,当执行到 joinPoint.proceed();才是按之前的顺序执行

姊妹篇

https://blog.youkuaiyun.com/WindwirdBird/article/details/105606701

多仓智能订单路由与动态优化方案设计 现在有很多商家会有多个仓库,并分布在不同的城市中,当用户下单后会根据商品库存及订单信息将订单分配到对应的仓库进行发货,如就近原则,本期基于多仓库设计一套订单路由方案,包含核心逻辑、规则优先级和实施方案,适用于多仓协同的订单自动分配场景 阅读详情

相关推荐

Windows下TensorRT+YOLOv11部署避坑指南:从CUDA配置到模型加密全流程

本文详细解析了在Windows平台部署YOLOv11与TensorRT的全流程,重点涵盖从CUDA环境配置、工程编译适配到模型加密保护的实战指南。基于wang-xinyu的tensorrtx仓库,提供了详细的版本选择、编译问题修复方案,并深入探讨了如何对TensorRT引擎进行加密,以实现高效且安全的端到端部署。

vscode5coder的博客 450

Java架构之切点切面增强器使用场景

关键字 @controllerAdvice 中包含了@component组件,所以能被扫描到 @aspect切面、@pointCut切入点、@before前置、@after后置、@around环绕、@afterthrowing异常抛出通知,切面修饰的类用组件注解 切点切面 切面修饰类,切入点修饰方法,通知修饰方法,切面由切点+通知 切点和通知一般传入五种参数 非环绕通知参数joinpoint、环绕通知参数proceedingjoinpoint、注解参数、返回值参数Object、thro

星爷的博客 675

Autosar Os降低CPU负载的方案总结

由于各个项目的实际场景不尽相同,可能需要有针对性的对症优化。如果你还有别的一些优化手段欢迎留言交流。

3679

Spring AOP 切面增强

Spring Aop 源码解析

1749

Windows与Linux安装miniconda并启动python项目及指令

官方介绍:Miniconda 是 conda 的免费最小安装程序。它是 Anaconda 的一个小型引导版本,仅​​包含 conda、Python、它们都依赖的包以及少量其他有用的包(例如 pip、zlib 等)。如果您需要更多软件包,请使用该 命令从 Anaconda 公共存储库或其他渠道(例如 conda-forge 或 bioconda)默认提供的数千个软件包中进行安装。minicoda下载地址最好挂个梯子。如果你需要指定版本的conda,可以在这里下载历史版本的miniconda。

一直走的很慢的鸭子 3193

如何利用GMS构建地下水数值模拟模型?

第一步:点图中三维网格的图标然后点Grid→Create Grid这个界面是用于创建一个有限差分网格的设置窗口。它允许你定义网格在三维空间中的范围、分辨率以及其他相关属性。这个界面提供了一个直观的方式来定义一个三维的有限差分网格,你需要指定每个维度上的范围、划分的单元格数量,以及一些控制单元格大小分布和网格方向的参数。这个网格通常会被用于数值模拟,例如地下水流模拟。在点击 "OK" 之前,请务必仔细检查所有参数是否符合你的建模需求。1. X-Dimension (X 轴维度)2. Y-Dimension (

weixin_44259522的博客 2094

Spring AOP 介绍与简单日志切面实现,引入切面@Aspect

文章目录一、AOP1、AOP含义2、模式演变3、OOP的弊端?4、AOP的应用场景二、Aspect概念1、AOP中主要概念2、通知的类型(advice)3、@Aspect驱动三、代码实现1、定一个日志切面类2、定义切点(Pointcut)3、定义通知(Advice)4、实现效果附录 一、AOP 1、AOP含义 AOP即面向切面编程。通过预编译方式和运行动态代理实现在不修改源代码的情况下给程序动态统一添加的一种技术。 AOP的编程思想就是把很多类对象中的横切问题点,从业务逻辑中分离出来, 从而达到解耦的目的

狂点Engineer 3297

第三课 NXP S32K144 MCAL 集成到Vector davinci SIP-第一弹

将NXP S32K144 MCAL 集成到Vector davinci SIP的第三方库,导出ARXMl给Vector davinci SIP使用

candleer的博客 5857

Spring AOP 源码解析:注解式切面增强机制

Spring AOP 源码解析:注解式切面增强机制IoC 和 AOP 被称为 Spring 两大基础模块,支撑着上层扩展的实现和运行。虽然 AOP 同样建立在 IoC 的实现基础之上,但是作为对 OOP(Object-Oriented Programing) 的补充,AOP(Aspect-Oriented Programming) 在程序设计领域拥有其不可替代的适用场景和地位...

java_zdc的博客 453

3.1 计算机视觉的发展和卷积神经网络概要(百度架构师手把手带你零基础实践深度学习原版笔记系列)

3.1 计算机视觉的发展和卷积神经网络(百度架构师手把手带你零基础实践深度学习原版笔记系列) 概要 计算机视觉作为一门让机器学会如何去“看”的科学学科,具体的说,就是让机器去识别摄像机拍摄的图片或视频中的物体,检测出物体所在的位置,并对目标物体进行跟踪,从而理解并描述出图片或视频里的场景和故事,以此来模拟人脑视觉系统。因此,计算机视觉也通常被叫做机器视觉,其目的是建立能够从图像或者视频中“感知”信息的人工系统。 计算机视觉技术经过几十年的发展,已经在交通(车牌识别、道路违章抓拍)、安防(人脸闸机.

aiAI 692

IEC61850规约客户端软件开发实战(第六章)

IEC 61850标准中的GOOSE(Generic Object Oriented Substation Event,面向通用对象的变电站事件)功能是变电站自动化系统的核心通信机制,主要用于实现智能电子设备(IED)间高可靠性、低延时的实时信号传输。本章简述其核心功能与技术特点。

半青年的博客 1780

使用AOP切面实现日志记录功能

说到AOP大家都可以想到他是面向切面的编程,它通过将横切关注点(例如日志记录、事务管理、权限控制等)从主要业务逻辑中分离出来,以模块化的方式进行管理。在AOP中,通过定义切面(Aspect)来捕获和处理横切关注点,然后将其应用于特定的目标对象或方法。官方的解释有点抽象,我们举个例子说明:假设我们需要在多个方法中添加日志记录功能。传统的方式是在每个方法中都添加日志代码,但这样会导致代码重复,并且当我们需要修改日志记录逻辑时,需要逐个修改所有方法。

心态的博客 6217

Open3D 使用RANSAC分割平面【2026最新版】

使用RANSAC从点云中分割平面,用segement_plane函数。博客长期更新,本文最新更新时间为:2026年2月28日。

点云侠的博客 37万+

Spring---AOP面向切面编程(三)

3.1 AOP:(Aspect Oriented Programming)面向切面编程 AOP:(Aspect Oriented Programming)面向切面编程; OOP:(Object Oriented Programming )面向对象编程; 面向切面编程:基于OOP基础之上新的编程思想; 指在程序运行期间,将某段代码动态的切入到指定方法的指定位置进行运行的这种编程方式,面向切面编程; ...

我是太阳 1万+

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

002-日志增强版

【代码】002-日志增强版。

xiaogang1226的博客 209

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

SSM框架 之 Spring-AOP(面向切面编程)

1AOP概述 AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传统 OOP(Object-Oriented Programming,面向对象编程)的补充。 两种编程思想。是对面向对象编程的一种补充。 面向切面编程:是指在程序运行期间将某段代码,动态的切入到某个类的指定方法的指定位置的这种编程思想叫做面向切面编程。 AOP编程操作的主要对

OnlyLove_longshao的博客 1万+

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

AOP 切面的进化:使用 SpEL 表达式打造更智能、更灵活的自定义注解

本文介绍了如何利用SpEL表达式增强Spring AOP切面的灵活性,解决传统AOP中动态传参的痛点。通过自定义注解集成SpEL表达式,开发者可以声明式地指定切面所需参数来源,避免硬编码获取方法参数。文章以"权益校验"场景为例,详细演示了实现步骤:首先定义支持SpEL表达式注入的注解,然后在业务方法上使用SpEL引用参数,最后在切面中通过SpEL解析器动态获取参数值。这种方法显著提升了代码可读性、降低耦合度,使切面逻辑更加智能和通用。

qq_52272700的博客 1128

手动实现一个迷你版的AOP(实战增强版)

在正式进行aop模块的介绍之前,我们需要先弄懂一些基本的术语概念。在软件业,AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,通过预编译方式和...

Java笔记虾 327

MyBatis增强版:MyBatis-Plus

简介 MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。https://mp.baomidou.com/ 配置步骤说明 创建数据库环境 建表语句 CREATE TABLE `tb_user`...

972

原生PHP编写 AOP 切面,自动记录所有 DB/Redis 操作的耗时,发现慢查询的庖丁解牛

为了让代理能无缝替换真实对象,必须定义统一的接口。IDatabase: 定义queryexecuteprepare等方法。ICache: 定义getsethGethSet等方法。// ... 其他需要监控的方法// ...维度核心要点关键行动本质动态代理 + 装饰器模式控制对象创建入口,返回代理实例架构定义标准接口,分离监控逻辑实现call_user_func_array + 微计时包裹真实调用,计算耗时差值难点Statement 递归代理,参数脱敏。

长风破浪会有时的博客 300

C语言实现轻量级AOP:嵌入式切面编程实战

面向切面编程(AOP)本质是关注点分离与横切逻辑注入的软件设计思想,不依赖特定语言或运行时环境。其核心原理在于编译期静态绑定、函数指针调度与确定性执行流程,技术价值体现为零堆内存占用、可预测栈深度、全链路可验证性。在资源受限的嵌入式系统中,AOP常用于固件调试、性能审计与安全日志等场景,尤其适用于裸机、无OS、小RAM(如4KB)及严格实时性要求(≤10μs抖动)的工业控制设备。本文基于函数指针表与跳转桩(Trampoline)机制,详解如何在C语言中构建可裁剪、可开关、纯静态链接的AOP内核。

weixin_30938149的博客 336

告别土味埋点:Java 优雅统计方法耗时的艺术与实践​

本文系统梳理了Java方法耗时统计的多种方案,从基础实现到企业级应用。传统手动埋点方式存在代码侵入性强、功能简陋等问题,而优雅方案应具备低侵入性、高复用性等特点。文章详细介绍了四种实现方式:1. JDK原生工具(System.currentTimeMillis和JDK9+的Instant/Duration);2. Spring的StopWatch工具类;3. AOP切面实现无侵入统计;4. 函数式编程与工具类封装。针对分布式场景,提出结合MDC实现全链路追踪的方案。

果酱 の 博客 1742

自定义注解 + AOP + Redisson:优雅实现分布式锁(增强版)

分布式锁是一种在分布式系统中用于控制多个进程或线程对共享资源访问的机制。互斥性:同一时刻只有一个客户端可以持有锁。可重入性:同一个客户端可以多次获取同一把锁。高可用性:锁服务需要具备高可用性,避免单点故障。自动释放:锁需要支持超时自动释放,避免死锁。定义一个注解// 锁的key,支持SpEL表达式 long waitTime() default 30;// 获取锁的最大等待时间(秒) long leaseTime() default 10;// 锁的持有时间(秒) }

双非二本自学到互联网公司的成长经历,有故事,有干货。 1522

Java后端框架模块整合

MyBatis 框架、Spring 框架、SpringMVC 框架、SpringBoot 框架

二哈喇子! 3262
上一篇: Idea断点for循环调试
下一篇: execution使用
WindwardBird
博客等级 码龄7年 19粉丝 94原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值