AI+JavaWeb(三)Web基础
Web基础
基础概念
- 静态资源:服务器上存储的不会改变的数据,通常不会随着用户的请求而变化,例如用于页面展示的HTML、CSS、JS、图片、视频等文件
- 动态资源:服务器端根据用户请求和其它数据动态生成的,内容可能会随每次请求而变化,例如负责逻辑处理的Servlet、JSP等,已经被淘汰,现在都用Spring框架。
- B/S架构:Browser/Server,浏览器/服务器架构模式。客户端只需浏览器,应用程序的逻辑和数据都存在服务器端。(维护方便,体验一般)

- C/S架构:Client/Server,客户端/服务器架构模式,需要单独开发维护客户端。(体验较好,开发维护麻烦)
Spring生态圈

目前大多项目不会基于Spring Framework进行开发,因为配置繁琐,入门难度大,而使用SpirngBoot进行开发,以简化配置,快速开发。

SpringBoot入门程序

基本流程
- 新建模块


- 创建处理请求的类HelloController,编写处理请求的方法
package org.zjq;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(String msg){
System.out.println("Received message" + msg);
return "Hello," + msg + "~";
}
}
- 运行启动类,调试程序
package org.zjq;
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);
}
}
- 浏览器中输入http://localhost:8080/hello?msg=ZJQ进行测试,注意?后的变量名需要和处理请求的方法的形参一致
spring官方脚手架连不上解决方案

HTTP协议
hyper Text Transfer protocol,超文本传输协议,规定了浏览器与服务器之间数据传输的规则。
特点
- 基于TCP协议:面向连接,可靠
- 基于请求-响应模型:一次请求对应一次响应
- 无状态的协议:对于事务处理没有记忆功能,每次请求-响应都是独立的,因此速度快但多次请求间不能共享数据,可通过会话技术来解决。
请求数据
浏览器发送请求数据
可通过HTTP GET和POST方法发送请求数据,主要区别如下:
- GET方法的请求参数在请求行中,没有请求体,如:
/person/findAll?name=ZJQ&gender=1,请求大小在浏览器中是有限制的; - POST方法的请求参数在请求体中,且请求大小非常大,可以理解为没有限制;

请求行
请求数据的第一行,包括请求方式、资源路径、协议等
例如:
GET /hello?msg=Spring HTTP/1.1
请求头
第二行开始,格式为key: value,字段解释如下:

例如:
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Connection: keep-alive
DNT: 1
Host: localhost:8080
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 Edg/145.0.0.0
sec-ch-ua: "Not:A-Brand";v="99", "Microsoft Edge";v="145", "Chromium";v="145"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
请求体 POST请求含有
POST请求中,存放请求参数
服务器获取请求数据
Web服务器(Tomcat)对HTTP协议的请求数据进行解析,并进行了封装(HTTPServletRequest),在调用controller方法时传递了该方法。这样,程序员无须直接对协议进行操作,让Web开发更加便捷。
示例代码:
package org.zjq;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Map;
@RestController
public class RequestController {
@RequestMapping("/request")
public String request(HttpServletRequest request){
//1.获取请求方式
String method = request.getMethod();
System.out.println("请求方式:" + method);
//2.获取请求的URL和URI
String url = request.getRequestURL().toString();
String uri = request.getRequestURI().toString();
System.out.println("请求的URL:" + url);//完整的资源路径
System.out.println("请求的URI:" + uri);//不含协议的资源路径
//3.获取请求协议
String protocol = request.getProtocol();
System.out.println("请求所用的协议:" + protocol);//HTTP 1.1
//4.获取请求参数
//获取所有参数
Map<String, String[]> parameterMap = request.getParameterMap();
System.out.println("请求中的所有参数如下:");
for(Map.Entry<String,String[]> entry:parameterMap.entrySet()){
String key = entry.getKey().toString();
String[] value = entry.getValue();
System.out.println("key:" + key + " value:" + Arrays.toString(value));
}
//获取单个参数
String name = request.getParameter("name");//获取name参数
String age = request.getParameter("age");//获取name参数
//5.获取请求头
//获取所有请求头信息
Enumeration<String> headersNames = request.getHeaderNames();
System.out.println("所有请求头信息如下:");
while(headersNames.hasMoreElements()){
String headerName = headersNames.nextElement();
String headerValue = request.getHeader(headerName);
System.out.println(headerName + ": " + headerValue);
}
//获取单个请求头信息
String Accept = request.getHeader("Accept");
String Host = request.getHeader("Host");
System.out.println("Accept:" + Accept);
System.out.println("Host:" + Host);
return "OK";
}
}
响应数据

响应行
由协议、状态码和描述构成。
状态码
- 1xx在websocket长连接中较为常见
- 2xx表示请求已接收并处理完成,是程序员的幸运数字
- 3xx重定向会对应多次请求,例如使用http访问Baidu时会发生一次重定向至https

- 4xx表示客户端错误,责任在客户端,例如404 Not Found表示请求了不存在的资源
- 5xx表示服务器错误,责任在服务端,例如程序抛出异常等
其它常见的状态码:

响应头

响应体

服务器返回响应数据
package org.zjq;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@RestController
public class ResponseController {
/*
设置响应数据 方式一
*/
@RequestMapping("/response1")
public void response1(HttpServletResponse response) throws IOException {
// 1.设置响应状态码
response.setStatus(201);
// 2.设置响应头,支持自定义响应头中的Key和value
response.setHeader("name","zjq");
// 3.设置响应体,响应体的内容会直接作为HTML显示在网页上
response.getWriter().write("<button>Hello Response</button>");
}
/*
设置响应数据 方式二
*/
@RequestMapping("/response2")
public ResponseEntity<String> response2(){
return ResponseEntity
.status(401) //状态码
.header("name","zjq") //响应头
.body("<text>hello response</text>");//响应体
}
}
注意:
- rest路径和方法是一一对应的,一个URI只能映射一个方法,一个方法只能被一个URI映射。
- 一般没有特殊要求的话,无需手动设置响应状态码和响应头,服务器会根据请求处理的逻辑,自动设置响应状态码和响应头。
案例:开发Web程序,完成用户列表的渲染展示
- 新建SpringBoot模块,包含Spring Web和Lombok依赖。

- 引入教学资源中的静态资源文件,如HTML、JS页面和user.txt文件
- 创建pojo.User类(使用Lombok注解)
package org.zjq.springboot_demo01.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data //该注解使得类中所有属性都具有了get和set方法
@NoArgsConstructor //无参构造
@AllArgsConstructor //全参构造
public class User {
private Integer id;
private String username;
private String password;
private String name;
private Integer age;
private LocalDateTime updateTime;
}
- 编写Controller.UserController类(SpringBoot框架)
package org.zjq.springboot_demo01.controller;
import cn.hutool.core.io.IoUtil;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.zjq.springboot_demo01.pojo.User;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
/*
用户信息Controller
*/
@RestController
public class UserController {
@RequestMapping("/list")
public List<User> list() throws IOException {
// 1.加载并读取user.txt文件,来读取用户数据
//项目开发中推荐方式:自动从 src/main/resources 目录加载资源,避免部署到其它环境导致路径问题
InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
//使用Hutool工具读取文本文件中所有行
ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8,new ArrayList<>());
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 2.解析用户信息,创建用户对象 -> list集合
List<User> list = lines.stream()
.map(line -> {
String[] parts = line.split(",");
Integer id = Integer.parseInt(parts[0]), age = Integer.parseInt(parts[4]);
String username = parts[1], password = parts[2], name = parts[3], date_str = parts[5];
LocalDateTime updateTime = LocalDateTime.parse(date_str, formatter);
return new User(id, username, password, name, age, updateTime);
})
.toList();
// 3.转换为JSON格式并返回给前端
return list; // Spring Boot 会自动将其转换为 JSON 字符串
//原理是@RestController底层封装了@ResponseBody注解,可以将Controller的返回值直接作为响应体的数据直接响应;返回值是对象/集合->json->响应
}
}
Lombok介绍
Project Lombok 是一个 Java 库,它通过集成到编辑器和构建工具中,利用编译时注解处理器(Annotation Processor)自动生成冗长的 Java 代码。其核心目的是通过消除模板代码(Boilerplate Code)来提高开发效率和代码的可读性。
核心原理
Lombok 并不在运行时起作用,而是在编译阶段介入。当 Java 编译器(javac)处理源代码时,Lombok 会根据代码中的注解,动态地修改抽象语法树(AST),插入对应的方法字节码。
因此,编译后的
.class文件中会包含完整的 Getter、Setter 等方法,而源码中保持简洁。
常用注解及其功能
以下是开发中最频繁使用的 Lombok 注解:
| 注解 | 生成内容 |
|---|---|
**@Getter / @Setter** |
为字段生成标准的 get 和 set 方法。 |
@ToString |
生成包含所有非静态字段的 toString() 方法。 |
@EqualsAndHashCode |
生成 equals(Object other) 和 hashCode() 方法。 |
@NoArgsConstructor |
生成一个无参构造函数。 |
@AllArgsConstructor |
生成一个包含所有字段的构造函数。 |
@Data |
最常用组合拳。等同于同时使用 @Getter, @Setter, @ToString, @EqualsAndHashCode 和 @RequiredArgsConstructor。 |
@Builder |
引入 建造者模式(Builder Pattern),允许通过链式调用创建对象。 |
@Slf4j |
自动在类中创建名为 log 的 SLF4J 日志对象。 |
Lombok 的优缺点分析
优点
- 减少冗余:大幅缩减实体类(POJO/DTO/VO)的代码行数,使业务逻辑更突出。
- 易于维护:修改字段名称或类型后,不需要手动更新 Getter/Setter,减少人为错误。
- 提高生产力:开发者可以将精力集中在核心算法和业务设计上,而非机械的属性封装。
缺点与争议
- IDE 依赖:由于代码是编译时生成的,IDE(如 IntelliJ IDEA 或 VS Code)必须安装专门的 Lombok 插件才能正确识别方法,否则会报红提示找不到方法。
- 强行侵入性:团队开发中,如果有人使用了 Lombok,所有成员的开发环境都必须配置相关插件。
- 调试困难:由于源码中不存在生成的代码,在断点调试(Debug)进入这些生成的方法时,体验不如原生代码直观。
- Java 版本兼容性:在 Java 进行大版本更新(如从 Java 8 迁移到 Java 17+)时,Lombok 有时需要等待更新才能兼容新的编译器特性。
使用示例
原生 Java 代码:
public class User {
private Long id;
private String name;
public User() {}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
// 还需要写很多 Getter/Setter/ToString...
}
使用 Lombok 后:
import lombok.Data;
@Data
public class User {
private Long id;
private String name;
}
总结
对于计算机专业的开发者而言,Lombok 是一把双刃剑。它在现代 Java 后端开发(尤其是 Spring Boot 项目)中几乎是标配,因为它极大地优化了数据模型的定义。但在使用时,应确保团队环境统一,并理解其背后修改 AST 的工作机制。
@RestController为什么支持将方法的返回值转换为JSON字符串
@RestController = @Controller + @ResponseBody
原理是@RestController底层封装了@ResponseBody注解,可以将Controller的返回值直接作为响应体的数据直接响应;返回值是对象/集合->json->响应
分层解耦
上述案例代码扩展性、复用性和可维护性较差,为改善并实现单一职责原则,设计了三层架构:Controller、service和dao层
三层架构


Controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据
Service:控制层,业务逻辑层,处理具体的业务逻辑
Dao:数据访问层(Data Access Object)(持久层),负责数据访问操作,包括数据的增删改查
示例:对用户列表改写为三层架构
目录结构

调用顺序

分层解耦
耦合:衡量软件中各个层/模块的依赖关联程度。
内聚:软件中各个功能模块内部的功能联系。
软件设计原则:高内聚、低耦合
解耦案例
当UserServiceImpl的类名变化时,UserController的代码也要变化,需要进行解耦。

控制反转IOC:将项目中的类交给IOC容器管理
控制反转:Inversion of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。

注意:上面声明bean的四大注解,若要生效,还需要被组件扫描注解@ComponentScan扫描,该注解虽然没有显式配置,但是实际上已经包含在启动类声明注解@SpringBootApplication中,默认扫描范围是启动类所在包及其子包。
依赖注入DI:程序需要什么对象,直接依赖容器为其提供
依赖注入:Dependency Injection,简称DI。容器为应用程序提供运行时所依赖的资源,称之为依赖注入。
注意:
- @Autowired注解,默认是按照类型进行注入的,如果存在多个类型相同的bean,将会报错:

多个类型相同的bean场景下指定注入
- 为指定的bean加入@Primary注解,若有多个Primary注解的bean,仍会导致冲突
@Primary
@Service("UserService2") //将当前类交给IOC容器管理
public class UserServiceImpl2 implements UserService {
...
}
- 注入时加入@Qualifier + @Autowired注解来指定,优先级最高
@RestController("UserController")
public class UserController {
@Autowired
@Qualifier("UserService2")
private UserService service;
...
}
- 注入时加入@Resource注解来指定
@RestController("UserController")
public class UserController {
@Resource(name = "UserService2")
private UserService service;
...
}
@Resource和@Autowired的区别

注入的三种方式
- 属性注入(企业常用)
优点:代码简洁、方便快速开发。
缺点:隐藏了类之间的依赖关系、可能会破坏类的封装性。
public class UserController {
@Autowired
private UserService service;
...
}
- 构造函数注入(官方推荐)
优点:能清晰的看到类的依赖关系、提高了代码的安全性
缺点:代码繁琐,如果构造参数过多,可能会导致构造函数臃肿
@RestController("UserController")
public class UserController {
private final UserService service;
@Autowired
UserController(UserService service){
this.service = service;
}
...
}
- setter函数注入
优点:保持了类的封装性,依赖关系更清晰
缺点:需要额外编写setter方法,增加代码量
@RestController("UserController")
public class UserController {
private UserService service;
@Autowired
public void setService(UserService service){
this.service = service;
}
...
}
Bean对象
IOC容器中创建、管理的对象,称之为Bean。
示例代码
UserDaoImpl.java
package org.zjq.springboot_demo01.dao.impl;
import cn.hutool.core.io.IoUtil;
import org.springframework.stereotype.Component;
import org.zjq.springboot_demo01.dao.UserDao;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@Component or @Resposity//将当前类交给IOC容器管理
public class UserDaoImpl implements UserDao {
public List<String> findAll(){
//项目开发中推荐方式:自动从 src/main/resources 目录加载资源,避免部署到其它环境导致路径问题
InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
//使用Hutool工具读取文本文件中所有行
ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8,new ArrayList<>());
return lines;
}
}
UserServiceImpl.java
package org.zjq.springboot_demo01.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.zjq.springboot_demo01.dao.UserDao;
import org.zjq.springboot_demo01.dao.impl.UserDaoImpl;
import org.zjq.springboot_demo01.pojo.User;
import org.zjq.springboot_demo01.service.UserService;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
@Component or @Service//将当前类交给IOC容器管理
public class UserServiceImpl implements UserService {
// private UserDao userDao = new UserDaoImpl();
@Autowired //程序运行时,会自动查询该类型的bean对象,并赋值给该成员变量
private UserDao userdao;
public List<User>findAll(){
List<String> lines = userdao.findAll();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 2.解析用户信息,创建用户对象 -> list集合
List<User> list = lines.stream()
.map(line -> {
String[] parts = line.split(",");
Integer id = Integer.parseInt(parts[0]), age = Integer.parseInt(parts[4]);
String username = parts[1], password = parts[2], name = parts[3], date_str = parts[5];
LocalDateTime updateTime = LocalDateTime.parse(date_str, formatter);
return new User(id, username, password, name, age, updateTime);
})
.toList();
return list;
}
}
UserController.java
package org.zjq.springboot_demo01.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.zjq.springboot_demo01.pojo.User;
import org.zjq.springboot_demo01.service.UserService;
import java.util.List;
/*
用户信息Controller
*/
@RestController
public class UserController {
// UserService service = new UserServiceImpl();
@Autowired //程序运行时,会自动查询该类型的bean对象,并赋值给该成员变量
private UserService service;
@RequestMapping("/list")
public List<User> list(){
List<User> list = service.findAll();
// 3.转换为JSON格式并返回给前端
return list; // Spring Boot 会自动将其转换为 JSON 字符串
//原理是@RestController底层封装了@ResponseBody注解,可以将Controller的返回值直接作为响应体的数据直接响应;返回值是对象/集合->json->响应
}
}

浙公网安备 33010602011771号