【SpringBoot3】面向切面 AspectJ AOP 使用详解
文章目录
- 一、AspectJ介绍
- 二、简单使用步骤
- 1、引入依赖
- 2、定义一个Aspect
- 3、开启AOP支持
- 三、AOP 核心概念
- 四、切点(Pointcut)
- 1. execution
- 2. within
- 3. this & target
- 4. args & @args
- 5. @within & @target & @annotation
- 五、通知(Advice)
- 1. @Before 示例:日志记录
- 2. @AfterReturning 示例:处理返回值
- 3. @AfterThrowing 示例:异常处理
- 4. @After 示例:资源释放
- 5. @Around 示例:方法执行前后处理
- 6. 参数相关 Advice 示例:验证参数
- 7. 注解相关 Advice 示例:基于注解的日志记录
- 8. @AfterThrowing 异常示例:特定异常处理
- 9. @Pointcut 单独定义切点,其他Advice直接引用
- 参考
一、AspectJ介绍
AspectJ是一个面向切面的框架,它扩展了Java语言,并定义了AOP(面向切面编程)语法。
AspectJ支持数据埋点、日志记录、性能统计、安全控制、事务处理、异常处理等多种横切关注点。通过AspectJ,开发者可以更加直观地定义和理解代码的行为,减少对业务逻辑的干扰。
- 特点:
- 模块化:AspectJ允许开发者将横切关注点以模块化的方式进行管理和重用,提高了代码的可维护性和可读性。
- 声明式编程:AspectJ使用注解或XML配置的方式来声明横切关注点,减少了重复的样板代码。
- 细粒度控制:AspectJ提供了丰富的切点表达式语言,可以精确地选择需要横切的连接点,实现对代码的细粒度控制。
- 跨模块切面:AspectJ可以在不同的模块之间进行切面的织入,使得横切关注点可以跨越多个模块进行统一管理。
- 与Java语言的兼容性:AspectJ是基于Java语言的扩展,与Java语法完全兼容,可以无缝地与现有的Java代码进行集成和使用。
二、简单使用步骤
1、引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、定义一个Aspect
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component; @Aspect
@Component
public class MyAspect { // 定义切点,表示对哪些类的哪些方法进行拦截 // execution表达式指定了方法签名 // 第一个"*"表示任意返回类型,第二个"*"表示类中的任意方法 // ".."表示任意参数 // 这里的例子表示拦截UserService中的所有方法 @Before("execution(* com.example.service.UserService.*(..))") public void beforeAdvice() { System.out.println("Before method: Do something before."); }
}
3、开启AOP支持
在Spring Boot应用中,通常通过@SpringBootApplication
注解启动应用,该注解包含了@EnableAspectJAutoProxy
,因此默认情况下Spring Boot应用是支持AOP的。
如果你的应用配置较为特殊,确保你的配置类上(通常是主类)添加了@EnableAspectJAutoProxy
注解以开启对AspectJ自动代理的支持。
三、AOP 核心概念
-
切面(Aspect):AOP 的模块单元,封装了切点、通知以及类型间声明。在Spring AOP中,切面通过使用常规类或使用
@Aspect
注解的常规类(@AspectJ
风格)来实现。 -
连接点(Join point):程序流中指定的一点,如方法调用、方法执行、字段访问等。
-
通知(Advice):切面在特定连接点上采取的动作。通知包括“环绕”、“前置”和“后置”通知。许多AOP框架,包括Spring,都将通知建模为拦截器,并在连接点周围维护一个拦截器链。
-
切点(Pointcut):匹配连接点的谓词。通知与切点表达式相关联。Spring默认使用AspectJ切点表达式语言。
-
引入(Introduction):代表一个类型声明额外的方法或字段。Spring AOP允许您向任何被通知的对象引入新接口(以及相应的实现)。例如,您可以使用引入来使bean实现
IsModified
接口,以简化缓存。 -
目标对象(Target object):被一个或多个切面通知的对象。也称为“被通知对象”。由于Spring AOP是通过使用运行时代理实现的,因此这个对象总是一个代理对象。
-
AOP代理(AOP proxy):由AOP框架创建的对象,用于实现切面契约(如通知方法的执行等)。在Spring框架中,AOP代理是JDK动态代理或CGLIB代理。
-
织入(Weaving):将切面与其他应用类型或对象链接起来以创建被通知对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。Spring AOP像其他纯Java AOP框架一样,在运行时执行织入。
Spring AOP包括以下类型的通知:
- 前置通知(Before advice):在连接点之前运行的通知,但它没有能力阻止执行程序执行(除非它抛出异常)。
- 后置返回通知(After returning advice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。
- 后置异常通知(After throwing advice):如果方法通过抛出异常退出时运行的通知。
- 后置(最终)通知(After (finally) advice):无论连接点以何种方式退出(正常或异常返回)都要运行的通知。
- 环绕通知(Around advice):围绕连接点(如方法调用)的通知。这是最强大的通知类型。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续到连接点,或者通过返回自己的返回值或抛出异常来短路被通知方法的执行。
环绕通知是最通用的通知类型。由于Spring AOP像AspectJ一样提供了全范围的通知类型,因此建议您使用能够实现所需行为的最不强大的通知类型。例如,如果您只需要使用方法的返回值更新缓存,那么实现后置返回通知比实现环绕通知更好,尽管环绕通知也可以完成同样的事情。使用最具体的通知类型提供了更简单的编程模型,并且减少了出错的可能性。例如,您不需要在用于环绕通知的JoinPoint上调用proceed()方法,因此您不会忘记调用它。
所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型)而不是Object数组。
四、切点(Pointcut)
Spring AOP支持使用AspectJ切入点表达式来指定切点。这些表达式可以非常灵活地定义需要拦截的方法集合。以下是一些常用的切点指示符和示例:
1. execution
execution
是最常用的切入点指示符,用于匹配方法执行的连接点。其语法结构如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
- modifiers-pattern(可选):方法的修饰符,如
public
、private
等。 - ret-type-pattern:方法的返回类型,
*
表示任意类型。 - declaring-type-pattern(可选):声明方法的类,支持使用
..
来表示包及其子包下的所有类。 - name-pattern:方法的名称。
- param-pattern:方法的参数列表,
..
表示任意数量和类型的参数,*
表示任意类型的一个参数,(*, String)
表示第一个参数是任意类型,第二个参数是String类型。 - throws-pattern(可选):方法抛出的异常类型。
示例
- 匹配任意类的任意方法:
execution(* *(..))
- 匹配
com.example.service
包下所有类的所有方法:execution(* com.example.service..*.*(..))
- 匹配
MyService
类中的doSomething
方法:execution(* com.example.service.MyService.doSomething(..))
- 匹配任意类的
save
方法,且方法参数为java.lang.String
类型:execution(* *.save(java.lang.String))
2. within
within
用于匹配指定类型内的方法执行,包括指定的接口、类或包。
示例
- 匹配
com.example.dao
包下的所有类的所有方法:within(com.example.dao.*)
- 匹配
com.example.dao
包及其子包中所有类中的所有方法:within(com.example.dao..*)
- 匹配实现了
UserService
接口的类的所有方法:within(com.example.service.UserService+)
3. this & target
- this:用于匹配当前AOP代理对象类型的执行方法,注意是AOP代理对象的类型匹配。
- target:用于匹配当前目标对象类型的执行方法,注意是目标对象的类型匹配。
示例
- 匹配当前AOP代理对象类型为
MyService
的所有方法执行:this(com.example.service.MyService)
- 匹配当前目标对象类型为
MyService
的所有方法执行:target(com.example.service.MyService)
4. args & @args
- args:用于匹配当前执行的方法传入的参数为指定类型的执行方法,参数类型列表中的参数必须是类型全限定名,通配符不支持。
- @args:匹配方法传入的参数所属类上拥有指定的注解的情况。
示例
- 匹配方法参数为
String
类型的方法:args(java.lang.String)
- 匹配方法参数类型上拥有
@MyAnnotation
注解的方法调用:@args(com.example.MyAnnotation)
5. @within & @target & @annotation
- @within:匹配类级别上应用了特定注解的类中的所有方法。
- @target:匹配运行时目标对象(代理对象)的类型上应用了特定注解的方法(在Spring AOP中常用于代理对象的切点定义)。
- @annotation:匹配方法级别上应用了特定注解的方法。
示例
- 匹配所有类上具体指定了
@MyAnnotation
注解的类内的所有方法:@within(com.example.MyAnnotation)
- 匹配当前目标对象类型持有
@MyAnnotation
注解的方法:@target(com.example.MyAnnotation)
- 匹配所有拥有
@MyAnnotation
注解的外部调用方法:@annotation(com.example.MyAnnotation)
五、通知(Advice)
Spring AOP提供了多种类型的Advice,包括:
- @Before:在目标方法执行之前执行。
- @AfterReturning:在目标方法正常执行完成后执行。
- @AfterThrowing:在目标方法抛出异常时执行。
- @After:无论目标方法执行结果如何,最终都会执行。
- @Around:在目标方法执行前后执行,并可以控制目标方法的执行。
每种类型的Advice都需要与切入点表达式结合使用,以确定其应用的范围。
1. @Before 示例:日志记录
在方法执行之前记录日志,常用于跟踪方法的调用。
@Aspect
@Component
public class LoggingAspect {// 定义切点,表示对哪些类的哪些方法进行拦截// execution表达式指定了方法签名// 第一个"*"表示任意返回类型,第二个"*"表示类中的任意方法// ".."表示任意参数// 这里的例子表示拦截UserService中的所有方法@Before("execution(* com.example.service.UserService.*(..))")public void logBeforeMethodExecution(JoinPoint joinPoint) {System.out.println("Before executing method: " + joinPoint.getSignature().getName());}// 定义切点,表示对哪些类的哪些方法进行拦截// execution表达式指定了方法签名// 第一个"*"表示任意返回类型,第二个"*"表示类中的任意方法// 第一次".."表示包及其子包下的所有类// 第二次".."表示任意参数@Before("execution(* com.example..*(..))")public void logBeforeMethodExecutionAll(JoinPoint joinPoint) {System.out.println("Before executing method all: " + joinPoint.getSignature().getName());}
}
2. @AfterReturning 示例:处理返回值
在方法成功执行并返回后,对返回值进行处理或记录。
@Aspect
@Component
public class ReturnValueAspect {@AfterReturning(pointcut = "execution(* com.example.service.UserService.getUserById(..))", returning = "result")public void logReturnValue(Object result) {System.out.println("Method returned: " + result);}
}
3. @AfterThrowing 示例:异常处理
在方法抛出异常时捕获异常,并进行相应的处理或记录。
@Aspect
@Component
public class ExceptionHandlingAspect {@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")public void handleException(Exception ex) {System.err.println("An exception occurred: " + ex.getMessage());}
}
4. @After 示例:资源释放
无论方法执行结果如何,都会在方法执行后执行,常用于资源释放。
@Aspect
@Component
public class ResourceReleaseAspect {@After("execution(* com.example.service.*.*(..))")public void releaseResources() {System.out.println("Resources are released after method execution");}
}
5. @Around 示例:方法执行前后处理
在方法执行前后都进行处理,可以控制方法的执行或添加额外的逻辑。
@Aspect
@Component
public class AroundAdviceAspect {@Around("execution(* com.example.service.UserService.*(..))")public Object logAroundMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("Before executing method: " + joinPoint.getSignature().getName());Object result = joinPoint.proceed(); // 执行目标方法System.out.println("After executing method: " + joinPoint.getSignature().getName());return result;}
}
6. 参数相关 Advice 示例:验证参数
在方法执行之前验证参数的有效性。
@Aspect
@Component
public class ParameterValidationAspect {@Before("execution(* com.example.service.UserService.addUser(com.example.model.User)) && args(user)")public void validateUserParameter(User user) {if (user == null || user.getName() == null || user.getName().isEmpty()) {throw new IllegalArgumentException("User parameter is invalid");}}
}
7. 注解相关 Advice 示例:基于注解的日志记录
根据方法上的注解来决定是否进行日志记录。可以使用注解全限定名或使用参数中的注解参数名。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Loggable {
}@Aspect
@Component
public class AnnotationDrivenLoggingAspect {@Before("@annotation(com.example.aspect.Loggable)")public void logMethodWithAnnotation(JoinPoint joinPoint) {System.out.println("Executing loggable method: " + joinPoint.getSignature().getName());}// 使用参数中的注解@Before("@annotation(loggable)")public void logMethodWithAnnotationArg(JoinPoint joinPoint, Loggable loggable) {System.out.println("Executing loggable method: " + joinPoint.getSignature().getName());}
}// 在需要日志记录的方法上使用@Loggable注解
@Service
public class MyService {@Loggablepublic void myLoggableMethod() {// ...}
}
8. @AfterThrowing 异常示例:特定异常处理
仅当方法抛出特定类型的异常时进行处理。
@Aspect
@Component
public class SpecificExceptionHandlingAspect {@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")public void handleSpecificException(CustomException ex) {System.err.println("A CustomException occurred: " + ex.getMessage());}
}// 自定义异常类
public class CustomException extends RuntimeException {public CustomException(String message) {super(message);}
}
9. @Pointcut 单独定义切点,其他Advice直接引用
@Aspect
@Component
public class LoggingAspect {// 定义切点,表示对哪些类的哪些方法进行拦截// execution表达式指定了方法签名// 第一个"*"表示任意返回类型,第二个"*"表示类中的任意方法// ".."表示任意参数// 这里的例子表示拦截UserService中的所有方法@Pointcut("execution(* com.example.service.UserService.*(..))")private void service() {}@Before("service()")public void logBeforeMethodExecution(JoinPoint joinPoint) {System.out.println("Before executing method: " + joinPoint.getSignature().getName());}}
参考
- https://docs.spring.io/spring-framework/reference/core/aop.html
相关文章:
【SpringBoot3】面向切面 AspectJ AOP 使用详解
文章目录 一、AspectJ介绍二、简单使用步骤1、引入依赖2、定义一个Aspect3、开启AOP支持 三、AOP 核心概念四、切点(Pointcut)1. execution2. within3. this & target4. args & args5. within & target & annotation 五、通知࿰…...

wav怎么转mp3格式?给你推荐几种音频格式转换方法
wav怎么转mp3格式?将wav文件转换为MP3格式是一个常见的操作,尤其适用于需要节省存储空间或确保文件兼容性的场景。wav文件保存了音频的所有原始数据,这使得它们的文件体积往往非常庞大。相比之下,MP3格式通过有损压缩技术显著减小…...

Redis的AOF持久化、重写机制、RDB持久化、混合持久化
1、AOF持久化 1.1.AOF持久化大致过程 概括:命令追加(append)、文件写入、文件同步(sync) Redis 每执行一条写操作命令,就把该命令以追加的方式写入到一个文件里,然后重启 Redis 的时候&#…...
Dom4j使用xpath查询xml文
Dom4j使用xpath查询带有命名空间的xml文件 方式1 忽略命名空间 DocumentFactory factory DocumentFactory.getInstance(); SAXReader reader new SAXReader(factory); Document document reader.read(xmlFilePath); Element rootElement document.getRootElement(); Nod…...
国家专精特新小巨人企业指标解析与扶持领域
一、什么是国家专精特新小巨人 (一)概念与定义 专精特新“小巨人”企业是指那些在细分市场中具有专业化、精细化、特色化和新颖化特征的中小企业中的佼佼者。这些企业在创新能力强、市场占有率高、掌握关键核心技术以及质量效益方面表现突出࿰…...

进程的属性
tips: task_struct就是linux下的PCB 操作系统不相信任何外部用户,而是只提供窗口,不可能直接与用户打交道,而是通过操作系统 tast_struct用来描述所有进程,用来管理 ; 和 && 可以同时跑两个命令 进…...
Git 中的refs
在 Git 中,refs 是用来存储 Git 对象(如提交、树、标签等)的引用。每个 ref 都是一个指针,指向一个特定的 Git 对象。以下是 Git 中几种常见的 refs 及其含义: 1. refs/heads/ 表示:本地分支。 用途&…...
408算法题leetcode--第六天
58. 最后一个单词的长度 58. 最后一个单词的长度思路:反向遍历时间:O(n);空间:O(1) class Solution { public:int lengthOfLastWord(string s) {int id s.size() - 1;while(s[id] ){--id;}int ret 0;while(id > 0 &&…...
ubuntu64位系统无法运行32位程序的解决办法
在 64 位的 Ubuntu 系统上运行 32 位程序时,如果出现问题,可能是由于缺少 32 位库支持。以下步骤可以帮助你解决这一问题: 1. 启用 32 位架构 首先,确保系统支持 32 位架构。你可以通过以下命令添加 32 位架构支持: …...
深入理解Go语言中的并发封闭与for-select循环模式
在现代编程中,并发已经成为提高程序性能和响应能力的关键手段。然而,在并发环境下,如何安全地访问和操作共享数据却是一大挑战。本文将深入探讨Go语言中的**封闭(confinement)**技术,以及常见的for-select循环模式,帮助您编写更高效、更安全的并发代码。 并发编程中的安…...

Java学习Day42:骑龙救!(springMVC)
springMVC与sevlet都是对应表现层web的,但是越复杂的项目使用SpringMVC越方便 基于Java实现MVC模型的轻量级web框架 目标: 小案例: 1.导入依赖 spring-context: 提供 Spring 框架的核心功能,如依赖注入、事件发布和其他应用上…...

原型模式详细介绍和代码实现
🎯 设计模式专栏,持续更新中, 欢迎订阅:JAVA实现设计模式 🛠️ 希望小伙伴们一键三连,有问题私信都会回复,或者在评论区直接发言 Java实现原型模式 介绍: 原型模式(Prototype Patte…...
ArcGIS Pro SDK (十三)地图创作 5 图层样式
ArcGIS Pro SDK (十三)地图创作 5 图层样式 文章目录 ArcGIS Pro SDK (十三)地图创作 5 图层样式1 风格管理1.1 如何按名称获取项目中的样式1.2 如何创建新样式1.3 如何向项目添加样式1.4 如何从项目中删除样式1.5 如何将样式项添加到样式1.6 如何从样式中删除样式项1.7 如…...

【Python报错已解决】 Requests.exceptions.ProxyError: HTTPSConnectionPool
🎬 鸽芷咕:个人主页 🔥 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! 专栏介绍 在软件开发和日常使用中,BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…...

现在量化中普遍使用QMT和PTrade?哪家可以同时提供QMT/PTrade?
QMT的特点 全面的功能集成: QMT集成了行情显示、策略研究、交易执行和风控管理于一体,为投资者提供了一站式的量化交易解决方案。 高效的交易执行能力: 通过全内存交易实现低延迟的交易执行,单笔延时小于1ms,确保了交易…...

【计算机网络】UDP 协议详解及其网络编程应用
文章目录 一、引言二、UDP1、UDP的协议格式2、UDP 报文的解包和分用3、UDP面向数据报的特点 三、UDP输入输出四、UDP网络编程 一、引言 UDP(User Datagram Protocol,用户数据报协议)是一种网络通信协议,它属于传输层的协议。是一…...

基于ESP32S3的链接大语言模型对话模块
本实物模块从实物外观、模块组成、API申请及功能说明四部分来介绍这款基于ESP32S3的大语言模型对话模块。 1、实物外观 2、模块介绍 本硬件平台主要由三个模块组成,包括MAX9814录音模块、MAX98357音频功放模块和ESP32S3模块。如下图所示。 MAX9814录音模块&#…...

Cpp输出多字符常量警告
Cpp输出多字符常量警告 Cpp中用单引号(single quotes)表示单个字符(single character),例如a,$,用双引号(double quotes)表示字符串文本(text),例如"Hello World! " 当在一个单引号里面存在多个字符时,Cpp…...

Maven从入门到精通(二)
一、什么是pom.xml pom.xml是Maven项目的核心配置文件,它是 项目对象模型 - Project Object Model(POM)的缩写。POM定义了项目的所有属性,包括项目的名称、版本、依赖关系、构建配置等。使用pom.xml,我们可以轻松地管…...
【Kubernetes】常见面试题汇总(二十四)
目录 71.假设一家公司想要修改它的部署方法,并希望建立一个更具可扩展性和响应性的平台。您如何看待这家公司能够实现这一目标以满足客户需求? 72.考虑一家拥有非常分散的系统的跨国公司,期待解决整体代码库问题。您认为公司如何解决他们的问…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...

汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...

(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...

基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...
在 Spring Boot 项目里,MYSQL中json类型字段使用
前言: 因为程序特殊需求导致,需要mysql数据库存储json类型数据,因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...
LangFlow技术架构分析
🔧 LangFlow 的可视化技术栈 前端节点编辑器 底层框架:基于 (一个现代化的 React 节点绘图库) 功能: 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...
鸿蒙HarmonyOS 5军旗小游戏实现指南
1. 项目概述 本军旗小游戏基于鸿蒙HarmonyOS 5开发,采用DevEco Studio实现,包含完整的游戏逻辑和UI界面。 2. 项目结构 /src/main/java/com/example/militarychess/├── MainAbilitySlice.java // 主界面├── GameView.java // 游戏核…...