当前位置: 首页 > news >正文

【Javaweb学习|黑马笔记|Day4】Web后端基础

【DAY4】Web后端基础

在这里插入图片描述

开学第十周把jw之前学的有点糊涂的内容复习了一遍,把Web后端基础过完了

文章目录

    • 【DAY4】Web后端基础
      • 1)SpringBootWeb入门
        • 1.1入门程序
          • 步骤
          • 解析
      • 2)HTTP协议
        • 2.1概述
        • 2.2HTTP请求协议
          • 2.2.1获取请求数据
        • 2.3响应协议
          • 2.3.1设置响应数据
      • 3)SpringBootWeb案例
        • 3.1代码实现
        • 3.2@ResponseBody
      • 4)分层解耦
        • 4.1三层架构
          • 4.1.1介绍
          • 4.1.2代码拆分
        • 4.2分层解耦
        • 4.3IOC&DI入门
        • 4.4IOC详解
          • 4.4.1bean的声明
          • 4.4.2 组件扫描
        • 4.5DI详解
          • 4.5.1@Autowired用法
          • 4.5.2注意事项

  • 静态资源:HTML、CSS、JS、图片等,内容固定,直接返回文件
  • 动态资源:根据用户请求、数据库数据、业务逻辑实时生成的内容,比如:
    • 用户登录后的个人主页
    • 商品列表(从数据库查)
    • 搜索结果页
    • RESTful API 返回的 JSON 数据

对于我们java程序开发的动态资源来说,我们通常会将这些动态资源部署在Tomcat这样的Web服务器中运行。 而浏览器与服务器在通信的时候,基本都是基于HTTP协议的。

  • BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。
    • 优点:维护方便
    • 缺点:体验一般
  • CS架构:Client/Server,客户端/服务器架构模式。需要单独开发维护客户端。
    • 优点:体验不错
    • 缺点:开发维护麻烦
对比项B/S 架构C/S 架构
客户端浏览器(Chrome/Firefox)独立应用程序(需安装)
动态资源部署全部在服务器(如 Tomcat)分散:部分在客户端,部分在服务器
更新方式服务器更新,用户无感需用户手动/自动更新客户端
跨平台性极强(只要有浏览器)弱(Windows/macOS/iOS/Android 各需一套)
网络依赖强(没网基本不能用)可部分离线(如 Word、单机游戏)
典型应用淘宝、Gmail、企业 OA 系统微信、QQ、Steam、AutoCAD、银行 U 盾客户端

静态资料开发技术:

  • Vue:一个用于构建用户界面的渐进式 JavaScript 框架,通过响应式数据绑定实现高效、组件化的前端开发。
  • Axios:一个基于 Promise 的HTTP 客户端库,用于浏览器和 Node.js 中发起 AJAX 请求,常与 Vue/React 配合使用。
  • Maven:一个 Java 项目的构建与依赖管理工具,通过 pom.xml 自动下载 jar 包、编译、测试和打包项目。
  • JUnit:Java 语言最主流的单元测试框架,用于编写和运行可重复的自动化测试代码,保障程序逻辑正确性。

下面开始学习动态资料开发技术

  • SpringBootWeb入门
  • HTTP协议
  • SpringBootWeb案例
  • 分层解耦

1)SpringBootWeb入门

Spring Boot 就是一个“帮你自动搭好舞台、配好灯光、调好音响”的工具,让你一上来就能直接演戏(写业务代码),不用自己搬砖搭台

具体来说,Spring Boot 帮你做了什么?

以前要手动做的事Spring Boot 自动帮你做
安装和配置 Tomcat✅ 内嵌 Tomcat,启动即运行
写几百行 XML 配置✅ “约定大于配置”,基本不用写配置
手动引入一堆 jar 包(如 JSON、数据库驱动)✅ 只需声明 starter(如 spring-boot-starter-web
手动管理对象创建和依赖✅ 自动依赖注入(@Autowired)
写 main 方法启动 Web 应用✅ 一个 main 方法 + @SpringBootApplication 就搞定
1.1入门程序

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

在这里插入图片描述

步骤

1). 创建SpringBoot工程(需要联网)

基于Spring官方骨架,创建SpringBoot工程。

在这里插入图片描述

基本信息描述完毕之后,勾选web开发相关依赖。

在这里插入图片描述

2). 定义HelloController类,添加方法hello,并添加注解

com.itheima这个包下新建一个类:HelloController

package org.example;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("name:" + name);return "hello" + name;}
}

3). 运行测试

用Debug的方式运行SpringBoot自动生成的引导类 (标识有@SpringBootApplication注解的类)

遇到网络问题时可以使用阿里云提供的脚手架,网址为:https://start.aliyun.com

解析

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

而我们运行引导类中的main方法,其实启动的就是springboot中内嵌的Tomcat服务器。 而我们所开发的项目,也会自动的部署在该tomcat服务器中,并占用8080端口号 。

2)HTTP协议

2.1概述

在这里插入图片描述

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

  • http是互联网上应用最为广泛的一种网络协议
  • http协议要求:浏览器在向服务器发送请求数据时,或是服务器在向浏览器发送响应数据时,都必须按照固定的格式进行数据传输

特点

  • 基于TCP协议: 面向连接,安全

TCP是一种面向连接的(建立连接之前是需要经过三次握手)、可靠的、基于字节流的传输层通信协议,在数据传输方面更安全

  • 基于请求-响应模型: 一次请求对应一次响应(先请求后响应)

请求和响应是一一对应关系,没有请求,就没有响应

  • HTTP协议是无状态协议: 对于数据没有记忆能力。每次请求-响应都是独立的

无状态指的是客户端发送HTTP请求给服务端之后,服务端根据请求响应数据,响应完后,不会记录任何信息。

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

  • 优点: 速度快

  • 请求之间无法共享数据会引发的问题:

    • 如:京东购物。加入购物车和去购物车结算是两次请求
    • 由于HTTP协议的无状态特性,加入购物车请求响应结束后,并未记录加入购物车是何商品
    • 发起去购物车结算的请求后,因为无法获取哪些商品加入了购物车,会导致此次请求无法正确展示数据
  • 具体使用的时候,我们发现京东是可以正常展示数据的,原因是Java早已考虑到这个问题,并提出了使用会话技术(Cookie、Session)来解决这个问题。具体如何来做,我们后面课程中会讲到。

2.2HTTP请求协议
  • 请求协议:浏览器将数据以请求格式发送到服务器。包括:请求行、请求头 、请求体

  • GET方式的请求协议:

在这里插入图片描述

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

    • 请求方式:GET
    • 资源路径:/brand/findAll?name=OPPO&status=1
      • 请求路径:/brand/findAll
      • 请求参数:name=OPPO&status=1
        • 请求参数是以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数据主体的大小(单位:字节)

举例说明:服务端可以根据请求头中的内容来获取客户端的相关信息,有了这些信息服务端就可以处理不同的业务需求。

比如:

  • 不同浏览器解析HTML和CSS标签的结果会有不一致,所以就会导致相同的代码在不同的浏览器会出现不同的效果
  • 服务端根据客户端请求头中的数据获取到客户端的浏览器类型,就可以根据不同的浏览器设置不同的代码来达到一致的效果(这就是我们常说的浏览器兼容问题)
  • 请求体 :存储请求参数
    • GET请求的请求参数在请求行中,故不需要设置请求体

POST方式的请求协议:

在这里插入图片描述

  • 请求行(以上图中红色部分):包含请求方式、资源路径、协议/版本
    • 请求方式:POST
    • 资源路径:/brand
    • 协议/版本:HTTP/1.1
  • 请求头(以上图中黄色部分)
  • 请求体(以上图中绿色部分) :存储请求参数
    • 请求体和请求头之间是有一个空行隔开(作用:用于标记请求头结束)

GET请求和POST请求的区别:

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

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

eg:

@RestController
public class RequestController {/*** 请求路径 http://localhost:8080/request?name=Tom&age=18* @param request* @return*/@RequestMapping("/request")public String request(HttpServletRequest request){//1.获取请求参数 name, ageString 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/1.1
    • 响应状态码:200
    • 状态码描述:OK
  • 响应头(以上图中黄色部分):响应数据的第二行开始。格式为key:value形式

    • http是个无状态的协议,所以可以在请求头和响应头中设置一些信息和想要执行的动作,这样,对方在收到信息后,就可以知道你是谁,你想干什么

    • 常见的HTTP响应头有:

    • Content-Type:表示该响应内容的类型,例如text/html,image/jpeg ;Content-Length:表示该响应内容的长度(字节数);Content-Encoding:表示该响应压缩算法,例如gzip ;Cache-Control:指示客户端应如何缓存,例如max-age=300表示可以最多缓存300秒 ;Set-Cookie: 告诉浏览器为当前页面所在的域设置cookie ;
      
  • 响应体(以上图中绿色部分): 响应数据的最后一部分。存储响应的数据

    • 响应体和响应头之间有一个空行隔开(作用:用于标记响应头结束)
    • 响应体是服务器“回答”客户端的内容

响应状态码

状态码分类说明
1xx响应中 — 临时状态码。表示请求已经接受,告诉客户端应该继续请求或者如果已经完成则忽略
2xx成功 — 表示请求已经被成功接收,处理已完成
3xx重定向 — 重定向到其它地方,让客户端再发起一个请求以完成整个处理
4xx客户端错误 — 处理发生错误,责任在客户端,如:客户端的请求一个不存在的资源,客户端未被授权,禁止访问等
5xx服务器端错误 — 处理发生错误,责任在服务端,如:服务端抛出异常,路由出错,HTTP版本不支持等

关于响应状态码,我们先主要认识三个状态码:

  • 200 ok 客户端请求成功
  • 404 Not Found 请求资源不存在
  • 500 Internal Server Error 服务端发生不可预期的错误
状态码英文描述解释
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
2.3.1设置响应数据

开发者通过程序设置响应数据,服务器据此生成 HTTP 响应;用户(浏览器)发送请求后,就能收到这份由程序“定制”的响应数据

服务器本身不会凭空生成响应,它会:

  1. 接收到 HTTP 请求
  2. 调用你写的 @RestController 方法
  3. 根据你在方法中设置的内容(状态码、Header、Body)
  4. 组装成完整的 HTTP 响应,再发回给浏览器

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

eg:

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

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

3)SpringBootWeb案例

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

在这里插入图片描述

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

3.1代码实现

1). 准备工作:再创建一个SpringBoot工程,并勾选web依赖、lombok依赖。

在这里插入图片描述

在这里插入图片描述

Lombok,它是一个 Java 开发中非常流行的注解处理器库,主要作用是:

通过注解自动生成常见的样板代码(boilerplate code),让你少写 getter、setter、构造方法等重复代码。

什么是 Lombok?

  • 它是一个 编译期工具(不是运行时依赖)
  • javac 编译 Java 源码时,Lombok 会自动插入代码
  • 最终生成的 .class 文件里,已经包含了完整的 getter/setter 等方法
  • 你在 IDE 中看不到这些方法,但程序运行时它们真实存在

2). 准备工作:引入资料中准备好的数据文件user.txt,以及static下的前端静态页面

在resources/static包中添加前端页面的代码

把所需要的资源如user.txt放到resources包下

3). 准备工作:定义封装用户信息的实体类。

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. @Data

    这是 最常用的组合注解,相当于一次性加上以下功能:

  • @Getter → 为所有字段生成 getter 方法
  • @Setter → 为所有字段生成 setter 方法
  • @ToString → 生成 toString() 方法(打印对象内容)
  • @EqualsAndHashCode → 生成 equals()hashCode() 方法
  • @RequiredArgsConstructor → 为 final 字段或 @NonNull 字段生成构造方法(但你没用到)
  1. @NoArgsConstructor
  • 自动生成一个 无参构造方法

  • 等价于你手写:

    public User() {}
    
  1. @AllArgsConstructor
  • 自动生成一个 包含所有字段的构造方法
  • 参数顺序和字段声明顺序一致

4). 开发服务端程序,接收请求,读取文本数据并响应

由于在案例中,需要读取文本中的数据,并且还需要将对象转为json格式,所以这里呢,我们在项目中再引入一个非常常用的工具包hutool。 然后调用里面的工具类,就可以非常方便快捷的完成业务操作。

  • pom.xml中引入依赖
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.27</version>
</dependency>
  • com.itheima包下新建一个子包controller,在其中创建一个UserController
import cn.hutool.core.io.IoUtil;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONUtil;
import com.itheima.pojo.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;@RestController
public class UserController {@RequestMapping("/list")public String list(){//1.加载并读取文件InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());//2.解析数据,封装成对象 --> 集合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());//3.响应数据//return JSONUtil.toJsonStr(userList, JSONConfig.create().setDateFormat("yyyy-MM-dd HH:mm:ss"));return userList;}}

当用户在浏览器中访问 http://你的服务器地址/list(比如 http://localhost:8080/list)时,Spring Boot 服务器会执行这个 list() 方法,并把结果返回给浏览器

功能:获取类路径下的资源文件输入流

类路径(Classpath)是 Java 虚拟机(JVM)或 Java 编译器用来查找 .class 文件、资源文件(如 .txt.properties.xml 等)的一组目录和 JAR 文件的集合

  • this.getClass()
    获取当前对象的 Class 对象。
  • .getClassLoader()
    获取加载这个类的 类加载器(ClassLoader)
    在 Spring Boot 或普通 Java 项目中,它通常能访问 src/main/resources 目录下的内容。
  • .getResourceAsStream("user.txt")
    类路径根目录 下查找名为 user.txt 的文件,并返回一个 InputStream(字节输入流)。

4). 启动服务测试,访问:http://localhost:8080/user.html

在访问静态资源时不用先访问static包,直接访问具体的文件就行

在user.html中有访问/list,在浏览器中访问/list后,Spring Boot 服务器会执行这个 list() 方法,并把结果返回给浏览器,再把访问回来的数据渲染到网页上

3.2@ResponseBody

Controller程序呢,除了接收请求外,还可以进行响应。

controller方法中的return的结果,怎么就响应给了浏览器?

答案:使用了@ResponseBody注解

@ResponseBody注解:

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

  • 位置:书写在Controller方法上或类上

  • 作用:将方法返回值直接响应给浏览器,如果返回值类型是实体对象/集合,将会转换为JSON格式后在响应给浏览器

    将controller返回值直接作为响应体的数据直接响应;返回值是对象/集合–>json–>响应体

@RestController中就封装了@ResponseBody,所以如果加了@RestController就不用写@ResponseBody

如案例中:解析文本文件中的数据,处理数据的逻辑代码,给页面响应的代码全部都堆积在一起了,全部都写在controller方法中了。

不管改动哪个部分的代码都需要修改Controller,代码难以维护。

所以如何解决这个问题呢?在现在的开发中,有非常成熟的解决思路,那就是分层开发

4)分层解耦

4.1三层架构
4.1.1介绍

上述案例的处理逻辑,从组成上看可以分为三个部分:

  • 数据访问:负责业务数据的维护操作,包括增、删、改、查等操作。
  • 逻辑处理:负责业务逻辑处理的代码。
  • 请求处理、响应数据:负责,接收页面的请求,给页面响应数据。

按照上述的三个组成部分,在我们项目开发中呢,可以将代码分为三层,如图所示:

在这里插入图片描述

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

基于三层架构的程序执行流程,如图所示:

在这里插入图片描述

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

简单来说就是三层架构中 浏览器先通过Controller接受请求,然后service进行逻辑处理,在逻辑处理过程中如果需要数据就进行Dao数据访问,最后再通过Controller响应数据给浏览器

思考:按照三层架构的思想,如果要对业务逻辑(Service层)进行变更,会影响到Controller层和Dao层吗?

答案:不会影响。 (程序的扩展性、维护性变得更好了)

4.1.2代码拆分

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

  • 控制层包名:com.itheima.controller
  • 业务逻辑层包名:com.itheima.service
  • 数据访问层包名:com.itheima.dao

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

  • 控制层包名:com.itheima.controller
  • 业务逻辑层包名:com.itheima.service
  • 数据访问层包名:com.itheima.dao

1). 控制层:接收前端发送的请求,对请求进行处理,并响应数据

com.itheima.controller 中创建UserController类,代码如下:

package com.itheima.controller;import com.itheima.pojo.User;
import com.itheima.service.UserService;
import com.itheima.service.impl.UserServiceImpl;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;@RestController
public class UserController {private UserService userService = new UserServiceImpl();@RequestMapping("/list")public List<User> list(){//1.调用ServiceList<User> userList = userService.findAll();//2.响应数据return userList;}}

2). 业务逻辑层:处理具体的业务逻辑

com.itheima.service中创建UserSerivce接口,代码如下:

package com.itheima.service;import com.itheima.pojo.User;
import java.util.List;public interface UserService {public List<User> findAll();}

com.itheima.service.impl 中创建UserSerivceImpl接口,代码如下:

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();@Overridepublic 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;}
}

3). 数据访问层:负责数据的访问操作,包含数据的增、删、改、查

com.itheima.dao中创建UserDao接口,代码如下:

package com.itheima.dao;import java.util.List;public interface UserDao {public List<String> findAll();}

com.itheima.dao.impl 中创建UserDaoImpl接口,代码如下:

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 {@Overridepublic 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分层解耦

如果说我们需要更换实现类,比如由于业务的变更,UserServiceImpl 不能满足现有的业务需求,我们需要切换为 UserServiceImpl2 这套实现,就需要修改Contorller的代码,需要创建 UserServiceImpl2 的实现new UserServiceImpl2()

Service中调用Dao,也是类似的问题。这种呢,我们就称之为层与层之间 耦合 了。 那什么是耦合呢 ?

内聚和耦合

  • **内聚:**软件中各个功能模块内部的功能联系。
  • **耦合:**衡量软件中各个层/模块之间的依赖、关联的程度。

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

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

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

目前层与层之间是存在耦合的,Controller耦合了Service、Service耦合了Dao。而 高内聚、低耦合的目的是使程序模块的可重用性、移植性大大增强。

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

在这里插入图片描述

解耦思路

耦合的原因:需要对象时,直接new一个, 这种做法造成了层与层之间代码的耦合

解决思路是:

  • 提供一个容器,容器中存储一些对象(例:UserService对象)
  • Controller程序从容器中获取UserService类型的对象

应用程序中用到这个对象,就直接从容器中获取

在这里插入图片描述

想要实现上述解耦操作,就涉及到Spring中的两个核心概念

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

    • 对象的创建权由程序员主动创建转移到容器(由容器创建、管理对象)。这个容器称为:IOC容器或Spring容器。
  • 依赖注入: Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。

    • 程序运行时需要某个资源,此时容器就为其提供这个资源。
    • 例:EmpController程序运行时需要EmpService对象,Spring容器就为其提供并注入EmpService对象。
  • **bean对象:**IOC容器中创建、管理的对象,称之为:bean对象。

4.3IOC&DI入门

将Service及Dao层的实现类,交给IOC容器管理

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

A. UserDaoImpl

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

B. UserServiceImpl

@Component
public class UserServiceImpl implements UserService {private UserDao userDao;@Overridepublic 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;}
}

2). 为Controller 及 Service注入运行时所依赖的对象

加上@Autowired的变量,应用程序运行时,会自动查询该类型的bean对象,并赋值给该成员变量

A. UserServiceImpl

@Component
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Overridepublic 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;}
}

B. UserController

@RestController
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/list")public List<User> list(){//1.调用ServiceList<User> userList = userService.findAll();//2.响应数据return userList;}}

启动服务,运行测试。

4.4IOC详解
4.4.1bean的声明

在之前的入门案例中,要把某个对象交给IOC容器管理,需要在类上添加一个注解:@Component

而Spring框架为了更好的标识web应用程序开发当中,bean对象到底归属于哪一层,又提供了@Component的衍生注解:

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

那么此时,我们就可以使用 @Service 注解声明Service层的bean。 使用 @Repository 注解声明Dao层的bean。

注:控制层的@RestController也封装的@Controller,所以不用另外加一个@Controller了

注意

  1. 声明bean的时候,可以通过注解的value属性指定bean的名字,eg @Repository("UserDao"),如果没有指定,默认为类名首字母小写。
  2. 使用以上四个注解都可以声明bean,但是在springboot集成web开发中,声明控制器bean只能用@Controller。
4.4.2 组件扫描

问题:使用前面学习的四个注解声明的bean,一定会生效吗?

答案:不一定。(原因:bean想要生效,还需要被组件扫描

  • 前面声明bean的四大注解,要想生效,还需要被组件扫描注解 @ComponentScan 扫描。
  • 该注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解 @SpringBootApplication默认扫描的范围是启动类所在包及其子包

在这里插入图片描述

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

4.5DI详解

在入门程序案例中,我们使用了@Autowired这个注解,完成了依赖注入的操作,而这个Autowired翻译过来叫:自动装配。

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

4.5.1@Autowired用法

@Autowired 进行依赖注入,常见的方式,有如下三种:

1). 属性注入

@RestController
public class UserController {//方式一: 属性注入@Autowiredprivate UserService userService;}
  • 优点:代码简洁、方便快速开发。
  • 缺点:隐藏了类之间的依赖关系、可能会破坏类的封装性。

2). 构造函数注入

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

3). setter注入

/*** 用户信息Controller*/
@RestController
public class UserController {//方式三: setter注入private UserService userService;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}}    
  • 优点:保持了类的封装性,依赖关系更清晰。
  • 缺点:需要额外编写setter方法,增加了代码量。

在项目开发中,基于@Autowired进行依赖注入时,基本都是第一种和第二种方式。(官方推荐第二种方式,因为会更加规范)但是在企业项目开发中,很多的项目中,也会选择第一种方式因为更加简洁、高效(在规范性方面进行了妥协)。

4.5.2注意事项

如果一个接口有多个实现类,那么直接@Autowided private UserDao userDao;会报错,因为不知道具体用的是哪个实现类

解决方法

  • @Primary
  • @Qualifier
  • @Resource

方案一:使用@Primary注解

当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现。

@Primary
@Service
public class UserServiceImpl implements UserService {
}

方案二:使用@Qualifier注解

指定当前要注入的bean对象。 在@Qualifier的value属性中,指定注入的bean的名称。 @Qualifier注解不能单独使用,必须配合@Autowired使用。

@RestController
public class UserController {@Qualifier("userServiceImpl")@Autowiredprivate UserService userService;

方案三:使用@Resource注解

是按照bean的名称进行注入。通过name属性指定要注入的bean的名称。

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

在这里插入图片描述

笔记内容主要基于黑马程序员的课程讲解,旨在加深理解和便于日后复习)
在这里插入图片描述
希望这篇笔记能对大家的学习有所帮助,有啥不对的地方欢迎大佬们在评论区交流

http://www.dtcms.com/a/585810.html

相关文章:

  • 做智能网站系统重庆企业
  • Vue 项目实战《尚医通》,首页静态搭建 banner,笔记07
  • 构建AI智能体:八十八、大模型编辑:从一本百科全书到可修订的活页本
  • 2025.11.07 力扣每日一题
  • 网站建设 技术协议wordpress 文本框
  • pcl 构造线、平面、圆、球、圆柱体、长方体、圆锥体点云数据
  • m 的手机网站怎么做小俊哥网站建设
  • 电科金仓KingbaseES数据库全面语法解析与应用实践
  • 化妆品网站建设经济可行性分析好看的设计网站
  • 工程门户网站建设新桥做网站
  • 【开题答辩过程】以《割草机器人工作管理系统的设计与开发》为例,不会开题答辩的可以进来看看
  • 线束之插头导航器显示连接物功能文本
  • JVM(一)----- 类加载过程
  • 猎聘网网站谁做的东莞网页网站制作
  • Spring 6.x HTTP interface 使用说明
  • 庙行镇seo推广网站江西移动网站
  • C++ 图片加背景音乐的处理
  • 进度条+ 基础开发工具----版本控制器git 调试器gdb/cgdb
  • 我在高职教STM32(新11)——LCD1602显示效果控制
  • 将 AI 注入终端:Gemini CLI 保姆级安装与实战指南
  • 珠海市住房城乡建设局网站衡阳网站建设公司哪家好
  • leetcode 345. 反转字符串中的元音字母 python
  • 创建一个网站 站点根文件夹为wordpress互动
  • 网站建设账务处理属于什么费用瑞安这边有没有做网站的
  • S14排序算法--基数排序
  • 人工智能之数学基础:连续型随机变量的期望
  • 江苏省住房和城乡建设厅网站首页东莞微联建站
  • SVG 简介
  • PostgreSQL 高可用实战:流复制主从集群(0 数据丢失 + 故障无缝切换)
  • antd Form表单实践模板(Form.useForm)