☰
学习笔记02-260930-Spring Boot 入门笔记: AOP、SpringBoot入门
2026/10/1 8:22:58 网站建设 项目流程

该文章为ai润色,原文笔记在最后。该文章仅为个人记录所写。

1. 一次创建草稿的请求怎么走

后端启动后,日志里能看到:

Tomcat started on port 8080 (http) with context path '/'

所以接口地址是http://localhost:8080/api/item。客户端发送 POST 请求,请求体里放标题、内容和用户 ID:

{ "title": "第一条练习草稿", "content": "练习", "ownerId": 1 }

按我现在的理解,这个过程可以先这样记:

客户端发出 HTTP 请求 ↓ Tomcat 接收请求,经过过滤器链 ↓ Spring MVC 的 DispatcherServlet 找到对应的处理方法 ↓ 已配置的拦截器进行前置处理 ↓ 读取 JSON 请求体,转换成 CreateItemRequest ↓ DemoController.createItem() ↓ ItemServiceImpl.createItem() ↓ 检查数据,创建 ServiceItem,保存到 Map ↓ 返回 ServiceItem,转换为 JSON ↓ 客户端收到 HTTP 响应

刚开始我写成“JSON 先进入 Controller,然后由@RequestBody转换”。更准确一点,是 Spring MVC 根据@RequestBody读取请求体,通过消息转换器完成转换,再把CreateItemRequest传给 Controller 方法。

Controller 接着调用itemService.createItem(request)。这个itemService是 Spring 注入的,不需要在 Controller 里自己new ItemServiceImpl()。

Service 里的流程比较容易看懂:检查标题和内容是否为空,创建ServiceItem,放入标题、内容、ownerId,设置DRAFT状态和创建时间,再生成 ID 并保存。

我把保存数据的工具类命名为simpleDatabase,但它里面其实是一个静态HashMap,没有连接数据库。新增和查询分别是:

items.put(item.getId(), item); items.get(id);

ID 也是在 Java 里通过++id生成的。以后换成真实数据库,主键可能用数据库自增、序列或 UUID,不能笼统地说“主键都是数据库自动自增”。

查询则从DemoController.selectById()进入ItemServiceImpl.selectById(),最后调用simpleDatabase.select(id),从 Map 里取出数据。

2. 顺便弄清 Spring Boot 的几个基础概念

启动类上的注解

启动类上常见的@SpringBootApplication是一个组合注解,主要包含这三个职责:

注解用来做什么
@SpringBootConfiguration表明这是一个 Spring Boot 配置类
@EnableAutoConfiguration根据依赖、配置和已有 Bean 等条件进行自动配置
@ComponentScan默认扫描启动类所在包及其子包,寻找组件

我先记住它们各自负责什么。尤其是“自动配置”有条件,并不是加一个注解就把所有功能都开启了。

配置文件和默认值

application.properties使用key=value,YAML 使用缩进表达层级。例如下面两段配置表达的是同一件事:

spring.application.name=${APP_NAME:unnamed}
spring: application: name: ${APP_NAME:unnamed}

${APP_NAME:unnamed}表示读取APP_NAME,没有时用unnamed。这里读取的是 Spring 环境中的配置值,来源可以包括系统环境变量和命令行参数。

读教程时,我还遇到了“缺省配置”这个词。其实“缺省”就是默认:某个配置项有默认值时,省略配置就按默认值来。主动设置后通常会覆盖默认值,具体还要看配置优先级。如果必要的配置没有默认值,省略后仍然可能报错。

Logback 是什么

Logback 是日志实现,用来记录启动信息、业务过程和异常等内容。我们在代码里通过 SLF4J 的接口写日志,Logback 可以负责实际输出。

Spring Boot 常用 starter 默认带有 Logback,所以没有自己写日志配置文件,控制台也能看到日志。如果要输出到文件、修改格式或调整日志级别,再进行配置。教程里的defaults.xml则提供了一些预设日志配置。

Maven 会不会自动挑一个能用的版本

我最初以为,额外依赖可以让 Maven 自动选版本;如果某个版本不可用,它还会换一个可用的。后来发现这个理解不对。

我的项目通过pom.xml声明依赖。Boot 父项目或 BOM 已经管理版本的依赖,通常可以省略单独的版本号;没有被管理的依赖,需要确定版本。dependencyManagement是管理规则,并不等于已经引入这个库。

多个依赖间接引入同一个库的不同版本时,Maven 通常先看依赖路径的距离:离当前项目更近的优先,距离相同才看声明顺序,依赖管理也会影响结果。如果选中的版本无法解析,构建通常会报错,不会自动换成任意其他版本。

3. AOP 为什么能把日志单独拿出来

假如创建、查询、修改这些业务都要打印日志,最直接的写法就是在每个方法里加日志。以后改日志格式,就需要挨个改。

AOP,也就是面向切面,可以把这些横跨多个方法的处理集中起来。先用切点选出要处理的方法,再用通知决定什么时候做什么。日志、权限检查、事务都是常见的例子。

学到这里,我又顺着问了“耦合”和“内聚”。

耦合可以理解成代码模块之间依赖的程度。依赖本身很正常,但如果一个模块知道另一个模块太多内部细节,一点变化就影响很多地方,修改就会比较困难。

内聚则是一个模块内部的内容是否围绕同一个职责。业务方法集中处理业务,日志模块集中处理日志,都可以有比较好的内聚。

所以,把合适的日志处理提取成切面,可以让业务方法更集中,也方便统一修改日志。不过,重复的业务代码不一定都适合做成 AOP,还是要看它承担的职责。

4. 我对动态代理的误解

这是今天绕得最久的地方。

我原来以为:

每个类都先生成一个动态代理 ↓ 需要 AOP 时,代理再进入 AOP 接受增强 ↓ 使用时先调用 AOP,再调用代理对象

现在我这样理解 Spring AOP:

Spring 判断某个 Bean 需要代理 ↓ 为它创建代理,组织目标对象和通知 ↓ 调用者拿到代理引用 ↓ 调用方法时执行匹配的通知,再调用目标方法

这里的增强不是把日志代码复制进原类源码。代理负责把通知与目标调用连接起来,通知在方法被调用时执行。

代理也是针对对象创建的。一个 Bean 已经有代理,调用它另一个不匹配切点的方法,仍然可能经过同一个代理,只是不执行这组通知。动态代理还可以用于 AOP 以外的场景。

JDK 动态代理和 CGLIB

方式与目标类的关系容易记错的地方
JDK 动态代理生成实现指定接口的代理类,不继承目标实现类不会复制目标类的所有方法和字段,也不会复制私有方法
CGLIB生成目标类的子类,通过可重写的方法拦截调用final类不能继承,final、private方法不能按这种方式增强

在日志里,我看到了这两个类名:

目标对象:com.example.demo.service.serviceImpl.ItemServiceImpl 代理对象:com.example.demo.service.serviceImpl.ItemServiceImpl$$SpringCGLIB$$0

Spring Boot 4.0 的 AOP 自动配置默认使用 CGLIB,但不能把它说成所有 Spring 环境都统一默认 CGLIB。Spring AOP 基于代理,目标对象内部的this.方法()调用通常会绕过代理,这也是以后排查切面为什么没执行时要注意的地方。

5. 给创建草稿加一个日志切面

我先写了一个LogAspect,在创建草稿前打印日志:

@Slf4j @Aspect @Component public class LogAspect { ​ @Before("execution(* com.example.demo.service.serviceImpl.ItemServiceImpl.createItem(..))") public void beforeCreate() { log.info("准备创建草稿"); } }

@Aspect声明切面,@Component让它在组件扫描时成为 Spring Bean,@Slf4j提供日志字段。项目还需要相应的 AOP 依赖和支持。

发送创建请求后,控制台出现了“准备创建草稿”,接口也照常返回。

切点表达式怎么读

第一次看到这一长串,我基本看不懂:

execution(* com.example.demo.service.serviceImpl.ItemServiceImpl.createItem(..))

拆开就清楚一些了:

  • execution:匹配方法执行。

  • 最前面的*:任意返回类型。

  • com.example.demo.service.serviceImpl:包名。

  • ItemServiceImpl:类名。

  • createItem:方法名。

  • (..):零个或多个任意类型参数。

调整匹配范围时,可以这样写:

# 匹配 ItemServiceImpl 的方法 execution(* com.example.demo.service.serviceImpl.ItemServiceImpl.*(..)) ​ # 匹配 service 包及其子包中的方法 execution(* com.example.demo.service..*.*(..))

参数位置里,()表示无参数,(*)表示恰好一个任意类型参数,(..)表示零个或多个参数。这些表达式选出的范围,还要受 Spring AOP 代理能力的限制。

几个术语放回代码里理解

术语我的简单理解对应例子
Aspect,切面集中放一类额外处理LogAspect
Join Point,连接点可以被增强的执行位置,Spring AOP 中是方法执行一次createItem()执行
Pointcut,切点筛选这些执行位置的规则execution(...)
Advice,通知或增强选中之后具体做什么创建前打印日志
Target Object,目标对象真正处理原业务的对象ItemServiceImpl
AOP Proxy,代理对象接收调用、执行通知并调用目标的对象日志里带SpringCGLIB的对象
Weaving,织入把切面和原调用联系起来的过程Spring AOP 通过代理组织调用
Interceptor,拦截器在调用前后加入处理的一种实现代理里的拦截调用链,与 MVC 请求拦截器不是同一个概念
Introduction,引介让代理额外实现某个接口原类源码不用增加该接口

6.@Around让我踩了一个返回值的坑

常见通知有这几种:

通知执行时机
@Before方法执行前
@AfterReturning方法正常返回后
@AfterThrowing方法抛出异常后
@After方法结束时,正常返回或异常都会触发
@Around围绕调用,可以决定是否继续、最终返回什么

我开始用@Around时,预期很简单:开始时打印一条日志,执行原来的创建方法,结束时再打印一条日志。

结果日志从around-Start一直打印到了around-End,POST 的响应体却是空的。状态码为 200,响应头里写着:

Content-Length: 0

当时我还把它描述成“没有返回 HTTP 连接”。仔细看才发现,HTTP 响应已经回来了,只是没有响应体。GET 也还能查到已有草稿,于是我一直在找为什么 POST 没有把对象返回。

问题出在环绕通知:我调用了proceed(),却没有把它的结果返回,而且通知方法写成了void。

改成下面的处理后,响应恢复正常:

@Around("execution(* com.example.demo.service.serviceImpl.ItemServiceImpl.createItem(..))") public Object aroundCreate(ProceedingJoinPoint joinPoint) throws Throwable { log.info("around-Start"); Object result = joinPoint.proceed(); log.info("around-End"); return result; }

所以,继续调用和返回调用结果是两件事。proceed()让调用继续,return result才把结果交回调用者。

为什么 Before 和 After 不用这样返回

@Before、@After是框架在调用前后安排的附加动作,它们的返回值不负责传递目标方法的结果。

@Around则包住了这次调用,代理调用最终拿到的返回值来自环绕通知。想保留原来的行为,就要把继续调用得到的结果返回。常用Object是为了适配不同返回类型;原方法为void时,得到的结果可以是null。

如果不调用proceed(),目标方法就不会继续执行,但不代表请求一定卡住。环绕通知也可以直接返回一个结果,只是它改变了原来的调用行为。

另外,我原来在@After里打印“创建草稿完成”。这个通知异常时也会执行,所以这句日志容易误导。要表达成功,应该考虑@AfterReturning;@After更适合表达“执行结束”。

7. JoinPoint 到底是不是原方法

我最开始把JoinPoint理解成原方法本身,后来又不明白:既然是原方法,怎么还能拿到代理对象的信息?

它其实是描述当前方法执行的上下文对象,可以从中取得不同的信息:

调用可以得到什么
getSignature()方法签名信息,例如名称
getArgs()本次调用的参数
getTarget()目标对象
getThis()Spring AOP 代理对象

ProceedingJoinPoint扩展了JoinPoint,用于环绕通知,额外提供proceed()。它会继续剩余通知链,最终调用目标方法。

对应到我的例子,getTarget().getClass().getName()打印原来的ItemServiceImpl,getThis().getClass().getName()打印带SpringCGLIB的代理类名。

8. 自定义注解:写一个标记,再让切面识别它

前面是按照包名、类名和方法名匹配。另一种常见方式,是在方法上加一个注解,再让切面匹配带有这个注解的方法。

这让我想到了事务:方法上写@Transactional,框架就会根据它做相应处理。自定义注解也可以提供这种标记方式,但注解本身不会自动执行处理逻辑。

我用的注解叫MetricTime:

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MetricTime { String value(); }

它可以放在方法上,例如:

@MetricTime("select") public ServiceItem selectById(Long id) { return simpleDatabase.select(id); }

注解是什么

我现在把注解理解成贴在类、方法等位置上的结构化说明,也叫元数据。它可以携带一些额外信息,但要由编译器、工具或框架读取并处理,才会产生相应作用。

例如@MetricTime("select"),表示这个方法带有MetricTime标记,其中value是select。切面可以根据这个标记选中方法,也可以读取value,用它作为日志里的名称。

@Target:可以写在哪里

我原来把它理解成“作用域”,现在觉得说“允许标记的位置”更直观。

值允许的位置
TYPE类、接口、枚举、record、注解类型等类型声明
METHOD方法声明
FIELD字段,包括枚举常量
PARAMETER方法或构造器的参数
CONSTRUCTOR构造器声明
LOCAL_VARIABLE局部变量声明
ANNOTATION_TYPE注解类型声明
PACKAGE包声明,通常写在package-info.java
TYPE_PARAMETER泛型类型参数声明
TYPE_USE使用类型的位置,例如泛型实参
MODULE模块声明
RECORD_COMPONENTrecord 的组件

允许多个位置时,可以写@Target({ElementType.METHOD, ElementType.TYPE})。

@Retention:保留到什么时候

它不负责决定“什么时候执行”,而是决定注解信息保留到哪个阶段。

值含义
SOURCE只保留在源码,编译时丢弃,例如@Override
CLASS写入 class 文件,运行时不能通过普通反射读取
RUNTIME写入 class 文件,运行时仍能通过反射读取

没有声明@Retention时,默认是CLASS。如果希望切面在运行时读取注解信息,这里就需要保留到RUNTIME。

value()是不是可选参数

不是。是否可选,要看有没有default:

public @interface MetricTime { String value(); String unit() default "ms"; }

这个示例里的value没有默认值,使用时必填;unit有默认值,可以省略。

@MetricTime("select")是@MetricTime(value = "select")的简写。名字叫value只是让写法可以更简短,不会让它自动变成可选属性。

9.@annotation的两种写法

这里又卡了一下,因为表达式里看起来都是注解,却有两种写法。

按注解类型匹配

@Around("@annotation(com.example.demo.aspect.MetricTime)")

它匹配带有@MetricTime的方法,通知方法不必额外声明MetricTime参数。如果想读取注解属性,可以从方法信息中获取注解:

MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); MetricTime metricTime = method.getAnnotation(MetricTime.class); String name = metricTime.value();

我之前只写了MetricTime metricTime;,接着就调用metricTime.value(),IDE 提示变量可能尚未初始化。现在看原因很直接:声明变量不会让它自动变成方法上的注解实例,需要先取得这个实例。

这里的MethodSignature来自org.aspectj.lang.reflect,Method来自java.lang.reflect。还要注意,使用接口代理时得到的Method可能来自接口;注解写在实现方法上时,需要定位到目标方法,不能直接假设getAnnotation()一定能读到。

匹配并绑定注解

@Around("@annotation(metricTime)") public Object time(ProceedingJoinPoint joinPoint, MetricTime metricTime) throws Throwable { String name = metricTime.value(); return joinPoint.proceed(); }

这个片段展示的是参数绑定:表达式中的metricTime对应通知方法的同名参数,Spring 会把匹配到的注解传进来,方法里就能直接读取它的属性。

两种写法都使用切点表达式。区别是,一种按类型匹配,另一种同时把注解绑定到参数。Spring 通知参数绑定

如果要做耗时统计,思路是在proceed()前记录开始时间,在finally中计算耗时,这样正常返回和异常结束都可以记录。计时之外,也要注意保留返回值和异常的行为。

原文内容如下(原文内容可能有错误,仅供参考):

AOP

面向切面,可以将在多个不同业务方法中但是又相同的部分(与业务逻辑本身关联不大但是又不可或缺的部分)单独提取出来,制作成切面,通过切点选出要处理的方法,再通过通知决定在方法执行前、执行后或执行前后做什么。这样业务方法可以更集中地表达自己的业务逻辑,共用处理也比较方便统一修改。例如安全检查、日志、事务等代码。

如图假如一个业务逻辑是从上到下的,但是一些与业务逻辑不直接相关的代码降低了内聚,但是有了切面,被切面覆盖的代码可以从业务逻辑中切出去,业务逻辑只需要关心具体业务,提高内聚,对于业务之外但是又不得不用的代码可以通过切面来配装,也可以通过修改切面代码来一键修改所有日志等实现,降低直接耦合

AOP原理

Spring AOP 可以通过代理对象,在符合切入点规则的方法调用前后加入额外处理。切入点负责选方法,代理负责在调用时把通知和原方法联系起来。Spring AOP默认使用CGLIB动态代理,也可以手动切换。

JDK动态代理:生成的是实现指定接口的代理类,不继承原来的实现类CGLIB动态代理:生成的是原类的子类,通过重写可重写的方法来进行代理,因此final、private等不能被重写的方法不能用这种方式增强

我的误解和改正:我之前一直以为每个类都会先默认创建一个动态代理。某个方法需要 AOP 时,代理对象再“进入 AOP”接受增强;使用时先调用 AOP,再由 AOP 调用代理对象。

但是实际上应该是:Spring AOP 不是一个存放代理对象的地方。Spring 判断某个 Bean 需要 AOP 处理后,才会为它准备代理对象;调用者实际调用的是代理对象。代理会在符合规则的方法被调用时执行相应的通知,再调用原对象的方法。这里的“增强”不是把通知代码复制进原类,也不是修改原类源码。代理对象承载了这套调用流程,通知是在方法被调用时才真正执行的。不是每个类都会因为 Spring AOP 而自动生成代理;动态代理也可以用于 AOP 以外的场景

装配AOP

  • Aspect:切面,即一个横跨多个核心逻辑的功能,或者称之为系统关注点;

  • Joinpoint:连接点,即定义在应用程序流程的何处插入切面的执行;

  • Pointcut:切入点,即一组连接点的集合;

  • Advice:增强,指特定连接点上执行的动作;

  • Introduction:引介,指为一个已有的Java对象动态地增加新的接口;

  • Weaving:织入,指将切面整合到程序的执行流程中;

  • Interceptor:拦截器,是一种实现增强的方式;

  • Target Object:目标对象,即真正执行业务的核心逻辑对象;

  • AOP Proxy:AOP代理,是客户端持有的增强后的对象引用。

先在SpringBoot中引入AOP依赖

然后定义一个切面类LogAspect

@Slf4j @Aspect @Component public class LogAspect { // 方法执行前通知 @Before(("execution(* com.example.demo.service.serviceImpl.ItemServiceImpl.createItem(..))")) public void beforeCreate(){ log.info("准备创建草稿"); } ​ // 方法执行后通知 @After(("execution(* com.example.demo.service.serviceImpl.ItemServiceImpl.createItem(..))")) public void afterCreate(){ log.info("创建草稿完成"); } ​ }

其中先通过@Aspect注解声明这是一个AOP切面,然后通过@Component将其注册为Bean

在AOP中,有这几个通知注解

通知何时执行
@Before方法执行前
@AfterReturning方法正常返回后
@AfterThrowing方法抛出异常后
@After方法结束时,无论正常还是异常
@Around围绕整个调用

切点表达式

而注解中需要添加切点表达式来规定AOP的范围,最常用的切点表达式如下:

execution(返回类型 包名.类名.方法名(参数))

execution(* com.example.demo.service.serviceImpl.ItemServiceImpl.createItem(..))从右往左读依次为

  • creatItem方法

  • (..):允许任意个、任意类型的参数

  • ItemServiceImpl类

  • com.example.demo.service.serviceImpl包

  • 最前面的*:允许返回任意类型

范围的变化:

表达式匹配范围
execution(* com.example.demo.service.serviceImpl.ItemServiceImpl.createItem(..))这个类的createItem方法
execution(* com.example.demo.service.serviceImpl.ItemServiceImpl.*(..))这个类的所有方法
execution(* com.example.demo.service..*.*(..))service包及其子包中,各类的所有方法

()表示无参数,(*)表示恰好一个任意类型的参数,(..)表示零个或多个参数

AOP术语与我的代码例子结合的解释:

术语简单理解在你的例子里
Aspect(切面)把一类额外处理集中放在一起LogAspect集中放日志规则
Join Point(连接点)程序运行中一个可以被处理的位置;Spring AOP 中主要是方法执行一次createItem()的执行
Pointcut(切入点)筛选连接点的规则:选哪些方法execution(...ItemServiceImpl.createItem(..))
Advice(通知/增强)选中方法后,具体做什么执行前打印“准备创建草稿”
Target Object(目标对象)真正完成原业务的对象ItemServiceImpl对象
AOP Proxy(代理对象)调用者接触的“中间人”,负责执行通知并继续调用目标对象你看到的ItemServiceImpl$$SpringCGLIB$$0
Weaving(织入)把切面与目标调用联系起来的过程Spring AOP 在运行时准备代理,使日志能参与方法调用
Interceptor(拦截器)实现“调用前后做额外处理”的一种方式可以把代理里的通知调用链理解为一层层拦截
Introduction(引介)让代理对象额外表现为“实现了某个新接口”它不修改原类的源码

Joinpoint和ProceedingJoinPoint

名称是什么常见用途
JoinPoint描述当前被通知处理的方法调用查看方法名getSignature()、参数getArgs()、目标对象getTarget()、代理对象getThis()
ProceedingJoinPointJoinPoint的扩展,供@Around使用除了查看调用信息,还能用proceed()继续调用

JoinPoint提供“这次调用的信息”;ProceedingJoinPoint允许你决定“是否继续这次调用”。在 Spring AOP 中,这里的调用点是方法执行

注解装配AOP

除了通过添加切点表达式来定义装配AOP,还可以通过使用注解的方式快速、灵活地定义装配AOP。

首先先定义一个注解

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MetricTime { String value(); }
@Target()规定这个注解可以写在哪里
值允许标记的位置
TYPE类、接口、枚举、record 的声明,包括注解类型
METHOD方法声明
FIELD成员字段,包括枚举常量
PARAMETER方法或构造器的参数
CONSTRUCTOR构造器声明
LOCAL_VARIABLE方法中的局部变量
ANNOTATION_TYPE注解类型的声明,例如你定义的MetricTime
PACKAGE包声明,通常写在package-info.java
TYPE_PARAMETER泛型参数声明,例如class Box<T>中的T
TYPE_USE使用某个类型的位置,例如List<@某注解 String>
MODULE模块声明
RECORD_COMPONENTrecord 的组件,例如record User(String name)中的name

可以允许多个位置:@Target({ElementType.METHOD, ElementType.TYPE})

@Retention()规定注解信息保留到哪个阶段
值注解信息保留到哪里
SOURCE只留在源码中,编译时丢弃,例如@Override
CLASS写入.class文件,运行时不能通过普通反射读取
RUNTIME写入.class文件,运行时仍能通过反射读取

注解中定义的内容叫属性,是否必填只看它有没有default:

public @interface MetricTime { String value(); // 没有默认值:必填 String unit() default "ms"; // 有默认值:可选 }

在切面中,通过将注解信息传给通知来使用注解实现AOP切面

写法作用通知方法需要什么参数
@annotation(com.example.demo.aspect.MetricTime)选中带有@MetricTime的方法不需要额外声明MetricTime参数
@annotation(metricTime)选中方法,同时把它的注解传给通知需要声明MetricTime metricTime,名字必须对应

如果使用第一种方法,需要通过反射使用Joinpoint来获取原注解中的元数据,而使用第二种则直接可以获取到元数据

SpringBoot

一个请求到后端的简单流程

浏览器客户端发起HTTP请求->请求经过过滤器->请求进入SpringMVC找到对应方法->请求经过拦截器->请求进入Controller(自动格式转化为相应类型)->请求进入Service处理相应的业务(需要调用数据库时,一般由Service调用Mapper调用数据库)->业务处理完毕后返回Controller返回结果并生成HTTP响应->处理好的数据返回客户端

配置文件

application.properties

采用key=value的格式,因为必须写出完整的key,而配置中key又有很大一部分前缀是相同的,导致可读性困难

# application.properties ​ spring.application.name=${APP_NAME:unnamed} ​ spring.datasource.url=jdbc:hsqldb:file:testdb spring.datasource.username=sa spring.datasource.password= spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver ​ spring.datasource.hikari.auto-commit=false spring.datasource.hikari.connection-timeout=3000 spring.datasource.hikari.validation-timeout=3000 spring.datasource.hikari.max-lifetime=60000 spring.datasource.hikari.maximum-pool-size=20 spring.datasource.hikari.minimum-idle=1 ​

application.yml

采用层级结构,key的重复前缀可省略,可读性好

# application.yml ​ spring: application: name: ${APP_NAME:unnamed} datasource: url: jdbc:hsqldb:file:testdb username: sa password: driver-class-name: org.hsqldb.jdbc.JDBCDriver hikari: auto-commit: false connection-timeout: 3000 validation-timeout: 3000 max-lifetime: 60000 maximum-pool-size: 20 minimum-idle: 1 ​

环境变量

可以使用${}来使用环境变量,环境变量可以是系统变量,也可以是在启动时传入命令配置

启动时的必要注解

@SpringBootApplication:组合注解,里面包含了三个必要的注解

1、@SpringBootConfiguration:表明这是一个Spring配置类

2、@EnableAutoConfiguration:根据已有依赖,自动配置需要的功能

3、@ComponentScan:扫描启动包以及其子包,寻找要交给Spring管理的类

依赖管理pom.xml

SpringBoot项目需要从pom.xml中配置依赖,并交由Maven管理。项目指定了 Spring Boot 父项目的版本;对于它已经管理版本的依赖,添加依赖时通常可以省略单独的版本号。如果要加入未被管理的依赖,就需要确认并指定版本。

当不同依赖间接引入同一个库的不同版本时,Maven 会按依赖管理和依赖树规则确定最终版本:一般优先采用离当前项目更近的版本;距离相同才考虑声明顺序。如果选定版本无法解析,构建通常会报错,不会自动换成任意其他版本。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询