Spring 核心 - AOP 面向切面编程入门, 通俗易懂

news/2025/10/2 18:10:12/文章来源:https://www.cnblogs.com/xiaoke520/p/19123838

Spring 核心 - AOP 面向切面编程入门, 通俗易懂

撰写本文目的只有一个,让你畅快阅读 AOP 知识,并搞定以下几个问题。

  • AOP 面向切面编程到底是什么?
  • AOP 术语:连接点,切入点,通知,切面,织入是什么东西?
  • @Aspect 注解为啥用的是 aspectj 包,不是 AOP 包?它们啥关系?
  • Spring 框架是如何实现 AOP ? 代理技术

不急解释 AOP 的概念, 我们先来看 AOP 解决了什么问题。

引言

在写项目的时,你是否遇到过随着系统体积增大,有一些新的需求——比如日志、用户鉴权、指标上报、redis 缓存填充/失效、事务等——需要对系统的多个类、方法改动,这种大量**人工改动**代码的行为容易遗漏、逻辑改动大,侵入风险高。可见,在这种场景下,手动改写代码必然是下下策。

很明显本文讲的是 AOP , AOP 必然可以解决此类问题,做到不侵入原有代码新增日志、用户鉴权等功能。

  • 举一个例子,让场景更形象化。
public class UserServiceImpl implements UserService {/*** find user list.** @return user list*/@Overridepublic List<User> findUserList() {System.out.println("执行方法: findUserList()");return Collections.singletonList(new User("fency", 18));}/*** add user*/@Overridepublic void addUser() {System.out.println("执行方法: addUser()");// do something}@Overridepublic void deleteUser() {System.out.println("执行方法: deleteUser()");}
}

要想对这三个方法加日志和鉴权,逐个添加是下策。如图:

下面有请主角登场。

AOP

介绍

AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程。

AOP 最早是 AOP 联盟的组织提出的,指定的一套规范,Spring 将 AOP 的思想引入框架之中,通过预编译方式运行期间动态代理实现程序的统一维护的一种技术。

AOP 是设计思想,Spring AOP 、AspectJ 才是技术实现, AOP 思想本质的作用解耦。我们将记录日志、鉴权功能解耦为切面,进而引出 AOP 理念:将分散在各个业务逻辑代码中相同的代码,通过横向切割的方式抽取到一个独立的模块中。用图表示:

意思是写一次日志代码,即可让三个方法实现日志记录功能。

下面用 Spring AOP 实战演示统一加日志。

实战 - 统一添加日志

创建一个 spring boot 项目,引一个 lombok 依赖。

1)引入 aop 依赖包

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

2)新建 UserServiceImpl 类, 我们要对它进行统一添加日志。实现 CommandLineRunner 接口的作用是:当Spring 启动完毕时,执行一次 CommandLineRunner.run() 方法, 模拟方法调用。

@Service
public class UserServiceImpl implements UserService {/*** find user list.** @return user list*/@Overridepublic List<User> findUserList() {System.out.println("执行方法: findUserList()");return Collections.singletonList(new User("fency", 18));}/*** add user*/@Overridepublic void addUser() {System.out.println("执行方法: addUser()");// do something}@Overridepublic void deleteUser() {System.out.println("执行方法: deleteUser()");}
}

单元测试执行

@SpringBootTest
class UserServiceImplTest {@Resourceprivate UserService userService;@Testvoid invokeMethod() {userService.addUser();userService.findUserList();userService.deleteUser();}
}

效果如图,没有日志。

3)添加 Aspect 切面类,统一为 UserService 的三个方法添加日志。

@Slf4j
@Aspect
@Component
public class LogAspect {/*** 切点:拦截所有 controller 包下的公共方法*/@Around("execution(* com.codebear.springboothelloword.service..*(..))")public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {String methodName = joinPoint.getSignature().toShortString();Object[] args = joinPoint.getArgs();log.info("Entering {} with args: {}", methodName, args);try {Object result = joinPoint.proceed(); // 执行业务方法log.info("Exiting {} with result: {}", methodName, result);return result;} catch (Throwable ex) {log.error("Exception in {}: {}", methodName, ex.getMessage(), ex);throw ex;}}
}

再次执行,可以看到每个方法前后都有日志,解决了最初的痛点,避免大量手动修改代码。

初学 AOP 可能会觉得不会灵活,所有的方法都有日志了,如果只想两个方法有日志怎么办?

探索一下切点表达式,便有答案。拿示例的切点表达式来说:

// 第一个 * 代表匹配任意方法的返回类型
// service..* 代表匹配这个包下的所有类都是切点
// (..)  代表任意方法的参数类型、个数都匹配
@Around("execution(* com.codebear.springboothelloword.service..*(..))")

AOP 术语

开始念经:**连接点、切入点、通知、切面、引入、目标对象、织入、AOP代理**,第一次看到这一堆还是会恶心一下吧。

其实这些术语不是 Spring AOP 独有的, 而是 AOP 设计思想的组成,所以这些术语也会比较抽象。

先说明,作为开发者,我认为不需要掌握全部的 AOP 思想,理解切面、通知、切入点、连接点就能写出 LogAspect 切面编程。

下面介绍一下,并且把术语代入到 Spring AOP 中。

连接点(JoinPoint):上述示例中这里有三个方法,任意单个方法就叫一个连接点。官方一点的解释,表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化、方法执行、方法调用、字段调用或处理异常等等,在AOP中表示为在哪里干


切入点(Pointcut):上述示例中,service包下的所有子包、类和方法都是切入点,因为切入点表达式编写成这样,切入点说白了就是表达式规则。官方解释:选择一组相关连接点,即可以认为连接点的集合,在AOP中表示为在哪里干的集合

通知(Advice):真正要织入的代码逻辑,比如上述例子,我们要给切入点织入日志记录逻辑,这段日志逻辑便是通知,为什么叫通知?那么抽象,因为有前置通知(before advice)、后置通知(after advice)、环绕通知(around advice),允许你在原有方法执行前、后、环绕等加入日志逻辑。在AOP中表示为干什么

切面(Aspect): 面向切面编程AOP的主角就是这位,以上述例子来讲,打上 @Aspect 注解的 LogAspect 类就是切面,目前这个类中只有一个环绕通知 logAround() , 当然可以增加通知,所以这一整个类编程范式就是切面编程,那么切面就是 LogAspect

引入(Introduction): 这个就偏概念了,上述示例中,UserService 有三个方法,加一个新的方法,这就叫引入,新增的方法也是一个新的连接点。在切面编程已经实现的情况下,UserService 新增方法,就叫做引入。

目标对象(Target Object):需要被织入横切关注点的对象,即该对象是切入点选择的对象,需要被通知的对象,从而也可称为被通知对象;由于Spring AOP 通过代理模式实现,从而这个对象永远是被代理对象。在AOP中表示为对谁干

织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring 和其他纯 Java AOP 框架一样,在运行时完成织入,运行时生成动态代理代码的这个过程叫做织入。在 AOP 中表示为怎么实现的

AOP 代理(AOP): AOP 框架使用代理模式创建的对象,从而实现在连接点处插入通知(即应用切面),就是通过代理来对目标对象应用切面。在 Spring 中,AOP 代理可以用 JDK 动态代理或 CGLIB 代理实现,而通过拦截器模型应用切面。在AOP中表示为怎么实现的一种典型方式

织入和代理应该有点难分清吧?

织入是生成代理对象的过程,有了代理对象,通知功能得以实现。可以看出 Spring AOP 本质上是通过动态代理实现。

一图胜千言

如果坚持读到这里,我相信你搞明白了 AOP 和 Spring AOP 的概念,一个设计思想,一个是具体实现,另外AspectJ 也是实现 AOP 的一门技术。 细心的你已经发现了上述示例 @Aspect 注解来源于 aspectj 包,并不是 aop 包的注解。

如下图所示,我们引入的 starter - aop 依赖包含了 aspectj 和 aop 两个技术,说明 spring 开发小组直接使用了 aspectj 包作为 Spring AOP 实现的一部分。

那必须要讲一讲 Spring AOP 和 AspectJ 之间的瓜葛。

Spring AOP 和 AspectJ 的渊源

**Aspect 是什么呢?**

AspectJ 是一个 java 语言实现的 AOP 框架,它能够对java代码进行AOP编译(一般在编译期进行),让java代码具有 AspectJ 的 AOP 功能(当然需要特殊的编译器)。

可以这样说 AspectJ 是目前实现 AOP 框架中最成熟,功能最丰富的语言,更幸运的是,AspectJ 与 java 程序完全兼容,几乎是无缝关联,因此对于有 java 编程基础的工程师,上手和使用都非常容易。

Spring AOP 和 AspectJ 是什么关系

1)AspectJ 是更强的 AOP 框架,是实际意义的 AOP 标准

2)Spring 为何不写类似 AspectJ 的框架? Spring AOP 使用纯 Java 实现, 它不需要专门的编译过程, 它一个重要的原则就是无侵入性(non-invasiveness); Spring 小组完全有能力写类似的框架,只是 Spring AOP 从来没有打算通过提供一种全面的 AOP 解决方案来与 AspectJ 竞争。Spring 的开发小组相信无论是基于代理(proxy-based)的框架如 Spring AOP 或者是成熟的框架如 AspectJ 都是很有价值的,他们之间应该是互补而不是竞争的关系

3) Spring 小组喜欢 @AspectJ 注解风格更胜于 Spring XML 配置; 所以在 Spring 2.0 使用了和 AspectJ 5 一样的注解,并使用 AspectJ 来做切入点解析和匹配但是,AOP 在运行时仍旧是纯的 Spring AOP,并不依赖于AspectJ 的编译器或者织入器(weaver), 但是注解和切点解析用的都是 AspectJ 的技术。

4)Spring 2.5 对 AspectJ 的支持:在一些环境下,增加了对 AspectJ 的装载时编织支持,同时提供了一个新的bean切入点。

看样子 AspectJ 哪哪都好呀,更强大,更全面,完全按照 AOP 标准来实现,为啥 Spring 小组还要开发 Spring AOP 呢?

以下Spring官方的回答:(总结来说就是 Spring AOP更易用,AspectJ更强大)。

  • Spring AOP 比完全使用 AspectJ 更加简单, 因为它不需要引入 AspectJ 的编译器/织入器到你开发和构建过程中。 如果你仅仅需要在 Spring bean 上通知执行操作,那么 Spring AOP 是合适的选择
  • 如果你需要通知 domain 对象或其它没有在 Spring 容器中管理的任意对象,那么你需要使用 AspectJ。
  • 如果你想通知除了简单的方法执行之外的连接点(如:调用连接点、字段get或set的连接点等等), 也需要使用AspectJ。

他们的关系稍微了解即可,回归 Spring AOP 的学习。

Spring AOP 的配置方式

Spring AOP 支持对 **XML 模式**和基于 **@AspectJ 注解**的两种配置方式。

本文例子使用的是 @AspectJ 注解式开发切面编程XML 就是写一堆 XML 配置,用的很少,知道可以 XML 配置即可,不做实战演示。

Spring 使用了 @AspectJ 框架为 AOP 的实现提供了一套注解,下面归纳一下。

注解名称 解释
@Aspect 用来定义一个切面。
@pointcut 用于定义切入点表达式。在使用时还需要定义一个包含名字和任意参数的方法签名来表示切入点名称,这个方法签名就是一个返回值为void,且方法体为空的普通方法。
@Before 用于定义前置通知,相当于BeforeAdvice。在使用时,通常需要指定一个value属性值,该属性值用于指定一个切入点表达式(可以是已有的切入点,也可以直接定义切入点表达式)。
@AfterReturning 用于定义后置通知,相当于AfterReturningAdvice。在使用时可以指定pointcut / value和returning属性,其中pointcut / value这两个属性的作用一样,都用于指定切入点表达式。
@Around 用于定义环绕通知,相当于MethodInterceptor。在使用时需要指定一个value属性,该属性用于指定该通知被植入的切入点。
@After-Throwing 用于定义异常通知来处理程序中未处理的异常,相当于ThrowAdvice。在使用时可指定pointcut / value和throwing属性。其中pointcut/value用于指定切入点表达式,而throwing属性值用于指定一个形参名来表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法抛出的异常。
@After 用于定义最终final 通知,不管是否异常,该通知都会执行。使用时需要指定一个value属性,该属性用于指定该通知被植入的切入点。
@DeclareParents 用于定义引介通知,相当于IntroductionInterceptor (不要求掌握)。

Spring AOP 的实现方式

Spring AOP 的实现方式是动态织入,动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的;**如 Java JDK的动态代理( Proxy,底层通过反射实现)或者 CGLIB 的动态代理(底层通过继承实现)**,Spring AOP 采用的就是基于运行时增强的代理技术。

所以我们看下如下的两个例子

  • 基于 JDK 代理例子
  • 基于 Cglib 代理例子

经典面试题,接口使用 JDK 动态代理,类使用 Cglib 动态代理。Spring Boot 2.x 开始默认优先使用 Cglib 动态代理技术,无论你的类是否实现了接口,Spring Boot 默认都会尝试使用 CGLIB 来创建代理对象。

参考文章:https://www.cnblogs.com/lyh233/p/16008251.html#_label1_1

spring boot 2.0 前默认优先使用 JDK 代理,2.0 后默认优先使用 Cglib 代理,Spring AOP 的底层实现方式是“同时支持 JDK 动态代理和 CGLIB,并根据配置和目标对象的情况,智能选择其中一种来工作”,不改配置默认就是 Cglib 。

再来一道经典面试题。

JDK 动态代理和 Cglib 代理动态代理的区别?

一张表格看懂
特性 JDK 动态代理 CGLIB 动态代理
核心原理 基于接口 基于继承
代理对象类型 生成目标接口的新类 (**com.sun.proxy.$Proxy**) 生成目标类的子类 (**...$$EnhancerBySpringCGLIB$$...**)
对目标类的要求 必须实现至少一个接口 可以是任何类(但不能是 **final** 类)
对方法的要求 只能代理接口中的方法 可以代理类中的 **public**/**protected**方法,不能代理 **final****static** 方法
依赖 JDK 原生支持,无需外部库 需要引入 **cglib**(或 Spring 内置的 **spring-core**

下面我们抛开 Spring,用最原生的代码来分别实现这两种代理,你会立刻明白它们的工作方式。

先从 Cglib 开始。

Cglib 动态代理技术原理

**CGLIB 动态代理**:它在运行时为你创建一个**目标类的子类**。这个子类会重写父类(你的原始类)的所有非 `**final**` 方法,并在重写的方法中加入额外的逻辑(切面代码)。**它和你的原始类是父子关系。**

场景预设

我们想在调用 OrderServicecreateOrder 方法前后,打印日志。

创建一个包 cglib ,一顿复制。

1) 添加 CGLIB 依赖,Spring boot 项目不用导入依赖。

<!-- Maven -->
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>

2)创建一个目标类(无需接口)

public class OrderService {public void createOrder(String productName) {System.out.println("【核心业务】正在创建订单: " + productName);}
}

3)创建 **MethodInterceptor**(核心逻辑拦截器),下一步发挥作用。

public class LogMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {// 1. 前置增强(日志)System.out.println("[CGLIB代理] - 准备执行方法: " + method.getName());// 2. 调用父类(目标对象)的原方法// 注意:这里用的是 proxy.invokeSuper,而不是 method.invokeObject result = proxy.invokeSuper(obj, args);// 3. 后置增强(日志)System.out.println("[CGLIB代理] - 方法执行完毕");return result;}
}

4)创建代理对象并使用

public class CglibProxyDemo {public static void main(String[] args) {// 1. 创建 Enhancer 对象,类似于 JDK 中的 Proxy 类Enhancer enhancer = new Enhancer();// 2. 设置父类(目标类)enhancer.setSuperclass(OrderService.class);// 3. 设置回调(拦截器)enhancer.setCallback(new LogMethodInterceptor());// 4. 创建代理对象OrderService proxyInstance = (OrderService) enhancer.create();// 5. 使用代理对象调用方法System.out.println("代理对象的类型: " + proxyInstance.getClass().getName());proxyInstance.createOrder("iPhone 15");}
}

运行结果:

JDK 动态代理技术原理

**JDK 动态代理**:它不关心你的类是什么,只关心你实现了哪些接口。它在运行时**通过反射**为你创建一个**全新的类**,这个新类实现了你指定的所有接口,并把所有方法调用都转发到一个 `**InvocationHandler**` 上。**它和你的原始类没有父子关系。**

场景预设

我们想在调用 UserServiceaddUser 方法前后,打印日志。

1)定义一个接口

public interface UserService {void addUser(String username);
}

2)创建接口的实现类(目标对象)

public class UserServiceImpl implements UserService {@Overridepublic void addUser(String username) {System.out.println("【核心业务】正在添加用户: " + username);}
}

3)创建 **InvocationHandler**(核心逻辑处理器)

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class LogInvocationHandler implements InvocationHandler {// 1. 持有目标对象的引用private Object target;public LogInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 2. 前置增强(日志)System.out.println("[JDK代理] - 准备执行方法: " + method.getName());// 3. 调用目标对象的原方法Object result = method.invoke(target, args);// 4. 后置增强(日志)System.out.println("[JDK代理] - 方法执行完毕");return result;}
}

4)创建代理对象并使用,可以看到 JDK 是反射技术实现代理。

import java.lang.reflect.Proxy;public class JdkProxyDemo {public static void main(String[] args) {// 1. 创建目标对象UserServiceImpl target = new UserServiceImpl();// 2. 创建代理对象UserService proxyInstance = (UserService) Proxy.newProxyInstance(target.getClass().getClassLoader(), // 类加载器target.getClass().getInterfaces(),   // 目标对象实现的接口new LogInvocationHandler(target)     // 事件处理器);// 3. 使用代理对象调用方法System.out.println("代理对象的类型: " + proxyInstance.getClass().getName());proxyInstance.addUser("张三");}
}

执行结果:

参考资料

https://www.cnblogs.com/lyh233/p/16008251.html#_label1_1
https://pdai.tech/md/spring/spring-x-framework-aop.html

本文由mdnice多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/925165.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025年筒袋磁力泵实力厂家推荐榜:高效耐用与创新技术深度解

2025年筒袋磁力泵实力厂家推荐榜:高效耐用与创新技术深度解在化工、制药、石油等工业领域,筒袋磁力泵作为一种高效、无泄漏的流体输送设备,正日益成为关键工艺环节的核心装备。其采用磁力耦合驱动技术,彻底解决了传…

2025电源适配器权威推荐榜:高效稳定、安全耐用的优质品牌之

2025电源适配器权威推荐榜:高效稳定、安全耐用的优质品牌之选在当今数字化的时代,各种电子设备充斥着我们的生活和工作,从智能手机、平板电脑到笔记本电脑、智能穿戴设备等,这些设备都离不开电源适配器来提供稳定的…

「LUCKY STUN穿透」IPv4和IPv6分离重定向

「LUCKY STUN穿透」IPv4和IPv6分离重定向关于本教程 在之前的教程中我们已经实现了通过cloudflare的页面规则 以及重定向规则实现实现stun穿透端口的“固定” 使用页面规则:「LUCKY STUN穿透」使用Cloudflare的页面规…

2025航空插头权威推荐榜:M8/m12/公母对接/5芯/五芯/三芯/4芯/3芯/12芯航空插头优质性能与可靠品质的源头厂家之选

行业背景航空插头,作为电子设备中电流、信号连接的关键部件,广泛应用于航空航天、国防、工业自动化、交通运输等众多领域。随着科技的飞速发展,各行业对电子设备的性能和可靠性要求越来越高,这也对航空插头的质量、…

2025经侦律师优质品牌推荐:上海浦信律所专业护航!

2025经侦律师优质品牌推荐:上海浦信律所专业护航!在当今复杂多变的商业环境中,经济犯罪的形式日益多样化和隐蔽化,经侦律师的重要性愈发凸显。他们不仅要具备扎实的法律知识,还要应对各种技术挑战,为客户提供专业…

建筑人才网官方网站评职称wordpress目录结构

php的错误处理是比较复杂的, 本文讲解php中所有错误相关的重要知识点做一次梳理, 便于理解php的错误机制. 基础知识 在此之前, 先熟悉一下php error的基础知识 预定义常量运行时配置异常错误处理函数预定义常量 定义了所有php的错误类型常量, 每一个常量都是一个整型数值, 它的…

制作营销网站公司竞价推广培训

在英语学习中&#xff0c;我们经常遇到一些句子包含两个成分&#xff0c;如“人物”或“宾语补充说明”。这些句子可能是双宾语结构&#xff0c;也可能是宾语补足语结构。虽然两者都出现在动词后&#xff0c;但它们的功能和意义完全不同&#xff0c;本篇文章将会介绍一下小技巧…

实用指南:[Windows] 随手剪-视频合并工具 v0.12多种格式多段视频50多种转场效果

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

欧易-(OKX)交易所注册及KYC认证全流程指南

作为全球领先的加密货币交易平台,欧易(OKX)为用户提供安全便捷的数字资产交易服务。本文将详细介绍OKX的注册流程和KYC认证步骤,帮助您快速完成账户开通。 一、OKX注册流程下载安装OKX APP 您可以通过以下方式获取…

Window配置WSL(Ubuntu)环境

WSL是用于Windows系统之上的Linux子系统。可以在Windows系统中获得Linux系统环境,并完全直连计算机硬件,无需通过虚拟机虚拟硬件。WSL是什么 WSL(Windows Subsystem for Linux)是Win10系统推出的全新功能,让我们可…

最流行的网站设计风格洛阳青峰网络让人去培训

组件库上都有详细的介绍&#xff0c;有自带的一些属性&#xff01;

备案核验单 网站类型wordpress无法上传文件

一、创建Maven项目二、设置SVN信息三、设置构建触发器四、设置Maven命令五、设置构建后发邮件信息&#xff08;参考文章一&#xff09;六、设置构建后拷贝文件到远程机器并执行命令来自为知笔记(Wiz)

使用SVCB/HTTPS记录隐藏和固定Web服务端口

使用SVCB/HTTPS记录隐藏和固定Web服务端口背景 在之前的教程中我们实现了使用 重定向 来 “固定” stun穿透后web服务的端口 其主要的效果是 在访问穿透后的web服务时免除输入端口 这个方法不仅适用于stun穿透 也适合运…

深圳市网站开发茂名手机网站制作

/* * 关于equals()和: 对于String简单来说就是比较两字符串的Unicode序列是否相当&#xff0c;如果相等返回true; * 而是比较两字符串的地址是否相同&#xff0c;也就是是否是同一个字符串的引用。 * * 为了高效的修改字符串Java引入了StringBuffer。 */public class test { p…

做网站哪个软件好东莞横沥医院

在当今数字化浪潮的推动下&#xff0c;企业的数字化转型已成为企业竞争力的关键因素。而在众多企业应用软件中&#xff0c;SAP&#xff08;系统应用和产品&#xff09;作为全球的企业资源规划&#xff08;ERP&#xff09;解决方案供应商&#xff0c;扮演着至关重要的角色。然而…

OI 笑传 #15

karawaredoki0 T1 rz题。 1题目背景 弗洛吉有一棵 \(n\) 个顶点的无根树,每个顶点 \(i\) 的初始权重为 \(i\)。他最多执行一次操作,一次操作是选择一条路径上的顶点序列 \(x_1,x_2,\cdots,x_k\),从集合 \(x_1,x_2,\…

物流网站前端模板下载外贸 网站 建设 制作 成都

净初级生产力(NPP)是指植物在单位时间单位面积上由光合作用产生的有机物质总量中扣除自养呼吸后的剩余部分&#xff0c;是生产者能用于生长、发育和繁殖的能量值&#xff0c;反映了植物固定和转化光合产物的效率&#xff0c;也是生态系统中其他生物成员生存和繁衍的物质基础。其…

广州建设厅官方网站小程序怎么开发自己的微信小程序

xxxx 不在 sudoers 文件中。此事将被报告。 在Ubuntu中&#xff0c;可以通过将用户添加到sudo组来为其提供sudo&#xff08;超级用户&#xff09;权限。 要添加sudo权限&#xff0c;按照以下步骤操作&#xff1a; 打开终端&#xff08;CtrlAltT&#xff09;。 输入以下命令并…

互联网制作网站网络系统搭建

语义化是指根据内容的结构化&#xff08;内容语义化&#xff09;&#xff0c;选择合适的标签&#xff08;代码语义化&#xff09;。通俗来讲就是用正确的标签做正确的事情。 语义化的优点如下&#xff1a; 对机器友好&#xff0c;带有语义的文字表现力丰富&#xff0c;更适合…

怎么做公司的中英文网站网站建设 顺德

1、进程 1.1什么是进程&#xff1a;进行中的程序&#xff08;正在运行中的程序&#xff09;-process过程 程序的一次执行过程 - 进程 hello.c -- 程序源代码 a.out -- 可执行程序 1.2程序和进程的关系&#xff1a; 程序<------>进程 1.3进程怎么来的&#xff1a; 程…