Web开发-后端基础篇

19040 字
48 分钟

与其花时间编写解释你写出的糟糕的代码的注释,不如花时间清理那堆糟糕的代码。

Maven

概述

Maven 是一个 基于 POM(Project Object Model) 的构建工具,它通过一个 pom.xml 文件来管理项目的配置,包括依赖、插件、构建流程等。

Maven的作用包括以下几点:

  • 依赖管理 :自动下载并管理项目所需的第三方库(如JUnit,Spring,MySQL驱动),避免手动引入jar包。
  • Jar包:用于打包Java类、资源文件和元数据的压缩文件格式。
  • 标准化项目结构 :Maven 提倡统一的项目目录结构,关于具体的结构我们下面再讲。
  • 构建自动化 :一条命令即可完成编译、测试、打包、安装、部署等流程。
  • 插件机制 :支持丰富的插件。

1755832290681

Maven项目结构

your-project/
├── pom.xml                     <-- Maven 项目的核心配置文件
├── src/
│   ├── main/
│   │   ├── java/              <-- 应用/库的源代码(包结构从这里开始)
│   │   ├── resources/         <-- 配置文件、资源文件(会被打包到类路径)
│   │   └── webapp/            <-- (可选)Web 应用的资源,如 HTML、JSP、WEB-INF(用于 Web 项目)
│   └── test/
│       ├── java/              <-- 测试代码(与 main/java 平行的包结构)
│       └── resources/         <-- 测试所需的资源文件
└── target/                     <-- 构建输出目录(编译后的类、打包文件等)

Maven Wrapper

Maven Wrapper 是一种让项目自带Maven的“启动器”,它的关键作用是:

  • 保证团队成员使用相同版本的Maven,避免版本不一致的尴尬问题
  • 不需要预先在系统中全局安装Maven,项目开箱即用
  • 常用于CL/CD环境,如GitHub Action,Jenkins,GitLab CL等。
    • CL(持续集成):频繁地(每天多次)将代码集成到主分支,并自动构建、测试 ,从而快速发现问题。
    • CD(持续交付):CI 之后,自动将构建好的产物发布到“可上线”的环境(如测试、预发布环境)

Maven Wrapper 包含的文件

my-project/
├── mvnw               <-- Unix/Linux/Mac 系统用的 shell 脚本
├── mvnw.cmd           <-- Windows 系统用的批处理脚本
└── .mvn/
    └── wrapper/
        ├── maven-wrapper.jar      <-- 启动器的核心代码
        └── maven-wrapper.properties  <-- 配置使用的 Maven 版本

当你通过mvnw允许命令时:

./mvnw clean install
  1. 检查 .mvn/wrapper/maven-wrapper.properties 中指定的 Maven 版本。
  2. 如果没有这个Maven版本,自动下载。
  3. 使用对应版本的Maven执行 clean install<span> </span>命令

所以在拿到手一个新的Maven项目时,可以优先使用mvnw命令,以确保使用相同的版本减少错误的发生。

Maven配置

Maven官网:Welcome to Apache Maven – Maven

Maven的安装包解压后目录如下: 1755832733852

  • 我们需要将 bin目录添加到环境变量中
  • conf中存储的为Maven的全局配置文件
    • 如果想要用户等级的配置文件,在目录 ${user.home}/.m2/settings.xml

配置阿里云公共镜像

代开maven的配置文件 conf/settings.xml,在 <mirrors></mirrors>标签中添加 mirror 子节点:

<mirror>
  <id>aliyunmaven</id>
  <mirrorOf>*</mirrorOf>
  <name>阿里云公共仓库</name>
  <url>https://maven.aliyun.com/repository/public</url>
</mirror>

配置IDEA

全局Maven配置

全局配置环境需要我们在不打开任何项目时进行配置。

若打开任意项目,进行配置,则为当前项目进行配置,过程一致。

首先点击左下角的齿轮,然后打开设置。 1755833305317

找到Maven设置:

1755833807930

  • Maven主路径:选择Maven文件夹的根目录
  • 用户设置文件:如果添加了私服等,请重重新选择你的配置文件。
  • 或者将你的配置文件放在默认路径下

然后选择运行java程序版本:

1755834008279

最后在Java编译器中也选择相同的Java版本:

1755834099318

创建Maven项目

IDEA中创建Maven项目我们首先新建项目:

1755834231125

填写项目名,以及组织名:

1755834621616

  • GroupidArtifactId 组成了项目的唯一标识符
  • 不同的 Groupid用来区分不同来源的库。
  • 依赖声明:GroupidArtifactId是引入依赖的重要依据。

项目创建完成:

1755835184015

依赖管理

添加依赖

我们打开 pom.xml

  1. 添加标签 <dependencies></dependencies>
<dependencies>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.5.18</version>
        </dependency>
    </dependencies>
  1. <dependencies>中使用 <dependency>引入坐标

  2. 点击刷新按钮,修改生效

    1755835819571

Maven仓库:Maven Repository: Search/Browse/Explore

可以在Maven仓库中搜索想要的插件。

依赖传递

直接依赖:在当前项目中通过依赖配置建立的依赖关系。

间接依赖:被依赖的资源如果依赖其他资源,当前项目间接依赖其他资源。

1755836175761

排除依赖

1755836434136

若项目A 依赖项目B,却并不想要导入项目B中的某个依赖,可以使用依赖排除。

  • 排除依赖:排除依赖指主动断开依赖的资源,被排除的资源无需指定版本。
<dependency>
  <groupld>com.itheima</groupld>
  <artifactld>maven-projectB</artifactld>
  <version>1.0-SNAPSHOT</version>
  <exclusions>
    <exclusion>
      <groupld>junit</groupld>
      <artifactld>junit</artifactld>
    </exclusion>
  </exclusions>
</dependency>

导入B包,却排除B包依赖的junit

依赖范围

Maven 依赖的 作用范围 (scope)决定了依赖在 编译、测试、运行、打包 等阶段的可用性。

默认情况下,依赖是 compile 范围,可在任何地方使用。

通过 <scope>...</scope> 标签设置依赖范围。

scope 值主程序 (main)测试程序 (test)打包/运行 (package)示例依赖典型用途
compile (默认)✅ 可用✅ 可用✅ 参与log4j常规依赖,编译、测试、运行都需要
test❌ 不可用✅ 可用❌ 不参与junit仅测试阶段需要
provided✅ 可用❌ 不可用❌ 不参与servlet-api编译需要,但运行环境会提供
runtime❌ 不可用❌ 不可用✅ 参与JDBC 驱动运行时需要,编译不需要

使用示例:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.10</version>
    <scope>test</scope>
</dependency>

生命周期

Maven 构建生命周期包含三个基本的生命周期:

  • clean:清理项目
  • default(build):核心构建流程
  • site:生成项目文档

1755837064023

在Maven中,当你执行生命周期中的任意一个阶段时,都会顺序执行该阶段前面的所有阶段。

但是三大生命周期之间的关系是独立的,例如执行 site不会执行 buildclean

常用阶段

生命周期阶段作用
clean移除上一次构建生成的文件
compile编译项目源代码。
test运行单元测试。
package将项目打包成可分发的文件,如 .jar.war
install将打包文件安装到本地仓库。

运行生命周期

方式一:在IDEA中直接双击对应生命周期

1755837778756

方式二:终端中直接输入 mvn + clean周期 + build周期 + site周期

1755837870027

生命周期的运行原理

生命周期本都是一个个抽象的概念,本身并不执行任何操作。其所有的操作都是由绑定的插件来完成的。

1755837934369

Web入门

spring 官网:Spring | Home

Spring 是一个 轻量级、开源的 Java 企业级应用开发框架 ,目标是通过 IoC(控制反转)AOP(面向切面编程) 简化企业应用开发,提升代码的可维护性与扩展性。

常见子项目:

子项目作用
Spring Boot快速构建独立可运行应用
Spring Data简化数据库访问
Spring Security安全与权限控制
Spring Cloud微服务架构支持
Spring Batch批处理任务

Spring发展到今天已经形成一种开发生态圈,Spring提供了若干子项目,每个项目用于完成特定的功能。

SpringBoot入门

Spring Boot 是 基于 Spring 框架的快速开发框架 ,旨在 简化 Spring 应用的创建、配置与部署 ,让开发者专注于业务逻辑,而不是繁琐的环境搭建与配置。

创建SpringBoot项目

1755872154802

我们不直接创建Maven项目,而是直接创建SpringBoot项目。然后点击下一步:

1755872265595

这里我们选择一些我们需要安装的包,这里我只选择了Spring Web包。

创建完毕:

1755872346789

简单体验SpringBoot

文件 SpringbootQuickStartApplication.java是SpringBoot的启动类。我们通过该类启动SpringBoot项目。

package top.ulna520.springbootquickstart;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootQuickStartApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootQuickStartApplication.class, args);
    }

}

我们相对于启动类创建java类:controller/HelloSpring.java

package top.ulna520.springbootquickstart.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController	//将返回值直接写入HTTP响应体
public class HelloSpring {
    @RequestMapping("/hello")	//定义请求路径的映射
    public String sayHello() {
        return "Hello Spring Boot!";
    }
}

直接点击上方启动按钮启动服务:

1755872869831

web服务器默认监听端口:8080

1755872851110

Http协议

Http概述

HTTP (Hyper Text Transfer Protocol,超文本传输协议)

  • 定义:规定了 浏览器服务器 之间数据传输的规则
  • 作用:用于传输 HTML、JSON、图片、视频等各种资源

核心特点:

特点说明影响
基于 TCP 协议面向连接,数据传输可靠需要三次握手,安全性高
请求-响应模型一次请求对应一次响应客户端主动发起,服务端被动响应
无状态协议不记录事务处理的上下文- 缺点:多次请求间无法共享数据“- 优点:处理速度快、资源占用少

1755873434278

Http-请求协议

HTTP 请求报文由 请求行 + 请求头 + 请求体 组成。

1755873833309

请求行(Request Line)

  • 格式:请求方法 请求路径 协议版本
  • 示例:
    • GET /brand/findAll?name=OPPO&status=1 HTTP/1.1
    • POST /brand HTTP/1.1
  • 作用:告诉服务器要做什么(方法)、访问哪个资源(路径)、使用哪个协议版本

常见请求方法:

  • GET :获取资源
  • POST :提交数据
  • PUT :更新资源(整体替换)
  • PATCH :部分更新资源
  • DELETE :删除资源

请求头(Request Headers)

  • 从第二行开始,格式为 Key: Value
  • 用于传递客户端环境信息、请求参数、数据格式等

常见请求头:

请求头说明
Host请求的主机名
User-Agent浏览器版本,例如 Chrome 浏览器的标识类似 Mozilla/5.0 ... Chrome/79,IE 浏览器的标识类似 Mozilla/5.0 (Windows NT ...) like Gecko
Accept表示浏览器能接受的资源类型,如 text/*image/**/* 表示所有
Accept-Language表示浏览器偏好的语言,服务器可以据此返回不同语言的网页
Accept-Encoding表示浏览器可以支持的压缩类型,例如 gzipdeflate
Content-Type请求主体的数据类型
Content-Length请求主体的大小(单位:字节)

请求体

  • 仅在部分方法(如 POST、PUT、PATCH)中存在
  • 用于传递数据(表单、JSON、XML 等)

示例:

{
  "status": 1,
  "brandName": "黑马",
  "companyName": "黑马程序员",
  "id": "",
  "description": "黑马程序员"
}

GET与POST对比

对比项GETPOST
数据位置URL 查询参数请求体
数据长度有限制(取决于浏览器/服务器)理论无限制
安全性参数暴露在 URL 中参数在请求体中,相对更隐蔽
常用场景查询数据提交数据、上传文件

HTTP-响应协议

HTTP 响应报文由 响应行 + 响应头 + 响应体 三部分构成。

1755874487939

响应行

  • 位置 :响应数据的第一行
  • 格式协议版本 状态码 状态描述
  • 示例:
  HTTP/1.1 200 OK
  • 作用 :告知客户端响应的协议版本、处理结果状态及简要说明

状态码大全:HTTP 响应状态码

状态码分类:

状态码范围分类说明
1xx临时状态码请求已接收,提示客户端继续请求或忽略
2xx成功请求已成功接收并处理完成
3xx重定向需要客户端发起新的请求以完成处理
4xx客户端错误请求有误,责任在客户端(如资源不存在、未授权、禁止访问等)
5xx服务器错误处理出错,责任在服务端(如程序异常)

响应头

  • 从第二行开始,格式为 Key: Value
  • 用于描述响应的元信息(数据类型、长度、连接方式等)

常见响应头:

响应头说明
Content-Type响应内容的类型,例如 text/htmlapplication/json
Content-Length响应内容的长度(字节数)
Content-Encoding响应内容的压缩算法,例如 gzip
Cache-Control缓存策略,例如 max-age=300 表示最多缓存 300 秒
Set-Cookie设置 Cookie 到浏览器,用于会话或状态管理

响应体

  • 位于响应头之后的空行后
  • 存放实际返回的数据内容(HTML、JSON、图片等)

示例(JSON 数据):

[
  {
    "id": 1,
    "brandName": "阿里巴巴",
    "companyName": "腾讯计算机系统有限公司",
    "description": "玩玩玩"
  }
]

HTTP-协议解析

Http协议解析用于解析客户端发送的HTTP请求,并发送标准的Http响应。通常我们并不自己手动完成这一部分。我们可以直接借助于第三方Web服务器解析http请求,如Tomcat、Jetty、WebLogic、WebSphere等

Web服务器

一种软件程序,对 HTTP 协议 的操作进行封装,使开发者无需直接处理协议细节,从而简化 Web 开发。

核心作用:

  • 协议封装 :屏蔽底层 HTTP 请求/响应的复杂性。
  • 资源提供 :向客户端(浏览器)提供 HTML、CSS、JS、图片等资源。
  • 动态处理 :与后端应用(如 Servlet、PHP、Node.js)协作,生成动态内容。

Tomcat

Tomcat官网:Apache Tomcat® - Welcome!

Tomcat是Apache软件基金会一个核心项目,是一个开源免费的轻量级Web服务器,支持Servlet/JSP少量JavaEE规范。

Tomcat也被称为Web容器、Servlet容器。Servlet程序需要依赖于Tomcat才能运行。

Tomcat安装目录:

1755876082862

启动:执行 bin/start.bat脚本

关闭

  • 直接关闭窗口
  • 执行 bin/shutdown.bat
  • Ctrl + C

端口配置

conf/server:(默认8080)

1755876396327

HTTP协议默认端口号为80,如果将Tomcat端口号改为80,则将来访问Tomcat时,将不用输入端口号。

部署应用程序:将项目放在webapps目录下即可。

SpringBoot中的依赖

起步依赖

Spring Boot 提供了 Starter 机制,利用maven的依赖传递,将常用依赖打包成一个统一入口,简化配置。

依赖名版本作用
spring-boot-starter-web2.7.4Web 应用开发的核心依赖,包含 Spring MVC、JSON 处理、内嵌 Tomcat 等
spring-boot-starter-test2.7.4单元测试依赖,包含 JUnit、Mockito、Spring Test 等

SpringBoot-Web程序的启动依赖于内置Tomcat服务器。

所有的起步依赖:Spring Boot Reference Documentation

在SpringBoot中,起步依赖的的引入无需指定版本version:

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

这是由于所有的SpringBoo项目都继承于一个版本的SpringBoot父工程:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

在父工程中会自动引入与版本号对应的起步依赖。

请求响应

SpringMVC流程

SpringBoot本身只是一个Spring 应用的快速开发脚手架,真正负责处理Web请求的是SpringBoot起步依赖 start-web包中的SpringMVC框架。

1755916210434

1755917254433

流程:

阶段主要参与者关键动作备注
1. 请求进入浏览器 → Tomcat浏览器发起 HTTP 请求URL 可能包含路径参数、查询参数
2. 封装请求Tomcat创建 HttpServletRequest / HttpServletResponse 对象由 Servlet 容器负责
3. 前端控制器DispatcherServlet统一入口,拦截请求在 web.xml 或 Java Config 中注册
4. 查找处理器HandlerMapping根据 URL 找到对应的 Handler(通常是 Controller 方法)支持多种映射策略(注解、XML 等)
5. 调用处理器HandlerAdapter适配并调用目标 Controller 方法负责参数绑定、类型转换
6. 返回结果Controller返回 ModelAndView 或其他类型(如 @ResponseBodyREST 接口可能直接返回 JSON
7. 视图解析ViewResolver将视图名解析为具体视图(JSP、Thymeleaf 等)REST 场景可跳过
8. 渲染视图视图对象将模型数据渲染为 HTML/JSON输出写入 HttpServletResponse
9. 响应返回Tomcat将响应发送给浏览器HTTP 状态码、响应头、响应体

BS架构:浏览器 / 服务器架构

CS架构:客户端 / 服务器架构

Postman

postman官网:Postman

Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件。

作用:常用于进行接口测试。

1755917995877

可以模拟浏览器发送各种请求,用来测试程序API

获取简单参数

原始方式

HttpServletRequest 对象代表了客户端发送给Servlet的HTTP请求。通过这个对象,你可以获取到请求的所有信息。

package top.ulna520.springbootquickstart.controller;

//import...

@RestController
public class UserController {
    @RequestMapping("/user")
    public String getNameAndAge(HttpServletRequest request) {
        String name = request.getParameter("name");
        String age = request.getParameter("age");

        int ageInt = Integer.parseInt(age);
        System.out.println("name:" + name + ",age:" + ageInt);
        return "name:" + name + ",age:" + ageInt;
    }
}

常用方法:

  • getParameter(String name):获取指定名称的请求参数的值(返回 String)。如果参数不存在或有多个同名参数,行为可能不确定。
  • getParameterValues(String name): 获取指定名称的所有请求参数的值(返回 String[])。
  • getParameterMap(): 获取所有请求参数的 Map<String, String[]>

使用GET请求:

1755919377600

SpringMVC方法

参数名与形参变量名相同,定义形参即可接收参数。

package top.ulna520.springbootquickstart.controller;

// import...

@RestController
public class UserController {
    @RequestMapping("/user")
    public String getNameAndAge(String name, Integer age) {
        System.out.println("name:" + name + ",age:" + age);
        return "name:" + name + ",age:" + age;
    }
}

RequestMapping允许所有的请求方式

  • 当没有客户端没有发送形参名一致的参数时,值为 null

使用post请求:

1755919648159

@RequestParam

如果方法形参名与请求参数名称不匹配,或者需要对于参数进行一些额外配置,可以使用RequestParam。

基本用法:

@GetMapping("/hello")
public String hello(@RequestParam("name") String username) {
    return "Hello, " + username;
}

常用属性:

属性名类型说明默认值
value / nameString请求参数名(两者等价,二选一即可)必填(除非参数名与方法形参名一致且开启参数名推断)
requiredboolean是否必须提供该参数true
defaultValueString当参数缺失或为空时使用的默认值,设置后 required 自动失效null

实体参数

创建实体

实体参数就是将Http请求的参数封装到一个实体对象中。

首先我们创建User类:Model/User.java

package top.ulna520.springbootquickstart.Model;

public class User {
    private String name;
    private Integer age;

    public User(Integer age, String name) {
        this.age = age;
        this.name = name;
    }
}

要想能够成功封装,一下两个条件满足一个即可:

  1. 只有一个 public有参构造函数,若有多个构造方法,Spring不知道该用哪个。
  2. 有无参构造方法(优先使用),以及有对饮成员的set方法。

推荐的方法:将 User 定义成标准 JavaBean

  • 提供 public 无参构造器;
  • 提供字段的 getter/setter;
  • 这样 Spring 的 DataBinder 就能通过无参构造创建实例并按名称填充属性
package top.ulna520.springbootquickstart.Model;

public class User {
    private String name;
    private Integer age;

    public void setAge(Integer age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }
}

实体变量

@RestController
public class UserController {
    @RequestMapping("/user")
    public String getNameAndAge(User user) {
        System.out.println(user);
        return user.toString();
    }
}

Spring会自动将参数封装进入变量,没有参数对应的值为null;

1755921743737

实例嵌套

我们再创建一个Address类,并且Address类是User类的一个成员变量:

Model/Address.java

package top.ulna520.springbootquickstart.Model;

public class Address {
    private String province;
    private String city;
    private String street;

    public void setProvince(String province) {
        this.province = province;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public void setStreet(String street) {
        this.street = street;
    }
}

Model/User.java

package top.ulna520.springbootquickstart.Model;

public class User {
    private String name;
    private Integer age;
    private Address address;

    public void setAge(Integer age) {
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public Address getAddress() {	//嵌套类必须要有grtter
        return address;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address=" + address +
                '}';
    }
}

如果没有嵌套类的getter,导致 Spring 无法自动创建并绑定 address,最终为 null。

1755922489489

数组集合参数

数组参数:请求参数名与形参数组名称相同且请求参数为多个,定义数组类型形参即可接收参数

// 3. 数组集合参数
@RequestMapping("/arrayParam")
public String arrayParam(String[] hobby) {
    System.out.println(Arrays.toString(hobby));
    return "OK";
}

集合参数:请求参数名与形参集合名称相同且请求参数为多个,@RequestParam绑定参数关系

@RequestMapping("/listParam")
public String listParam(@RequestParam List<String> hobby) {
    System.out.println(hobby);
    return "OK";
}

这里 @RequestParam用于绑定集合参数。

日期时间类型的参数

请求示例

http://localhost:8080/dateParam?updateTime=2022-12-12 10:05:45

控制器示例

@RequestMapping("/dateParam")
public String dateParam(
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime
){
    System.out.println(updateTime);
    return "OK";
}

@DateTimeFormat 注解

  • 将请求参数的字符串按指定格式解析为 LocalDateTimeLocalDateLocalTime 等日期类型。
  • 常用属性:
    • pattern:日期时间格式化模式(必须与请求参数格式一致)。
    • 常见模式:yyyy-MM-ddyyyy-MM-dd HH:mm:ss

请求参数的日期格式必须与pattern完全一致,否则抛出400错误。

Json参数

postman发送json请求

1755923999960

Sping接收Json数据

首先定义好与Json数据对应数据模型如:User 类,Address类。

@RestController
public class UserController {
    @RequestMapping("/user")
    public String getNameAndAge(@RequestBody User user) {
        System.out.println(user);
        return user.toString();
    }
}
  • @RequestBody注解用于将请求体中的json数据绑定到Java对象。

路径参数

声明动态参数:

@RequestMapping("/path/{id}")	//声明动态参数
public String pathParam(@PathVariable Integer id) {
    System.out.println(id);
    return "OK";
}
  • @PathVariable 注解:用于获取路径参数

示例:

@GetMapping("/{year}/{month}/{day}/{filename}/")
    public Message returnBlogByPath(
        @PathVariable int year,
        @PathVariable int month,
        @PathVariable int day,
        @PathVariable String filename)

响应

@ResponseBody

  • 类型 :方法注解 / 类注解

  • 位置

    • 方法上 :仅作用于该方法
    • 类上 :作用于类中所有方法
  • 功能

    • 将方法返回值直接写入 HTTP 响应体(Response Body)
    • 如果返回值是 实体对象 / 集合 ,会自动序列化为 JSON 格式响应

示例:

@RequestMapping("/hello")
@ResponseBody
public String hello() {
    System.out.println("Hello World~");
    return "Hello World~";
}

如果一整个类的方法都直接返回数据,可以直接使用 @RestController注解。

等价于:

@Controller
@ResponseBody

@Controller

  • 用于标注一个是控制器组件(Controller),由SpringMVC扫描并注册到容器中,用来接收和处理前端请求。

统一响应封装

背景问题:

  • 不同接口返回类型不一致(String、对象、集合),前端解析复杂
  • 缺乏统一的响应格式,不便于状态码、提示信息、数据的统一管理

设定一个返回类,所有的返回信息由Result类进行包装:

public class Result {
    // 响应码:1 成功;0 失败
    private Integer code;
    // 提示信息
    private String msg;
    // 返回数据
    private Object data;
    // getter/setter/构造方法...
}

静态资源

static目录作用:

  • 存放静态资源 (无需 Controller 处理,直接由 Spring MVC 静态资源处理器返回)
  • 常见文件类型:
    • HTML、CSS、JavaScript
    • 图片(JPG、PNG、GIF…)
    • 字体文件(TTF、WOFF…)
    • 其他前端静态资源

默认静态资源目录(优先级)

Spring Boot 内置了静态资源映射,默认会从以下位置查找(优先级从高到低):

  1. classpath:/META-INF/resources/
  2. classpath:/resources/
  3. classpath:/static/最常用
  4. classpath:/public/
  5. ${user.dir}/public/(运行目录下的 public

优先级规则 :同名文件会返回优先级高的目录中的版本。

访问规则

URL访问路径 = 应用根路径 + 文件名

例如:

src/main/resources/static/logo.png

访问:

http://localhost:8080/logo.png

与Controller的优先级

如果URL同时匹配Controller路径和静态资源路径:

  • Controller优先

例如:、

  • /foo 有一个 Controller 方法
  • /static/foo 下也有一个文件
  • 访问 /foo → 返回 Controller 的结果

默认首页

如果静态资源目录下有index.html,访问 /会直接返回它。

获取resources文件夹下的资源

ClassLoader方式(简单)

假设我们要读取 src/main/resources/readme.txt

This is a great way to get

Controller中:

@RestController
public class ContentController {
    @RequestMapping("/content")
    public String getContent(@RequestBody User user) {
        String file = getClass().getClassLoader().getResource("readme.txt").getPath();
        String content = "";
        try {
            InputStreamReader isr = new InputStreamReader(new FileInputStream(file), "UTF-8");
            BufferedReader br = new BufferedReader(isr);
            String line = null;
            while ((line = br.readLine()) != null) {
                content += line;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return  content;
    }
}

成功读取

1755958277427

Spring的 ClassPathResource

@RestController
public class ContentController {
    @RequestMapping("/content")
    public String getContent(@RequestBody User user) {
        ClassPathResource resource = new ClassPathResource("readme.txt");
        String content = "";
        try {
            InputStreamReader isr = new InputStreamReader(resource.getInputStream(), "UTF-8");
            BufferedReader br = new BufferedReader(isr);
            String line = null;
            while ((line = br.readLine()) != null) {
                content += line;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return  content;
    }
}

分层解耦

三层架构

1755958735921

  • controller:控制层,接收前端发送的请求,对请求进行处理,并返回响应数据
  • service:业务逻辑层,处理具体的业务逻辑
  • dao:数据访问层 / 持久层, 负责数据访问操作,包括数据的增、删、改、查(CRUD)

IOC & DI

对象的创建与依赖管理交给外部容器,而不是由代码自己 new 出来。

概念全称定义作用
IOCInversion of Control(控制反转)对象的创建控制权由程序自身转移到外部容器降低代码耦合度,让对象的依赖关系由容器管理
DIDependency Injection(依赖注入)容器在运行时将应用所需的依赖资源注入到对象让对象无需自己创建依赖,专注于业务逻辑
Bean-IOC 容器中创建和管理的对象统一生命周期管理,可被注入到其他对象中

没有IOC/DI时:

  • 每个 Controller 可能会自己 new 一个 Service 实例
    • 结果:
    • 对象创建分散、重复
    • 耦合度高(Controller 依赖具体实现类)
    • Service 的状态和配置无法统一管理

有IOC/DI时

  • Service对象 由 IOC 容器 创建并管理(通常是单例 Bean)。
  • 多个 Controller 只需声明依赖(@Autowired),容器会自动将依赖注入。
  • 容器会在运行时将同一个 Service 实例注入到不同的 Controller 中。

IOC/DI入门

@Component

作用

  • 声明一个类是 Spring 管理的 Bean
  • 相当于告诉 IOC 容器:“这个类需要被扫描、实例化,并纳入容器管理”
  • 默认 Bean 名是类名首字母小写(可通过 @Component("beanName") 自定义)

衍生注解

注解说明位置
@Component声明 bean 的基础注解不属于以下三类时,用此注解
@Controller@Component 的衍生注解标注在控制器类上
@Service@Component 的衍生注解标注在业务类上
@Repository@Component 的衍生注解标注在数据访问类上(由于与 MyBatis 整合,用的少)

针对经典的三层架构,Spring已经预设了经典的类型注解,平时我们优先使用 @Controller@Service@Repository

不属于这几类我们再使用:@Component

@Autowired注解

作用

  • 自动注入依赖对象 (DI 的实现方式之一)
  • 告诉 Spring:在容器中找到匹配的 Bean,并注入到当前字段、构造器或 Setter 方法中

注入规则

  1. 默认按类型(byType)匹配

    • 容器中必须有且只有一个该类型的 Bean
  2. 若有多个同类型 Bean

    • 再按名称(byName)匹配(字段名或参数名)
  3. 可配合 @Qualifier("beanName")

    • 精确指定要注入的 Bean
    • 默认按类型注解,有多个同类型注解 @Qualifier才生效
    @Autowired
    @Qualifier("dogA") // 精确指定注入 dogA

其他相关注解:

  • @Primary:指定优先对象。被 @Primary注解修饰的对象将优先注解。

    @Primary
    @Component
  • @Resource:通过名称Bean进行依赖注入(来源JDK 包)

    @Resource(name = "dogA")
    //无需Autowired注解