黑马Tlias JavaWeb后台管理系统 04 基础知识

静态资源:HTML、CSS、JS文件以及图片、音频、视频等这些资源。

动态资源:服务器上存储的,会根据用户请求和其他数据动态生成的内容。之前一般通过Servlet、JSP等技术处理,现在都是直接基于Spring框架来构建动态资源。

Tomcat:Web服务器,部署动态资源的位置。浏览器与服务器在通信的时候,基本都是基于HTTP协议的。

这就是BS架构

  • BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。
    • 优点:维护方便
    • 缺点:体验一般
  • CS架构:Client/Server,客户端/服务器架构模式。需要单独开发维护客户端。
    • 优点:体验不错
    • 缺点:开发维护麻烦

SpringBootWeb入门

1.1 概述

Spring官网

https://spring.io

Spring全家桶

SpringFramework是最基础、最核心的技术。

但是使用SpringFramework进行开发,存在两个问题:1. 配置繁琐;2.入门难度大

而通过SpringBoot可以快速构建应用程序:1. 简化配置;2. 快速开发

1.2 入门程序

1.2.1 需求

需求:基于SpringBoot的方式开发一个Web应用,浏览器发起请求/hello后,给浏览器返回字符串"Hello xxx~"。

1.2.2 开发步骤

  1. 创建SpringBoot工程,并勾选Web开发相关依赖(需要联网)

输入名字,选择保存地址、语言、构建工具、打包方式

(这里package name多了一层,后面删掉了)

选择SpringBoot版本,并勾选相关依赖

:::info
SpringBoot官方提供的脚手架,里面只能选择最新的几个版本,如果需要用到旧版本,等创建完毕后可以在pom.xml文件里更改。

:::

点击create创建工程之后,项目结构如下:

把java文件夹设置为根目录,才能被系统识别到

如果没配置好或者下载很慢可以用阿里云的,但是版本低一点

如果xml文件是橙色的,同时无法引入依赖,代码飘红,右击选择Add as Maven Project

此时右侧Maven窗口会出现新的项目,并且开始下载依赖

在同一个项目里创建了多个module导致新建的module未被Maven检测到

也可以在Maven窗口添加对应的xml文件,详见以下地址:

https://blog.youkuaiyun.com/tlk20071/article/details/103629336

  1. 定义HelloController类,添加方法hello,并添加注解
package com.itheima;

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

@RestController //标识当前类是一个请求处理类
public class HelloController {

    @RequestMapping("/hello") //标识请求路径
    public String hello(String name){
        System.out.println("HelloController ... hello: " + name);
        return "Hello " + name;
    }

}

  1. 运行测试

运行SpringBoot自动生成的引导类(@SpringBootApplication的类)

打开浏览器,输入http://localhost:8080/hello?name=itheima

1.3 入门解析

为什么只需要一个main方法就可以将Web应用启动了?

因为我们在创建SpringBoot项目的时候,选择了Web开发的起步依赖spring-boot-starter-web,而它又依赖了spring-boot-starter-tomcat,由于maven的依赖传递特性,那么我们创建的springboot项目中已经有了tomcat的依赖,其实就是springboot中内嵌了tomcat。

main方法启动了内嵌的Tomcat服务器,我们的项目也会自动的部署服务器中,并占用8080端口号。

:::info
起步依赖:

  • 一种为开发者提供简化配置和集成的机制,使得构建Spring应用程序更加轻松。起步依赖本质上是一组预定义的依赖项集合,它们一起提供了在特定场景下开发Spring应用所需的所有库和配置。
    • spring-boot-starter-web:包含了web应用开发所需要的常见依赖。
    • spring-boot-starter-test:包含了单元测试所需要的常见依赖。
  • 官方提供的starter:https://docs.spring.io/spring-boot/docs/3.1.3/reference/htmlsingle/#using.build-systems.starters

:::

HTTP协议

2.1 HTTP概述

2.1.1 介绍

HTTP:Hyper Text Transfer Protocol(超文本传输协议),规定了浏览器与服务器之间的数据传输得到规则

F12,打开开发者工具,选择Network(网络)

Ctrl + R记录网络活动

Response Headers 响应标头

Request Headers 请求标头

内容代表了服务器和浏览器之间传输数据的固定格式。

2.1.2 特点

  • 基于TCP协议:面向连接(三次握手,四次挥手),安全,基于字节流
  • 基于请求-响应模型:一次请求对应一次响应
  • HTTP协议是无状态协议:对于数据没有记忆能力,每次请求-响应都是独立的。

缺点:多次请求间不能共享数据。

有点:速度快

请求间无法共享数据会引发问题,例:

加入购物车和结算时两次请求,加入购物车请求响应结束后,并未记录加入的是什么商品,导致无法正确展示数据。

Java使用会话技术(Cookie、Session)来解决这个问题。

2.2 HTTP请求协议

2.2.1 介绍

  • 请求协议:请求行、请求头、请求体
  • GET方式的请求协议:

请求行:请求数据第一行(请求方式、资源路径、协议)

请求头:第二行开始,格式 key:value

请求体:POST请求,存放请求参数

请求行(以上图中红色部分) :HTTP请求中的第一行数据。由:请求方式、资源路径、协议/版本组成(之间使用空格分隔)

- 请求方式:GET  
- 资源路径:/hello?name=itheima
    * 请求路径:/hello
    * 请求参数:name=itheima
        + 请求参数是以key=value形式出现
        + 多个请求参数之间使用&连接
    * 请求路径和请求参数之间使用?连接
- 协议/版本:HTTP/1.1

请求头(以上图中黄色部分) :第二行开始,上图黄色部分内容就是请求头。格式为key: value形式

- http是个无状态的协议,所以在请求头设置浏览器的一些自身信息和想要响应的形式。这样服务器在收到信息后,就可以知道是谁,想干什么了
- 常见的HTTP请求头有:
请求头含义
Host表示请求的主机名
User-Agent浏览器版本。 例如:Chrome浏览器的标识类似Mozilla/5.0 …Chrome/79 ,IE浏览器的标识类似Mozilla/5.0 (Windows NT …)like Gecko
Accept表示浏览器能接收的资源类型,如text/*,image/或者/*表示所有;
Accept-Language表示浏览器偏好的语言,服务器可以据此返回不同语言的网页;
Accept-Encoding表示浏览器可以支持的压缩类型,例如gzip, deflate等。
Content-Type请求主体的数据类型
Content-Length数据主体的大小(单位:字节)

:::info
服务端可以根据请求头中的内容来获取客户端的相关信息,处理不同的业务需求

比如:

  • 不同浏览器解析HTML和CSS标签的结果会不一致,所以会导致相同的代码在不同的浏览器会出现不同的效果
  • 服务端根据客户端请求头中的数据获取到客户端的浏览器类型就可以根据不同的浏览器设置不同的代码来达到一致的效果

:::

请求体:存储请求的参数

GET请求的参数在请求行中,故不需要设置请求体

  • POST方式的请求协议:

请求行(以上图中红色部分):包含请求方式、资源路径、协议/版本

- 请求方式:POST
- 资源路径:/brand
- 协议/版本:HTTP/1.1

请求头(以上图中黄色部分)

请求体(以上图中绿色部分) :存储请求参数

- 请求体和请求头之间是有一个空行隔开(作用:用于标记请求头结束)

GET请求和POST请求的区别:

区别方式GET请求POST请求
请求参数请求参数在请求行中。
例:/brand/findAll?name=OPPO&status=1
请求参数在请求体中
请求参数长度请求参数长度有限制(浏览器不同限制也不同)请求参数长度没有限制
安全性安全性低。原因:请求参数暴露在浏览器地址栏中。安全性相对高

2.2.2 获取请求数据

Tomcat对HTTP协议的请求数据进行解析,并进行了封装(HttpServletRequest),并在调用Controller方法的时候传递给了该方法。

请求路径 http://localhost:8080/request?name=Tom&age=18

@RestController
public class RequestController {

    /**
     * 请求路径 http://localhost:8080/request?name=Tom&age=18
     * @param request
     * @return
     */
    @RequestMapping("/request")
    public String request(HttpServletRequest request){
        //1.获取请求参数 name, age
        String name = request.getParameter("name");
        String age = request.getParameter("age");
        System.out.println("name = " + name + ", age = " + age);
        
        //2.获取请求路径
        String uri = request.getRequestURI();
        String url = request.getRequestURL().toString();
        System.out.println("uri = " + uri);
        System.out.println("url = " + url);
        
        //3.获取请求方式
        String method = request.getMethod();
        System.out.println("method = " + method);
        
        //4.获取请求头
        String header = request.getHeader("User-Agent");
        System.out.println("header = " + header);
        return "request success";
    }

}

效果如图:

2.3 HTTP响应协议

2.3.1 格式介绍

  • 响应协议:响应行、响应头、响应体

  • 响应行:响应数据的第一行。

协议/版本:HTTP/1.1

响应状态码:200

状态码描述:OK

  • 响应头:第二行开始。key:value

表示你是谁和你想干什么

常见的HTTP响应头:

Content-Type:表示该响应内容的类型,例如text/html,image/jpeg ;

Content-Length:表示该响应内容的长度(字节数);

Content-Encoding:表示该响应压缩算法,例如gzip ;

Cache-Control:指示客户端应如何缓存,例如max-age=300表示可以最多缓存300秒 ;

Set-Cookie: 告诉浏览器为当前页面所在的域设置cookie ;
  • 响应体:响应数据的最后一部分。存储响应的数据
    • 响应体和响应头之间有一个空行隔开(用于标记响应头结束)

2.3.2 响应状态码

状态码分类说明
1xx响应中 — 临时状态码。表示请求已经接受,告诉客户端应该继续请求或者如果已经完成则忽略
2xx成功 — 表示请求已经被成功接收,处理已完成
3xx重定向 — 重定向到其它地方,让客户端再发起一个请求以完成整个处理
4xx客户端错误 — 处理发生错误,责任在客户端,如:客户端的请求一个不存在的资源,客户端未被授权,禁止访问等
5xx服务器端错误 — 处理发生错误,责任在服务端,如:服务端抛出异常,路由出错,HTTP版本不支持等
  • <font style="color:rgb(46,161,33);">200 ok</font> 客户端请求成功
  • <font style="color:rgb(216,57,49);">404 Not Found</font> 请求资源不存在
  • <font style="color:rgb(216,57,49);">500 Internal Server Error</font> 服务端发生不可预期的错误

2.3.3 设置响应数据

Web服务器对HTTP协议的相应数据进行了封装(HttpServletResponse),并在调用Controller方法的时候传递给了该方法。

package com.itheima;

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("/response")
    public void response(HttpServletResponse response) throws IOException {
        //1.设置响应状态码
        response.setStatus(401);
        //2.设置响应头
        response.setHeader("name","itcast");
        //3.设置响应体
        response.setContentType("text/html;charset=utf-8");
        response.setCharacterEncoding("utf-8");
        response.getWriter().write("<h1>hello response</h1>");
    }

    @RequestMapping("/response2")
    public ResponseEntity<String> response2(HttpServletResponse response) throws IOException {
        return ResponseEntity
                .status(401)
                .header("name","itcast")
                .body("<h1>hello response</h1>");
    }

}

http://localhost:8080/response

浏览器访问测试

:::info
响应状态码和响应头如果没有特殊要求的话,通常不手动设定。服务器会根据请求处理的逻辑,自动设置响应状态码和响应头。

:::

SpringBootWeb案例

3.1 需求说明

需求:基于SpringBoot开发Web程序,完成用户列表的渲染展示

当在浏览器地址栏,访问前端静态页面(http://localhost:8080/user.html)后,在前端页面上,会发送ajax请求,请求服务端(http://localhost:8080/list),服务端程序加载user.txt文件中的数据,读取出来后最终给前端页面响应json格式的数据,前端页面再将数据渲染展示在表格中。

3.2 代码实现

  1. 再创建一个SpringBoot工程,并勾选Web依赖、lombok依赖

  1. 引入数据文件user.txt,以及static下的前端静态页面

  1. 定义封装用户信息的实体类

在com.itheima下再定义一个包pojo,专门用来存放实体类。定义一个实体类User:

package com.itheima.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;

/**
 * 封装用户信息
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String username;
    private String password;
    private String name;
    private Integer age;
    private LocalDateTime updateTime;
}
  1. 开发服务端程序,接收请求,读取文本数据并响应

在案例中,需要读取文本中的数据,并且还需要将对象转为json格式。一个常用的工具包hutool可以帮我们快捷地完成操作。

pom.xml中引入hutool依赖

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.27</version>
</dependency>
  1. 启动服务测试,访问http://localhost:8080/user.html

3.3 @ResponseBody

controller方法中的return结果,使用@ResponseBody注解就可以响应给浏览器

@ResponseBody注解

  • 类型:方法注解、类注解
  • 位置:书写在Controller方法上或者类上
  • 作用:将方法返回值直接响应给浏览器,如果返回值类型是实体对象/集合,将会转换为JSON格式后再响应给浏览器。

@RestController注解包含了@Controller和@ResponseBody两个注解,所以一般在类上加上这个注解就无须在方法上加上@ResponseBody注解了。

3.4 问题分析

虽然已经实现了逻辑,但是解析文本文件中的数据的代码、处理数据的逻辑代码、给页面响应的代码全部堆积在Controller方法中了。

如果业务逻辑更加复杂,那么修改每个部分的代码都需要改动Controller,使得整个工程代码的复用性比较差,而且难以维护。解决办法就是分层开发。

分层解耦

4.1 三层架构

4.1.1 介绍

进行程序的设计开发时,尽可能地让每一个接口、类、方法的职责更单一(单一职责原则)。

使类、接口、方法的复杂度更低,可读性更强,扩展性更好,更利于后期的维护。

分成三层:

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

  • 前端发起请求,由Controller层接收(Controller响应数据给前端)
  • Controller层调用Service层来进行逻辑处理(Service层处理完后,把结果返回给Controller层)
  • Service层调用Dao层(逻辑处理过程中需要用到的一些数据要从Dao层获取)
  • Dao层操作文件中的数据(Dao拿到的数据会返回给Service层)

4.1.2 代码拆分

我们使用三层架构思想,来改造下之前的程序:

  • 控制层包名:com.itheima.controller
  • 业务逻辑层包名:com.itheima.service
  • 数据访问层包名:com.itheima.dao
  1. 控制层:接收前端发送的请求,对请求进行处理并响应数据
package com.itheima.service;

import com.itheima.pojo.User;
import java.util.List;

public interface UserService {

    public List<User> findAll();

}
  1. 业务逻辑层:处理具体的业务逻辑

创建接口和实现类

package com.itheima.service;

import com.itheima.pojo.User;
import java.util.List;

public interface UserService {

    public List<User> findAll();

}
package com.itheima.service.impl;

import com.itheima.dao.UserDao;
import com.itheima.dao.impl.UserDaoImpl;
import com.itheima.pojo.User;
import com.itheima.service.UserService;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;

public class UserServiceImpl implements UserService {

    private UserDao userDao = new UserDaoImpl();

    @Override
    public List<User> findAll() {
        List<String> lines = userDao.findAll();
        List<User> userList = lines.stream().map(line -> {
            String[] parts = line.split(",");
            Integer id = Integer.parseInt(parts[0]);
            String username = parts[1];
            String password = parts[2];
            String name = parts[3];
            Integer age = Integer.parseInt(parts[4]);
            LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            return new User(id, username, password, name, age, updateTime);
        }).collect(Collectors.toList());
        return userList;
    }
}
  1. 数据访问层:负责数据的访问操作,包含增删改查

创建接口和实现类

package com.itheima.dao;

import java.util.List;

public interface UserDao {

    public List<String> findAll();

}
package com.itheima.dao.impl;

import cn.hutool.core.io.IoUtil;
import com.itheima.dao.UserDao;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

public class UserDaoImpl implements UserDao {
    @Override
    public List<String> findAll() {
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
        ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
        return lines;
    }
}

4.2 分层解耦

4.2.1 问题分析

现在的程序中,我们需要什么对象,就直接new一个对象

但是如果我们需要更换实现类,UserServiceImpl不能满足现有的业务需求,就需要创建一个新的UserServiceImpl2去实现。

Service中调用Dao也是一样的道理。这种情况我们就称为层与层之间耦合

:::info
软件设计原则:高内聚低耦合。

**高内聚:**指的是一个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高,即 “高内聚”。

**低耦合:**指的是软件中各个层、模块之间的依赖关联程序越低越好。

:::

我们的最终目标就是做到层与层之间尽可能地降低耦合,甚至解除耦合。

4.2.2 解耦思路

  1. 不能在Controller中使用new对象

但是不能new就意味着没有业务层对象,程序运行不了。解决思路是提供一个容器,用来存储我们的对象。

  1. 将要用的对象交给一个容器管理
  2. 应用程序中需要的对象就直接从容器中获取

上述解耦操作涉及到Spring中的两个核心概念:

控制反转:Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。

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

bean对象:IOC容器中创建、管理的对象。

4.3 IOC&DI入门

将Service以及Dao层的视线类交给IOC容器管理

在实现类加上@Component注解,就代表把当前类产生的对象交给IOC容器管理。

UserDaoImpl

UserServiceImpl,注意这里已经可以不用new UserDao的对象了。

为Controller以及Service注入运行时所依赖的对象

@Autowired注解

UserServiceImpl

UserController

启动服务,打开浏览器http://localhost:8080/user.html,能够正常访问。

此时已经完成了层与层之间的解耦。

4.4 IOC详解

4.4.1 Bean声明

由IOC容器创建的对象称为bean对象。

在入门案例中,我们使用@Component在类上添加注解。

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

此时注解更改如下:

:::info

  • 声明bean时,可以通过注解的value属性指定bean的名字,默认为类名首字母小写
  • 在springboot集成web开发中,声明控制器只能用@Controller。

:::

4.4.2 组件扫描

:::info
注解声明的bean不一定会生效,因为在此之前还需要被组件扫描

:::

  • 组件扫描注解@ComponentScan
  • 该注解没有显式配置,但是实际上已经包含在了启动类声明注解@SpringBootApplication中,默认扫描的范围是启动类所在的包及其子包

项目开发中只需要按照如上目录结构将项目中的所有的业务类。都放在启动类所在包的子包中,就无须考虑组件扫描的问题。

4.5 DI详解

@Autowired 自动装配

默认是按照类型进行自动装配的(去IOC容器中找某个类型的对象,然后完成注入操作)

4.5.1 @Autowired用法

  1. 属性注入
@RestController
public class UserController{

    @Autowired
    private UserService userService;
}
  • 优点:代码简洁、方便快速开发
  • 缺点:隐藏了类之间的依赖关系、可能会破坏类的封装性
  1. 构造函数注入
@RestController
public class UserController{

    private final UserService userService;//final修饰的变量不可改变

    @Autowired//如果当前类中只存在一个构造函数,则可以省略
    public UserController(UserSerivice userService) {
        this.userService = userService;
    }
}
  • 优点:能清晰地看到类的依赖关系、提高了代码的安全性
  • 缺点:代码繁琐,如果构造参数过多会导致构造函数臃肿
  • 只有一个构造函数,@Autowired注解可以省略(通常来说也只有一个构造函数)
  1. setter注入
@RestController
public class UserController{

    private UserService userService;

    @Autowired
    public void setUserService(UserSerivice userService) {
        this.userService = userService;
    }
}
  • 优点:保持了类的封装性,依赖关系更清晰
  • 缺点:需要额外编写setter方法,增加了代码量

:::info
官方推荐第二种,因为更加规范。但是很多项目也会选择第一种,因为更加简洁、高效。

:::

4.5.2 注意事项

如果在IOC容器中存在多个相同类型的bean对象

例如准备两个UseerService的实现类并且交给IOC容器管理

此时会报错

解决方案

  1. @Primary

确定默认的实现

@Primary
@Service
public class UserServiceImpl implements UserService {
}
  1. @Qualifier

指定当前要注入的bean对象,与@Autowired一起使用

@RestController
public class UserController {

    @Qualifier("userServiceImpl")
    @Autowired
    private UserService userService;
  1. @Resource

通过name属性指定要注入的bean名称

@RestController
public class UserController {
        
    @Resource(name = "userServiceImpl")
    private UserService userService;

:::info

  • @Autowired是spring框架提供的注解,而@Resource是JDK提供的注解
  • @Autowired默认是按照类型注入,而@Resource是按照名称注入

:::

附录

常见状态码

状态码英文描述解释
200OK客户端请求成功,即处理成功,这是我们最想看到的状态码
302Found指示所请求的资源已移动到由Location响应头给定的 URL,浏览器会自动重新访问到这个页面
304Not Modified告诉客户端,你请求的资源至上次取得后,服务端并未更改,你直接用你本地缓存吧。隐式重定向
400Bad Request客户端请求有语法错误,不能被服务器所理解
403Forbidden服务器收到请求,但是拒绝提供服务,比如:没有权限访问相关资源
404Not Found请求资源不存在,一般是URL输入有误,或者网站资源被删除了
405Method Not Allowed请求方式有误,比如应该用GET请求方式的资源,用了POST
428Precondition Required服务器要求有条件的请求,告诉客户端要想访问该资源,必须携带特定的请求头
429Too Many Requests指示用户在给定时间内发送了太多请求(“限速”),配合 Retry-After(多长时间后可以请求)响应头一起使用
431Request Header Fields Too Large请求头太大,服务器不愿意处理请求,因为它的头部字段太大。请求可以在减少请求头域的大小后重新提交。
500Internal Server Error服务器发生不可预期的错误。服务器出异常了,赶紧看日志去吧
503Service Unavailable服务器尚未准备好处理请求,服务器刚刚启动,还未初始化好

状态码大全:

https://cloud.tencent.com/developer/chapter/13553

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值