SpringBootWeb学习笔记——12万字详细总结!
0. 写在前面
注:这套笔记是根据黑马程序员B站2023-3-21的视频学习的成果,其中省略了前端基础部分、Maven部分和数据库基础部分,详情可见目录。
注注:目前文章内结尾处多幅图片加载不出来,因为图片还存在本地没被传上来,过段时间再改~
所有的Spring项目都基于Spring Framework
Spring Framework配置繁琐,入门难度大
文章目录
- 0. 写在前面
- 1. SpringBootWeb入门
- 简单的构成分析
- 工程启动后
- 2. HTTP协议
- 特点
- 请求协议
- 相应协议
- 协议解析
- 3. Tomcat
- 基本使用
- 部署项目
- 4. SpringBootWeb入门程序解析
- 起步依赖
- 5. 请求响应
- 前端控制器
- 请求对象:获取请求数据
- 响应对象:设置响应数据
- B/S架构:浏览器/服务器架构模式。客户端只需要浏览器,应用程序和数据都存放在服务端。
- C/S架构:客户端/服务器架构模式。
- 工具
- 简单参数
- 实体参数
- 响应数据
- 案例
- 6. 分层解耦
- 三层架构
- IOC&DI引入
- IoC-DI入门
- IoC详解
- 组件扫描
- DI详解
- 7. SQL
- 8. Mybatis
- 快速入门
- 单元测试
- 配置注解SQL提示(23版IDEA自带,无需配置)
- JDBC介绍
- 数据库连接池
- Lombok工具包
- MyBatis基本操作案例,准备工作
- 删除
- 删除(预编译SQL)
- 新增
- 新增(主键返回)
- 更新
- 查询(根据id查询)
- 查询(条件查询)
- XML映射文件(也叫配置文件)
- 9. MyBatis动态SQL
- \<if\>
- \<where\>
- if案例
- \<set\>
- \<foreach\>
- \<sql\>和\<include\>
- 10. 部门管理员工管理案例
- 环境搭建
- 开发规范restful
- 开发规范统一响应结果
- 开发流程
- 部门管理部分
- 员工管理部分
- 插播:文件上传
- 本地存储
- 插播结束:阿里云OSS
- 修改员工
- 参数配置化
- yml配置文件
- @ConfigurationProperties
- 登录功能
- 登录校验
- 11. 会话技术
- 客户端会话跟踪技术:Cookie
- 服务器会话跟踪技术:Session
- 令牌技术
- JWT令牌
- JWT生成和校验
- 登录后下发令牌(注意:令牌存储由前端负责,案例中存放在本地存储空间)
- 12. 统一拦截,过滤器Filter
- 过滤器Filter快速入门
- Filter执行流程
- Filter拦截路径
- 过滤器链
- 案例实现登录校验过滤器
- 13. 拦截器Interceptor快速入门
- 拦截器拦截路径
- 拦截器执行流程
- 案例实现登录校验拦截器
- 14. 异常处理
- 异常处理方案
- 15. 事务管理
- Spring事务管理
- 16. 事务进阶
- rollbackFor属性
- propagation属性
- 17. AOP
- AOP快速入门
- AOP核心概念
- AOP执行流程
- AOP通知类型
- AOP通知顺序
- 切入点表达式
- execution
- annotation
- 连接点
- AOP案例
- 终章:原理篇
- 配置优先级
- 获取bean
- bean作用域
- 第三方bean
- SpringBoot原理
- Spring家族
- 起步依赖原理
- 自动配置概述
- 自动配置原理
- 源码跟踪分析
- @Conditional
- 自定义starter
- Web总结
- 三层架构
1. SpringBootWeb入门
一个简单的应用:浏览器发起/hello请求后,给浏览器返回一个字符串Hello World~

步骤:
- 创建springboot工程,并勾选web开发相关依赖。
- 定义
HelloController类,添加方法hello,并添加注解。 - 运行测试
简单的构成分析
- 一个自动生成的类,和模块名一致:启动类,用来启动springboot工程,想启动工程只需要运行这个
main函数package com.itheima;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;// 启动类 --- 启动springboot工程 @SpringBootApplication public class SpringBootQuickStartApplication {public static void main(String[] args) {SpringApplication.run(SpringBootQuickStartApplication.class, args);} } resources/static和/templates暂时用不上.properties是默认配置文件,也支持一些其他的后缀格式- 创建的
HelloController类是一个普通的Java类,需要添加注解@RestController把他变成请求处理类。还需要指定当前方法处理的是那个请求,使用注解@RequestMapping('/hello')指定处理的请求路径,浏览器将来请求/hello这个地址时,最终就会调用这个方法package com.itheima.controller;import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;// 请求处理类,加上注解后,这就是一个请求处理类,不加注解就是普通类 @RestController public class HelloController {// 指定当前方法处理的是哪个请求,/hello请求。浏览器请求/hello地址时,最终就会调用这个hello方法@RequestMapping("/hello")public String hello() {System.out.println("Hello World~");return "Hello World~";} }
工程启动后
工程如果成功启动,在浏览器输入localhost:8080就可以看到函数return的Hello World,也可以在控制台看到输出的Hello World,说明入门程序没有问题。
2. HTTP协议
超文本传输协议,规定了浏览器与服务器间数据传输的规则,实际上就是规定了数据格式
F12查看网络里的hello请求
request header是请求的数据
response header是响应的数据
特点
- 基于TCP协议:面向连接,安全
- 基于请求-响应模型:一次请求对应一次响应
- HTTP协议是无状态协议:对事物没有记忆力,每次请求-响应都是独立的
- 缺点:多次请求间不能共享数据
- 优点:速度快
请求协议
就是请求数据的格式,分为三个部分
- 请求行,数据第一行,
请求方式+资源路径+协议 - 请求头,第二行开始,是
key: value的格式 - 请求体,POST请求,存放请求参数
- GET:请求参数在请求行中,没有请求体,请求大小有限制
- POST:请求参数在请求体中,请求大小没有限制
相应协议
同样分为三个部分
- 响应行,数据第一行,
协议+状态码+描述 - 响应头,第二行开始,是
key: value的格式 - 响应体,最后一部分,存放响应数据
注意状态码200、404、500
协议解析
Tomcat
3. Tomcat
最流行的HTTP协议解析程序,使程序员不必直接对协议进行操作。主要功能是“提供网上信息浏览服务”,又称Servlet容器
安装:解压安装包
卸载:删除文件夹
基本使用
bin/startup.bat运行
bin/shutdown.bat关闭
常见问题:
startup.bat闪退,检查环境变量是否有JAVA_HOME(不带bin)- 端口号冲突,找到对应程序将其关闭(任务管理器、详细信息、找对应端口号的程序)或修改Tomcat使用的端口号
conf/server.xml
注意:HTTP协议默认端口号为80,若将Tomcat端口号改为80,则访问Tomcat时,不用输入端口号
部署项目
将项目放置到webapps下就部署完成了
4. SpringBootWeb入门程序解析
起步依赖
spring-boot-starter-web:包含了web应用开发所需要的常见依赖spring-boot-starter-test:包含了单元测试所需要的常见依赖
借助依赖传递特性将某个功能所需的常见依赖聚合到一起。
在pom中会看到起步依赖没有写<version>,这是因为SpringBoot项目都有一个父工程,在pom中表现为<parent>标签里的内容,所有起步依赖的版本都在父工程中进行了统一管理,会自动引入和SpringBoot版本对应的起步依赖版本
可以看到spring-boot-starter-web中有一个tomcat相关的依赖,所以启动项目时会将内置的Tomcat启动起来并占用Tomcat默认端口号8080,这个Tomcat和外部安装的Tomcat不是同一个,外部的Tomcat会很少使用,多数都是使用内置的Tomcat
5. 请求响应
Tomcat不能识别我们写的如HelloController的controller程序,但Tomcat可以识别Servlet规范。SpringBoot底层提供了一个非常核心的Servlet程序DispatcherServlet,它实现了Servlet规范中的Servlet接口(可看继承体系)。
浏览器发起的请求都会先经过DispatcherServlet,由DispatcherServlet将请求转给controller程序进行处理,处理完的结果返回给DispatcherServlet,DispatcherServlet再给浏览器响应数据
前端控制器
`DispatcherServlet`
如何在Servlet中获取请求数据?浏览器发请求会携带HTTP请求数据,web服务器负责对请求协议的解析。Tomcat就会对数据进行解析,并将解析后所有请求信息封装到一个对象HttpServletRequest中,也叫请求对象
请求对象:获取请求数据
应用程序就可以从HttpServletRequest对象中获取请求数据了,然后对请求进行处理。然后Tomcat服务器需要根据响应数据的格式给浏览器响应数据。借助另一个对象设置响应数据,HttpServletResponse,Tomcat会根据在这个对象中设置的响应信息来响应数据给浏览器。
响应对象:设置响应数据
HttpServletResponse
B/S架构:浏览器/服务器架构模式。客户端只需要浏览器,应用程序和数据都存放在服务端。
C/S架构:客户端/服务器架构模式。
主要关注controller程序,最重要的是获取请求参数和设置响应数据
工具
postman或apifox
简单参数
get post方式的处理方法相同
发送的是普通数据如name=zhangsan,创建controller/RequestController类进行操作
-
原始方式获取请求参数:通过
HttpServletRequest对象手动获取
请求地址:http://localhost:8080/simpleParam?name=tom&age=10
实现请求处理方法,处理对/simpleParam的请求package com.itheima.controller;import jakarta.servlet.http.HttpServletRequest;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class RequestController {// 原始方式获取简单参数@RequestMapping("/simpleparam")public String simpleParam(HttpServletRequest request) {// 获取请求参数String name = request.getParameter("name");String ageStr = request.getParameter("age");int age = Integer.parseInt(ageStr);System.out.println(name + ":" + age);return "OK";}}缺点:繁琐,需要手动类型转换
-
SpringBoot方式
简单参数:参数名与形参变量名相同,即可自动接收参数
请求地址:http://localhost:8080/simpleParam,参数为name=Tom和age=20
示例如下:
修改RequestController中的方法package com.itheima.controller;import jakarta.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;@RestController public class RequestController { // SpringBoot方式获取参数@RequestMapping("/simpleParam")public String simpleParam(String name, Integer age) {System.out.println(name + ":" + age);return "OK";} }如果参数名和形参名不同,默认情况下会接收不到,变量存储值为
null。可以通过注解@RequestParam完成映射@RequestMapping("/simpleParam") public String simpleParam(@RequestParam(name= "name") String username, Integer age) {System.out.println(username + ":" + age);return "OK"; }注意:
@RequestParam中的required默认为true,表示该参数必须传递,如果不传递则报错,若参数是可选的,可修改required为false。
实体参数
-
简单实体对象:请求参数名与形参对象属性名相同,定义一个POJO类接收即可。
注意:定义的类中属性名必须和请求参数名相同才能成功封装
示例如下:
请求地址:http://localhost:8080/simplePojo?name=ITCAST&age=16
定义一个User类,其中的属性名必须和请求参数名相同,这里为name、agepackage com.itheima.pojo;public class User {public String name;public Integer age;public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';} }修改
RequestController中的方法,处理对/simple/Pojo的请求package com.itheima.controller;import com.itheima.pojo.User; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;@RestController public class RequestController {@RequestMapping("/simplePojo")public String simplePojo(User user) {System.out.println(user);return "OK";} } -
复杂实体对象:按照对象层次关系接收嵌套
POJO属性参数
示例如下:
请求地址:http://localhost:8080/complexPojo?name=ITCAST&age=16&address.province=北京&address.city=北京
写一个Address类package com.itheima.pojo;/*** Created with IntelliJ IDEA.** @author : wu_qing* @version : 1.0* @Project : LearnSpringBoot* @Package : com.itheima.pojo* @ClassName : .java* @createTime : 2024/2/10 21:58* @Email : 1553232108@qq.com* @Description :*/ public class Address {public String province;public String city;public String getProvince() {return province;}public void setProvince(String province) {this.province = province;}public String getCity() {return city;}public void setCity(String city) {this.city = city;}@Overridepublic String toString() {return "Address{" +"province='" + province + '\'' +", city='" + city + '\'' +'}';} }将
Address添加到User类的属性中
修改请求处理方法package com.itheima.controller;import com.itheima.pojo.User; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;@RestController public class RequestController {@RequestMapping("/complexPojo")public String complexPojo(User user) {System.out.println(user);return "OK";} } -
数组集合参数/多同名参数:
数组方法:请求参数名与数组名相同且请求参数为多个,定义数组类型形参即可
集合方法:使用@RequestParam绑定参数关系,将多个请求参数的值封装到集合
示例如下:
请求地址:http://localhost:8080/arrayParam?hobby=game&hobby=java&hobby=sing
http://localhost:8080/listParam?hobby=game&hobby=java&hobby=sing
修改请求处理方法package com.itheima.controller;import com.itheima.pojo.User; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;@RestController public class RequestController {// 数组方法@RequestMapping("/arrayParam")public String arrayParam(String[] hobby) {System.out.println(Arrays.toString(hobby));return "OK";}// 集合方法获取同名参数@RequestMapping("/listParam")public String listParam(@RequestParam List<String> hobby) {System.out.println(hobby);return "OK";} } -
日期参数:使用
@DateTimeFormat注解完成日期参数格式转换
需要指定传来的日期参数格式,如@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss)"
注意:这里写的是yyyy-MM-dd HH:mm:ss的格式,那么请求参数里也必须是4位-2位-2位 2位:2位:2位
请求地址:http://localhost:8080/dateParam?updateTime=2024-12-10 22:17:39,如这里的月份如果为1月必须写为01补全位数package com.itheima.controller;import com.itheima.pojo.User; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;@RestController public class RequestController {// 日期参数@RequestMapping("/dateParam")public String dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime) {System.out.println(updateTime);return "OK";} } -
JSON参数:需要
POST方式发送,要求JSON数据键名与对象属性名相同,定义POJO类型即可接收参数,需要使用@RequestBody标识,将JSON数据封装到实体对象中
示例如下:
请求地址:http://localhost:8080/jsonParam
数据:{ "name": "zhangsan", "age": 16, "address": { "province": "北京", "city": "北京" } }
实现请求处理方法package com.itheima.controller;import com.itheima.pojo.User; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;@RestController public class RequestController {// JSON参数@RequestMapping("/jsonParam")public String jsonParam(@RequestBody User user) {System.out.println(user);return "OK";} } -
路径参数
通过请求URL直接传递参数,如http://localhost:8080/path/1,如果注解写@RequestMapping("/path/1"),将来/1变成/2、/3、/100那就不能再处理这个请求,所以这个参数应该是动态的。使用{...}来标识该路径参数,就可以写@RequestMapping("/path/{id})代表这地方不是固定值而是路径参数,参数名叫id,这样就可以在controller中声明一个形参叫id,使用@pathVariable来指定获取路径参数并将路径参数的id绑定给方法参数的id,路径参数参数名需要与方法形参参数名保持一致
示例如下:
请求地址:http://localhost:8080/path/1
实现请求处理方法package com.itheima.controller;import com.itheima.pojo.User; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;@RestController public class RequestController {// 路径参数@RequestMapping("/path/{id}")public String pathParam(@PathVariable Integer id) {System.out.println(id);return "OK";} }多路径参数:在请求路径中使用
/分隔,再写其他的参数,请求路径也要相应改变@RequestMapping("/path/{id}/{name}")
注意:每个参数都需要使用@pathVariable来绑定
示例如下:
请求地址:http://localhost:8080/path/1/Tom
实现请求处理方法package com.itheima.controller;@RestControllerpublic class RequestController { // 多路径参数@RequestMapping("/path/{id}/{name}")public String pathParam2(@PathVariable Integer id, @PathVariable String name) {System.out.println(id);System.out.println(name);return "OK";} }
响应数据
controller程序的return值就是响应(返回给浏览器)的值。所有的响应数据都需要依赖核心的@ResponseBody注解,需要写在controller方法上或类上。他的作用就是将方法返回值直接作为响应数据给客户端浏览器,如果返回值类型是实体对象/集合,会转为JSON格式再响应,**但是似乎从来没见过?**事实上是因为@RestController = @Controller + @ResponseBody,已经包含了@ResponseBody注解。写@RestController等价于写@Controller + @ResponseBody。
在类上加了@ResponseBody注解,代表当前类下所有返回值都会作为响应数据,如果是对象或集合会先转JSON再来响应
示例如下:
请求地址分别为:
http://localhost:8080/hello
http://localhost:8080/getAddr
http://localhost:8080/listAddr
新建一个ResponseController类
package com.itheima.controller;import com.itheima.pojo.Address;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;
import java.util.List;@RestController
public class ResponseController {@RequestMapping("/hello")public String hello() {System.out.println("Hello World!");return "Hello World~";}@RequestMapping("/getAddr")public Address getAddr() {Address addr = new Address();addr.setProvince("广东");addr.setCity("深圳");return addr;}@RequestMapping("/listAddr")public List<Address> listAddr() {List<Address> list = new ArrayList<>();Address addr = new Address();addr.setProvince("北京");addr.setCity("北京");Address addr2 = new Address();addr2.setProvince("广东");addr2.setCity("深圳");list.add(addr);list.add(addr2);return list;}
}
对这三个请求处理方法分别发出请求,可以看到第一个返回的就是一个字符串,第二个返回的是JSON格式的数据,第三个返回的是JSON数组的数据
说明:每个对外暴露的方法都称为功能接口,注解中写的路径为接口的访问路径。开发文档就是描述功能接口的请求路径,请求参数以及响应数据的。
可以发现每个接口响应的数据很随意,没有任何规范,前端很难解析响应回去的数据,开发成本会增加,项目不便管理且很难维护。
一般会给所有的功能接口设置统一的响应结果,可以考虑使用一个对象result来接收,主要有3个属性:
- int code,响应码,可以和前端约定:1表示成功,0表示失败
- string msg,提示信息
- object data,返回的数据
返回的result对象经过ResponseBody的处理后,就会响应一个JSON格式的数据,前端只会收到一种格式的数据,只需要根据这一种格式来解析。项目管理和维护就会更加方便。
示例如下:
实现一个Result类
package com.itheima.pojo;public class Result {private Integer code; // 验证码,1成功,0失败private String msg; // 提示信息private Object data; // 数据public Result() {}public Result(Integer code, String msg, Object data) {this.code = code;this.msg = msg;this.data = data;}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public Object getData() {return data;}public void setData(Object data) {this.data = data;}public static Result success(Object data) {return new Result(1, "success", data);}public static Result success() {return new Result(1, "success", null);}public static Result error(String msg) {return new Result(0, msg, null);}@Overridepublic String toString() {return "Result{" +"code=" + code +", msg='" + msg + '\'' +", data=" + data +'}';}
}
类中写了三个静态方法,分别是成功且返回数据,成功且不返回数据,失败
再修改ResponseController类中的方法,使三个功能接口具有相同的响应数据格式
package com.itheima.controller;import com.itheima.pojo.Address;
import com.itheima.pojo.Result;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;
import java.util.List;@RestController
public class ResponseController {/*@RequestMapping("/hello")public String hello() {System.out.println("Hello World!");return "Hello World~";}@RequestMapping("/getAddr")public Address getAddr() {Address addr = new Address();addr.setProvince("广东");addr.setCity("深圳");return addr;}@RequestMapping("/listAddr")public List<Address> listAddr() {List<Address> list = new ArrayList<>();Address addr = new Address();addr.setProvince("北京");addr.setCity("北京");Address addr2 = new Address();addr2.setProvince("广东");addr2.setCity("深圳");list.add(addr);list.add(addr2);return list;}*/@RequestMapping("/hello")public Result hello() {System.out.println("Hello World!");// return new Result(1, "success", "Hello World!");return Result.success("Hello World!"); // 和上边的效果一样,一个用构造,一个用静态方法,都是返回一个result对象}@RequestMapping("/getAddr")public Result getAddr() {Address addr = new Address();addr.setProvince("广东");addr.setCity("深圳");return Result.success(addr);}@RequestMapping("/listAddr")public Result listAddr() {List<Address> list = new ArrayList<>();Address addr = new Address();addr.setProvince("北京");addr.setCity("北京");Address addr2 = new Address();addr2.setProvince("广东");addr2.setCity("深圳");list.add(addr);list.add(addr2);return Result.success(list);}
}
案例
- 在POM中引入dom4j,用来解析XML文件
<!-- 解析XML -->
<dependency><groupId>org.dom4j</groupId><artifactId>dom4j</artifactId><version>2.1.3</version>
</dependency>
- 引入工具类XMLParserUtils,实体类Emp,XML文件emp.xml
- 引入静态页面,放在resources/static目录下
- 写controller程序
package com.itheima.controller;import com.itheima.pojo.Emp;
import com.itheima.pojo.Result;
import com.itheima.utils.XmlParserUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController
public class EmpController {@RequestMapping("/listEmp") // 和前端页面发送请求的路径相同public Result list() {// 1. 加载解析XML文件String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();System.out.println(file);List<Emp> empList = XmlParserUtils.parse(file, Emp.class);// 2. 对数据进行转换处理 gender和jobempList.stream().forEach(emp -> {// 处理gender 1男 2女String gender = emp.getGender();if ("1".equals(gender)) {emp.setGender("男");} else if ("2".equals(gender)) {emp.setGender("女");}// 处理job 1: 讲师, 2: 班主任 , 3: 就业指导String job = emp.getJob();if ("1".equals(job)) {emp.setJob("讲师");} else if ("2".equals(job)) {emp.setJob("班主任");} else if ("3".equals(job)) {emp.setJob("就业指导");}});// 3. 响应数据return Result.success(empList);}
}
说明:SpringBoot项目的静态资源默认存放目录为:classpath:/static、classpath:/public、classpath:/resources,classpath是类路径,对Maven项目来说resources目录就是类路径,一般就使用static目录
6. 分层解耦
EmpController中的代码包含数据访问,逻辑处理,接受请求和响应数据。需要尽量让每个类 接口 方法的职责更加单一,这是单一职责原则。能够使类 接口 方法的可读性 可维护性 拓展性更好
三层架构
- Controller:表示层/控制层,就是编写的Controller程序,负责接收请求、进行处理、响应数据。
- Service:逻辑层,处理具体的业务逻辑。
- Dao:数据访问层(Data Access Object)/持久层,负责数据访问操作,包括增删改查。
前端发起请求先到达Controller,调用Service进行逻辑处理,处理的前提是拿到数据,所以再调用Dao层去操作文件中的数据,拿到数据再返回给Service,处理之后的结果返回给Controller,再响应数据给前端。
数据访问Dao层的实现方式可能有很多,如访问文件的数据、数据库的数据、别人提供接口的数据,要灵活的切换各种实现,可以通过面向接口的方式进行编程
需要先定一个Dao的接口来增强灵活性和拓展性
- 先来一个dao包下的EmpDao接口
package com.itheima.dao;import com.itheima.pojo.Emp;import java.util.List;public interface EmpDao {// 获取员工列表public List<Emp> listEmp();
}
- 再来一个impl/EmpDaoA(方式很多所以叫A来区分)实现类来实现接口的方法,逻辑就是Controller中解析XML文件获取数据部分
package com.itheima.dao.impl;import com.itheima.dao.EmpDao;
import com.itheima.pojo.Emp;
import com.itheima.utils.XmlParserUtils;import java.util.List;public class EmpDaoA implements EmpDao {@Overridepublic List<Emp> listEmp() {// 1. 加载解析XML文件String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();System.out.println(file);List<Emp> empList = XmlParserUtils.parse(file, Emp.class);return empList;}
}
- 然后是Service层,先来一个接口增加灵活性EmpService
package com.itheima.service;import com.itheima.pojo.Emp;import java.util.List;public interface EmpService {// 获取员工列表public List<Emp> listEmp();
}
- 再来impl/EmpServiceA实现类来实现接口方法,逻辑就是Controller中数据处理部分,但是要从Dao中获取数据
package com.itheima.service.impl;import com.itheima.dao.EmpDao;
import com.itheima.dao.impl.EmpDaoA;
import com.itheima.pojo.Emp;
import com.itheima.service.EmpService;import java.util.List;public class EmpServiceA implements EmpService {private EmpDao empDao = new EmpDaoA();@Overridepublic List<Emp> listEmp() {// 1. 调用Dao获取数据List<Emp> empList = empDao.listEmp();// 2. 对数据进行转换处理 gender和jobempList.stream().forEach(emp -> {// 处理gender 1男 2女String gender = emp.getGender();if ("1".equals(gender)) {emp.setGender("男");} else if ("2".equals(gender)) {emp.setGender("女");}// 处理job 1: 讲师, 2: 班主任 , 3: 就业指导String job = emp.getJob();if ("1".equals(job)) {emp.setJob("讲师");} else if ("2".equals(job)) {emp.setJob("班主任");} else if ("3".equals(job)) {emp.setJob("就业指导");}});return empList;}
}
- 最后修改Controller程序,从Service中接收处理完的数据并响应给前端
package com.itheima.controller;import com.itheima.pojo.Emp;
import com.itheima.pojo.Result;
import com.itheima.service.EmpService;
import com.itheima.service.impl.EmpServiceA;
import com.itheima.utils.XmlParserUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController
public class EmpController {private EmpService service = new EmpServiceA();@RequestMapping("/listEmp") // 和前端页面发送请求的路径相同public Result list() {// 调用Service获取数据,然后响应List<Emp> empList = service.listEmp();// 3. 响应数据return Result.success(empList);}// @RequestMapping("/listEmp") // 和前端页面发送请求的路径相同
// public Result list() {
// // 1. 加载解析XML文件
// String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
// System.out.println(file);
// List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
//
// // 2. 对数据进行转换处理 gender和job
// empList.stream().forEach(emp -> {
// // 处理gender 1男 2女
// String gender = emp.getGender();
// if ("1".equals(gender)) {
// emp.setGender("男");
// } else if ("2".equals(gender)) {
// emp.setGender("女");
// }
//
// // 处理job 1: 讲师, 2: 班主任 , 3: 就业指导
// String job = emp.getJob();
// if ("1".equals(job)) {
// emp.setJob("讲师");
// } else if ("2".equals(job)) {
// emp.setJob("班主任");
// } else if ("3".equals(job)) {
// emp.setJob("就业指导");
// }
// });
//
// // 3. 响应数据
// return Result.success(empList);
// }
}
整体过程:前端发起请求之后,先到达Controller程序,他只负责接受请求响应数据,所以直接调用Service层中的方法,Service层只负责逻辑处理,所以直接调用Dao中的方法获取数据,由Dao层来负责数据访问操作,将查询的数据返回给Service,Service处理完后返回给Controller,Controller拿到结果再响应给前端页面。
IOC&DI引入
-
内聚:各个功能模块内部的功能联系
员工管理的Service中只会编写与员工相关的逻辑处理,与员工无关的逻辑处理不会放在这个类中。
-
耦合:衡量软件中各个层/模块之间的依赖、关联的程度
Controller中new了一个Service的实现类,如果Service层类名发生变化,Controller的代码也需要修改。Service与Dao也有这样的耦合关系。
-
软件设计原则:高内聚低耦合
解耦:不能直接new Service对象
提供一个容器来存储一些对象,如果想用EmpServiceA,只需要将其创建的对象A放在容器当中。Controller在运行时需要依赖于EmpService,就可以去容器中查找EmpService这个类型的对象,看到A就是这个类型,就可以从容器中找到对象然后将其赋值给Controller中的empService。
如果要切换实现类,从A切换为B,只需要将B创建的对象放到容器中,Controller运行时也只需要在容器中查找对象,找到对象后赋值给empService。这样即使Service发生变化,Controller代码也不需要修改。
两个问题:
- 对象怎么交给容器管理
- 容器怎么为程序提供它所依赖的资源
涉及到Spring中两个概念:
- 控制反转IoC(Inversion Of Control),Spring框架第一大核心,对象创建的控制权由应用程序转移到了外部容器。反转前由程序自身控制对象创建,反转后由容器控制。容器也叫IoC容器或Spring容器
- 依赖注入DI(Dependency Injection),容器为程序提供运行时依赖的资源,如Controller运行时依赖EmpService,就可以让容器为它提供。
- bean对象,IoC容器中创建管理的对象,称之为bean对象
IoC-DI入门
解耦Controller与Service,Service与Dao
-
将Service层及Dao层的实现类,交给IoC容器管理,为类加注解
@Component -
为Controller和Service注入运行时依赖的对象,为属性加注解
@Autowiredpackage com.itheima.dao.impl;import com.itheima.dao.EmpDao; import com.itheima.pojo.Emp; import com.itheima.utils.XmlParserUtils; import org.springframework.stereotype.Component;import java.util.List;@Component // 将当前类交给IoC容器管理,成为IoC容器中的bean public class EmpDaoA implements EmpDao {@Overridepublic List<Emp> listEmp() {// 1. 加载解析XML文件String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();System.out.println(file);List<Emp> empList = XmlParserUtils.parse(file, Emp.class);return empList;} }
package com.itheima.service.impl;
import com.itheima.dao.EmpDao;
import com.itheima.dao.impl.EmpDaoA;
import com.itheima.pojo.Emp;
import com.itheima.service.EmpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class EmpServiceA implements EmpService {
@Autowired
private EmpDao empDao;
@Overridepublic List<Emp> listEmp() {// 1. 调用Dao获取数据List<Emp> empList = empDao.listEmp();// 2. 对数据进行转换处理 gender和jobempList.stream().forEach(emp -> {// 处理gender 1男 2女String gender = emp.getGender();if ("1".equals(gender)) {emp.setGender("男");} else if ("2".equals(gender)) {emp.setGender("女");}// 处理job 1: 讲师, 2: 班主任 , 3: 就业指导String job = emp.getJob();if ("1".equals(job)) {emp.setJob("讲师");} else if ("2".equals(job)) {emp.setJob("班主任");} else if ("3".equals(job)) {emp.setJob("就业指导");}});return empList;}
}
```java
package com.itheima.controller;import com.itheima.pojo.Emp;
import com.itheima.pojo.Result;
import com.itheima.service.EmpService;
import com.itheima.service.impl.EmpServiceA;
import com.itheima.utils.XmlParserUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController
public class EmpController {@Autowired // 运行时,IoC容器会提供该类型的bean对象,并赋值给该变量private EmpService service;@RequestMapping("/listEmp") // 和前端页面发送请求的路径相同public Result list() {// 调用Service获取数据,然后响应List<Emp> empList = service.listEmp();// 3. 响应数据return Result.success(empList);}
}
这样就完成了控制反转和依赖注入,也完成了层与层之间的解耦,若要切换到EmpServiceB,只需要将A的@Component注释掉,给B添加上@Component注解和自动注入,Dao层和Controller层的代码都不需要改动。
IoC详解
除了@Component外,还提供了三个衍生注解@Controller @Service @Repository来表示bean对象到底归属于哪一层。实际上由于与Mybatis整合``@Repository`用的较少。
某一个类不能归到这三层,又想交给IoC容器管理,就可以使用@Component注解,典型的就是一些工具类。
另外,Controller程序也不需要使用@Controller注解,因为@RestController注解已经包括了@Controller注解。
bean对象的默认名字为类名首字母小写,可以手动指定名字@Repository(value = "daoA&
相关文章:
SpringBootWeb学习笔记——12万字详细总结!
0. 写在前面 注:这套笔记是根据黑马程序员B站2023-3-21的视频学习的成果,其中省略了前端基础部分、Maven部分和数据库基础部分,详情可见目录。 注注:目前文章内结尾处多幅图片加载不出来,因为图片还存在本地没被传上来,过段时间再改~ 所有的Spring项目都基于Spring Fra…...
Code Composer Studio (CCS) - 文件比较
Code Composer Studio [CCS] - 文件比较 References 鼠标单击选中一个文件,再同时按住 Ctrl 鼠标左键来选中第二个文件,在其中一个文件上鼠标右击选择 Compare With -> Each Other. References [1] Yongqiang Cheng, https://yongqiang.blog.csdn.n…...
【GIT学习】仓库过大的清理办法
1、.git目录过大 要解决.git目录过大的问题,可以尝试以下方法: 使用git gc命令清理不再需要的缓存。这将帮助减小仓库的大小。 在命令行中输入以下命令: git gc --prunenow --aggressive使用git repack -ad命令来重新打包已经提交的文件。 …...
[office] excel如何设置图片大小 #其他#其他
excel如何设置图片大小 如果你是EXCEL初学者,因为EXCEL功能强大,刚开始肯定很难记住许多的操作技巧,这里讲一下如何插入图片并根据需要改变图片的大小。下面让小编为你带来excel设置图片大小的方法。 excel设置图片大小步骤: 步骤…...
【动态规划专栏】专题二:路径问题--------4.下降路径最小和
本专栏内容为:算法学习专栏,分为优选算法专栏,贪心算法专栏,动态规划专栏以及递归,搜索与回溯算法专栏四部分。 通过本专栏的深入学习,你可以了解并掌握算法。 💓博主csdn个人主页:小…...
LabVIEW读取excel日期
LabVIEW读取excel日期 | Excel数据表格中有日期列和时间列,如下表所示: 通过LabVIEW直接读取Excel表格数据,读出的日期列和时间列数据与原始表格不一致,直接读出来的数据如下表所示: 日期、时间列数据异常 问题产生原因…...
K8s ingress-nginx根据请求目录不同将请求转发到不同应用
K8s ingress-nginx根据请求目录不同将请求转发到不同应用 1. 起因 有小伙伴做实验想要实现以下需求: 输入www.pana.com/app1访问app1的svc 输入www.pana.com/app2访问app2的svc 2. 实验 2.1 Dockerfile 先准备Dockerfile FROM nginx:1.20ADD index.html /usr/share/ngin…...
【Linux】git操作 - gitee
1.使用 git 命令行 安装 git yum install git 2.使用gitee 注册账户 工作台 - Gitee.com 进入gitee,根据提示注册并登录 新建仓库 仓库名称仓库简介初始换仓库 3.Linux-git操作 进入仓库,选择“克隆/下载” 复制下面的两行命令进行git配置 然后将仓库clo…...
EXCEL使用VBA一键批量转换成PDF
EXCEL使用VBA一键批量转换成PDF 上图是给定转换路径 Sub 按钮1_Click() Dim a(1 To 1000) As String Dim a2 As String Dim myfile As String Dim wb As Workbook a2 Trim(Range("a2"))myfile Dir(a2 & "\" & "*.xls")k 0Do While m…...
【大厂AI课学习笔记】【2.2机器学习开发任务实例】(8)模型训练
好吧,搞了半天,都是围绕数据在干活,这也就验证了,我们说的,数据准备等工作,要占到机器学习项目一半以上的工作量和时间。而且数据决定了模型的天花板,算法只是去达到上限。 我们今天来学习模型…...
【Flink网络通讯(一)】Flink RPC框架的整体设计
文章目录 1. Akka基本概念与Actor模型2. Akka相关demo2.1. 创建Akka系统2.2. 根据path获取Actor并与之通讯 3. Flink RPC框架与Akka的关系4.运行时RPC整体架构设计5. RpcEndpoint的设计与实现 我们从整体的角度看一下Flink RPC通信框架的设计与实现,了解其底层Akka通…...
【Flink】FlinkSQL读取hive数据(批量)
一、简介: Hive在整个数仓中扮演了非常重要的一环,我们可以使用FlinkSQL实现对hive数据的读取,方便后续的操作,本次例子为Flink1.13.6版本 二、依赖jar包准备: 官网地址如下: Overview | Apache Flink 1、我们需要准备相关的jar包到Flink安装目录的lib目录下,我们需…...
list链表
1. list基本概念 功能:将数据进行链式存储 链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的 链表的组成:链表由一系列结点组成 结点的组成:一个是存储数据…...
<网络安全>《42 网络攻防专业课<第八课 - SQL注入漏洞攻击与防范>》
1 SQL注入漏洞利用及防范 1 SQL注入的地位 2 SQL注入的危害及本质 这些危害包括但不局限于: 数据库信息泄漏:数据库中存放的用户的隐私信息的泄露。网页篡改:通过操作数据库对特定网页进行篡改。网站被挂马,传播恶意软件&#…...
微服务开发工具及环境搭建
后端 安装jdk a. 官网下载b. 安装c. 配置环境变量参考: 博客 安装IDEA a. 官网下载社区版(免费) IntelliJ IDEA Community b. 安装 下载链接 前端 安装node 及 npm 下载链接 安装vscode 下载链接 安装Hbuilderx 下载链接 虚拟机环境 …...
HTML学习笔记——08:表单<form>
HTML <form> 元素表示文档中的一个区域,此区域包含交互控件,用于向 Web 服务器提交信息。 例如:登录页面。 作用:搜集不同类型的用户输入,并向服务器传送数据。 注意:表单本身并不可见!…...
什么是跨端,常用的跨端技术
跨平台是跨操作系统,跨端是指客户端 常见的客户端有,web、android、ios 等,客户端的特点是有界面、由逻辑,所以包含逻辑跨端和渲染跨端。 常用的跨端技术方案 React Native: 由 Facebook 推出的开源框架,…...
【书生·浦语大模型实战营】第6节:OpenCompass 大模型评测(笔记版)
OpenCompass 大模型评测 1.关于评测的三个问题 为什么需要评测:模型选型、能力提升、应用场景效果测评。测什么:知识、推理、语言;长文本、智能体、多轮对话、情感、认知、价值观。怎样测:自动化客观测评、人机交互测评、基于大…...
为什么需要写Java单元测试总结
目录 前言 一、为什么写单元测试 写单测好处 1、提升效率 2、场景覆盖全 单测怎么写 1、集成测试 2、单元测试 Mock框架 1、Mockito单元测试 2、Mockito 中文文档地址 二、强制要求 1.好的单元测试必须遵守AIR原则。 2.单元测试应该是全自动执行的,并…...
Gin框架: 控制器, 中间件的分层设计案例
对控制器的分组与继承 1 )设计项目目录结构 yourGinProject/ 根目录├── go.mod go mod 文件├── go.sum go sum 文件├── main.go main 文件└── tpls html模板目录│ └── web│ │ └── index.html├── routers 路由目录│ …...
Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...
Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
scikit-learn机器学习
# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...
Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成
一个面向 Java 开发者的 Sring-Ai 示例工程项目,该项目是一个 Spring AI 快速入门的样例工程项目,旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计,每个模块都专注于特定的功能领域,便于学习和…...
【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制
目录 节点的功能承载层(GATT/Adv)局限性: 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能,如 Configuration …...
零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程
STM32F1 本教程使用零知标准板(STM32F103RBT6)通过I2C驱动ICM20948九轴传感器,实现姿态解算,并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化,适合嵌入式及物联网开发者。在基础驱动上新增…...
DBLP数据库是什么?
DBLP(Digital Bibliography & Library Project)Computer Science Bibliography是全球著名的计算机科学出版物的开放书目数据库。DBLP所收录的期刊和会议论文质量较高,数据库文献更新速度很快,很好地反映了国际计算机科学学术研…...
