当前位置: 首页 > news >正文

【SpringBoot教程】SpringBoot自定义注解与AOP实现切面日志

🙋大家好!我是毛毛张!
🌈个人首页: 神马都会亿点点的毛毛张

QQ_1743599722821

文章目录

  • 1.前言
  • 2.SpringAOP实现切面日志快速入门
    • 1.1 创建SpringBoot项目
    • 1.2 依赖配置`pom.xml`
    • 1.3 自定义日志注解
    • 1.4 配置 AOP 切面
    • 1.5 怎么使用呢?
    • 1.6 实体类
    • 1.7 启动类
    • 1.8 测试
    • 1.9 只想在开发环境和测试环境中使用?
    • 1.10 多切面如何指定优先级?
    • 1.11 总结
  • 3.Spring AOP
      • 1.谈谈自己对于 AOP 的了解
        • 切面织入有哪几种方式?
        • AOP常见注解
        • AOP 有哪些环绕方式?AOP 常见的通知类型有哪些?
        • AspectJ 是什么?
        • Spring AOP 发生在什么时候?
        • 简单总结一下 AOP
        • AOP和 OOP 的关系?
      • 2.多个切面的执行顺序如何控制?
      • 3.动态代理和静态代理的区别
      • 4.说说 JDK 动态代理和 CGLIB 代理?
        • 选择 CGLIB 还是 JDK 动态代理?
        • 你会用 JDK 动态代理和 CGLIB 吗?
      • 5.说说 Spring AOP 和 AspectJ AOP 区别?
      • 6.说说 AOP 和反射的区别?
      • 7.AOP的使用场景有哪些?日志记录、事务管理、权限控制、性能监控
  • 参考文献

1.前言

  • 毛毛张今天要分享的是 Spring 中最基础也是最关键的概念之一:AOP(面向切面编程)。AOP 是一种编程范式,用于将横切关注点(如日志、事务管理、安全等)从业务逻辑中分离出来。它通过动态地将这些横切关注点插入到程序的执行流程中,提高了代码的模块化和可维护性。Spring AOP 是 Spring 框架中实现 AOP 的核心模块。
  • AOP 是 Spring 框架中的一个核心内容。在 Spring 中,AOP 代理可以用 JDK 动态代理或者 CGLIB 代理 CglibAopProxy 实现。Spring 中 AOP 代理由 Spring 的 IOC 容器负责生成和管理,其依赖关系也由 IOC 容器负责管理。
    • JDK 动态代理:适用于实现了接口的类,通过动态生成代理类来实现 AOP 功能。
    • CGLIB 代理:适用于没有实现接口的类,通过生成目标类的子类来实现 AOP 功能。
    • IOC 容器:Spring 的核心组件,负责管理 Bean 的生命周期和依赖注入。AOP 代理的生成和管理也依赖于 IOC 容器。
  • AOP 有两种主要实现方式:Spring AOP 和 AspectJ
    • Spring AOP
      • 基于运行时增强,使用动态代理实现。
      • 适用于 Spring 容器中的 Bean,功能相对简单,适合轻量级 AOP 场景。
      • 性能稍逊于 AspectJ,因为需要在运行时生成代理实例。
    • AspectJ
      • 基于编译时增强,通过字节码操作实现静态织入。
      • 功能更强大,支持更丰富的切点表达式和织入方式。
      • 性能更优,因为织入在编译时完成,运行时没有额外开销。
  • Spring AOP 已经集成了 AspectJ 的核心功能,因此在 Spring AOP 中可以使用 AspectJ 的注解来实现 AOP 功能。Spring AOP 的底层实现基于 AspectJ,但它提供了一种更简单、更轻量的方式来使用 AOP 功能。在 Spring AOP 中,你可以使用 AspectJ 的注解,如 @Aspect@Pointcut@Before@After@Around 等,来定义切面、切点和通知。
  • 毛毛张今天将分为两部分来介绍 Spring AOP:
    • 第一部分是通过一个 Spring AOP 实现切面日志的快速入门案例来介绍如何使用 AOP
    • 第二部分再来深入介绍 Spring AOP 的底层以及相关的核心概念和面试的八股

2.SpringAOP实现切面日志快速入门

  • 在介绍快速入门案例之前,我们先看下切面日志输出效果咋样:

QQ_1743645145048

  • 从上图中可以看到,每个对于每个请求,开始与结束一目了然,并且打印了以下参数:
    • URL: 请求接口地址;
    • Description: 接口的中文说明信息;
    • HTTP Method: 请求的方法,是 POST, GET, 还是 DELETE 等;
    • Class Method: 被请求的方法路径 : 包名 + 方法名;
    • IP: 请求方的 IP 地址;
    • Request Args: 请求入参,以 JSON 格式输出;
    • Response Args: 响应出参,以 JSON 格式输出;
    • Time-Consuming: 请求耗时,以此估算每个接口的性能指数;

怎么样?看上去效果还不错呢?接下来看看,我们要如何一步一步实现它呢?

1.1 创建SpringBoot项目

  • 创建一个SpringBoot项目,这里详细的步骤毛毛张就省略了,更多的关于如何快速创建一个SpringBoot新项目可以参见毛毛张的这篇博客:【SpringBoot教程】IDEA快速搭建正确的SpringBoot版本和Java版本的项目

  • 下面是毛毛张的创建的项目的完整结构目录:
    QQ_1743645903123

  • 后端完整代码毛毛张以及上传至Github:https://github.com/zzxrepository/SpringBootTutorial/tree/master/springboot-aop

  • 如果觉得写的不错,不妨给毛毛张的Github仓库给个星标吧!感谢!

1.2 依赖配置pom.xml

  • 下面是实现AOP的核心依赖:

    <!-- aop 依赖 -->
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    
  • 但是为了项目的完整性和我们的测试,还引入了其它的依赖,下面是完整的项目依赖:

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- Maven 项目的根元素,定义了项目的坐标、依赖、构建配置等信息xmlns 和 xmlns:xsi 是命名空间声明,用于定义 POM 文件的 XML Schemaxsi:schemaLocation 指定了 POM 文件的 Schema 位置,用于验证 POM 文件的正确性
    -->
    <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"><!-- POM 文件的模型版本号,固定值 4.0.0 --><modelVersion>4.0.0</modelVersion><groupId>com.zzx</groupId><artifactId>springboot-aop-demo</artifactId><version>0.0.1-SNAPSHOT</version><name>springboot-aop-demo</name><description>springboot-aop-demo</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-parent</artifactId><version>2.7.6</version></parent><properties><java.version>1.8</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><spring-boot.version>2.7.6</spring-boot.version></properties><!-- 定义项目的依赖,Maven 会自动下载这些依赖并添加到项目的类路径中--><dependencies><!-- Spring Boot Web 依赖,用于构建 Web 应用 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- MySQL 数据库连接驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!-- Spring Boot AOP 依赖,用于实现面向切面编程 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><!-- Google Gson 库,用于 JSON 数据的序列化和反序列化 --><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.5</version></dependency><!-- Alibaba Fastjson 库,用于 JSON 数据的处理 --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.58</version></dependency><!-- Spring Boot 开发工具,提供热部署等功能 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><!-- Lombok 库,用于简化 Java 代码 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- Spring Boot 测试依赖,用于编写和运行测试用例 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><!-- 管理项目的依赖版本,确保所有模块使用相同的依赖版本--><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><!-- 定义项目的构建配置,包括编译、打包、部署等步骤--><build><plugins><!-- Maven 编译插件,用于编译 Java 代码配置了 Java 版本和编码--><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin><!-- Spring Boot Maven 插件,用于打包可执行的 JAR 文件配置了主类和是否跳过插件执行--><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>${spring-boot.version}</version><configuration><mainClass>com.zzx.SpringbootAopDemoApplication</mainClass><skip>true</skip></configuration><executions><execution><id>repackage</id><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build>
    </project>
    

1.3 自定义日志注解

  • 首先自定义注解WebLog

    package com.zzx.aspect;
    import java.lang.annotation.*;
    /** 
    * @date 2023/10/6 
    * @time 下午9:19 
    * @discription 
    **/ 
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    @Documented
    public @interface WebLog {/*** 日志描述信息* @return**/ String description() default "";
    }
    
  • 代码解释:

    • @Retention(RetentionPolicy.RUNTIME):什么时候使用该注解,我们定义为运行时;
    • @Target({ElementType.METHOD}):注解用于什么地方,我们定义为作用于方法上;
    • @Documented:注解是否将包含在 JavaDoc 中;
    • public @interface WebLog :注解名为 WebLog;
    • String description() default "";:定义一个属性,默认为空字符串;

1.4 配置 AOP 切面

  • 在配置 AOP 切面之前,我们需要了解下 aspectj 相关注解的作用:

    • @Aspect:声明该类为一个注解类;
    • @Pointcut:定义一个切点,后面跟随一个表达式,表达式可以定义为切某个注解,也可以切某个 package 下的方法;
  • 切点定义好后,就是围绕这个切点做文章了:

    • @Before: 在切点之前,织入相关代码;
    • @After: 在切点之后,织入相关代码;
    • @AfterReturning: 在切点返回内容后,织入相关代码,一般用于对返回值做些加工处理的场景;
    • @AfterThrowing: 用来处理当织入的代码抛出异常后的逻辑处理;
    • @Around: 环绕,可以在切入点前后织入代码,并且可以自由的控制何时执行切点;
      image
  • 切面完整代码:

    package com.zzx.aspect;import com.google.gson.Gson;
    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.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
    import java.lang.reflect.Method;/*** WebLogAspect 是一个面向切面编程(AOP)的类,用于记录 Web 请求的日志。* 它在方法执行前后以及环绕方法执行时记录日志,包括请求的 URL、方法描述、HTTP 方法、类方法、IP 地址、请求参数、响应参数和耗时等信息。* 该类使用 Spring 的 @Component 注解标记为一个 Spring 组件,并使用 @Aspect 注解标记为一个切面类。* @Order 注解定义了该切面的执行顺序,数字越小优先级越高。*/
    @Aspect
    @Component
    @Order(1)
    public class WebLogAspect {/*** 定义一个日志记录器,用于记录 WebLogAspect 类的日志。*/private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);/*** 定义一个系统换行符常量,用于格式化日志输出。*/private static final String LINE_SEPARATOR = System.lineSeparator();/*** 定义一个切点,以自定义 @WebLog 注解为切点。* 该切点使用 @Pointcut 注解标记,表示匹配带有 @WebLog 注解的方法。*/@Pointcut("@annotation(com.zzx.aspect.WebLog)")public void webLogPointcut() {}/*** 定义另一个切点,使用 execution 表达式拦截 com.zzx.controller 包及其子包下的所有方法。* 该切点也使用 @Pointcut 注解标记。*/@Pointcut("execution(* com.zzx.controller..*.*(..))")public void controllerMethodsPointcut() {}/*** 在带有 @WebLog 注解的方法执行前执行的前置通知。* 该方法记录请求的详细信息,包括 URL、方法描述、HTTP 方法、类方法、IP 地址和请求参数。* @param joinPoint 切点对象,包含方法执行的上下文信息。*/@Before("webLogPointcut()")public void doBeforeWithWebLog(JoinPoint joinPoint) {ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();String methodDescription = getMethodDescription(joinPoint);logger.info("========================================== Start (WebLog) ===========================================");logger.info("URL            : {}", request.getRequestURL());logger.info("Description    : {}", methodDescription);logger.info("HTTP Method    : {}", request.getMethod());logger.info("Class Method   : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());logger.info("IP             : {}", request.getRemoteAddr());logger.info("Request Args   : {}", new Gson().toJson(joinPoint.getArgs()));}/*** 在 com.zzx.controller 包及其子包下的所有方法执行前执行的前置通知。* 该方法记录请求的详细信息,包括 URL、HTTP 方法、类方法、IP 地址和请求参数。* @param joinPoint 切点对象,包含方法执行的上下文信息。*/@Before("controllerMethodsPointcut()")public void doBeforeWithControllerMethods(JoinPoint joinPoint) {ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();logger.info("========================================== Start (Controller Methods) ===========================================");logger.info("URL            : {}", request.getRequestURL());logger.info("HTTP Method    : {}", request.getMethod());logger.info("Class Method   : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());logger.info("IP             : {}", request.getRemoteAddr());logger.info("Request Args   : {}", new Gson().toJson(joinPoint.getArgs()));}/*** 环绕通知,用于在带有 @WebLog 注解的方法执行前后执行。* 该方法记录方法的执行时间以及响应参数。* @param joinPoint 切点对象,包含方法执行的上下文信息。* @return 方法的返回值。* @throws Throwable 如果方法执行过程中抛出异常,该异常将被抛出。*/@Around("webLogPointcut()")public Object doAroundWithWebLog(ProceedingJoinPoint joinPoint) throws Throwable {long startTime = System.currentTimeMillis();Object result = joinPoint.proceed();logger.info("Response Args  : {}", new Gson().toJson(result));logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);return result;}/*** 环绕通知,用于在 com.zzx.controller 包及其子包下的所有方法执行前后执行。* 该方法记录方法的执行时间以及响应参数。* @param joinPoint 切点对象,包含方法执行的上下文信息。* @return 方法的返回值。* @throws Throwable 如果方法执行过程中抛出异常,该异常将被抛出。*/@Around("controllerMethodsPointcut()")public Object doAroundWithControllerMethods(ProceedingJoinPoint joinPoint) throws Throwable {long startTime = System.currentTimeMillis();Object result = joinPoint.proceed();logger.info("Response Args  : {}", new Gson().toJson(result));logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);return result;}/*** 后置通知,用于在带有 @WebLog 注解的方法或 com.zzx.controller 包及其子包下的所有方法执行后执行。* 该方法记录一个结束标志。*/@After("webLogPointcut() || controllerMethodsPointcut()")public void doAfter() {logger.info("=========================================== End ===========================================" + LINE_SEPARATOR);}/*** 获取方法的描述信息。* 该方法检查方法是否带有 @WebLog 注解,如果有,则返回注解中的描述信息;否则返回空字符串。* @param joinPoint 切点对象,包含方法执行的上下文信息。* @return 方法的描述信息。*/private String getMethodDescription(JoinPoint joinPoint) {Method method = getMethod(joinPoint);if (method != null && method.isAnnotationPresent(WebLog.class)) {return method.getAnnotation(WebLog.class).description();}return "";}/*** 获取方法对象。* 该方法从切点对象中提取方法签名,并尝试获取对应的方法对象。* @param joinPoint 切点对象,包含方法执行的上下文信息。* @return 方法对象,如果获取失败则返回 null。*/private Method getMethod(JoinPoint joinPoint) {try {Signature signature = joinPoint.getSignature();if (signature instanceof MethodSignature) {MethodSignature methodSignature = (MethodSignature) signature;return methodSignature.getMethod();}return null;} catch (Exception e) {logger.error("Failed to get method description", e);return null;}}
    }
    

1.5 怎么使用呢?

  • 在上面的代码中,毛毛张定义了两种切点,一种是通过自定义注解 @WebLog,另一种是通过包名拦截所有方法。以下是详细的使用方法:

    • 使用自定义注解 @WebLog 作为切点
      • 定义切点:在 WebLogAspect 类中,通过 @Pointcut("@annotation(com.zzx.aspect.WebLog)") 定义了一个切点,表示拦截所有带有 @WebLog 注解的方法。
      • 使用注解:在 Controller 类的每个接口方法上添加 @WebLog 注解即可启用切面日志。如果不想为某个接口打印出入参日志,只需不添加该注解即可。
    • 使用包名拦截所有方法作为切点
      • 定义切点:在 WebLogAspect 类中,通过 @Pointcut("execution(* com.zzx.controller..*.*(..))") 定义了一个切点,表示拦截 com.zzx.controller 包及其子包下的所有方法。
      • 使用切点:无需在方法上添加任何注解,只要方法位于指定的包或子包中,就会被拦截并记录日志。
  • 为此,毛毛张写了两个测试方法分别对应上面的两种切点的使用:

    package com.zzx.controller;import com.zzx.aspect.WebLog;
    import com.zzx.entity.User;
    import org.springframework.web.bind.annotation.*;@RestController
    public class TestController {@GetMapping("/user/login")@WebLog(description = "登录接口")public String login(@RequestParam String username, @RequestParam String password) {User user = new User();user.setUsername(username);user.setPassword(password);return user.toString();}@GetMapping("/user/register")public String register(@RequestParam String username, @RequestParam String password) {User user = new User();user.setUsername(username);user.setPassword(password);return user.toString();}
    }
    
  • 由于登陆接口既使用了@WebLog注解,同时也是通过包名拦截所有方法作为切点的包下,所以会输出两次日志;而注册日志则只会输出一次

1.6 实体类

  • 实体类:
    package com.zzx.entity;import lombok.Data;@Data
    public class User {private String username;private String password;
    }

1.7 启动类

  • 启动类代码:
    package com.zzx;import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
    public class SpringbootAopDemoApplication {public static void main(String[] args) {SpringApplication.run(SpringbootAopDemoApplication.class, args);}}
    

1.8 测试

  • 可以在浏览器依次发送如下请求:
    • http://localhost:8080/user/login?username=mmzhang&password=123
    • http://localhost:8080/user/register?username=mmzhang&password=123
  • 可得到如下测试结果:
    QQ_1743647967352

1.9 只想在开发环境和测试环境中使用?

  • 对于那些性能要求较高的应用,不想在生产环境中打印日志,只想在开发环境或者测试环境中使用,要怎么做呢?我们只需为切面添加 @Profile 就可以了,如下图所示:
    QQ_1743648050919

这样就指定了只能作用于 dev 开发环境和 test 测试环境,生产环境 prod 是不生效的!

1.10 多切面如何指定优先级?

  • 假设说我们的服务中不止定义了一个切面,比如说我们针对 Web 层的接口,不止要打印日志,还要校验 token 等。要如何指定切面的优先级呢?也就是如何指定切面的执行顺序?

    • 我们可以通过 @Order(i)注解来指定优先级,注意:i 值越小,优先级则越高
  • 假设说我们定义上面这个日志切面的优先级为 @Order(10), 然后我们还有个校验 token 的切面 CheckTokenAspect.java,我们定义为了 @Order(11), 那么它们之间的执行顺序如下:
    image

  • 我们可以总结一下:

    • 在切点之前,@Order 从小到大被执行,也就是说越小的优先级越高;
    • 在切点之后,@Order 从大到小被执行,也就是说越大的优先级越高;

1.11 总结

  • 上面代码毛毛张给出了详细的注释,完整的项目代码已经上传至毛毛张Github仓库:https://github.com/zzxrepository/SpringBootTutorial/tree/master/springboot-aop
  • 如果觉得写的不错,不妨给毛毛张的Github仓库给个星标吧!感谢!

3.Spring AOP

1.谈谈自己对于 AOP 的了解

AOP(面向切面编程)是一种编程范式,其核心思想是通过模块化手段将横切关注点(如日志记录、事务管理、权限控制、性能监控等)从业务逻辑中剥离,从而实现关注点分离。AOP 是面向对象编程(OOP)的补充和扩展。

这些与核心业务无关但被多个模块共同调用的功能称为"切面",通过切面(Aspect)、切点(Pointcut)和通知(Advice)等机制进行封装:切面定义功能模块,切点定位目标方法,通知则描述增强逻辑的执行时机(如方法执行前/后/异常时)。

为什么要用 AOP ?:这种设计显著减少了系统重复代码,降低了模块耦合度,同时通过集中化管理共通逻辑,为系统的可维护性和可扩展性提供了结构化支持。Spring框架的AOP实现正是这一范式的典型应用,使开发者能够在不侵入业务代码的前提下,灵活地为程序添加通用能力。

AOP 切面编程涉及到的一些专业术语:

术语含义理解
目标 (Target)被通知的对象,通常是业务逻辑的实现,是我们希望增强的对象。业务逻辑本身,Spring AOP 通过代理模式实现,目标对象是被代理的对象。
代理 (Proxy)向目标对象应用通知后创建的代理对象,用于拦截目标对象的方法调用。Spring AOP 使用的代理对象,负责拦截目标对象的方法调用并执行通知逻辑。
切面 (Aspect)切入点和通知的结合,表示在哪些连接点执行什么样的通知逻辑。切面就是通知和切入点的结合,定义了“在哪些地方干什么”。
连接点 (JoinPoint)目标对象的类中定义的所有方法,都是潜在的拦截点。Spring 允许你用通知的地方,方法的前前后后(包括抛出异常)。
切点 (Pointcut)从连接点中选择特定的点,用于定义哪些连接点会被通知拦截。指定通知到哪个方法,说明“在哪干”。
通知 (Advice)拦截到连接点后执行的逻辑,分为前置、后置、异常、最终、环绕通知五类。我们要实现的功能,如日志记录、性能统计、事务处理等,说明“什么时候要干什么”。
织入 (Weaving)将通知应用到目标对象,生成代理对象的过程。可以在编译期、类装载期或运行期完成。切点定义了哪些连接点会得到通知,织入是将通知逻辑插入到目标对象的过程。
引入 (Introduction)在运行期为类动态添加方法和字段。引入是在一个接口/类的基础上引入新的接口或功能,增强类的能力。
AOP 代理 (AOP Proxy)Spring AOP 使用的代理对象,可以是 JDK 动态代理或 CGLIB 代理。通过代理对目标对象应用切面,代理对象负责拦截目标对象的方法调用并执行通知逻辑。
切面织入有哪几种方式?

①编译期织入:切面在目标类编译时被织入。

②类加载期织入:切面在目标类加载到 JVM 时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。

③运行期织入:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP 容器会为目标对象动态地创建一个代理对象。

Spring AOP 采用运行期织入,而 AspectJ 可以在编译期织入和类加载时织入。

形象的解释:织入就像电影特效

想象一下,您正在制作一部电影。电影的原始拍摄内容(目标对象)已经完成,但您希望在后期制作中添加一些特效(切面逻辑),比如爆炸、魔法效果等。这些特效并不是原始拍摄的一部分,但它们可以增强电影的视觉效果。

AOP常见注解
  • 在配置 AOP 切面之前,我们需要了解下 aspectj 相关注解的作用:
    • @Aspect:声明该类为一个注解类;
    • @Pointcut:定义一个切点,后面跟随一个表达式,表达式可以定义为切某个注解,也可以切某个 package 下的方法;
  • 切点定义好后,就是围绕这个切点做文章了:
    • @Before: 在切点之前,织入相关代码;
    • @After: 在切点之后,织入相关代码;
    • @AfterReturning: 在切点返回内容后,织入相关代码,一般用于对返回值做些加工处理的场景;
    • @AfterThrowing: 用来处理当织入的代码抛出异常后的逻辑处理;
    • @Around: 环绕,可以在切入点前后织入代码,并且可以自由的控制何时执行切点;
AOP 有哪些环绕方式?AOP 常见的通知类型有哪些?

AOP 一般有 5 种环绕方式:

  • 前置通知 (@Before):目标对象的方法调用之前触发
  • 后置通知 (@After):目标对象的方法调用之后触发
  • 环绕通知 (@Around):编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法
  • 返回通知 (@AfterReturning):目标对象的方法调用完成,在返回结果值之后触发
  • 异常通知 (@AfterThrowing):目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
    在这里插入图片描述
AspectJ 是什么?

AspectJ 是一个 AOP 框架,它可以做很多 Spring AOP 干不了的事情,比如说支持编译时、编译后和类加载时织入切面。并且提供更复杂的切点表达式和通知类型。

Spring AOP 发生在什么时候?

Spring AOP 基于运行时代理机制,这意味着 Spring AOP 是在运行时通过动态代理生成的,而不是在编译时或类加载时生成的。

在 Spring 容器初始化 Bean 的过程中,Spring AOP 会检查 Bean 是否需要应用切面。如果需要,Spring 会为该 Bean 创建一个代理对象,并在代理对象中织入切面逻辑。这一过程发生在 Spring 容器的后处理器(BeanPostProcessor)阶段。

简单总结一下 AOP

AOP,也就是面向切面编程,是一种编程范式,旨在提高代码的模块化。比如说可以将日志记录、事务管理等分离出来,来提高代码的可重用性。

AOP 的核心概念包括切面(Aspect)、连接点(Join Point)、通知(Advice)、切点(Pointcut)和织入(Weaving)等。

① 像日志打印、事务管理等都可以抽离为切面,可以声明在类的方法上。像 @Transactional 注解,就是一个典型的 AOP 应用,它就是通过 AOP 来实现事务管理的。我们只需要在方法上添加 @Transactional 注解,Spring 就会在方法执行前后添加事务管理的逻辑。

② Spring AOP 是基于代理的,它默认使用 JDK 动态代理和 CGLIB 代理来实现 AOP。

③ Spring AOP 的织入方式是运行时织入,而 AspectJ 支持编译时织入、类加载时织入。

AOP和 OOP 的关系?

AOP 和 OOP 是互补的编程思想:

  1. OOP 通过类和对象封装数据和行为,专注于核心业务逻辑。
  2. AOP 提供了解决横切关注点(如日志、权限、事务等)的机制,将这些逻辑集中管理。

2.多个切面的执行顺序如何控制?

1、通常使用@Order 注解直接定义切面顺序

// 值越小优先级越高
@Order(3)
@Component
@Aspect
public class LoggingAspect implements Ordered {

2、实现Ordered 接口重写 getOrder 方法。

@Component
@Aspect
public class LoggingAspect implements Ordered {// ....@Overridepublic int getOrder() {// 返回值越小优先级越高return 1;}
}

3.动态代理和静态代理的区别

  • 代理是一种常用的设计模式,目的是:为其他对象提供一个代理以控制对某个对象的访问,将两个类的关系解耦。代理类和委托类都要实现相同的接口,因为代理真正调用的是委托类的方法。
  • 区别:
    • 静态代理:由程序员创建或者是由特定工具创建,在代码编译时就确定了被代理的类是一个静态代理,静态代理通常只代理一个类

      // 接口
      interface UserService {void saveUser();
      }// 目标类
      class UserServiceImpl implements UserService {public void saveUser() {System.out.println("保存用户");}
      }// 静态代理类(手动编写)
      class UserServiceProxy implements UserService {private UserService target;public UserServiceProxy(UserService target) {this.target = target;}public void saveUser() {System.out.println("执行前增强"); // 增强逻辑target.saveUser();               // 调用目标方法System.out.println("执行后增强"); // 增强逻辑}
      }
      
    • 动态代理:在代码运行期间,运用反射机制动态创建生成,动态代理代理的是一个接口下的多个实现类。

      // 接口和目标类同上(略)// 动态代理处理器
      class MyInvocationHandler implements InvocationHandler {private Object target;public MyInvocationHandler(Object target) {this.target = target;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("执行前增强"); // 增强逻辑Object result = method.invoke(target, args); // 调用目标方法System.out.println("执行后增强"); // 增强逻辑return result;}
      }// 使用动态代理
      UserService proxy = (UserService) Proxy.newProxyInstance(UserServiceImpl.class.getClassLoader(),new Class[]{UserService.class},new MyInvocationHandler(new UserServiceImpl())
      );
      proxy.saveUser(); // 输出:增强 + 保存用户
      

4.说说 JDK 动态代理和 CGLIB 代理?

AOP 是通过动态代理实现的,代理方式有两种:JDK 动态代理和 CGLIB 代理。

①、JDK 动态代理是基于接口的代理,只能代理实现了接口的类。

使用 JDK 动态代理时,Spring AOP 会创建一个代理对象,该代理对象实现了目标对象所实现的接口,并在方法调用前后插入横切逻辑。

优点:只需依赖 JDK 自带的 java.lang.reflect.Proxy 类,不需要额外的库;缺点:只能代理接口,不能代理类本身。

②、CGLIB 动态代理是基于继承的代理,可以代理没有实现接口的类。 使用 CGLIB 动态代理时,Spring AOP 会生成目标类的子类,并在方法调用前后插入横切逻辑。

图片来源于网络

优点:可以代理没有实现接口的类,灵活性更高;缺点:需要依赖 CGLIB 库,创建代理对象的开销相对较大。

JDK 动态代理示例代码:

public interface Service {void perform();
}public class ServiceImpl implements Service {public void perform() {System.out.println("Performing service...");}
}public class ServiceInvocationHandler implements InvocationHandler {private Object target;public ServiceInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Before method");Object result = method.invoke(target, args);System.out.println("After method");return result;}
}public class Main {public static void main(String[] args) {Service service = new ServiceImpl();Service proxy = (Service) Proxy.newProxyInstance(service.getClass().getClassLoader(),service.getClass().getInterfaces(),new ServiceInvocationHandler(service));proxy.perform();}
}

CGLIB 动态代理示例代码:

public class Service {public void perform() {System.out.println("Performing service...");}
}public class ServiceInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("Before method");Object result = proxy.invokeSuper(obj, args);System.out.println("After method");return result;}
}public class Main {public static void main(String[] args) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(Service.class);enhancer.setCallback(new ServiceInterceptor());Service proxy = (Service) enhancer.create();proxy.perform();}
}
选择 CGLIB 还是 JDK 动态代理?
  • 如果目标对象没有实现任何接口,则只能使用 CGLIB 代理。如果目标对象实现了接口,通常首选 JDK 动态代理。
  • 虽然 CGLIB 在代理类的生成过程中可能消耗更多资源,但在运行时具有较高的性能。对于性能敏感且代理对象创建频率不高的场景,可以考虑使用 CGLIB。
  • JDK 动态代理是 Java 原生支持的,不需要额外引入库。而 CGLIB 需要将 CGLIB 库作为依赖加入项目中。
你会用 JDK 动态代理和 CGLIB 吗?

①、JDK 动态代理实现:

三分恶面渣逆袭:JDK动态代理类图

第一步,创建接口

public interface ISolver {void solve();
}

第二步,实现对应接口

public class Solver implements ISolver {@Overridepublic void solve() {System.out.println("疯狂掉头发解决问题……");}
}

第三步,动态代理工厂:ProxyFactory,直接用反射方式生成一个目标对象的代理,这里用了一个匿名内部类方式重写 InvocationHandler 方法。

public class ProxyFactory {// 维护一个目标对象private Object target;public ProxyFactory(Object target) {this.target = target;}// 为目标对象生成代理对象public Object getProxyInstance() {return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("请问有什么可以帮到您?");// 调用目标对象方法Object returnValue = method.invoke(target, args);System.out.println("问题已经解决啦!");return null;}});}
}

第五步,客户端生成一个代理对象实例,通过代理对象调用目标对象方法

public class Client {public static void main(String[] args) {//目标对象:程序员ISolver developer = new Solver();//代理:客服小姐姐ISolver csProxy = (ISolver) new ProxyFactory(developer).getProxyInstance();//目标方法:解决问题csProxy.solve();}
}

②、CGLIB 动态代理实现:

三分恶面渣逆袭:CGLIB动态代理类图

第一步:定义目标类(Solver),目标类 Solver 定义了一个 solve 方法,模拟了解决问题的行为。目标类不需要实现任何接口,这与 JDK 动态代理的要求不同。

public class Solver {public void solve() {System.out.println("疯狂掉头发解决问题……");}
}

第二步:动态代理工厂(ProxyFactory),ProxyFactory 类实现了 MethodInterceptor 接口,这是 CGLIB 提供的一个方法拦截接口,用于定义方法的拦截逻辑。

public class ProxyFactory implements MethodInterceptor {//维护一个目标对象private Object target;public ProxyFactory(Object target) {this.target = target;}//为目标对象生成代理对象public Object getProxyInstance() {//工具类Enhancer en = new Enhancer();//设置父类en.setSuperclass(target.getClass());//设置回调函数en.setCallback(this);//创建子类对象代理return en.create();}@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("请问有什么可以帮到您?");// 执行目标对象的方法Object returnValue = method.invoke(target, args);System.out.println("问题已经解决啦!");return null;}}
  • ProxyFactory 接收一个 Object 类型的 target,即目标对象的实例。
  • 使用 CGLIB 的 Enhancer 类来生成目标类的子类(代理对象)。通过 setSuperclass 设置代理对象的父类为目标对象的类,setCallback 设置方法拦截器为当前对象(this),最后调用 create 方法生成并返回代理对象。
  • 重写 MethodInterceptor 接口的 intercept 方法以提供方法拦截逻辑。在目标方法执行前后添加自定义逻辑,然后通过 method.invoke 调用目标对象的方法。

第三步:客户端使用代理,首先创建目标对象(Solver 的实例),然后使用 ProxyFactory 创建该目标对象的代理。通过代理对象调用 solve 方法时,会先执行 intercept 方法中定义的逻辑,然后执行目标方法,最后再执行 intercept 方法中的后续逻辑。

public class Client {public static void main(String[] args) {//目标对象:程序员Solver developer = new Solver();//代理:客服小姐姐Solver csProxy = (Solver) new ProxyFactory(developer).getProxyInstance();//目标方法:解决问题csProxy.solve();}
}

5.说说 Spring AOP 和 AspectJ AOP 区别?

  1. 实现机制
    • Spring AOP 是基于 运行时增强 的动态代理技术,依赖于 Spring 容器。如果目标对象实现了接口,Spring AOP 使用 JDK 动态代理;如果没有实现接口,则使用 Cglib 生成目标对象的子类作为代理。
    • AspectJ AOP 是基于 编译时增强字节码操作技术,通过修改字节码实现静态织入。AspectJ 可以单独使用,也可以与 Spring 集成。
  2. 织入时机
    • Spring AOP 是运行时动态织入。
    • AspectJ 支持多种织入时机:
      • 编译期织入:在编译时修改字节码。如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。
      • 编译后织入:对已生成的 .class 文件或 .jar 包进行增强。
      • 类加载后织入:在类加载时动态增强。
  3. 性能对比
    • Spring AOP 是动态代理,运行时会增加方法调用的栈深度,性能稍逊于 AspectJ。
    • AspectJ 是静态织入,运行时没有额外开销,性能更优,尤其在切面较多时表现更好。
  4. 功能对比
    • Spring AOP 功能相对简单,主要解决企业级开发中常见的方法织入问题。
    • AspectJ 功能更强大,支持更丰富的切点表达式和织入方式,适合复杂的 AOP 场景。
  5. 使用场景
    • 如果切面逻辑简单且数量较少,Spring AOP 足够使用。
    • 如果切面逻辑复杂或数量较多,建议使用 AspectJ。
  6. 集成关系
    • Spring AOP 已经集成了 AspectJ,开发者可以在 Spring 中同时使用两者。

整体对比如下:

Spring AOP和AspectJ对比

6.说说 AOP 和反射的区别?

  1. 反射:用于检查和操作类的方法和字段,动态调用方法或访问字段。反射是 Java 提供的内置机制,直接操作类对象。
  2. 动态代理:通过生成代理类来拦截方法调用,通常用于 AOP 实现。动态代理使用反射来调用被代理的方法。

7.AOP的使用场景有哪些?日志记录、事务管理、权限控制、性能监控

AOP 的使用场景有很多,比如说日志记录、事务管理、权限控制、性能监控等。

参考文献

  • https://www.cnblogs.com/shenMaQN/p/17748150.html
  • https://developer.aliyun.com/article/723070
  • https://blog.csdn.net/Fzyabc/article/details/142882899
  • 沉默王二
  • 小林coding

相关文章:

【SpringBoot教程】SpringBoot自定义注解与AOP实现切面日志

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 文章目录 1.前言2.SpringAOP实现切面日志快速入门1.1 创建SpringBoot项目1.2 依赖配置pom.xml1.3 自定义日志注解1.4 配置 AOP 切面1.5 怎么使用呢&#xff1f;1.6 实…...

C++学习之路,从0到精通的征途:stack_queue的模拟实现及deque原理介绍

目录 一.容器适配器 1.什么是适配器 2.STL标准库中stack和queue的底层结构 3.deque的原理介绍 deque是如何借助其迭代器维护其假想连续的结构呢&#xff1f; 头插 尾插 遍历 4.deque的优缺点 二.stack的模拟实现 三.queue的模拟实现 一.容器适配器 1.什么是适配…...

文件上传漏洞篇:upload-labs靶场搭建

一、文件上传漏洞简述 文件上传漏洞是一种常见的Web安全漏洞&#xff0c;当网站或应用程序允许用户上传文件&#xff08;如图片、文档等&#xff09;时&#xff0c;若未对上传的文件进行充分的安全检查&#xff0c;攻击者可能利用此漏洞上传恶意文件&#xff08;如Web Shell、…...

TikTok 矩阵账号运营实操细节:打造爆款矩阵

在 TikTok 的流量版图里&#xff0c;打造 TikTok 矩阵账号能显著提升影响力与吸粉能力。而借助 AI 工具&#xff0c;更可为 TikTok 矩阵运营效率的提升赋能&#xff0c;让运营如虎添翼。下面就为大家详细讲讲其中的实操细节&#xff0c;并结合一些伪代码示例辅助理解。 一、矩…...

Nginx安全防护与HTTPS部署实战

一.核心安全配置 1.编译安装Nginx &#xff08;1&#xff09;安装支持软件 Nginx的配置及运行需要pcre、zlib等软件包的支持&#xff0c;因此应预先安装这些软件的开发包&#xff08;devel&#xff09;&#xff0c;以便提供相应的库和头文件&#xff0c;确保Nginx的安装顺利…...

【C语言】初阶数据结构相关习题(一)

&#x1f386;个人主页&#xff1a;夜晚中的人海 今日语录&#xff1a;人的生命似洪水在奔流&#xff0c;不遇着岛屿、暗礁&#xff0c;难以激起美丽的浪花。——奥斯特洛夫斯基 文章目录 ⭐一、判定是否互为字符重排&#x1f389;二、 回文排列&#x1f680;三、字符串压缩&am…...

MySQL从入门到精通(一):MySQL介绍及数据库相关概念

目录 一、MySQL 介绍 二、数据库相关概念 &#xff08;一&#xff09;数据库基础知识 &#xff08;二&#xff09;主流的关系型数据库管理系统 三、关系型数据库与非关系型数据库 &#xff08;一&#xff09;定义 &#xff08;二&#xff09;数据模型对比 &#xff08;…...

宁德时代区块链+数字孪生专利解析:去中心化身份认证重构产业安全底座

引言&#xff1a;当动力电池巨头瞄准数字孪生安全 2025年5月6日&#xff0c;金融界披露宁德时代未来能源&#xff08;上海&#xff09;研究院与母公司宁德时代新能源科技股份有限公司联合申请的一项关键专利——“身份验证方法、系统、电子设备及存储介质”。这项技术将区块链…...

Kotlin数据类在Android开发中的应用

在 Android 开发中,Kotlin 的数据类(Data Class)因其简洁性和自动生成的功能特性,成为了提升开发效率的利器。以下是我总结的 7 大核心妙用场景,配合代码示例助您快速掌握: 1️⃣ JSON 解析利器 → 网络请求模型 与 Retrofit/Moshi 完美配合,自动生成 equals()/hashCod…...

程序员学商务英语之Shipment Claim 运输和索赔

Dia-3: Claim 1 索赔 1. He claimed that he would quit smoking. 他宣布他将要禁烟。 2. BYD is inferior to Tesla. 差 be worse than… 比亚迪比特斯拉差。 Tesla is superior to BYD. 特斯拉比比亚迪好。 be better than… 3. The survey report reveals/s…...

Kotlin密封类优化Android状态管理

Kotlin 的密封类&#xff08;Sealed Class&#xff09;确实是 Android 开发中管理复杂 UI 状态的利器。它通过类型安全的层次结构&#xff0c;让状态管理代码更加清晰简洁。让我们从实际开发场景出发&#xff0c;深入探讨其应用&#xff1a; 一、密封类核心优势 受限的类继承…...

基于图像处理的道路监控与路面障碍检测系统设计与实现 (源码+定制+开发) 图像处理 计算机视觉 道路监控系统 视频帧分析 道路安全监控 城市道路管理

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…...

依赖注入详解与案例(前端篇)

依赖注入详解与案例&#xff08;前端篇&#xff09; 一、依赖注入核心概念与前端价值 依赖注入&#xff08;Dependency Injection, DI&#xff09; 是一种通过外部容器管理组件/类间依赖关系的设计模式&#xff0c;其核心是控制反转&#xff08;Inversion of Control, IoC&…...

Spark 的 Shuffle 机制:原理与源码详解

Apache Spark 是一个分布式数据处理框架&#xff0c;专为大规模数据分析设计。其核心操作之一是 Shuffle&#xff0c;这是一个关键但复杂的机制&#xff0c;用于在某些操作期间在集群中重新分配数据。理解 Shuffle 需要深入探讨其目的、机制和实现&#xff0c;既包括概念层面&a…...

IdeaVim配置指南

一、什么是 IdeaVim&#xff1f; IdeaVim 是 JetBrains 系列 IDE&#xff08;如 IntelliJ IDEA, WebStorm, PyCharm 等&#xff09;中的一个插件&#xff0c;让你在 IDE 里使用 Vim 的按键习惯&#xff0c;大大提升效率。 安装方法&#xff1a; 在 IDE 中打开 设置(Settings) →…...

[监控看板]Grafana+Prometheus+Exporter监控疑难排查

采用GrafanaPrometheusExporter监控MySQL时发现经常数据不即时同步&#xff0c;本示例也是本地搭建采用。 Prometheus面板 1&#xff0c;Detected a time difference of 11h 47m 22.337s between your browser and the server. You may see unexpected time-shifted query res…...

P56-P60 统一委托,关联游戏UI,UI动画,延迟血条

这一部分首先把复杂的每个属性委托全部换成了简洁可复用的委托,之后重新修改了UI蓝图,然后在新增了一个与之前表格关联的动画与血条延迟下降的蓝图 OverlayAuraWidgetController.h // Fill out your copyright notice in the Description page of Project Settings. #pragma …...

智能修复大模型生成的 JSON 字符串:Python 实现与优化

在使用大语言模型(LLM)生成 JSON 格式数据时,常因模型输出不完整、语法错误或格式不规范导致 JSON 解析失败。本文介绍如何通过 json_repair 库实现对 LLM 生成 JSON 字符串的自动修复,并改进原始提取函数以提升容错能力。 一、LLM 生成 JSON 的常见问题 LLM 输出的 JSON …...

【PPT制作利器】DeepSeek + Kimi生成一个初始的PPT文件

如何基于DeepSeek Kimi进行PPT制作 步骤&#xff1a; Step1&#xff1a;基于DeepSeek生成文本&#xff0c;提问 Step2基于生成的文本&#xff0c;用Kimi中PPT助手一键生成PPT 进行PPT渲染-自动渲染 可选择更改模版 生成PPT在桌面 介绍的比较详细&#xff0c;就是这个PPT模版…...

华为设备端口隔离

端口隔离的理论与配置指南 一、端口隔离的理论 基本概念 端口隔离&#xff08;Port Isolation&#xff09;是一种在交换机上实现的安全功能&#xff0c;用于限制同一VLAN内指定端口间的二层通信。被隔离的端口之间无法直接通信&#xff0c;但可通过上行端口访问公共资源&#…...

YOLO12改进-C3K2模块改进-引入离散余弦变换DCT 减少噪声提取图像的细节、边缘和纹理等微观特征

离散余弦变换&#xff08;Discrete Cosine Transform, DCT&#xff09;由 Nasir Ahmed 于 1974 年提出&#xff0c;最初是为了优化数据压缩。其核心思想是将信号从空间域转换为频率域&#xff0c;从而实现冗余信息的压缩。DCT 在图像和视频处理领域应用广泛&#xff0c;例如 JP…...

基于大模型的自然临产阴道分娩全流程预测与方案研究报告

目录 一、引言 1.1 研究背景与目的 1.2 研究意义 1.3 国内外研究现状 二、大模型技术原理与应用概述 2.1 大模型基本原理 2.2 在医疗领域的应用现状 2.3 用于分娩预测的优势 三、术前预测与准备方案 3.1 产妇身体状况评估指标 3.2 大模型预测流程与方法 3.3 基于预…...

用 Tailwind CSS 优化你的 Vue 3 项目! ! !

Vue 3 的响应式魅力 TailwindCSS 的原子级美学 前端开发的舒适巅峰&#xff01; 在现代前端开发中&#xff0c;组件驱动 原子化 CSS 正在成为新的标准。如果你已经在使用 Vue 3&#xff0c;那不妨试试 Tailwind CSS —— 一个强大的原子化 CSS 框架&#xff0c;它能让你几乎…...

PostgreSQL数据库的array类型

PostgreSQL数据库相比其它数据库&#xff0c;有很多独有的字段类型。 比如array类型&#xff0c;以下表的pay_by_quarter与schedule两个字段便是array类型&#xff0c;即数组类型。 CREATE TABLE sal_emp (name text,pay_by_quarter integer[],schedule t…...

融智学视角集大成范式革命:文理工三类AI与网络大数据的赋能

融智学视角下的“集大成”范式革命&#xff1a;AI与大数据的终极赋能 一、化繁为简的工具革命&#xff1a;AI与大数据的三重解构 信息压缩的数学本质 Kolmogorov复杂度极限突破&#xff1a; K_AI(x)min_p∈P_NN ℓ(p)λ⋅dist(U(p),x) &#xff08;神经网络程序p的描述长度语…...

【2025】Visio 2024安装教程保姆级一键安装教程(附安装包)

前言 大家好&#xff01;最近很多朋友在问我关于Visio 2024的安装问题&#xff0c;尤其是对于那些需要制作专业流程图和组织结构图的小伙伴来说&#xff0c;这款软件简直是必不可少的办公神器&#xff01;今天就给大家带来这篇超详细保姆级的Visio 2024安装教程&#xff0c;不…...

C++【继承】

继承 1.继承1.1 继承的概念1.2继承的定义1.2.1定义格式1.2.2继承基类成员访问方式的变化 1.3继承模板 2.基类和派生类之间的转换 1.继承 1.1 继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许我们在保持原有类特性的基础上…...

理解字、半字与字节 | 从 CPU 架构到编程实践的数据类型解析

注&#xff1a;本文为 “字、半字、字节” 相关文章合辑。 略作重排&#xff0c;未全校。 如有内容异常&#xff0c;请看原文。 理解计算机体系结构中的字、半字与字节 在计算机科学中&#xff0c;理解“字 (Word)”、“半字 (Half-Word)”和“字节 (Byte)”等基本数据单元的…...

VMware搭建ubuntu保姆级教程

目录 VMware Ubuntu 虚拟机配置指南 创建虚拟机 下载 Ubuntu ISO 新建虚拟机 网络配置&#xff08;双网卡模式&#xff09; 共享文件夹设置 SSH 远程访问配置 VMware Ubuntu 虚拟机配置指南 创建虚拟机 下载 Ubuntu ISO 【可添加我获取】 官网&#xff1a;Get Ubunt…...

内容社区系统开发文档

1 系统分析 1.1 项目背景 1.2 需求分析 2 系统设计 2.1 系统功能设计 2.2 数据库设计 2.2.1 数据库需求分析 2.2.2 数据库概念结构设计 2.2.3 数据库逻辑结构设计 2.2.4 数据库物理结构设计 2.2.5 数据库视图设计 2.2.6 函数设计 2.2.7 存储过程设计 2.2.8 触发器…...

Ubuntu开放端口

在 Ubuntu 中&#xff0c;我们可以使用 ufw (Uncomplicated Firewall) 来管理防火墙。以下是打开 80 和 8090 端口的步骤&#xff1a; 首先检查防火墙状态 sudo ufw status 如果防火墙没有启用&#xff0c;先启用它&#xff1a; sudo ufw enable 允许 80 端口&#xff08;…...

PyTorch 与 TensorFlow 中基于自定义层的 DNN 实现对比

深度学习双雄对决&#xff1a;PyTorch vs TensorFlow 自定义层大比拼 目录 深度学习双雄对决&#xff1a;PyTorch vs TensorFlow 自定义层大比拼一、TensorFlow 实现 DNN1. 核心逻辑 二、PyTorch 实现自定义层1. 核心逻辑 三、关键差异对比四、总结 一、TensorFlow 实现 DNN 1…...

质量员考试案例题有哪些常见考点?

质量员考试案例题常见考点如下&#xff1a; 施工质量控制 施工工艺与工序&#xff1a;如混凝土浇筑时的振捣时间、方法&#xff0c;若振捣不充分会导致混凝土出现蜂窝、麻面等质量问题。 施工环境&#xff1a;例如在高温天气下进行砌筑作业&#xff0c;未对砌块进行适当处理或…...

Axure疑难杂症:深度理解与认识“事件”“动作”(玩转交互)

亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢! Axure产品经理精品视频课已登录CSDN可点击学习https://edu.csdn.net/course/detail/40420 课程主题:深度理解与认识“事件”“动作” 主要内容:事件、动作定义、本质、辩证关系、执行顺序 应用场景:原型交互 …...

【AI知识库云研发部署】RAGFlow + DeepSeek

gpu 安装screen&#xff1a;yum install screen 配置ollama&#xff1a; 下载官方安装脚本并执行&#xff1a; curl -fsSL https://ollama.com/install.sh | sh 通过screen后台运行ollama&#xff1a;screen -S ollama 在screen会话中启动服务&#xff1a; export OLLA…...

HTML07:表格标签

表格 基本结构 单元格行列跨行跨列 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>表格学习</title><style>td {text-align: center;vertical-align: middle;}</style> </he…...

【专家库】Kuntal Chowdhury

昆塔尔乔杜里 Kuntal Chowdhury 是 NVIDIA 的 6G 开发者关系经理和技术布道师。他致力于推动与 NVIDIA 平台和工具的开发者和早期采用者生态系统的联系&#xff0c;以促进 6G 研究社区的蓬勃发展。在此之前&#xff0c;他是 BlueFusion, Inc. 的创始人&#xff0c;这是一家创新…...

IAA-Net:一种实孔径扫描雷达迭代自适应角超分辨成像方法——论文阅读

IAA-Net:一种实孔径扫描雷达迭代自适应角超分辨成像方法 1. 论文的研究目标与实际意义1.1 研究目标1.2 实际问题与产业意义2. 论文的创新方法、公式与优势2.1 方法框架与核心步骤2.2 核心公式与推导2.2.1 回波模型与目标函数2.2.2 正则化加权矩阵设计2.2.3 迭代更新公式2.2.4 …...

[论文阅读]MCP Guardian: A Security-First Layer for Safeguarding MCP-Based AI System

MCP Guardian: A Security-First Layer for Safeguarding MCP-Based AI System http://arxiv.org/abs/2504.12757 推出了 MCP Guardian&#xff0c;这是一个框架&#xff0c;通过身份验证、速率限制、日志记录、跟踪和 Web 应用程序防火墙 &#xff08;WAF&#xff09; 扫描来…...

提示词工程:通向AGI时代的人机交互艺术

‌引言&#xff1a;从基础到精通的提示词学习之旅‌ 欢迎来到 ‌"AGI时代核心技能"‌ 系列课程的第二模块——‌提示词工程‌。在这个模块中&#xff0c;我们将系统性地探索如何通过精心设计的提示词&#xff0c;释放大型语言模型的全部潜力&#xff0c;实现高效、精…...

地级市-机器人、人工智能等未来产业水平(2009-2023年)-社科数据

地级市-机器人、人工智能等未来产业水平&#xff08;2009-2023年&#xff09;-社科数据https://download.csdn.net/download/paofuluolijiang/90623814 https://download.csdn.net/download/paofuluolijiang/90623814 此数据集统计了2009-2023年全国地级市在机器人、人工智能等…...

神经网络中之多类别分类:从基础到高级应用

神经网络中之多类别分类&#xff1a;从基础到高级应用 摘要 在机器学习领域&#xff0c;多类别分类是解决复杂问题的关键技术之一。本文深入探讨了神经网络在多类别分类中的应用&#xff0c;从基础的二元分类扩展到一对多和一对一分类方法。我们详细介绍了 softmax 函数的原理…...

破解工业3D可视化困局,HOOPS Visualize助力高效跨平台协作与交互!

一、当前3D可视化面临的痛点 &#xff08;1&#xff09;性能瓶颈 现有的许多3D可视化工具在处理大型复杂模型时往往力不从心。例如在航空航天、汽车制造等高端制造业&#xff0c;动辄涉及数以亿计的三角面片和海量的纹理细节。这些超大规模的模型在渲染时常常出现卡顿、延迟&…...

感知器准则感知器神经元模型——等价

不同的东西&#xff0c;很多刊物有误。但两者等价。 感知器神经元模型的误差反馈学习 y y y&#xff1a;期望值 y ^ \hat{y} y^​&#xff1a;实际输出值 权重更新公式为&#xff1a; w i ← w i η ( y − y ^ ) x i w_i \leftarrow w_i \eta(y - \hat{y})x_i wi​←wi​…...

Qt学习Day0:Qt简介

0. 关于Qt Qt是C的实践课&#xff0c;之前在C中学习的语法可以有具体的应用场景。Qt的代码量很大&#xff0c;不要死记硬背&#xff0c;学会查询文档的能力更加重要。 建议提升一下相关单词的储备量&#xff1a; 1. Qt是什么&#xff1f; Qt是一个基于C语言的图形用户界面&a…...

JAVA设计模式——(十二)原型模式(Prototype Pattern)

JAVA设计模式——&#xff08;十二&#xff09;原型模式&#xff08;Prototype Pattern&#xff09; 介绍理解实现Email类测试 应用 介绍 用原型实例指定创建对象的种类&#xff0c;并且通过复制原型已有的对象用于创建新的对象。 理解 原型实例便是我们需要复制的类的实例&…...

C++命名空间

什么是命名空间 命名空间是一种用来避免命名冲突的机制&#xff0c;它可以将一段代码的名称隔离开&#xff0c;使其与其他代码的名称不冲突 简单来说,就是编译器检测到相同的名称的函数,变量,或者其他的相同名称的东西,也许会有疑问,怎么能出现相同的名称的变量呢.这就是C引入的…...

Hello Robot 推出Stretch 3移动操作机器人 提升开源与可用性

Stretch 3机器人是Hello Robot推出的新一代移动操作机器人&#xff0c;专注于提升开源开发与实际应用能力。它结合了先进的设计理念和工程技术&#xff0c;旨在为家庭任务和辅助技术提供智能化解决方案。通过优化硬件性能和软件兼容性&#xff0c;这款机器人不仅增强了灵活性&a…...

[Linux_69] 数据链路层 | Mac帧格式 | 局域网转发 | MTU MSS

目录 0.引入 1.以太网帧格式 2.重谈局域网转发的原理(基于协议) 小结 3.认识MTU 3.1MTU对IP协议的影响 3.2MTU对UDP协议的影响 3.3MTU对于TCP协议的影响 0.引入 在去年的这篇文章中&#xff0c;我们有对网络进行过一个概述[Linux#47][网络] 网络协议 | TCP/IP模型 | 以…...

I2C总线驱动开发:MPU6050应用

引言 I2C&#xff08;Inter-Integrated Circuit&#xff09;总线作为嵌入式系统中广泛使用的通信协议&#xff0c;在传感器、外设控制等领域扮演着重要角色。本文将深入探讨I2C总线的工作原理、Exynos4412平台裸机驱动实现、Linux内核中的I2C子系统架构&#xff0c;并以MPU605…...