Spring Boot入门
介绍
官网: spring.io
Spring发展到今天已经形成了一种开发生态圈, Spring提供了若干个子项目, 每个项目用于完成特定的功能
快速入门
需求: 使用SpringBoot开发一个web应用, 浏览器发起请求 /hello后, 给浏览器返回字符串"Hello World"
步骤:
- 创建springboot工程, 并勾选web开发相关依赖
- 定义HelloController类, 添加方法hello, 并添加注解
- 运行测试
在com.tyrant.hello包下创建HelloControl请求处理类
package com.tyrant.hello;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
//请求处理类
@RestController
public class HelloController {
@RequestMapping("/hello")
public String test1(){
System.out.println("Hello Spring");
return "Hello Spring";
}
}
创建时遇到的一些问题:
- 创建时版本选择了3.x版本, 导致默认的jdk版本为17, 运行时报错, 应该选择2.x版本
- 删除时直接删除了本地文件, 没有删除项目中的模块, 然后创建之前同名的模块时发现报模块已存在, 无法创建, 解决方法是在项目结构中将模块删除
- 引入
@RestControll
注解时放的位置不对,,,,是我太粗心 - 我将创建spring脚手架模块时Server URL改为了阿里云的镜像源: https://start.aliyun.com/
HTTP协议
介绍
概念: Hyper Text Transfer Protocol, 超文本传输协议, 规定了浏览器和服务器之间数据传输的规则
特点:
- 基于TCP协议, 面向连接, 安全
- 基于请求-响应模型的: 一次请求对应一次响应
- HTTP协议是无状态的协议: 对于事务处理没有记忆能力, 每次请求-响应都是独立的
- 缺点: 多次请求间不能共享数据
- 优点: 速度快
请求协议
HTTP请求数据格式
-
请求行: 请求数据第一行(请求方式, 资源路径, 协议)
-
请求头: 第二行开始, 格式key: val
key value 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 请求主体的大小 -
请求体: POST请求独有, 存放请求参数
请求方式-GET: 请求参数在请求行中, 没有请求体, 如/brand/findAll?name=OPPO&status=1 GET请求大小是有限制的
请求方式-POST: 请求参数在请求体中, POST请求大小是没有限制的
响应协议
HTTP相应格式
- 响应行: 响应数据第一行(协议, 状态码, 描述)
- 响应头: 第二行开始, 格式key: value
- 响应体: 最后一部分,存放响应数据
状态码分类 | 描述 |
---|---|
1xx | 响应中-临时状态码, 表示请求已接收, 告诉客户端应该继续请求或者如果它已完成则忽略它 |
2xx | 成功-表示请求已经被成功接收, 处理已完成 |
3xx | 重定向-重定向到其他地方, 让客户端再发起一次请求以完成整个处理 |
4xx | 客户端错误-处理发生错误, 责任在客户端, 如: 请求了不存在的资源, 客户端未被授权, 禁止访问等 |
5xx | 服务器错误-处理发生错误, 责任在服务端, 如: 程序抛出异常等 |
状态码大全: https://cloud.tencent.com/developer/chapter/13553
key | value |
---|---|
Content-Type | 表示该响应内容的类型, 例如text/html, application/json |
Content-Length | 表示该相应内容的长度(字节数) |
Content-Encoding | 表示该相应压缩算法, 例如gzip |
Cache-Control | 指示客户端应如何缓存, 例如max-age=300表示可以最多缓存300秒 |
Set-Cookie | 告诉浏览器为当前页面所在的域设置cookie |
Tomcat
介绍
web服务器
- 对HTTP协议操作进行封装, 简化web程序开发
- 部署web项目, 对外提供网上信息浏览服务
概念:
- Tomcat时Apache软件基金会的一个核心项目, 是一个开源免费的轻量级Web服务器, 支持Servlet/JSP少量JavaEE规范
JavaEE:
- Java Enterprise Edition, Java企业版, 指Java企业级开发的技术规范总和, 包含13项技术规范: JDBC, JNDI, EJB, RMI, JSP, Servlet, XML, JMS, Java IDL, JTS, JTA, JavaMail, JAF
Tomcat也被称为Web容器, Servlet容器, Servlet程序需要依赖于Tomcat才能运行
官网:
基本使用
配置Tomcat端口号(conf/server.xml)
<!-- 69行, 配置port -->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
部署
将项目放置到webapps目录下, 即部署完成
入门程序解析(内嵌tomcat)
起步依赖:
- spring-boot-starter-web: 包含了web应用开发所需要的常见依赖
- spring-boot-starter-test: 包含了单元测试所需要的常见依赖
内嵌Tomcat服务器
- 基于Springboot开发的web应用程序, 内置了tomcat服务器, 当启动类运行时, 会自动启动内嵌的tomcat服务器
请求响应
请求
Postman
介绍: Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件
作用: 常用于进行接口测试
安装: 搜索postman官网下载安装即可
简单参数
原始方式: 在原始的web程序中, 获取请求参数, 需要通过HttpServletRequest对象手动获取
SpringBoot方式:
- 简单参数只要参数名与形参变量名相同, 定义形参即可接收参数
- 如果方法形参名称与请求参数名称不匹配, 可以使用@RequestParam完成映射
注意: @PequestParam中的required属性默认为true, 代表该请求参数必须传递, 如果不传递将报错, 如果该参数是可选的, 可以将required属性设置为false
/*测试请求参数接收*/
@RestController
public 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方式
/* @RequestMapping("/simpleParam")
public String simpleParam(String name , Integer age){
System.out.println(name + ": " + age);
return "OK";
}*/
@RequestMapping("/simpleParam")
public String simpleParam(@RequestParam(name="name") String username , Integer age){
System.out.println(username + ": " + age);
return "OK";
}
}
实体参数
简单实体对象: 请求参数名与形参对象名相同, 定义POJO接收即可
POJO: Plain Old Java Object
复杂实体对象: 请求参数名与形参对象属性名相同, 按照对象层次结构即可接收嵌套POJO属性参数
//简单实体参数
@RequestMapping("/simplePojo")
public String simplePojo(User user){
System.out.println(user);
return "OK";
}
//复杂实体参数
@RequestMapping("/complexPojo")
public String complexPojo(User user){
System.out.println(user);
return "OK";
}
数组集合参数
数组参数: 请求参数名与型参数组名称相同且请求参数为多个, 定义数组类型形参即可接收参数
集合参数: 请求参数名与形参集合名称相同且请求参数为多个, @RequestParam 绑定参数关系
//数组参数
@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";
}
日期参数
日期参数: 使用@DataTimeFormat 注解完成日期参数格式转换
//时间参数
@RequestMapping("/dataParam")
//注意时间的格式
public String dataParam(@DateTimeFormat (pattern = "yyyy-MM-dd HH:mm:ss")LocalDateTime updateTime){
System.out.println(updateTime);
return "OK";
}
JSON参数
JSON参数: JSON数据键名与形参对象属性名相同, 定义POJO类型形参即可接收参数, 需要使用@RequsetBody标识符
//JSON参数
@RequestMapping("/jsonParam")
public String jsonParam(@RequestBody User user){
System.out.println(user);
return "OK";
}
路径参数
路径参数: 通过请求URL直接传递参数, 使用{...}来标识该路径参数, 需要使用@PathVariable 获取路径参数
//路径参数
@RequestMapping("/path/{id}")
public String pathParam(@PathVariable Integer id){
System.out.println(id);
return "OK";
}
@RequestMapping("/path/{id}/{name}")
public String pathParam(@PathVariable Integer id, @PathVariable String name){
System.out.println(id+"/"+name);
return "OK";
}
响应
响应数据的核心注解@ResponseBody
类型: 方法注解, 类注解
位置: Controller方法上/类上
作用: 将方法返回值直接响应, 如果返回值类型是实体对象或者集合, 将会转换为JSON格式响应
说明: @RestController = @Controller + @ResponseBody
@RestController
public class ResponseController {
@RequestMapping("/hello")
public String hello(){
System.out.println("hello spring");
return "hello spring";
}
@RequestMapping("/getAddr")
public Address getAddr(){
Address addr = new Address();
addr.setCity("深圳");
addr.setProvince("广东");
System.out.println(addr);
return addr;
}
@RequestMapping("/listAddr")
public List<Address> listAddr(){
List<Address> list = new ArrayList<>();
Address addr = new Address();
addr.setCity("深圳");
addr.setProvince("广东");
Address addr2 = new Address();
addr2.setCity("深圳");
addr2.setProvince("广东");
list.add(addr);
list.add(addr2);
System.out.println(list);
return list;
}
}
统一响应结果: Result(code, msg, data)
public class Result {
private Integer code;//1: 成功, 0: 失败
private String msg;//提示信息
private Object data;//数据 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);
}
@Override
public String toString() {
return "Result{" +
"code=" + code +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
}
@RestController
public class ResponseController {
@RequestMapping("/hello")
public Result hello(){
System.out.println("hello spring");
//return new Result(1, "success", "hello spring");
return Result.success("hello spring");
}
@RequestMapping("/getAddr")
public Result getAddr(){
Address addr = new Address();
addr.setCity("深圳");
addr.setProvince("广东");
System.out.println(addr);
return Result.success(addr);
}
@RequestMapping("/listAddr")
public Result listAddr(){
List<Address> list = new ArrayList<>();
Address addr = new Address();
addr.setCity("深圳");
addr.setProvince("广东");
Address addr2 = new Address();
addr2.setCity("深圳");
addr2.setProvince("广东");
list.add(addr);
list.add(addr2);
System.out.println(list);
return Result.success(list);
}
}
案例
需求: 获取员工数据, 返回同意响应数据, 在页面渲染展示
步骤:
- 在pom.xml文件中引入dom4j的依赖, 用于解析XML文件
- 引入资料中提供的解析XML的工具类XMLParserUtils, 对应的实体类Emp, XML文件emp.xml
- 引入资料中提供的静态页面文件, 放在resources下的static目录下
- 编写Controler程序, 处理请求, 响应数据
分层解耦
三层架构
controller: 控制层, 接受前端发送的请求, 对请求进行处理, 并响应数据
service: 业务逻辑层, 处理具体的业务逻辑
dao: 数据访问层(Data Access Object) (持久层), 负责数据访问操作, 包括数据的增删改查
分层解耦
内聚: 软件中各个功能模块内部的功能联系
耦合: 衡量软件中各个层/模块之间的依赖, 关联的成都
软件设计原则: 高内聚, 低耦合
控制反转: Inversion Of Control, 简称IOC, 对象的创建控制权由程序自身转移到外部(容器), 这种思想称为控制反转
依赖注入: Denpendency Injection, 简称DI, 容器为应用程序提供运行时, 所依赖的资源, 称之为依赖注入
Bean对象: IOC容器中创建, 管理的对象, 称之为bean
IOC & DI 入门
步骤:
- Service层及Dao层的实现类, 交给IOC层容器处理
- 为Controller及Service注入运行时, 依赖的对象
- 运行测试
IOC详解
注解 | 说明 | 位置 |
---|---|---|
@Component | 声明bean的基础注解 | 不属于以下三类时, 用此注解 |
@Controller | @Component的衍生注解 | 标注在控制器上 |
@Service | @Component的衍生注解 | 标注在业务层上 |
@Repository | @Component的衍生注解 | 标注在数据访问类上(由于与mybatis整合, 用的少) |
注意事项:
- 声明bean的时候, 可以通过value属性指定bean的名字, 如果没有指定, 默认为类名首字母小写
- 使用以上四个注解都可以声明bean, 但是springboot集成web开发中, 声明控制器bean只能用@Controller
Bean组件扫描
- 前面声明bean的四大注解, 想要生效, 还需要被组件扫描注解@ComponentScan扫描
- @ComponentScan注解虽然没有显式配置, 但是实际上已经包含在了启动类声明注解@SpringBootApplication中, 默认扫描的范围是启动类所在包及其子包
解决方案一: 手动设置扫描包
解决方案二: 按照规范放置包的位置
DI详解
@Autowired注解, 默认是按照类型进行, 如果存在多个相同类型的bean, 将会报出如下错误
Field empService in com.tyrant.controller.EmpController required a single bean, but 2 were found:
- empServiceA: defined in file [D:\Code\java\webProject01\springboot-quickstart\target\classes\com\tyrant\service\impl\EmpServiceA.class]
- empServiceB: defined in file [D:\Code\java\webProject01\springboot-quickstart\target\classes\com\tyrant\service\impl\EmpServiceB.class]
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
解决方案:
-
@Primary指定想要生效的类
@Primary @Service Public class EmpServiceA implement EmpService{}
-
@Autowired + @Qualifier指定想要装载的bean对象
@RestController public class EmpController{ @Autowired @Qualifier("empServiceA") private EmpService empService; }
-
@Resource(name = "bean的名称")
@RestController public class EmpController{ @Resource(name = "empServiceB") private EmpService empService; }
@Resource 与Autowired区别
- @Autowired是spring框架提供的注解, 而@Resource是JDK提供的注解
- @Autowired默认是按照类型注入, 而@Resource默认是按照名称注入