JavaWeb-04-Web后端基础(SpringBootWeb、HTTP协议、分层解耦、IOC和DI)
目录
一、SpringBootWeb入门
1.1 概述
1.2 入门程序
1.2.1 需求
1.2.2 开发步骤
1.3 入门解析
二、HTTP协议
2.1 HTTP概述
2.1.1 介绍
2.1.2 特点
2.2 HTTP请求协议
2.2.1 介绍
2.2.2 获取请求数据
2.3 HTTP响应协议
2.3.1 格式介绍
2.3.2 响应状态码
2.3.3 设置响应数据
三、SpringBootWeb案例
3.1 需求说明
3.2 代码实现
3.3 @ResponseBody
四、分层解耦
4.1 三层架构
4.1.1 介绍
4.1.2 代码拆分
4.2 分层解耦
4.2.1 问题分析
4.2.2 解耦思路
4.3 IOC&DI入门
4.4 IOC详解
4.4.1 Bean的声明
4.4.2 组件扫描
4.5 DI详解
4.5.1 @Autowired用法
4.5.2 注意事项
静态资源: 服务器上存储的不会改变的数据,通常不会根据用户的请求而变化。比如: HTML 、 CSS 、 JS 、图片、视频等 ( 负责页面展示 )动态资源:服务器端根据用户请求和其他数据动态生成的,内容可能会在每次请求时都发生变化。比如: Servlet 、 JSP 等 ( 负责逻辑处理)(注:现在基本使用Spring框架 )B/S 架构: Browser/Server ,浏览器 / 服务器架构模式。客户端只需浏览器,应用程序的逻辑和数据都存在服务器端。 ( 维护方便 体验一般 )C/S 架构 : Client/Server ,客户端 / 服务器架构模式。需要单独开发维护客户端。( 体验不错 开发维护麻烦 )
一、SpringBootWeb入门
1.1 概述
Spring家族旗下这么多的技术,最基础、最核心的是 SpringFramework。其他的spring家族的技术,都是基于SpringFramework的,SpringFramework中提供很多实用功能,如:依赖注入、事务管理、web开发支持、数据访问、消息服务等等。
直接基于SpringBoot进行项目构建和开发,不仅是Spring官方推荐的方式,也是现在企业开发的主流。
1.2 入门程序
1.2.1 需求
需求:基于SpringBoot的方式开发一个web应用,浏览器发起请求/hello后,给浏览器返回字符串 "Hello xxx ~"。
1.2.2 开发步骤
第1步:创建SpringBoot工程,并勾选Web开发相关依赖
第2步:定义HelloController类,添加方法hello,并添加注解
1)创建SpringBoot工程(需要联网) (案例是在已经创建的空项目上进行的,空项目创建见上一章3.1)
注意!注意!注意! 如果下载失败可能是网络问题,建议更换成阿里云
https://start.aliyun.com/
下图中该类叫做启动类(可简单理解为主类)
2)定义HelloController类,添加方法hello,并添加注解
在下图路径新建一个类
上图类中的代码如下:
package com.orange.springbootweb_quickstart;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController //表示当前类是一个请求处理类
public class HelloController {@RequestMapping("/hello")public String sayHello(String name) {System.out.println("name: "+ name);return "Hello " + name+ "~~~~" ;}}
接着到启动类中选择debug的方式进行运行(如下图)
在控制台可以看到以下内容(如下图)(可以看到端口是8080)
接下来在浏览器中输入下图内容并按下回车
1.3 入门解析
1). 为什么一个main方法就可以将Web应用启动了?
因为我们在创建springboot项目的时候,选择了web开发的起步依赖 spring-boot-starter-web
。而spring-boot-starter-web
依赖,又依赖了spring-boot-starter-tomcat
,由于maven的依赖传递特性,那么在我们创建的springboot项目中也就已经有了tomcat的依赖,这个其实就是springboot中内嵌的tomcat。(tomcat:web服务器)
而我们运行引导类中的main方法,其实启动的就是springboot中内嵌的Tomcat服务器。 而我们所开发的项目,也会自动的部署在该tomcat服务器中,并占用8080端口号 。(如下图)
起步依赖:
一种为开发者提供简化配置和集成的机制,使得构建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 介绍
概念:Hyper Text Transfer Protocol,超文本传输协议,规定了浏览器和服务器之间数据传输的规则。
http是互联网上应用最为广泛的一种网络协议
http协议要求:浏览器在向服务器发送请求数据时,或是服务器在向浏览器发送响应数据时,都必须按照固定的格式进行数据传输
2.1.2 特点
基于TCP协议: 面向连接,安全
TCP是一种面向连接的(建立连接之前是需要经过三次握手)、可靠的、基于字节流的传输层通信协议,在数据传输方面更安全
基于请求-响应模型: 一次请求对应一次响应(先请求后响应)
请求和响应是一一对应关系,没有请求,就没有响应
HTTP协议是无状态协议: 对于数据没有记忆能力。每次请求-响应都是独立的
无状态指的是客户端发送HTTP请求给服务端之后,服务端根据请求响应数据,响应完后,不会记录任何信息。
缺点: 多次请求间不能共享数据
优点: 速度快
请求之间无法共享数据会引发的问题:
如:京东购物。加入购物车和去购物车结算是两次请求
由于HTTP协议的无状态特性,加入购物车请求响应结束后,并未记录加入购物车是何商品
发起去购物车结算的请求后,因为无法获取哪些商品加入了购物车,会导致此次请求无法正确展示数据
具体使用的时候,我们发现京东是可以正常展示数据的,原因是Java早已考虑到这个问题,并提出了使用会话技术(Cookie、Session)来解决这个问题。具体如何来做,我们后面课程中会讲到。
2.2 HTTP请求协议
2.2.1 介绍
请求协议:浏览器将数据以请求格式发送到服务器。包括:请求行、请求头 、请求体
GET方式的请求协议:
请求行(以上图中红色部分) :HTTP请求中的第一行数据。由:
请求方式
、资源路径
、协议/版本
组成(之间使用空格分隔)
请求方式:GET
资源路径:/brand/findAll?name=OPPO&status=1
请求路径:/brand/findAll
请求参数:name=OPPO&status=1
请求参数是以key=value形式出现
多个请求参数之间使用
&
连接请求路径和请求参数之间使用
?
连接协议/版本:HTTP/1.1
请求头(以上图中黄色部分) :第二行开始,上图黄色部分内容就是请求头。格式为key: value形式
http是个无状态的协议,所以在请求头设置浏览器的一些自身信息和想要响应的形式。这样服务器在收到信息后,就可以知道是谁,想干什么了
-
常见的HTTP请求头有:
举例说明:服务端可以根据请求头中的内容来获取客户端的相关信息,有了这些信息服务端就可以处理不同的业务需求。
比如:
不同浏览器解析HTML和CSS标签的结果会有不一致,所以就会导致相同的代码在不同的浏览器会出现不同的效果
服务端根据客户端请求头中的数据获取到客户端的浏览器类型,就可以根据不同的浏览器设置不同的代码来达到一致的效果(这就是我们常说的浏览器兼容问题)
请求体 :存储请求参数
GET请求的请求参数在请求行中,故不需要设置请求体
POST方式的请求协议:
请求行(以上图中红色部分):包含请求方式、资源路径、协议/版本
请求方式:POST
资源路径:/brand
协议/版本:HTTP/1.1
请求头(以上图中黄色部分)
请求体(以上图中绿色部分) :存储请求参数
请求体和请求头之间是有一个空行隔开(作用:用于标记请求头结束)
GET请求和POST请求的区别:
2.2.2 获取请求数据
Web服务器(Tomcat)对HTTP协议的请求数据进行解析,并进行了封装(HttpServletRequest),并在调用Controller方法的时候传递给了该方法。这样,就使得程序员不必直接对协议进行操作,让Web开发更加便捷。
代码演示如下(项目创建看1.2入门程序):
代码如下:
package com.orange.springbootweb_quickstart;import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class RequestController {/*** 请求路径 http://localhost:8080/request?name=橙序研&age=22* @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响应协议
2.3.1 格式介绍
-
响应行(以上图中红色部分):响应数据的第一行。响应行由
协议及版本
、响应状态码
、状态码描述
组成-
协议/版本: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 ;
-
响应体(以上图中绿色部分): 响应数据的最后一部分。存储响应的数据
-
响应体和响应头之间有一个空行隔开(作用:用于标记响应头结束)
-
2.3.2 响应状态码
重定向解释:先向A服务器发出请求,但是A没有却提供了Location,于是浏览器会再次向B服务器发出请求(但其实用户并不知道)
可以看到设置了状态码302,并且设置了Location,于是便进行重定向到Location(如下图)
下图可以看出,重定向后的状态码为成功了
常见的如下:
200 ok
客户端请求成功
404 Not Found
请求资源不存在
500 Internal Server Error
服务端发生不可预期的错误
状态码大全:
2.3.3 设置响应数据
Web服务器对HTTP协议的响应数据进行了封装(HttpServletResponse),并在调用Controller方法的时候传递给了该方法。这样,就使得程序员不必直接对协议进行操作,让Web开发更加便捷。
方式一(项目文件管理参考2.2.2中的例子):
代码如下:
package com.orange.springbootweb_quickstart;import jakarta.servlet.http.HttpServletResponse;
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","orange");//3. 设置响应体response.getWriter().write("<h1>hello response</h1>");}
}
方式二:
代码如下:
package com.orange.springbootweb_quickstart;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","orange");//3. 设置响应体response.getWriter().write("<h1>hello response</h1>");}// 方式二@RequestMapping("/response2")public ResponseEntity<String> response2(){return ResponseEntity.status(401).header("name","orange2").body("<h1>hello response2</h1>");}}
响应状态码 和 响应头如果没有特殊要求的话,通常不手动设定。服务器会根据请求处理的逻辑,自动设置响应状态码和响应头。
三、SpringBootWeb案例
3.1 需求说明
需求:基于SpringBoot开发web程序,完成用户列表的渲染展示
当在浏览器地址栏,访问前端静态页面(http://localhost:8080/user.html)后,在前端页面上,会发送ajax请求,请求服务端(http://localhost:8080/list),服务端程序加载 user.txt 文件中的数据,读取出来后最终给前端页面响应json格式的数据,前端页面再将数据渲染展示在表格中。
3.2 代码实现
1). 准备工作:再创建一个SpringBoot工程(见1.2.2),并勾选web依赖、lombok依赖。
在pom文件中在引入以下依赖(记得刷新)
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.22</version></dependency>
2)资料准备如下图:
四个准备文件内容如下(链接: https://pan.baidu.com/s/1Q8TpWxIL-Q44Q05fJIOHgA?pwd=0706 提取码: 0706 )
准备一个txt文件,内容如下
1,daqiao,1234567890,大乔,22,2024-07-15 15:05:45
2,xiaoqiao,1234567890,小乔,18,2024-07-15 15:12:09
3,diaochan,1234567890,貂蝉,21,2024-07-15 15:07:16
4,lvbu,1234567890,吕布,28,2024-07-16 10:05:15
5,zhaoyun,1234567890,赵云,27,2024-07-16 11:03:28
6,zhangfei,1234567890,张飞,31,2024-07-16 11:03:28
7,guanyu,1234567890,关羽,34,2024-07-16 12:05:12
8,liubei,1234567890,刘备,37,2024-07-16 15:03:28
3)创建代码如下
创建一个Java文件(如下图):
User代码如下:
package com.orange.springbootweb_001.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;}
如果报以下错误:
java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field 'com.sun.tools.javac.tree.JCTree qualid'
解决方法一:更新lombok 版本
解决方法二:使用以下代码:
package com.orange.springbootweb_001.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.time.LocalDateTime;public class User {private Integer id;private String username;private String password;private String name;private Integer age;private LocalDateTime updateTime;public User(Integer id, String username, String password, String name, Integer age, LocalDateTime updateTime) {this.id = id;this.username = username;this.password = password;this.name = name;this.age = age;this.updateTime = updateTime;}public LocalDateTime getUpdateTime() {return updateTime;}public void setUpdateTime(LocalDateTime updateTime) {this.updateTime = updateTime;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}}
再创建如下包和类(如下图)
代码如下:
package com.orange.springbootweb_001.controller;import cn.hutool.core.io.IoUtil;
import com.orange.springbootweb_001.pojo.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.io.FileInputStream;
import java.io.FileNotFoundException;
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;// 用户信息Controller
@RestController
public class UserController {@RequestMapping("/list")public List<User> list() throws FileNotFoundException {//1. 加载并读取user.txt文件,获取用户数据//InputStream in = new FileInputStream("src/main/resources/user.txt");InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8,new ArrayList<>());//2. 解析用户信息,封装为User对象 ->list集合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);}).toList();//3. 返回数据(json)return userList;}
}
完整项目分享:
通过网盘分享的文件:springbootweb_001
链接: https://pan.baidu.com/s/1Xth0setM-nuRjKjqqw4HEA?pwd=0706 提取码: 0706
--来自百度网盘超级会员v4的分享
3.3 @ResponseBody
前面我们学习过HTTL协议的交互方式:请求响应模式(有请求就有响应)。那么Controller程序呢,除了接收请求外,还可以进行响应。
在我们前面所编写的controller方法中,都已经设置了响应数据。
controller方法中的return的结果,怎么就可以响应给浏览器呢?
答案:使用@ResponseBody注解
@ResponseBody注解:
-
类型:方法注解、类注解
-
位置:书写在Controller方法上或类上
-
作用:将方法返回值直接响应给浏览器,如果返回值类型是实体对象/集合,将会转换为JSON格式后在响应给浏览器
但是在我们所书写的Controller中,只在类上添加了@RestController注解、方法添加了@RequestMapping注解,并没有使用@ResponseBody注解,怎么给浏览器响应呢?
这是因为,我们在类上加了@RestController注解,而这个注解是由两个注解组合起来的,分别是:@Controller 、@ResponseBody。 那也就意味着,我们在类上已经添加了@ResponseBody注解了,而一旦在类上加了@ResponseBody注解,就相当于该类所有的方法中都已经添加了@ResponseBody注解。
提示:前后端分离的项目中,一般直接在请求处理类上加@RestController注解,就无需在方法上加@ResponseBody注解了
四、分层解耦
4.1 三层架构
4.1.1 介绍
在我们进行程序设计以及程序开发时,尽可能让每一个接口、类、方法的职责更单一些(单一职责原则)。
单一职责原则:一个类或一个方法,就只做一件事情,只管一块功能。
这样就可以让类、接口、方法的复杂度更低,可读性更强,扩展性更好,也更利于后期的维护。
我们之前开发的程序呢,并不满足单一职责原则:
那其实我们上述案例的处理逻辑呢,从组成上看可以分为三个部分:
数据访问:负责业务数据的维护操作,包括增、删、改、查等操作。
逻辑处理:负责业务逻辑处理的代码。
请求处理、响应数据:负责,接收页面的请求,给页面响应数据。
按照上述的三个组成部分,在我们项目开发中呢,可以将代码分为三层,如下图所示:
Controller:控制层。接收前端发送的请求,对请求进行处理,并响应数据。
Service:业务逻辑层。处理具体的业务逻辑。
Dao:数据访问层(Data Access Object),也称为持久层。负责数据访问操作,包括数据的增、删、改、查。
4.1.2 代码拆分
完整项目:通过网盘分享的文件:springbootweb_001_拆分项目
链接: https://pan.baidu.com/s/1_ZrT8gsT24huK2aqKRQvrg?pwd=0706 提取码: 0706
--来自百度网盘超级会员v4的分享
我们使用三层架构思想,来改造下之前的程序:
控制层包名:
com.
orange.controller
业务逻辑层包名:
com.
orange.service
数据访问层包名:
com.
orange.dao
项目架构如下图所示:
(下面顺序倒着来,好理解一点)
3). 数据访问层:负责数据的访问操作,包含数据的增、删、改、查
在 com.orange.dao
中创建UserDao接口,代码如下:
package com.orange.springbootweb_001.dao;import java.util.List;public interface UserDao {public List<String> findAll();
}
在 com.orange.dao.impl
中创建UserDaoImpl接口,代码如下
package com.orange.springbootweb_001.dao.impl;import cn.hutool.core.io.IoUtil;
import com.orange.springbootweb_001.dao.UserDao;import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;public class UserDaoImpl implements UserDao {public List<String> findAll() {//1. 加载并读取user.txt文件,获取用户数据//InputStream in = new FileInputStream("src/main/resources/user.txt");InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8,new ArrayList<>());return lines;}
}
2). 业务逻辑层:处理具体的业务逻辑
在 com.orange.service
中创建UserSerivce接口,代码如下:
package com.orange.springbootweb_001.service;import com.orange.springbootweb_001.pojo.User;import java.util.List;public interface UserService {public List<User> findAll();
}
在 com.orange.service.impl
中创建UserSerivceImpl接口,代码如下:
package com.orange.springbootweb_001.service.impl;import com.orange.springbootweb_001.dao.UserDao;
import com.orange.springbootweb_001.dao.impl.UserDaoImpl;
import com.orange.springbootweb_001.pojo.User;
import com.orange.springbootweb_001.service.UserService;import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;public class UserServiceImpl implements UserService {//调用Dao,获取数据private UserDao userDao = new UserDaoImpl();@Overridepublic List<User> findAll() {List<String> lines = userDao.findAll();//2. 解析用户信息,封装为User对象 ->list集合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);}).toList();return userList;}
}
1). 控制层:接收前端发送的请求,对请求进行处理,并响应数据
在 com.orange.controller
中创建UserController类,代码如下:
package com.orange.springbootweb_001.controller;import cn.hutool.core.io.IoUtil;
import com.orange.springbootweb_001.pojo.User;
import com.orange.springbootweb_001.service.UserService;
import com.orange.springbootweb_001.service.impl.UserServiceImpl;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.io.FileInputStream;
import java.io.FileNotFoundException;
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;// 用户信息Controller
@RestController
public class UserController {// 调用service,获取数据private UserService userService = new UserServiceImpl();@RequestMapping("/list")public List<User> list() throws FileNotFoundException {//3. 返回数据(json)return userService.findAll();}
}/*package com.orange.springbootweb_001.controller;import cn.hutool.core.io.IoUtil;
import com.orange.springbootweb_001.pojo.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.io.FileInputStream;
import java.io.FileNotFoundException;
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;// 用户信息Controller
@RestController
public class UserController {@RequestMapping("/list")public List<User> list() throws FileNotFoundException {//1. 加载并读取user.txt文件,获取用户数据//InputStream in = new FileInputStream("src/main/resources/user.txt");InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8,new ArrayList<>());//2. 解析用户信息,封装为User对象 ->list集合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);}).toList();//3. 返回数据(json)return userList;}
}*/
4.2 分层解耦
4.2.1 问题分析
由于我们现在在程序中,需要什么对象,直接new一个对象 new UserServiceImpl(),如下图
如果说我们需要更换实现类,比如由于业务的变更,UserServiceImpl 不能满足现有的业务需求,我们需要切换为 UserServiceImpl2 这套实现,就需要修改Contorller的代码,需要创建 UserServiceImpl2 的实现new UserServiceImpl2(),如下图
Service中调用Dao,也是类似的问题。这种呢,我们就称之为层与层之间 耦合 了。 那什么是耦合呢 ?
首先需要了解软件开发涉及到的两个概念:内聚和耦合。
-
内聚:软件中各个功能模块内部的功能联系。
-
耦合:衡量软件中各个层/模块之间的依赖、关联的程度。
软件设计原则:高内聚低耦合。
高内聚:指的是一个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高,即 "高内聚"。
低耦合:指的是软件中各个层、模块之间的依赖关联程序越低越好。
4.2.2 解耦思路
1)将要用到的对象交给一个容器管理。
2). 应用程序中用到这个对象,就直接从容器中获取
想要实现上述解耦操作,就涉及到Spring中的两个核心概念:
控制反转: Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。
对象的创建权由程序员主动创建转移到容器(由容器创建、管理对象)。这个容器称为:IOC容器或Spring容器。
依赖注入: Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。
程序运行时需要某个资源,此时容器就为其提供这个资源。
例:EmpController程序运行时需要EmpService对象,Spring容器就为其提供并注入EmpService对象。
bean对象:IOC容器中创建、管理的对象,称之为:bean对象。
4.3 IOC&DI入门
1). 将Service及Dao层的实现类,交给IOC容器管理
在实现类加上 @Component
注解,就代表把当前类产生的对象交给IOC容器管理。
A. UserDaoImpl
package com.orange.springbootweb_001.dao.impl;import cn.hutool.core.io.IoUtil;
import com.orange.springbootweb_001.dao.UserDao;
import org.springframework.stereotype.Component;import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;@Component
public class UserDaoImpl implements UserDao {public List<String> findAll() {//1. 加载并读取user.txt文件,获取用户数据//InputStream in = new FileInputStream("src/main/resources/user.txt");InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8,new ArrayList<>());return lines;}
}
B. UserServiceImpl
package com.orange.springbootweb_001.service.impl;import com.orange.springbootweb_001.dao.UserDao;
import com.orange.springbootweb_001.dao.impl.UserDaoImpl;
import com.orange.springbootweb_001.pojo.User;
import com.orange.springbootweb_001.service.UserService;
import org.springframework.stereotype.Component;import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;@Component
public class UserServiceImpl implements UserService {//调用Dao,获取数据private UserDao userDao = new UserDaoImpl();@Overridepublic List<User> findAll() {List<String> lines = userDao.findAll();//2. 解析用户信息,封装为User对象 ->list集合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);}).toList();return userList;}
}
2). 为Controller 及 Service注入运行时所依赖的对象
加入注解:@Autowired,表示程序在运行的时候会自动从容器中在到这个类型的bean对象,并赋值给成员变量
A. UserServiceImpl
package com.orange.springbootweb_001.service.impl;import com.orange.springbootweb_001.dao.UserDao;
import com.orange.springbootweb_001.dao.impl.UserDaoImpl;
import com.orange.springbootweb_001.pojo.User;
import com.orange.springbootweb_001.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;@Component
public class UserServiceImpl implements UserService {//调用Dao,获取数据@Autowiredprivate UserDao userDao;@Overridepublic List<User> findAll() {List<String> lines = userDao.findAll();//2. 解析用户信息,封装为User对象 ->list集合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);}).toList();return userList;}
}
B. UserController
package com.orange.springbootweb_001.controller;import cn.hutool.core.io.IoUtil;
import com.orange.springbootweb_001.pojo.User;
import com.orange.springbootweb_001.service.UserService;
import com.orange.springbootweb_001.service.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.io.FileInputStream;
import java.io.FileNotFoundException;
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;// 用户信息Controller
@RestController
public class UserController {// 调用service,获取数据@Autowiredprivate UserService userService ;@RequestMapping("/list")public List<User> list() throws FileNotFoundException {//3. 返回数据(json)return userService.findAll();}
}/*package com.orange.springbootweb_001.controller;import cn.hutool.core.io.IoUtil;
import com.orange.springbootweb_001.pojo.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.io.FileInputStream;
import java.io.FileNotFoundException;
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;// 用户信息Controller
@RestController
public class UserController {@RequestMapping("/list")public List<User> list() throws FileNotFoundException {//1. 加载并读取user.txt文件,获取用户数据//InputStream in = new FileInputStream("src/main/resources/user.txt");InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8,new ArrayList<>());//2. 解析用户信息,封装为User对象 ->list集合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);}).toList();//3. 返回数据(json)return userList;}
}*/
启动服务,运行测试。 打开浏览器,地址栏直接访问:http://localhost:8080/user.html 。 依然正常访问,就说明入门程序完成了。 已经完成了层与层之间的解耦。
4.4 IOC详解
4.4.1 Bean的声明
要把某个对象交给IOC容器管理,需要在对应的类上加上如下注解之一:
注意1:声明bean的时候,可以通过注解的value属性指定bean的名字,如果没有指定,默认为类名首字母小写。
注意2:使用以上四个注解都可以声明bean,但是在springboot集成web开发中,声明控制器bean只能用@Controller。
那么此时,我们就可以使用 @Service
注解声明Service层的bean。 使用 @Repository
注解声明Dao层的bean。 代码实现如下:
Service层:
package com.orange.springbootweb_001.service.impl;import com.orange.springbootweb_001.dao.UserDao;
import com.orange.springbootweb_001.dao.impl.UserDaoImpl;
import com.orange.springbootweb_001.pojo.User;
import com.orange.springbootweb_001.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;@Service
public class UserServiceImpl implements UserService {//调用Dao,获取数据@Autowiredprivate UserDao userDao;@Overridepublic List<User> findAll() {List<String> lines = userDao.findAll();//2. 解析用户信息,封装为User对象 ->list集合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);}).toList();return userList;}
}
Dao层:
package com.orange.springbootweb_001.dao.impl;import cn.hutool.core.io.IoUtil;
import com.orange.springbootweb_001.dao.UserDao;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;@Repository
public class UserDaoImpl implements UserDao {public List<String> findAll() {//1. 加载并读取user.txt文件,获取用户数据//InputStream in = new FileInputStream("src/main/resources/user.txt");InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8,new ArrayList<>());return lines;}
}
4.4.2 组件扫描
问题:使用前面学习的四个注解声明的bean,一定会生效吗?
答案:不一定。(原因:bean想要生效,还需要被组件扫描)
-
前面声明bean的四大注解,要想生效,还需要被组件扫描注解
@ComponentScan
扫描。 -
该注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解
@SpringBootApplication
中,默认扫描的范围是启动类所在包及其
所以,我们在项目开发中,只需要按照如上项目结构,将项目中的所有的业务类,都放在启动类所在包的子包中,就无需考虑组件扫描问题。
4.5 DI详解
4.5.1 @Autowired用法
依赖注入,是指IOC容器要为应用程序去提供运行时所依赖的资源,而资源指的就是对象。
在入门程序案例中,我们使用了@Autowired这个注解,完成了依赖注入的操作,而这个Autowired翻译过来叫:自动装配。
@Autowired
注解,默认是按照类型进行自动装配的(去IOC容器中找某个类型的对象,然后完成注入操作)
在项目开发中,基于@Autowired进行依赖注入时,基本都是第一种和第二种方式。(官方推荐第二种方式,因为会更加规范)但是在企业项目开发中,很多的项目中,也会选择第一种方式因为更加简洁、高效(在规范性方面进行了妥协)。
4.5.2 注意事项
那如果在IOC容器中,存在多个相同类型的bean对象
在下面的例子中,我们准备了两个UserService的实现类,并且都交给了IOC容器管理。 代码如下:
此时,我们启动项目会发现,控制台报错了:
出现错误的原因呢,是因为在Spring的容器中,UserService这个类型的bean存在两个,框架不知道具体要注入哪个bean使用,所以就报错了。
如何解决上述问题呢?Spring提供了以下几种解决方案:
-
@Primary
-
@Qualifier
-
@Resource
@Autowird 与 @Resource的区别
@Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解
@Autowired 默认是按照类型注入,而@Resource是按照名称注入