目录
一、接收请求数据
1. 接收表单参数
1.原始方式【了解】
2.SpringBoot方式
3.参数名不一致@RequestParam
2.实体参数
1.简单实体对象
2.复杂实体对象
3.数组集合参数
4.日期参数
3. JSON参数
1.Postman发送JSON数据
2.服务端接收JSON数据
4. 路径参数(rest风格)
二、返回响应结果
1. @ResponseBody
2. 统一响应结果
1.响应结果不规范的问题
2.如何统一返回结果
三、分层解耦
1. 三层架构
2. 分层解耦
四、Spring的IOC&DI
1. IoC和DI介绍
2. IOC&DI入门
3. IOC详解
1.bean的声明
2.bean的名称
4.DI详解
一、接收请求数据
接收HTTP请求的数据,主要是接收客户端提交过来的各种参数。而分析HTTP请求的格式,需要接收的数据主要有:
-
表单参数。客户端在请求行或者请求体里提交的表单参数,格式:
参数名=值&参数名=值&...
如果是GET或DELETE请求,表单参数在请求行里提交过来
如果是POST或PUT请求,表单参数在请求体里提交过来
-
JSON参数。客户端使用axios的post和put方法可以提交json参数,格式:
{"key1":value1, "key2":value2}
-
路径参数。直接在请求路径里携带参数,例如
GET http://locahost:8080/emp/1
,其中的1
就是路径参数表示要查询id为1的员工信息
1. 接收表单参数
简单参数:在向服务器发起请求时,向服务器传递的是一些普通的请求数据。
postman工具我上一篇文章有介绍使用(下面是postman工具的页面)
postman工具可以发送不同方式的请求,浏览器只能发送get请求(所以用这个工具测试)
1.原始方式【了解】
通过Servlet中提供的API:HttpServletRequest
请求对象 来 获取请求信息。它提供了获取表单参数的API:
-
String value = HttpServletRequest对象.getParameter("参数名")
用法
-
在Controller的方法中,直接增加
HttpServletRequest
类型的形参 -
然后在方法里就可以通过该对象来获取请求参数了
@RestController
public class RequestController {
//原始方式
@RequestMapping("/simpleParam")
public String simpleParam(HttpServletRequest request){
// http://localhost:8080/simpleParam?name=Tom&age=10
// 请求参数: name=Tom&age=10 (有2个请求参数)
// 第1个请求参数: name=Tom 参数名:name,参数值:Tom
// 第2个请求参数: age=10 参数名:age , 参数值:10
String name = request.getParameter("name");//name就是请求参数名
String ageStr = request.getParameter("age");//age就是请求参数名
int age = Integer.parseInt(ageStr);//需要手动进行类型转换
System.out.println(name+" : "+age);
return "OK";
}
}
2.SpringBoot方式
Springboot的环境中,对原始的API进行了封装,接收参数的形式更加简单。
用法
-
给Controller里的方法添加形参,一个形参用于接收一个表单参数
-
要求:方法的形参名 和 表单参数名 相同
@RestController
public class RequestController {
// http://localhost:8080/simpleParam?name=Tom&age=10
// 第1个请求参数: name=Tom 参数名:name,参数值:Tom
// 第2个请求参数: age=10 参数名:age , 参数值:10
//springboot方式
@RequestMapping("/simpleParam")
public String simpleParam(String name , Integer age ){//形参名和请求参数名保持一致
System.out.println(name+" : "+age);
return "OK";
}
}
postman测试( GET 请求):
postman测试( POST请求 ):
3.参数名不一致@RequestParam
如果方法形参名称与请求参数名称不一致,controller就接收不到参数值了
用法
-
在方法形参前面加上
@RequestParam("表单参数名")
-
注解的属性说明:
-
value
:用于配置表单参数名。表示要接收哪个表单参数 -
required
:设置为true的话,表示要求客户端必须传这个参数,否则会报错
-
@RestController
public class RequestController {
// http://localhost:8080/simpleParam?name=Tom&age=20
// 请求参数名:name
//springboot方式
@RequestMapping("/simpleParam")
public String simpleParam(@RequestParam("name") String username , Integer age ){
System.out.println(username+" : "+age);
return "OK";
}
}
@RequestMapping("/simpleParam")
public String simpleParam(@RequestParam(name = "name", required = false) String username, Integer age){
System.out.println(username+ ":" + age);
return "OK";
}
2.实体参数
1.简单实体对象
如果请求参数比较多,就需要写大量的方法形参,非常繁琐。
SpringBoot允许我们将多个请求参数封装到一个实体类对象中。
用法
-
给Controller里方法上添加JavaBean形参
-
要求:请求参数名与实体类的属性名相同
/**
User类
*/
public class User {
private String name;
private Integer age;
//get和set方法 略
//toString方法 略
}
---------------------
/**
Controller方法
*/
@RestController
public class RequestController {
//实体参数:简单实体对象
@RequestMapping("/simplePojo")
public String simplePojo(User user){
System.out.println(user);
return "OK";
}
}
Postman测试:
-
参数名和实体类属性名一致时才会正确,不然会变成null
2.复杂实体对象
复杂实体对象指的是,在实体类中有一个或多个属性,也是实体对象类型的。例如User
类中有一个Address
属性(Address也是一个实体类)
用法
-
在Controller里方法中直接添加实体类形参
-
要求:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套实体类属性参数。
/**
Address实体类
*/
public class Address {
private String province;
private String city;
//get和set方法 略
//toString方法 略
}
---------------
/**
User实体类
*/
public class User {
private String name;
private Integer age;
private Address address; //地址对象
//get和set方法 略
//toString方法 略
}
--------------
/**
Controller方法
*/
@RestController
public class RequestController {
//实体参数:复杂实体对象
@RequestMapping("/complexPojo")
public String complexPojo(User user){
System.out.println(user);
return "OK";
}
}
Postman测试
3.数组集合参数
数组集合参数的使用场景:在HTML的表单中,有一个表单项是支持多选的(复选框),可以提交选择的多个值。
1.接收成数组
用法
-
在Controller的方法上直接添加数组形参
-
要求表单参数名与形参名称相同
@RestController
public class RequestController {
//数组集合参数
@RequestMapping("/arrayParam")
public String arrayParam(String[] hobby){
System.out.println(Arrays.toString(hobby));
return "OK";
}
}
Postman测试:
在前端请求时,有两种传递形式:
2.接收成集合
用法
-
在Controller里方法上直接添加集合形参
-
要求表单参数名与形参名相同
-
给形参添加
@RequestParam
注解
默认情况下,请求中参数名相同的多个值,是封装到数组。如果要封装到集合,要使用@RequestParam绑定参数关系
@RestController
public class RequestController {
//数组集合参数
@RequestMapping("/listParam")
//加@RequestParam,不加就会当成 (new 对象) 找属性值,所以会报错
public String listParam(@RequestParam List<String> hobby){
System.out.println(hobby);
return "OK";
}
}
和数组一样的两种请求方式
4.日期参数
因为日期的格式多种多样(如:2022-12-12 10:05:45 、2022/12/12 10:05:45),通过@DateTimeFormat注解,以及其pattern属性来设置日期的格式。
用法
-
在日期类型(Date,LocalDate,LocalDateTime)的参数/属性上,添加注解
@DateTimeFormat(pattern="日期格式")
-
客户端提交的日期格式, 和注解里指定的日期格式 必须是匹配的
@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";
}
}
3. JSON参数
客户端在提交参数时,也会经常使用JSON格式。例如:
-
axios.post("url地址", {"name":"tom", "age":20})
-
axios.put("url地址", {"name":"tom", "age":20})
1.Postman发送JSON数据
2.服务端接收JSON数据
做法
-
Controller的方法中,要求使用实体类封装参数,所以要添加一个实体类形参
-
实体类要求:JSON的键名与实体类属性名相同
-
在方法的形参实体类上加
@RequestBody
注解
/**
Address类
*/
public class Address {
private String province;
private String city;
//get和set方法 略
//toString方法 略
}
----------------
/**
User类
*/
public class User {
private String name;
private Integer age;
private Address address;
//get和set方法 略
//toString方法 略
}
----------------
/**
Controller方法
*/
@RestController
public class RequestController {
//JSON参数
@RequestMapping("/jsonParam")
public String jsonParam(@RequestBody User user){
System.out.println(user);
return "OK";
}
}
4. 路径参数(rest风格)
直接在请求的URL中传递参数,这样的参数称为“路径参数”或“路径变量”。例如:
-
GET http://localhost:8080/user/1 其中的1是提交的参数
做法
-
前端:通过请求URL直接传递参数
-
后端:
-
在Controller的方法上标识请求路径时,使用
{…}
占位符来匹配路径参数;有几个参数,就写几个占位符 -
有几个路径参数要接收,就在Controller的方法上添加几个形参,每个形参加
@PathVariable("占位符名")
获取路径参
-
@RestController
public class RequestController {
//路径参数
@RequestMapping("/path/{id}")
public String pathParam(@PathVariable Integer id){
System.out.println(id);
return "OK";
}
}
postman测试
多参数实例:
@RestController
public class RequestController {
//路径参数
@RequestMapping("/path/{id}/{name}")
public String pathParam2(@PathVariable Integer id, @PathVariable String name){
System.out.println(id+ " : " +name);
return "OK";
}
}
二、返回响应结果
1. @ResponseBody
@ResponseBody
注解用于加在Controller类上,也可以加在方法上。它的作用是:
-
将方法返回值直接响应给浏览器
-
如果返回值类型是实体对象/集合,将会转换为JSON格式,然后再响应给浏览器
SpringBootWeb提供了一个 @RestController
,加在Controller类上。这一个注解相当于@Controller
+ @ResponseBody
-
@Controller
:加在Controller类上,SpringBoot才会扫描、识别这个类,才能被客户端访问到 -
@ResponseBody
:加在Controller类上,相当于类里所有方法都加了@ResponseBody
所以,只要Controller类上加了@RestController
,我们就不用关心响应结果的注解了
@RestController源码:
@Target({ElementType.TYPE}) //元注解(修饰注解的注解)
@Retention(RetentionPolicy.RUNTIME) //元注解
@Documented //元注解
@Controller
@ResponseBody
public @interface RestController {
@AliasFor(
annotation = Controller.class
)
String value() default "";
}
案例:
@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;
}
}
2. 统一响应结果
1.响应结果不规范的问题
在我们前面所编写的这些Controller方法中,返回值各种各样,没有任何的规范。如果我们开发一个大型项目,项目中controller方法将成千上万,使用这种方式将造成整个项目难以维护。
2.如何统一返回结果
在真实的项目开发中,无论是哪种方法,我们都会定义一个统一的返回结果。前端只需要按照统一格式的返回结果进行解析(仅一种解析方案),就可以拿到数据
统一的返回结果使用类来描述,在这个结果中包含:(重点)
-
响应状态码:当前请求是成功,还是失败
-
状态码信息:给页面的提示信息
-
返回的数据:给前端响应的数据(字符串、对象、集合)
/**
实体类
*/
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;
}
//get和set方法 略
//增删改 成功响应(不需要给前端返回数据)
public static Result success(){
return new Result(1,"success",null);
}
//查询 成功响应(把查询结果做为返回数据响应给前端)
public static Result success(Object data){
return new Result(1,"success",data);
}
//失败响应
public static Result error(String msg){
return new Result(0,msg,null);
}
}
-----------------
/**
Controller类
*/
@RestController
public class ResponseController {
//响应统一格式的结果
@RequestMapping("/hello")
public Result hello(){
System.out.println("Hello World ~");
//return new Result(1,"success","Hello World ~");
return Result.success("Hello World ~");
}
//响应统一格式的结果
@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);
}
}
三、分层解耦
1. 三层架构
从组成上看可以分为三个部分:
-
数据访问:负责业务数据的维护操作,包括增、删、改、查等操作。
-
逻辑处理:负责业务逻辑处理的代码。
-
请求处理、响应数据:负责,接收页面的请求,给页面响应数据。
按照上述的三个组成部分,在我们项目开发中呢,可以将代码分为三层:
-
Controller:控制层。接收前端发送的请求,对请求进行处理,并响应数据。
-
Service:业务逻辑层。处理具体的业务逻辑。
-
Dao:数据访问层(Data Access Object),也称为持久层。负责数据访问操作,包括数据的增、删、改、查。
基于三层架构的程序执行流程:
-
前端发起的请求,由Controller层接收(Controller响应数据给前端)
-
Controller层调用Service层来进行逻辑处理(Service层处理完后,把处理结果返回给Controller层)
-
Serivce层调用Dao层(逻辑处理过程中需要用到的一些数据要从Dao层获取)
-
Dao层操作文件中的数据(Dao拿到的数据会返回给Service层)
三层架构的好处:
-
复用性强
-
便于维护
-
利用扩展
2. 分层解耦
-
内聚:软件中各个功能模块内部的功能联系。(就是模块之间都可以调用)
-
耦合:衡量软件中各个层/模块之间的依赖、关联的程度。(功能和模块都写在一块)
软件设计原则:高内聚低耦合。
高内聚指的是:一个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高,即 "高内聚"。
低耦合指的是:软件中各个层、模块之间的依赖关联程序越低越好
四、Spring的IOC&DI
1. IoC和DI介绍
控制反转: Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。
对象的创建权由程序员主动创建转移到容器(由容器创建、管理对象)。这个容器称为:IOC容器或Spring容器
依赖注入: Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。
程序运行时需要某个资源,此时容器就为其提供这个资源。
例:EmpController程序运行时需要EmpService对象,Spring容器就为其提供并注入EmpService对象
IOC容器中创建、管理的对象,称之为:bean对象
2. IOC&DI入门
-
删除Controller层、Service层中new对象的代码
-
Service层及Dao层的实现类,交给IOC容器管理
-
为Controller及Service注入运行时依赖的对象
-
Controller程序中注入依赖的Service层对象
-
Service程序中注入依赖的Dao层对象
-
代码:
/**
Controller层
*/
@RestController
public class EmpController {
@Autowired //运行时,从IOC容器中获取该类型对象,赋值给该变量
private EmpService empService ;
@RequestMapping("/listEmp")
public Result list(){
//1. 调用service, 获取数据
List<Emp> empList = empService.listEmp();
// 响应数据
return Result.success(empList);
}
}
-------------
/**
Service层
*/
@Component //将当前对象交给IOC容器管理,成为IOC容器的bean
public class EmpServiceA implements EmpService {
@Autowired //运行时,从IOC容器中获取该类型对象,赋值给该变量
private EmpDao empDao ;
@Override
public List<Emp> listEmp() {
//1. 调用dao, 获取数据
List<Emp> empList = empDao.listEmp();
//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("就业指导");
}
});
return empList;
}
}
-------------------
/**
Dao层
*/
@Component //将当前对象交给IOC容器管理,成为IOC容器的bean
public class EmpDaoA implements EmpDao {
@Override
public List<Emp> listEmp() {
//1. 加载并解析emp.xml
String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
System.out.println(file);
List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
return empList;
}
}
3. IOC详解
1.bean的声明
哪个类需要让Spring帮我们创建对象,就给类上加注解,Spring将会帮我们创建对象放到容器。这个过程叫:bean的注册或者bean的声明
bean:就是Spring创建出来的对象,起的称呼
/**
Controller层
*/
@RestController //@RestController = @Controller + @ResponseBody
public class EmpController {
@Autowired //运行时,从IOC容器中获取该类型对象,赋值给该变量
private EmpService empService ;
@RequestMapping("/listEmp")
public Result list(){
//1. 调用service, 获取数据
List<Emp> empList = empService.listEmp();
// 响应数据
return Result.success(empList);
}
}
----------------
/**
Service层
*/
@Service
public class EmpServiceA implements EmpService {
@Autowired //运行时,从IOC容器中获取该类型对象,赋值给该变量
private EmpDao empDao ;
@Override
public List<Emp> listEmp() {
//1. 调用dao, 获取数据
List<Emp> empList = empDao.listEmp();
//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("就业指导");
}
});
return empList;
}
}
--------------
/**
Dao层
*/
@Repository
public class EmpDaoA implements EmpDao {
@Override
public List<Emp> listEmp() {
//1. 加载并解析emp.xml
String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
System.out.println(file);
List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
return empList;
}
}
2.bean的名称
在IOC容器中,每一个Bean都有一个属于自己的名字,可以通过注解的value属性指定bean的名字。如果没有指定,默认为类名首字母小写。
注意事项:
-
声明bean的时候,可以通过value属性指定bean的名字,如果没有指定,默认为类名首字母小写。
-
使用以上四个注解都可以声明bean,但是在springboot集成web开发中,声明控制器bean只能用@Controller。
4.DI详解
Dependency Inject,依赖注入。
DI依赖注入,需要什么对象,Spring就从它的容器里查找并给我们提供好对象,赋值进来
依赖注入相关的注解:
使用@Primary注解:当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现
使用@Qualifier注解:指定当前要注入的bean对象。 在@Qualifier的value属性中,指定注入的bean的名称。
-
@Qualifier注解不能单独使用,必须配合@Autowired使用
使用@Resource注解:是按照bean的名称进行注入。通过name属性指定要注入的bean的名称
面试题 : @Autowird 与 @Resource的区别
-
@Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解
-
@Autowired 默认是按照类型注入,而@Resource是按照名称注入