一:SpringBoot
1.1、SpringBoot框架结构
1.1.1、代码文件结构
在 Spring Boot 或微服务架构中,每个服务的文件目录结构通常遵循一定的约定。以下是一个常见的 Spring Boot 服务目录结构示例,以及各个文件和目录的简要说明:
my-service
│
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── myservice
│ │ │ ├── MyServiceApplication.java # 启动类
│ │ │ ├── controller # 控制器包
│ │ │ │ └── MyController.java # 处理请求的控制器
│ │ │ ├── service # 服务包
│ │ │ │ └── MyService.java # 业务逻辑
│ │ │ ├── repository # 数据访问包
│ │ │ │ └── MyRepository.java # 数据访问层
│ │ │ └── model # 模型包
│ │ │ └── MyModel.java # 实体类
│ │ └── resources
│ │ ├── application.properties # 配置文件
│ │ └── static # 静态资源(如 HTML, CSS, JS)
│ │ └── index.html # 主页
│ └── test
│ └── java
│ └── com
│ └── example
│ └── myservice
│ └── MyServiceApplicationTests.java # 测试类
│
├── pom.xml # Maven 项目管理文件
|---target marven 生成的文件
└── README.md # 项目说明文件
主要目录和文件说明:
-
src/main/java
: 存放 Java 代码的主目录。MyServiceApplication.java
: Spring Boot 启动类,包含main
方法。controller
: 处理 HTTP 请求的控制器类。service
: 包含业务逻辑的服务类。repository
: 数据访问层,通常接口定义,与数据库交互。model
: 存放实体类,定义核心数据模型。
-
src/main/resources
: 存放资源文件的目录。application.properties
: 应用配置文件,定义数据库连接、端口等配置。static
: 静态资源目录,存放前端资源。
-
src/test/java
: 存放测试代码的目录。MyServiceApplicationTests.java
: 单元测试类,用于测试应用功能。
-
pom.xml
: Maven 项目的配置和依赖管理文件。 -
README.md
: 项目的说明文档。 -
target:项目构建的输出结果存放位置,构建、测试和其他过程产生的文件都集中在这里。每次执行 Maven 构建命令(如 mvn clean 或 mvn package)时,target 目录会被重新生成。
这个结构可以根据项目的具体需求做相应调整,但以上是一个基本的布局示例。
1.1.2、架构分层
在 Spring Boot 的架构中,"model" 通常指的是与数据库中的表对应的 Java 实体类,它们用于表示数据结构。这个层级也可以称为 "Entity"。具体来说,层级可以描述如下:
- Controller:处理 HTTP 请求,接收参数并调用服务层。
- Service:包含业务逻辑,处理来自控制层的请求,调用持久层进行数据操作。
- Mapper/Repository:与数据库交互,执行 CRUD 操作,可以使用 MyBatis 的 Mapper 或 Spring Data JPA 的 Repository。
- Model/Entity:表示数据库表的结构,定义属性和与之对应的数据库字段。
1、Controller
Controller 层的职责是接收 HTTP 请求、处理请求参数和调用相应的 Service 层方法。
controller只加工数据,业务都在service
2、Service
Service 层负责具体的业务逻辑处理,一个controller类可以写多个接口,项目复杂时可以对应多个Service文件,项目体量简单时也可以对应1个Service文件。
3、Mapper
Mapper用于与数据库交互,执行 CRUD 操作。Mapper中函数的返回类型由xml中sql具体返回类型决定。
// Mapper
public interface LoginMapper {
List<user> getAllUser();
}
// xml
<mapper namespace="com.sunquanBlog.mapper.LoginMapper">
<resultMap id="indexResultMap" type="com.sunquanBlog.model.user">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="password" property="password"/>
<result column="email" property="email"/>
<result column="createdAt" property="createdAt"/>
<result column="role" property="role"/>
</resultMap>
<select id="getAllUser" resultMap="indexResultMap">
SELECT *
FROM user
where
role = #{role}
</select>
</mapper>
1.2、External Libraries
java中external libraries跟前端项目中 node_modules作用与概念一致,都是依赖包或者说外部库
具体使用上有区别,如下
- Java项目:外部库由构建工具Maven等管理,不直接放在项目根目录中;依赖关系通过
pom.xml
或build.gradle
文件定义。 - 前端项目:依赖库直接下载到项目根目录中的
node_modules
文件夹中;依赖关系通过webpack等工具,package.json
文件定义。
1.3、从0构建SpringBoot
1.3.1、 下载框架
打开 Spring Initializr ,选择java、springboot版本,并选择相关依赖即可下载一个基础框架。
- Spring Web 依赖
基础框架中,单纯启动主应用是无法处理http请求及响应的,因为没有安装web服务器。
Spring Web 依赖就解决了以上问题,如果安装并且按照 Spring Boot 的惯例和结构组织代码,Spring Boot 会自动配置和启动一个嵌入式的 Web 服务器(如 Tomcat)来处理 HTTP 请求。只要你创建了一个控制器并定义了路由,Spring Boot 就会自动扫描、发现并注册这些组件。
- MySQL 依赖
提供与MySQL数据库的连接能力,使配置application.properties后,应用能够通过JDBC与数据库进行交互。
- MyBatis
- Spring Boot DevTools:
用于开发过程中自动重启
1.3.2、启动服务
用Idea打开基础框架后,安装依赖,启动主应用,访问路由即可看到你的第一个接口。
contrller->service->mapper->model
二、Maven
Java开发中的Maven就类似前端开发中的webpack
Apache Maven 是一个项目管理和构建工具,它基于项目对象模型(POM)的概念,通过一小段描述信息来管理项目的构建、报告和文档。官网 :http://maven.apache.org
三、MyBatis
3.1.1、什么是MyBatis?
mybatis是一个优秀的基于java的持久层框架,它内部封装了 jdbc,使开发者只需要关注sql语句本身,而不需要花费精力 去处理加载驱动、创建连接、创建statement等繁杂的过程。
它支持定制化 SQL、存储过程以及高级映射。MyBatis 可以简化数据库访问操作,提供了以下主要功能:
-
SQL 映射:用户可以直接在 XML 文件或注解中定义 SQL 语句,从而控制 SQL 的执行。同时,MyBatis 还支持动态 SQL 功能。
-
对象关系映射:MyBatis 能够将数据库结果集自动映射到 Java 对象,支持复杂数据类型的映射。
-
灵活性:MyBatis 则没有强制要求使用特定的编程模型,开发者可以自由选择 SQL 语句的编写方式,更符合项目需求。
-
支持多种数据库:MyBatis 可以与多种关系型数据库搭配使用,适应性强。
-
缓存机制:MyBatis 提供了一级缓存和二级缓存,旨在提高数据访问性能。
MyBatis 适合于需要复杂 SQL 操作的项目,开发者可以对 SQL 语句进行精细控制。与其他 ORM 框架相比,MyBatis 更加轻量级和灵活,但需要开发者自己管理 SQL 逻辑。
3.2.1、如何使用?
3.2.1.1、实体类
在项目下创建一个User.java的实体。
public class User {
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
3.2.1.2、实体类映射文件
<mapper namespace="userMapper">
<select id="findAll" resultType="路径/User">
select * from db1.user
</select>
</mapper>
namespace:命名空间。起名可以随意。例如使用时我们可以用userMapper.findAll来使用对应的select语句。
resultType:结果类型。从数据库查询到的数据封装到哪个位置。例如我们这里的select子句查询到的结果要封装到User实体集中,但是这里用user而不适用domain.User是因为在接下来的核心配置文件SqlMapConfig.xml中我们通过typeAliases标签将domain.User更名为user。
3.2.1.3、三种文件的关系
- Model:定义数据结构(表字段 ↔ 对象属性)
- Mapper 接口:声明数据库操作方法(抽象层)
- Mapper XML:实现具体 SQL 和映射规则(实现层)
三者通过 namespace
+ 方法名 严格绑定,最终由 MyBatis 在运行时动态代理完成调用。
+-------------------+ +-------------------+ +-------------------+
| Model 文件 | | Mapper 接口 | | Mapper XML |
| (FriendUrl.java) | | (FriendUrlMapper) | | (FriendUrlMapper.xml) |
+-------------------+ +-------------------+ +-------------------+
| | |
| 定义数据结构 | 声明操作方法 | 实现SQL映射
| (字段与表对应) | (如 selectById) | (SQL + 结果映射)
| | |
+--------------------------------+------------------------------+
通过 namespace 和 method 名称关联
四、框架专有名词
SpringBoot代码结构中启动类,controller控制器、service服务、repository数据访问、model模型,这些专有名称怎么理解?前端有没有类似的概念可以类比着理解?
以下是每个部分的简要说明,以及与前端的类比:
控制器 (controller
)
- 作用:控制器负责前端HTTP请求处理和路由,并调用相应的服务。
- 类比:可以类比于前端的路由组件(如 React Router 中的路由),负责处理用户的请求和展示对应的视图。
服务 (service
)
- 作用:包含业务逻辑,处理来自控制器的请求,进行数据处理,然后调用数据访问层(Repository)。
- 类比:类似于前端的服务模块(如 API 调用逻辑),负责处理请求的具体实现,比如数据获取和处理。
数据访问 (repository
)
- 作用:与数据库交互,负责执行 CRUD(创建、读取、更新、删除)操作。
- 类比:可以与前端的 API 请求管理工具(如 Axios 或 Fetch API)相对应,这些工具用于与后端接口交互并获取数据。
模型 (model
)
- 作用:定义数据模型,通常对应数据库表的结构,包含数据的属性和验证规则,包装数据的对象。
- 类比:类似于前端的状态管理(如 Redux 的
store
),用来定义和管理应用的数据结构。
启动类 (MyServiceApplication.java
)
- 作用:这是应用的入口,包含
main
方法,负责启动 Spring Boot 应用。 - 类比:类似于前端应用的入口文件(如
index.js
或main.js
),是应用加载的起点。
假设你有一个获取用户信息的请求:
- 前端向
/users/{id}
发送 GET 请求。 - 控制器的
getUserById
方法被调用。 - 控制器调用服务的
findById
方法,传入用户 ID。 - 服务通过调用仓储(Repository)的
findById
方法查询数据库。 - 仓储(Repository)返回用户信息给服务,服务处理后返回给控制器。
- 控制器将用户信息返回给前端。
这种设计遵循了分层架构的原则,各层关注不同的职责,使代码更加模块化和易于维护。
五、微服务
微服务相关知识有优秀系列博文分享给大家,我也看的这些:地址
六、Eureka
负责服务的注册与发现,使得服务能够找到彼此。
Eureka一般跟springBoot、springClud配合使用,一般需要起多个服务。有时springboot报某个服务未启动
Load balancer does not have available server for client: serviceA
但IDEA可能也显示这个服务是启动状态,这时就要去看Eureka的页面了,以Eureka为准。因为该端口可能被占用了
七、Fegin
Fegin提供了一种声明式的 HTTP 客户端调用方式,简化了 RESTful 服务的调用。使用 Feign 可以让你的代码更加简洁易读,并自动处理负载均衡和服务调用的细节。
7.1、有Eureka还需Fegin吗?
在 Spring Boot 中使用 Eureka 进行服务注册和发现,再使用 RestTemplate 或其他手动的 HTTP 客户端进行服务调用,可以解决服务启动和互相调用的问题。但 Feign 代替手动,实现服务的自动调用。
7.1.1、实例Demo
实例地址:微服务组件之OpenFeign服务调用组件的介绍及其使用方法
如上示例虽然写的很清楚,但是我对了七八遍还是没看懂。如下为AI帮我梳理的注解
当你通过浏览器或工具请求 http://localhost:7070/api/service/consumer/getUserInfo?username=root
时,执行过程如下:
1. 接收请求
- 请求首先被发送到
service-consumer
微服务。- 因为请求的 URL 匹配到
DemoController
的@GetMapping
注解,Spring 会调用getUserInfo
方法。2. 调用 Feign 客户端
- 在
DemoController
的getUserInfo
方法中,使用apiProviderFeign.getUserInfo(username)
调用 Feign 客户端。username
被传递为root
。3. 发送 Feign 请求
- Feign 客户端
ApiProviderFeign
收到请求,并将其转换为对service-provider
微服务的 HTTP POST 请求。- 请求的 URL 为
http://localhost:8081/api/service/provider/getUserInfo
(假设service-provider
在 8081 端口)。- 请求 Body 中包含参数
username=root
。4. 处理服务提供者请求
service-provider
微服务接收到请求并匹配到ApiController
的getUserInfo
方法。- 方法内部创建一个
Map
,并将username
和一些静态值放入其中。5. 返回结果
getUserInfo
方法返回一个包含用户信息的Map
(例如:{"id": "1001", "username": "root", "pass": "123456"}
)。- Feign 客户端将接收到的结果转换为一个
Map<String, String>
对象,并返回给DemoController
。6. 返回响应给客户端
DemoController
中的getUserInfo
方法将结果返回给最初发起请求的客户端(浏览器或 HTTP 客户端)。
7.1.2、Demo的接口
理解 ApiProviderFeign
需要明确几个概念:
1. 接口的作用
ApiProviderFeign
是一个声明式的 Feign 客户端接口,主要用于定义如何调用外部服务(即service-provider
)。它并不需要实现具体的方法,因为 Feign 会在运行时为这个接口创建代理对象。2. 实现和动态代理
尽管
ApiProviderFeign
是一个接口,但在 Spring Cloud 的上下文中,Feign 会根据这个接口自动生成一个实现类。所以,你不需要手动实现ApiProviderFeign
,也不需要看到其实现代码。
- 当你在代码中使用
@FeignClient
注解时,Feign 会扫描并创建一个实现了ApiProviderFeign
接口的动态代理对象。3. 如何调用接口
- 在
DemoController
中,apiProviderFeign
是通过 Spring 的依赖注入 (@Autowired
) 获取到的。其实是获取到了 Feign 代理对象,它实现了ApiProviderFeign
接口。- 调用
apiProviderFeign.getUserInfo(username)
实际上是调用这个动态代理对象的方法,而这个方法的逻辑是内部通过 HTTP 调用service-provider
的相应接口。4. 调用方式
接口本身并不能被调用,必须通过实现它的类或代理对象来进行调用。在这段代码中,Feign 创建的代理对象实现了
ApiProviderFeign
,使得调用apiProviderFeign.getUserInfo(username)
成为可能。总结
ApiProviderFeign
作为 Feign 客户端接口定义了如何与service-provider
对接。- Spring Cloud Feign 会在应用启动时为这个接口生成实现,并注入到使用它的类中。
apiProviderFeign
是通过依赖注入获得的代理对象,可以直接调用接口定义的方法。
7.7.3、接口注解理解?
在
service-consumer
子工程中,@PostMapping("/api/service/provider/getUserInfo")
注解的理解如下:1. 请求类型
@PostMapping
是一个组合注解,用于处理 HTTP POST 请求。它指示该方法会处理发往指定 URL 的 POST 请求。2. 路径定义
"/api/service/provider/getUserInfo"
是该方法处理请求的具体路径。这意味着当调用ApiProviderFeign.getUserInfo
方法时,将发起一个 POST 请求,路径为/api/service/provider/getUserInfo
。3. Feign 执行
当你在
DemoController
中通过apiProviderFeign.getUserInfo(username)
调用时,Feign 会:
- 生成一个对应的 HTTP POST 请求。
- 将请求的 URL 设置为
"/api/service/provider/getUserInfo"
。- 传递必要的参数(在这个例子中是
username
)。4. 服务交互
该注解确保了
service-consumer
能够正确地调用service-provider
提供的 GET 用户信息的功能。这个路径与service-provider
中ApiController
的处理路径相对应。总结
@PostMapping
注解表明这个方法处理 POST 请求。- 路径指定了请求的目标。
- 此注解与 Feign 客户端配合使用,使得
service-consumer
可以正确地发起请求并与service-provider
进行交互。
这里接口的@PostMapping注解指示服务提供者具体地址。
因此Demo中service-provider工程并未直接实现service-consumer工程的interface,所以需要service-provider和interface都需要明确写明@PostMapping路径。
但如果service-provider直接实现该interface,则service-provider无需再写@PostMapping,spiringboot可直接找到。此种情况下,service-consumer中@PostMapping中地址无实际意义可以随意填写(不能包含数字),只要不重复即可,但不写不行。
7.2、哪些调用需要Fegin?
包含启动类的文件夹可以视为独立服务,服务之间的调用需要通过 Feign 来实现。
没有启动类的文件夹仅用于存放文件,不需要通过 Feign 进行调用,其他服务可以直接访问这些文件。
八、实体类
实体类(entity)通常是用来表示数据库表对应的对象。
它们通常包含与数据库字段相对应的属性,并提供相应的 getter 和 setter 方法。实体类的主要目的是用于存储和传递数据。
九、Mapper
Mapper是数据访问层(DAO层)的一种表现形式。
Mapper层通常用于定义与数据库交互的接口,这些接口中的方法对应着数据库中的CRUD(创建、读取、更新、删除)操作。在MyBatis等ORM框架中,Mapper接口并没有直接的实现类,取而代之的是一个XML文件,该文件中定义了SQL语句和Java接口的绑定关系。当调用Mapper接口的方法时,实际上是通过动态代理机制,根据接口方法与XML文件中的SQL语句进行匹配,从而执行相应的数据库操作。
Mapper和实体类在Java应用程序中扮演着不同的角色,Mapper负责数据访问和操作,而实体类则负责数据的表示和封装。
9.1、因mapper,不需要实体类了?
不完全正确。尽管 MyBatis 和 Mapper 提供了便捷的数据访问能力,但实体类仍然是必要的。原因如下:
-
对象映射:实体类用来表示数据库表的结构,每个实体类的属性对应数据库表中的字段。MyBatis 通过这些实体类将数据库记录映射为 Java 对象。
-
代码清晰性:实体类增进了代码的可读性和维护性,使得数据结构更清晰。
-
业务逻辑:在某些情况下,实体类不仅仅用于数据映射,它们可能还包含与业务相关的方法和逻辑,这样可以更好地组织代码。
因此,虽然 MyBatis 和 Mapper 简化了数据库操作,但实体类仍然是不可或缺的部分。在使用 MyBatis 时,你仍然需要定义实体类以便进行数据映射和操作。
十、日志管理
日志有一个级别的概念,如下图从上往下越来越少使用。
SpringBoot可以通过配置日志级别来控制日志量,避免大量没必要的日志记录,导致最终查看日志时非常不便。
级别 | 说明 | 适用场景 |
---|---|---|
TRACE | 最详细的日志(如方法入参、内部状态) | 调试复杂问题 |
DEBUG | 调试信息(如 SQL 语句、变量值) | 开发环境调试 |
INFO | 关键业务流程日志(如服务启动、接口调用) | 生产环境常用 |
WARN | 警告(不影响运行,但需关注) | 潜在问题 |
ERROR | 错误(影响功能,需修复) | 异常捕获 |
FATAL | 致命错误(系统崩溃) | 极少使用 |
可以在application.properties中建议设置,比如
# 日志保存路径
logging.file.path=./logs/
# 日志文件名称
logging.file.name=CodingLife-app.log
# 全局默认日志级别 warn是较高的级别,导致日志量很少
logging.level.root=WARN
# 唯独你的项目里调整为较低的info级别,便于重要操作记录
logging.level.com.sunquanBlog=INFO
# 日志面板输入格式
logging.pattern.console=%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
# 日志文件中记录格式
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
使用注解与log方法调用,记录日志。如下
@SpringBootApplication
@Slf4j
public class SunqBlogApplication {
public static void main(String[] args) {
SpringApplication.run(SunqBlogApplication.class, args);
log.info("项目已启动成功");
}
}
调用log方法前,记得先pom引入
<!-- Lombok 注解支持 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version> <!-- 使用最新版本 -->
<scope>provided</scope> <!-- 编译时生效,不打包到运行时 -->
</dependency>
十一、系统性实践
我现在的工作偏产品和项目管理较多,写代码时间越来越少。
为了拓宽开发能力,将学到的知识实际用起来。
业余时间我还开发了个人网站,用SpringBoot写的接口。
网站地址:Coding Life,代码全部开源,欢迎大家指导