AI+JavaWeb(三)Web基础

Web基础

基础概念

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

Spring生态圈

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

SpringBoot入门程序

image

基本流程

  1. 新建模块
    image
    image
  2. 创建处理请求的类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 + "~";
    }
}
  1. 运行启动类,调试程序
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);
    }

}
  1. 浏览器中输入http://localhost:8080/hello?msg=ZJQ进行测试,注意?后的变量名需要和处理请求的方法的形参一致

spring官方脚手架连不上解决方案

image

HTTP协议

hyper Text Transfer protocol,超文本传输协议,规定了浏览器与服务器之间数据传输的规则。

特点

  1. 基于TCP协议:面向连接,可靠
  2. 基于请求-响应模型:一次请求对应一次响应
  3. 无状态的协议:对于事务处理没有记忆功能,每次请求-响应都是独立的,因此速度快但多次请求间不能共享数据,可通过会话技术来解决

请求数据

浏览器发送请求数据

可通过HTTP GET和POST方法发送请求数据,主要区别如下:

  1. GET方法的请求参数在请求行中,没有请求体,如:/person/findAll?name=ZJQ&gender=1,请求大小在浏览器中是有限制的;
  2. POST方法的请求参数在请求体中,且请求大小非常大,可以理解为没有限制;
    image
请求行

请求数据的第一行,包括请求方式、资源路径、协议等
例如:

GET /hello?msg=Spring HTTP/1.1
请求头

第二行开始,格式为key: value,字段解释如下:
image

例如:

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";
    }
}

响应数据

image

响应行

由协议、状态码和描述构成。

状态码
  1. 1xx在websocket长连接中较为常见
  2. 2xx表示请求已接收并处理完成,是程序员的幸运数字
  3. 3xx重定向会对应多次请求,例如使用http访问Baidu时会发生一次重定向至https
    image
  4. 4xx表示客户端错误,责任在客户端,例如404 Not Found表示请求了不存在的资源
  5. 5xx表示服务器错误,责任在服务端,例如程序抛出异常等

其它常见的状态码:
image

响应头

image

响应体

image

服务器返回响应数据

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>");//响应体
    }
}

注意:

  1. rest路径和方法是一一对应的,一个URI只能映射一个方法,一个方法只能被一个URI映射。
  2. 一般没有特殊要求的话,无需手动设置响应状态码和响应头,服务器会根据请求处理的逻辑,自动设置响应状态码和响应头。

案例:开发Web程序,完成用户列表的渲染展示

  1. 新建SpringBoot模块,包含Spring Web和Lombok依赖。
    image
  2. 引入教学资源中的静态资源文件,如HTML、JS页面和user.txt文件
  3. 创建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;
}
  1. 编写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 的优缺点分析

优点
  1. 减少冗余:大幅缩减实体类(POJO/DTO/VO)的代码行数,使业务逻辑更突出。
  2. 易于维护:修改字段名称或类型后,不需要手动更新 Getter/Setter,减少人为错误。
  3. 提高生产力:开发者可以将精力集中在核心算法和业务设计上,而非机械的属性封装。
缺点与争议
  1. IDE 依赖:由于代码是编译时生成的,IDE(如 IntelliJ IDEA 或 VS Code)必须安装专门的 Lombok 插件才能正确识别方法,否则会报红提示找不到方法。
  2. 强行侵入性:团队开发中,如果有人使用了 Lombok,所有成员的开发环境都必须配置相关插件。
  3. 调试困难:由于源码中不存在生成的代码,在断点调试(Debug)进入这些生成的方法时,体验不如原生代码直观。
  4. 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层

三层架构

image
image
Controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据
Service:控制层,业务逻辑层,处理具体的业务逻辑
Dao:数据访问层(Data Access Object)(持久层),负责数据访问操作,包括数据的增删改查

示例:对用户列表改写为三层架构

目录结构

image

调用顺序

image

分层解耦

耦合:衡量软件中各个层/模块的依赖关联程度
内聚:软件中各个功能模块内部的功能联系
软件设计原则:高内聚、低耦合

解耦案例

当UserServiceImpl的类名变化时,UserController的代码也要变化,需要进行解耦。
image

控制反转IOC:将项目中的类交给IOC容器管理

控制反转:Inversion of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。
image
注意:上面声明bean的四大注解,若要生效,还需要被组件扫描注解@ComponentScan扫描,该注解虽然没有显式配置,但是实际上已经包含在启动类声明注解@SpringBootApplication中,默认扫描范围是启动类所在包及其子包

依赖注入DI:程序需要什么对象,直接依赖容器为其提供

依赖注入:Dependency Injection,简称DI。容器为应用程序提供运行时所依赖的资源,称之为依赖注入。
注意:

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

image

注入的三种方式
  1. 属性注入(企业常用)
    优点:代码简洁、方便快速开发。
    缺点:隐藏了类之间的依赖关系、可能会破坏类的封装性。
public class UserController {
    @Autowired
    private UserService service;
	...
}
  1. 构造函数注入(官方推荐)
    优点:能清晰的看到类的依赖关系、提高了代码的安全性
    缺点:代码繁琐,如果构造参数过多,可能会导致构造函数臃肿
@RestController("UserController")
public class UserController {
    private final UserService service;
    
    @Autowired
    UserController(UserService service){
        this.service = service;
    }
	...
}
  1. 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->响应
    }
}
posted @ 2026-03-18 16:21  安河桥北i  阅读(5)  评论(0)    收藏  举报