跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

Spring AOP 核心概念与通知类型详解

Spring AOP 核心概念包括切点、连接点、通知和切面。通知类型有环绕、前置、后置、返回后及异常后通知,执行顺序受@Order 控制。切点表达式支持 execution 和 annotation 两种主要方式,用于精确匹配目标方法。通过自定义注解可灵活定义切面逻辑。

SecGuard发布于 2026/2/7更新于 2026/5/2924 浏览
Spring AOP 核心概念与通知类型详解

文章配图

Spring AOP 核心概念

切点 (Pointcut)

切点 (Pointcut),也称之为切入点。 Pointcut 的作用就是提供一组规则 (使用 AspectJ pointcut expression language 来描述), 告诉程序对哪些方法来进行功能增强.

文章配图

上面的表达式 execution(* com.wmh.springaop.controller..(..)) 就是 切点表达式。

连接点 (Join Point)

满足切点表达式规则的方法,就是连接点。也就是可以被 AOP 控制的方法。

对于下面的代码,com.wmh.springaop.controller 路径下所有类的所有方法,都是连接点。

文章配图

切点和连接点的关系

连接点是满足切点表达式的元素。切点可以看做是保存了众多连接点的一个集合。

通知 (Advice)

通知就是具体要做的工作,指哪些重复的逻辑,也就是共性功能 (最终体现为一个方法)。 比如上述代码中记录业务方法的耗时时间,就是通知。

文章配图

在 AOP 面向切面编程当中,我们把这部分重复的代码逻辑抽取出来单独定义,这部分代码就是通知的内容。

切面 (Aspect)

切面 (Aspect) = 切点 (Pointcut) + 通知 (Advice)。 通过切面就能够描述当前 AOP 程序需要针对于哪些方法,在什么时候执行什么样的操作。 切面既包含了通知逻辑的定义,也包括了连接点的定义。

文章配图

切面所在的类,我们一般称为切面类 (被@Aspect 注解标识的类)。

通知类型

上面我们讲了什么是通知,接下来学习通知的类型。@Around 就是其中一种通知类型,表示环绕通知。 Spring 中 AOP 的通知类型有以下几种:

• @Around: 环绕通知,此注解标注的通知方法在目标方法前,后都被执行 • @Before: 前置通知,此注解标注的通知方法在目标方法前被执行 • @After: 后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行 • @AfterReturning: 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行 • @AfterThrowing: 异常后通知,此注解标注的通知方法发生异常后执行

下面通过代码加深对上面通知类型的理解:

AspectDemo
package com.wmh.springaop.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class AspectDemo {
    @Before("execution(* com.wmh.springaop.controller..*(..))")
    public void doBefore(){
        log.info("AspectDemo do before....");
    }

    @After("execution(* com.wmh.springaop.controller..*(..))")
    public void doAfter(){
        log.info("AspectDemo do after....");
    }

    @Around("execution(* com.wmh.springaop.controller..*(..))")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("AspectDemo do around before....");
        Object result = joinPoint.proceed();
        log.info("AspectDemo do around after...");
        return result;
    }

    @AfterReturning("execution(* com.wmh.springaop.controller..*(..))")
    public void doAfterReturning(){
        log.info("AspectDemo do after returning....");
    }

    @AfterThrowing("execution(* com.wmh.springaop.controller..*(..))")
    public void doAfterThrowing(){
        log.info("AspectDemo do after throwing....");
    }
}
TestController
package com.wmh.springaop.controller;
import com.wmh.springaop.config.MyAspect;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RequestMapping("/test")
@RestController
public class TestController {
    @RequestMapping("/t1")
    public String t1(){
        log.info("执行 t1 方法...");
        return "t1";
    }

    @RequestMapping("/t2")
    public String t2(){
        log.info("执行 t2 方法...");
        int a = 10/0;
        return "t2";
    }
}
通知类型的执行顺序

上面代码正常情况下的运行结果:

文章配图

由此我们知道,正常情况下通知类型的执行顺序是:

文章配图

发生异常时的运行结果:

文章配图

由此我们知道,发生异常的情况下通知类型的执行顺序是:

文章配图

比较正常情况下和发生异常的情况下的运行结果:

文章配图

从上面我们可以看到,当发生异常时@AfterReturning 表示的通知方法不会执行了,@AfterThrowing 表示的通知方法执行了。 而且,当@Around 环绕通知中原始方法调用时有异常,通知中的环绕后的代码逻辑就不会执行了,因为原始方法调用出异常了。

关于@Around 表示的方法返回值问题

当@Around 标识的方法有返回值且类型为 Object 时:

AspectDemo

package com.wmh.springaop.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class AspectDemo {
    @Around("execution(* com.wmh.springaop.controller..*(..))")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("AspectDemo do around before....");
        Object result = joinPoint.proceed();
        log.info("AspectDemo do around after...");
        return result;
    }
}

TestController

package com.wmh.springaop.controller;
import com.wmh.springaop.config.MyAspect;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RequestMapping("/test")
@RestController
public class TestController {
    @RequestMapping("/t1")
    public String t1(){
        log.info("执行 t1 方法...");
        return "t1";
    }
}

运行结果:

文章配图

当@Around 表示的方法没有返回值时:

AspectDemo

package com.wmh.springaop.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class AspectDemo {
    @Around("execution(* com.wmh.springaop.controller..*(..))")
    public void doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("AspectDemo do around before....");
        Object result = joinPoint.proceed();
        log.info("AspectDemo do around after...");
    }
}

注意事项

• @Around 环绕通知需要调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行。 • @Around 环绕通知方法的返回值,必须指定为 Object,来接收原始方法的返回值,否则原始方法执行完毕,是获取不到返回值的。 • 一个切面类可以有多个切点。

@Pointcut

上面代码存在一个问题,就是存在大量重复的切点表达式 execution(* com.wmh.springaop.controller..*(..)) , Spring 提供了 @PointCut 注解,把公共的切点表达式提取出来,需要用到时引用该切入点表达式即可。

上述代码就可以修改为:

package com.wmh.springaop.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class AspectDemo {
    @Pointcut("execution(* com.wmh.springaop.controller..*(..))")
    public void pt(){};

    @Before("pt()")
    public void doBefore(){
        log.info("AspectDemo do before....");
    }

    @After("pt()")
    public void doAfter(){
        log.info("AspectDemo do after....");
    }

    @Around("pt()")
    public void doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("AspectDemo do around before....");
        Object result = joinPoint.proceed();
        log.info("AspectDemo do around after...");
    }

    @AfterReturning("pt()")
    public void doAfterReturning(){
        log.info("AspectDemo do after returning....");
    }

    @AfterThrowing("pt()")
    public void doAfterThrowing(){
        log.info("AspectDemo do after throwing....");
    }
}

当切点定义使用 private 修饰时,仅能在当前切面类中使用,当其他切面类也要使用当前切点定义时,就需要把 private 改为 public。引用方式为:全限定类名。方法名()

package com.wmh.springaop.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;

@Order(1)
@Slf4j
@Component
@Aspect
public class AspectDemo2 {
    @Before("com.wmh.springaop.aspect.AspectDemo.pt()")
    public void doBefore(){
        log.info("AspectDemo2 do before....");
    }

    @After("com.wmh.springaop.aspect.AspectDemo.pt()")
    public void doAfter(){
        log.info("AspectDemo2 do after....");
    }
}
切面优先级@Order

当我们在一个项目中,定义了多个切面类时,并且这些切面类的多个切入点都匹配到了同一个目标方法。当目标方法运行的时候,这些切面类中的通知方法都会执行,那么这几个通知方法的执行顺序是什么样的呢?我们还是通过程序来求证: 定义多个切面类: 为简单化,只写了 @Before 和 @After 两个通知。

AspectDemo2

package com.wmh.springaop.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class AspectDemo2 {
    @Before("com.wmh.springaop.aspect.AspectDemo.pt()")
    public void doBefore(){
        log.info("AspectDemo2 do before....");
    }

    @After("com.wmh.springaop.aspect.AspectDemo.pt()")
    public void doAfter(){
        log.info("AspectDemo2 do after....");
    }
}

AspectDemo3

package com.wmh.springaop.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class AspectDemo3 {
    @Before("com.wmh.springaop.aspect.AspectDemo.pt()")
    public void doBefore(){
        log.info("AspectDemo3 do before....");
    }

    @After("com.wmh.springaop.aspect.AspectDemo.pt()")
    public void doAfter(){
        log.info("AspectDemo3 do after....");
    }
}

AspectDemo4

package com.wmh.springaop.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class AspectDemo4 {
    @Before("com.wmh.springaop.aspect.AspectDemo.pt()")
    public void doBefore(){
        log.info("AspectDemo4 do before....");
    }

    @After("com.wmh.springaop.aspect.AspectDemo.pt()")
    public void doAfter(){
        log.info("AspectDemo4 do after....");
    }
}

TestController

package com.wmh.springaop.controller;
import com.wmh.springaop.config.MyAspect;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RequestMapping("/test")
@RestController
public class TestController {
    @RequestMapping("/t1")
    public String t1(){
        log.info("执行 t1 方法...");
        return "t1";
    }
}

运行结果:

文章配图

通过上述程序的运行结果,可以看出: 存在多个切面类时,默认按照切面类的类名字母排序:

• @Before 通知:字母排名靠前的先执行 • @After 通知:字母排名靠前的后执行

但这种方式不方便管理,我们的类名更多还是具备一定含义的。 Spring 给我们提供了一个新的注解,来控制这些切面通知的执行顺序:@Order 修改上面的代码:

AspectDemo2

package com.wmh.springaop.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Order(1)
@Slf4j
@Component
@Aspect
public class AspectDemo2 {
    @Before("com.wmh.springaop.aspect.AspectDemo.pt()")
    public void doBefore(){
        log.info("AspectDemo2 do before....");
    }

    @After("com.wmh.springaop.aspect.AspectDemo.pt()")
    public void doAfter(){
        log.info("AspectDemo2 do after....");
    }
}

AspectDemo3

package com.wmh.springaop.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Order(100)
@Slf4j
@Component
@Aspect
public class AspectDemo3 {
    @Before("com.wmh.springaop.aspect.AspectDemo.pt()")
    public void doBefore(){
        log.info("AspectDemo3 do before....");
    }

    @After("com.wmh.springaop.aspect.AspectDemo.pt()")
    public void doAfter(){
        log.info("AspectDemo3 do after....");
    }
}

AspectDemo4

package com.wmh.springaop.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Order(25)
@Slf4j
@Component
@Aspect
public class AspectDemo4 {
    @Before("com.wmh.springaop.aspect.AspectDemo.pt()")
    public void doBefore(){
        log.info("AspectDemo4 do before....");
    }

    @After("com.wmh.springaop.aspect.AspectDemo.pt()")
    public void doAfter(){
        log.info("AspectDemo4 do after....");
    }
}

运行结果:

文章配图

通过上述程序的运行结果,得出结论: @Order 注解标识的切面类,执行顺序如下:

• @Before 通知:数字越小先执行 • @After 通知:数字越大先执行

@Order 控制切面的优先级,先执行优先级较高的切面,再执行优先级较低的切面,最终执行目标方法。

文章配图

切点表达式

上面的代码中,我们一直在使用切点表达式来描述切点。下面我们来介绍一下切点表达式的语法。 切点表达式常见有两种表达方式:

  1. execution(RR):根据方法的签名来匹配
  2. @annotation(RR):根据注解匹配
execution 表达式

execution() 是最常用的切点表达式,用来匹配方法,语法为:

execution(<访问修饰符> <返回类型> <包名。类名。方法 (方法参数)> <异常>)

其中:访问修饰符和异常可以省略。

文章配图

切点表达式支持通配符表达:

  1. :匹配任意字符,只匹配一个元素 (返回类型,包,类名,方法或者方法参数) a. 包名使用 * 表示任意包 (一层包使用一个) b. 类名使用 * 表示任意类 c. 返回值使用 * 表示任意返回值类型 d. 方法名使用 * 表示任意方法 e. 参数使用 * 表示一个任意类型的参数
  2. ..:匹配多个连续的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数 a. 使用 .. 配置包名,标识此包以及此包下的所有子包 b. 可以使用 .. 配置参数,任意个任意类型的参数

切点表达式示例

TestController 下的 public 修饰,返回类型为 String 方法名为 t1, 无参方法

execution(public String com.example.demo.controller.TestController.t1())

省略访问修饰符

execution(String com.example.demo.controller.TestController.t1())

匹配所有返回类型

execution(* com.example.demo.controller.TestController.t1())

匹配 TestController 下的所有无参方法

execution(* com.example.demo.controller.TestController.*())

匹配 TestController 下的所有方法

execution(* com.example.demo.controller.TestController.*(..))

匹配 controller 包下所有的类的所有方法

execution(* com.example.demo.controller...(..))

匹配所有包下面的 TestController

execution(* com..TestController.*(..))

匹配 com.example.demo 包下,子包下的所有类的所有方法

execution(* com.example.demo..*(..))

annotation 表达式

execution 表达式更适用有规则的,如果我们要匹配多个无规则的方法呢,比如:TestController 中的 t1() 和 UserController 中的 u1() 这两个方法。 这个时候我们使用 execution 这种切点表达式来描述就不是很方便了。 我们可以借助自定义注解的方式以及另一种切点表达式 @annotation 来描述这一类的切点。

实现步骤:

  1. 编写自定义注解
  2. 使用 @annotation 表达式来描述切点
  3. 在连接点的方法上添加自定义注解

自定义注解@MyAspect

package com.wmh.springaop.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect { }

说明

1.@Target 标识了 Annotation 所修饰的对象范围,即该注解可以用在什么地方。 常用取值: ElementType.TYPE: 用于描述类、接口 (包括注解类型) 或 enum 声明 ElementType.METHOD: 描述方法 ElementType.PARAMETER: 描述参数 ElementType.TYPE_USE: 可以标注任意类型 2. @Retention 指 Annotation 被保留的时间长短,标明注解的生命周期 @Retention 的取值有三种: 1. RetentionPolicy.SOURCE:表示注解仅存在于源代码中,编译成字节码后会被丢弃。这意味着在运行时无法获取到该注解的信息,只能在编译时使用。比如 @SuppressWarnings,以及 lombok 提供的注解 @Data,@Slf4j 2. RetentionPolicy.CLASS:编译时注解。表示注解存在于源代码和字节码中,但在运行时会被丢弃。这意味着在编译时和字节码中可以通过反射获取到该注解的信息,但在实际运行时无法获取。通常用于一些框架和工具的注解。 3. RetentionPolicy.RUNTIME:运行时注解。表示注解存在于源代码,字节码和运行时中。这意味着在编译时,字节码中和实际运行时都可以通过反射获取到该注解的信息。通常用于一些需要在运行时处理的注解,如 Spring 的 @Controller @ResponseBody

切面类

使用 @annotation 切点表达式定义切点,只对 @MyAspect 生效 切面类代码如下:

package com.wmh.springaop.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class MyAspectDemo {
    @Around("@annotation(com.wmh.springaop.config.MyAspect)")
    public Object doAround(ProceedingJoinPoint joinPoint){
        log.info("do around before...");
        Object o = null;
        try {
            o = joinPoint.proceed();
        } catch (Throwable e) {
            log.error("发生异常,e:", e);
        }
        log.info("do around after...");
        return o;
    }
}

添加自定义注解

TestController

package com.wmh.springaop.controller;
import com.wmh.springaop.config.MyAspect;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RequestMapping("/test")
@RestController
public class TestController {
    @MyAspect
    @RequestMapping("/t1")
    public String t1(){
        log.info("执行 t1 方法...");
        return "t1";
    }

    @RequestMapping("/t2")
    public String t2(){
        log.info("执行 t2 方法...");
        int a = 10/0;
        return "t2";
    }
}

UserController

package com.wmh.springaop.controller;
import com.wmh.springaop.config.MyAspect;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
    @RequestMapping("/u1")
    public String u1(){
        log.info("执行 u1 方法...");
        return "u1";
    }

    @MyAspect
    @RequestMapping("/u2")
    public String u2(){
        log.info("执行 u2 方法...");
        return "u2";
    }
}

访问 test/t1 运行结果:

文章配图

访问 user/u2 运行结果:

文章配图

目录

  1. Spring AOP 核心概念
  2. 切点 (Pointcut)
  3. 连接点 (Join Point)
  4. 切点和连接点的关系
  5. 通知 (Advice)
  6. 切面 (Aspect)
  7. 通知类型
  8. AspectDemo
  9. TestController
  10. 通知类型的执行顺序
  11. 关于@Around 表示的方法返回值问题
  12. @Pointcut
  13. 切面优先级@Order
  14. 切点表达式
  15. execution 表达式
  16. annotation 表达式
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Flutter 三方库 shelf_modular 的鸿蒙化适配指南
  • DeepSeek-R1-Distill-Qwen-1.5B 部署指南:vLLM+Open WebUI 低显存运行方案
  • IDEA 中 AI 编程插件实测:Copilot、TRAE 与灵码深度对比
  • 滑动窗口算法实战:最大连续 1 与最小操作数
  • 基于视觉语言动作的竞速无人机自主导航 RaceVLA 架构解析
  • Windows 下安装 OpenClaw 并接入飞书机器人指南
  • Trae AI IDE 使用指南
  • 机器人灵巧手(Dexterous Hand)顶会论文精选 RSS CoRL ICRA IROS
  • YOLOv8 旋转框角度回归优化:CSL 与 DCL 编码实战
  • Three.js + WebGL 粒子动画实测:10 万粒子流畅运行
  • Java 并发核心:单例模式、生产者消费者、定时器及线程池实现
  • LIBERO 数据集:终身机器人学习基准测试平台
  • Kali Linux 2025.4 正式发布:告别 X11、Wayland 全适配、工具与镜像升级
  • 播客转多平台内容矩阵全自动化实战(OpenAI Whisper + Claude)
  • 改进A*算法路径规划:Matlab 实现
  • DeepSeek-R1-Distill-Llama-8B 快速部署指南
  • 高性能 Go 缓存库 Ristretto:从算法原理到生产级架构实践
  • Whisper Streaming 快速入门:搭建实时语音转录环境
  • 位运算算法精讲:两数之和、唯一数字及缺失数字
  • OpenCLaw Web UI 无法访问 Not Found 问题排查与解决

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online