热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

第二十天SpringBootWeb请求、响应、分层解耦

目录SpringBootWeb请求响应

目录

SpringBootWeb请求响应

前言

1. 请求

1.1 Postman

1.2 简单参数

1.3 实体参数

1.4 数组集合参数

1.5 日期参数

1.6 JSON参数

1.7 路径参数

2. 响应

2.1 介绍

2.2 @ResponseBody

2.3 统一响应结果

2.4 案例

3. 分层解耦

3.1 三层架构

3.2 分层解耦

3.3 IOC&DI



SpringBootWeb请求响应

前言

在上一次的课程中,我们开发了springbootweb的入门程序。 基于SpringBoot的方式开发一个web应用,浏览器发起请求 /hello 后 ,给浏览器返回字符串 “Hello World ~”。

其实呢,是我们在浏览器发起请求,请求了我们的后端web服务器,也就是内置的Tomcat。而我们在开发web程序时呢,定义了一个控制器类Controller,请求会被部署在Tomcat中的Controller接收,然后Controller再给浏览器一个响应,响应一个字符串 “Hello World”。 而在请求响应的过程中是遵循HTTP协议的。

但是呢,这里要告诉大家的时,其实在Tomcat这类Web服务器中,是不识别我们自己定义的Controller的。但是我们前面讲到过Tomcat是一个Servlet容器,是支持Serlvet规范的,所以呢,在tomcat中是可以识别 Servlet程序的。 那我们所编写的XxxController 是如何处理请求的,又与Servlet之间有什么联系呢?

其实呢,在SpringBoot进行web程序开发时,它内置了一个核心的Servlet程序 DispatcherServlet,称之为 核心控制器。 DispatcherServlet 负责接收页面发送的请求,然后根据执行的规则,将请求再转发给后面的请求处理器Controller,请求处理器处理完请求之后,最终再由DispatcherServlet给浏览器响应数据。
 


 

那将来浏览器发送请求,会携带请求数据,包括:请求行、请求头;请求到达tomcat之后,tomcat会负责解析这些请求数据,然后呢将解析后的请求数据会传递给Servlet程序的HttpServletRequest对象,那也就意味着 HttpServletRequest 对象就可以获取到请求数据。 而Tomcat,还给Servlet程序传递了一个参数 HttpServletResponse,通过这个对象,我们就可以给浏览器设置响应数据 。

可以自己实验,体验一下原生Serverlet开发:

实验步骤:

1、创建类HelloServerlet继承 HttpServerlet,重写service方法

2、在启动类上加上一个注解:@ServletComponentScan

package com.itheima.springbootquikstart;import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @author HuanLe* @version 1.0*/
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {@Overridepublic void service(HttpServletRequest request, HttpServletResponse response) throws IOException {String username = request.getParameter("username");String age = request.getParameter("age");System.out.println("收到数据:" + username + " - " + age);response.setHeader("content-type", "text/html;charset=utf8");response.getWriter().println("hello Servlet哈哈!!");response.getWriter().println("响应接收到的数据: " + username + " " + age);}
}

执行后到效果:

那上述所描述的这种浏览器/服务器的架构模式呢,我们称之为:BS架构。


 

• BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。

那今天呢,我们的课程内容主要就围绕着:请求、响应进行。 今天课程内容,主要包含三个部分:

  • 请求
  • 响应
  • 分层解耦

1. 请求

在本章节呢,我们主要讲解,如何接收页面传递过来的请求数据。

1.1 Postman
 

1.1.1 介绍
 

  • Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件。
  • 作用:常用于进行接口测试
  • 特征

    • 简单
    • 实用
    • 美观
    • 大方

1.1.2 安装

百度网盘 请输入提取码百度网盘为您提供文件的网络备份、同步和分享服务。空间大、速度快、安全稳固,支持教育网加速,支持手机端。注册使用百度网盘即可享受免费存储空间

界面介绍:

如果我们需要将测试的请求信息保存下来,就需要创建一个postman的账号,然后登录之后才可以。

1.2 简单参数

1.2.1 原始方式

通过Servlet中提供的API HttpServletRequest 可以获取请求的相关信息,比如获取请求参数:

@RequestMapping("/simpleParam")
public String simpleParam(HttpServletRequest request){String name = request.getParameter("name");String age = request.getParameter("age");System.out.println(name+" : "+age);return "OK";
}


 

在Controller中,我们要想获取Request对象,可以直接在方法的形参中声明 HttpServletRequest 对象。然后就可以通过该对象来获取请求信息:
 

获取请求参数:request.getParameter("参数名")

postman测试:

1.2.2 SpringBoot方式

在Springboot的环境中,对原始的API进行了封装,接收参数的形式更加简单。 如果是简单参数,参数名与形参变量名相同,定义同名的形参即可接收参数。

@RequestMapping("/simpleParam")
public String simpleParam(String name , Integer age ){System.out.println(name+" : "+age);return "OK";
}

postman测试( GET 请求):

postman测试( POST请求 ):

我们可以点击上面的菜单栏: View ----> Show Postman Console

1.2.3 参数名不一致

如果方法形参名称与请求参数名称不一致,可以使用 @RequestParam 完成映射。
 

如果请求参数名为 username,controller方法形参使用name进行接收,是不能够直接封装的,需要在方法形参前面加上 @RequestParam 然后通过value属性执行请求参数名,从而完成映射,代码如下:

@RequestMapping("/simpleParam")
public String simpleParam(@RequestParam("username") String name , Integer age){System.out.println(name+" : "+age);return "OK";
}

1.3 实体参数

如果请求参数比较多,通过上述的方式一个参数一个参数的接收,会比较繁琐。 此时,我们可以考虑将请求参数封装到一个 pojo 对象中。 此时,要想完成数据封装,需要遵守如下规则:请求参数名与POJO的属性名相同

1.3.1 简单实体对象
 

1). 定义POJO实体类

public class User {private String name;private 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 +'}';}
}

lombom使用

在pom清单加依赖


//
// org.projectlombok
// lombok
//
//使用:import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/*** @author HuanLe* @version 1.0*/
@Data@NoArgsConstructor//无参构造@AllArgsConstructor//满参构造器@EqualsAndHashCode//hashCode和equalspublic class User {private String name;private Integer age;// 为什么不用int?使用Integer的原因是,如果前端没有穿数据默认是null。如果使用int默认为0.//构造器//getter/setter//toString//让lombok去完成}

2). controller 方法

@RequestMapping("/simplePojo")
public String simplePojo(User user){System.out.println(user);return "OK";
}

3). postman

1.3.2 复杂实体对象

复杂实体对象指的是,在实体类中有一个或多个属性,也是实体类型的。如下:

public class Address {private String province;private 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 + '\'' +'}';}
}

public class User {private String name;private Integer age;private Address address;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;}public Address getAddress() {return address;}public void setAddress(Address address) {this.address = address;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +", address=" + address +'}';}
}

复杂实体对象的封装,需要遵守如下规则:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套POJO属性参数。

1). Controller 方法

@RequestMapping("/complexPojo")
public String complexPojo(User user){System.out.println(user);return "OK";
}

2). Postman

1.4 数组集合参数

1.4.1 数组

  • 数组参数:请求参数名与形参对象属性名相同且请求参数为多个,定义数组类型形参即可接收参数

1). Controller方法

@RequestMapping("/arrayParam")
public String arrayParam(String[] hobby){System.out.println(Arrays.toString(hobby));return "OK";
}

2). Postman

在前端请求时,有两种传递形式:

方式一: xxxxxxxxxx? hobby=game&hobby=java

方式二:xxxxxxxxxxxxx?hobby=game,java

1.4.2 集合

  • 集合参数:请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam 绑定参数关系

1). Controller方法

@RequestMapping("/listParam")
public String listParam(@RequestParam List hobby){System.out.println(hobby);return "OK";
}

2). Postman

在前端请求时,有两种传递形式:

方式一: xxxxxxxxxx? hobby=game&hobby=java

方式二:xxxxxxxxxxxxx?hobby=game,java

1.5 日期参数

上述演示的都是一些普通的参数,在一些特殊的需求中,可能会涉及到日期类型数据的封装。比如,如下需求:

那对于日期类型的参数在进行封装的时候,需要通过 @DateTimeFormat注解,以及其中的pattern属性来设置日期的格式。因为日期的格式多种多样,请求pattern属性中如何制定,前端传递参数时就怎么传递。

1). Controller方法

@RequestMapping("/dateParam")
public String dateParam(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime){System.out.println(updateTime);return "OK";
}

2). Postman

1.6 JSON参数
 

其实呢,在前后端进行交互时,如果是比较复杂的参数,前后端通过会使用JSON格式的数据进行传输。 而传递json格式的参数,服务端,在Controller中,我们通常会使用实体类进行封装。 具体的封装规则如下:
 

JSON数据键名与形参对象属性名相同,定义POJO类型形参即可接收参数。需要使用 @RequestBody 标识。

1). 实体类

public class Address {private String province;private String city;//省略GET , SET 方法
}public class User {private String name;private Integer age;private Address address;//省略GET , SET 方法
}

2). Controller方法

@RequestMapping("/jsonParam")
public String jsonParam(@RequestBody User user){System.out.println(user);return "OK";
}

3). Postman

注意:在测试时,要使用post请求,并且到body中写JSON字符串

如下:

1.7 路径参数

处理上述演示的在请求体传递参数,以及在请求的url后面通过 ?xxx=xxx 的形式传递参数以外,在现在的开发中,经常还会直接在请求的URL中传递参数。比如:

http://localhost:8080/user/1

http://localhost:880/user/1/0

上述的这种传递请求参数的形式呢,我们称之为:路径参数。

  • 路径参数:通过请求URL直接传递参数,使用{…}来标识该路径参数,需要使用 @PathVariable 获取路径参数

1). Controller方法

@RequestMapping("/path/{id}")
public String pathParam(@PathVariable Integer id){System.out.println(id);return "OK";
}@RequestMapping("/path/{id}/{name}")
public String pathParam2(@PathVariable Integer id, @PathVariable String name){System.out.println(id+ " : " +name);return "OK";
}

2). Postman

2. 响应

2.1 介绍

在上述的方法中,我们在测试的时候,统一给页面响应了一个简单的字符串 "OK"。 其实,我们也可以直接将一个实体对象,或者一个集合直接响应回去。

比如如下这样:

A. 响应字符串 OK

@RequestMapping("/simpleParam")
public String simpleParam( String name , Integer age){System.out.println(name+" : "+age);return "OK";
}

B. 响应实体对象

@RequestMapping("/getUser")
public User getUser(){User user = new User();user.setName("Tom");user.setAge(10);return user;
}

C. 返回集合数据

@RequestMapping("/list")
public List list(){User user &#61; new User();user.setName("Tom");user.setAge(10);List userList &#61; new ArrayList<>();userList.add(user);return userList;
}

那在服务端&#xff0c;我们直接响应了一个对象 或者 集合&#xff0c;那最终前端获取到的数据是什么样子的呢&#xff1f;我们可以测试一下&#xff0c;通过postman发送请求&#xff0c;测试效果如下&#xff1a;

我们响应的是一个java对象 或者 集合&#xff0c; 怎么最终返回的确实JSON格式的数据呢 &#xff1f; 其实啊&#xff0c;这是 &#64;ResponseBody 注解的作用。

2.2 &#64;ResponseBody

  • 名称&#xff1a;&#64;ResponseBody
  • 类型&#xff1a;方法注解、类注解
  • 位置&#xff1a;SpringMVC控制器方法上/类上
  • 作用&#xff1a;将当前方法返回值直接返回&#xff0c;如果是 实体/集合 转换为JSON返回

而我们的案例中&#xff0c;并没有直接使用 &#64;ResponseBody&#xff0c;原因是因为我们使用的是 &#64;RestController注解&#xff0c;该注解中已经封装了&#64;ResponseBody注解&#xff0c;已经包含了&#64;ResponseBody注解的作用&#xff0c;我们无需要额外添加。

2.3 统一响应结果
 

但是呢&#xff0c;我们发现&#xff0c;我们在上述所编写的这些个Controller的方法&#xff0c;返回值各种各样&#xff0c;没有任何的规范。如果我们开发一个大型项目也是这样&#xff0c;那整个项目将难以维护。那在真实的项目开发中是什么样子的呢&#xff1f;

在真实的项目开发中&#xff0c;无论是增删改查的那种方法&#xff0c;我们都会定义一个统一的返回结果&#xff0c;在这个返回结果中&#xff0c;包含一下信息&#xff1a;
 

A. 当前请求是成功&#xff0c;还是失败。

B. 当前给页面的提示信息。
 

C . 返回的数据。
 

对于上述的这些数据呢&#xff0c;我们一般都会定义在一个实体类Result中。 代码如下&#xff1a;

public class Result {private Integer code;//响应码&#xff0c;1 代表成功; 0 代表失败private String msg; //响应码 描述字符串private Object data; //返回的数据public Result() { }public Result(Integer code, String msg, Object data) {this.code &#61; code;this.msg &#61; msg;this.data &#61; data;}public Integer getCode() {return code;}public void setCode(Integer code) {this.code &#61; code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg &#61; msg;}public Object getData() {return data;}public void setData(Object data) {this.data &#61; data;}//增删改 成功响应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);}
}

2.4 案例

2.4.1 需求说明
 

获取用户数据&#xff0c;返回统一响应结果&#xff0c;在页面渲染展示
 

  • 需求&#xff1a;加载并解析xml文件&#xff0c;完成省、市数据转换&#xff0c;并完成页面展示。

2.4.2 准备工作

  • XML文件&#xff0c;已经准备好&#xff0c;直接导入进来&#xff0c;放在 src/main/resources 目录下。
  • 解析XML文件的工具类&#xff0c;已经准备好&#xff0c;无需自己实现&#xff0c;直接在 创建一个包 com.itheima.utils &#xff0c;然后将工具类拷贝进来。
  • 前端页面资源&#xff0c;已经准备好&#xff0c;直接拷贝进来&#xff0c;放在 src/main/resources 下的static目录下。

在SpringBoot项目中&#xff0c;静态资源可以存放的目录&#xff1a;

"classpath:/META-INF/resources/"

"classpath:/resources/"

"classpath:/static/"

"classpath:/public/"

classpath&#xff1a;

代表的是类路径&#xff0c;在maven的项目中&#xff0c;其实指的就是 src/main/resources 或者 src/main/java&#xff0c;但是java目录是存放java代码的&#xff0c;所以相关的配置文件及静态资源文档&#xff0c;就放在 src/main/resources下。

2.4.3 代码实现

import com.itheima.springbootquikstart.pojo.Address;
import com.itheima.springbootquikstart.pojo.Result;
import com.itheima.springbootquikstart.pojo.User;
import com.itheima.springbootquikstart.util.XmlParserUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;/*** &#64;author HuanLe* &#64;version 1.0*/&#64;RestController
public class UserController {&#64;RequestMapping("/listUser")public Result listUser() {//1. 获取数据String file &#61; ClassLoader.getSystemResource("user.xml").getFile();//调用工具类去解析List list &#61; XmlParserUtils.parse(file);//2.处理数据&#xff08;省后面加 省/市 &#xff0c; 市加 市/区&#xff09;for (User user : list) {//获取用户的地址Address address &#61; user.getAddress();//省address.setProvince(address.getProvince() &#43; "省/市");address.setCity(address.getCity() &#43; "市/区");}//3.将数据封装到Result对象中,返回return Result.success(list);}}

2.4.4 测试
 

代码编写完毕之后&#xff0c;我们就可以运行引导类&#xff0c;启动服务进行测试了。 打开浏览器&#xff0c;在浏览器地址栏输入&#xff1a;
 

http://localhost:8080/user.html

2.4.5 问题分析

上述案例的功能&#xff0c;我们虽然已经实现&#xff0c;但是呢&#xff0c;我们会发现案例中&#xff1a;解析XML数据&#xff0c;获取数据的代码&#xff0c;处理数据的逻辑的代码&#xff0c;给页面响应的代码全部都堆积在一起了&#xff0c;全部都写在控制器Controller中了。 当然这个业务逻辑还是比较简单的&#xff0c;如果业务逻辑再稍微复杂一点&#xff0c;我们会看到Controller 方法的代码量就很大了&#xff0c;我们要修改操作数据部分的代码&#xff0c;需要改动Controller&#xff1b; 我们要完善逻辑处理部分的代码&#xff0c;我们需要改动Controller&#xff1b;我们需要修改数据响应的代码&#xff0c;我们还是需要改动Controller。
 

这样呢&#xff0c;就会造成我们整个工程代码的复用性比较差&#xff0c;而且代码难以维护。 那如何解决这个问题呢&#xff0c;其实在现在的开发中&#xff0c;有非常成熟的解决思路&#xff0c;那就是分层开发。

3. 分层解耦

3.1 三层架构

3.1.1 介绍

那其实我们上述案例的处理逻辑呢&#xff0c;从组成上看可以分为三个部分&#xff1a;

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

按照上述的三个组成部分&#xff0c;在我们的业务开发中呢&#xff0c;按照这三个部分&#xff0c;我们将代码分为三层&#xff1a;
 

  • Controller&#xff1a;接收前端发送的请求&#xff0c;对请求进行处理&#xff0c;并响应数据
  • Service&#xff1a;处理具体的业务逻辑
  • Dao&#xff1a;负责数据的访问操作&#xff0c;包含数据的增、删、改、查

3.1.2 代码拆分

1). UserController

接收前端发送的请求&#xff0c;对请求进行处理&#xff0c;并响应数据

import com.itheima.domain.Result;
import com.itheima.domain.User;
import com.itheima.service.UserService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;/*** &#64;author HuanLe* &#64;version 1.0*/&#64;RestController
public class UserController {private UserServiceA userService &#61; new UserServiceA();&#64;RequestMapping("/listUser")public Result listUser() throws Exception {// 调用service, 获取数据List userList &#61; userService.listUser();// 响应数据return Result.success(userList);}}

2). UserServiceA

处理具体的业务逻辑

import com.itheima.dao.UserDao;
import com.itheima.domain.Address;
import com.itheima.domain.User;
import java.util.List;/*** &#64;author HuanLe* &#64;version 1.0*/public class UserServiceA {private UserDaoA userDao &#61; new UserDaoA();public List listUser() throws Exception {//调用 dao 层, 查询数据List userList &#61; userDao.listUser();//2. 对数据进行逻辑处理 - 对地址 address 中的 province 属性后, 添加上 "省/市"for (User user : userList) {Address address &#61; user.getAddress();address.setProvince(address.getProvince()&#43;" 省/市");address.setCity(address.getCity()&#43;" 市/区");user.setAddress(address);}return userList;}}

3). UserDaoA
 

负责数据的访问操作&#xff0c;包含数据的增、删、改、查

import com.itheima.controller.UserController;
import com.itheima.domain.User;
import com.itheima.utils.XmlParserUtils;
import java.util.List;/*** &#64;author HuanLe* &#64;version 1.0*/public class UserDaoA {public List listUser() throws Exception {//1. 从文件中查询数据String file &#61; UserController.class.getClassLoader().getResource("user.xml").getFile();List userList &#61; XmlParserUtils.parse(file);System.out.println(userList);return userList;}}

3.2 分层解耦

3.2.1 耦合问题

那这里呢&#xff0c;我们首先需要了解软件开发领导涉及到的两个概念&#xff1a;内聚和耦合。

1).内聚&#xff1a;软件中各个功能模块内部的功能联系。
 

2).耦合&#xff1a;衡量软件中各个层/模块之间的依赖、关联的程度。

3).软件设计原则&#xff1a;高内聚低耦合。
 

像我们前面开发的加载XML数据&#xff0c;展示用户信息列表的案例中。我们基于三层架构对原始的代码进行了改造&#xff0c;分为了controller、service以及dao三层。在拆分的这三层中 controller调用service&#xff0c; service调用dao 。而在controller中调用service&#xff0c;我们就需要在controller中new一个service对象。 而在service中需要调用dao&#xff0c;我们就在service中new一个dao对象。 那么此时 controller的代码就耦合了service &#xff0c; 而service的代码也就耦合了dao。


 

而在软件开发领域&#xff0c;我们经常会提到一种设计原则&#xff1a;高内聚&#xff0c;低耦合。 高内聚指的是&#xff1a;一个模块中各个元素之间的联系的紧密程度&#xff0c;如果各个元素&#xff08;语句、程序段&#xff09;之间的联系程度越高&#xff0c;则内聚性越高&#xff0c;即 "高内聚"。低耦合指的是&#xff1a;软件中各个层、模块之间的依赖关联程度越低越好。

高内聚、低耦合的目的是使程序模块的可重用性、移植性大大增强。

3.2.2 解耦思路


 

而之前我们的代码&#xff0c;在编写的时候&#xff0c;需要什么对象&#xff0c;就直接new一个就可以了。 这样呢&#xff0c;层与层之间代码就耦合了&#xff0c;当service层的实现变了之后&#xff0c; 我们还需要修改controller层的代码。

此时我们来看&#xff0c;如果service层的实现一旦发生变化&#xff0c;比如&#xff1a; 我们要使用UserServiceB&#xff0c;不使用原来的UserServiceA了。 那么此时&#xff0c;我们还需要修改UserController层的代码。 需要将Controller中变量声明&#xff0c;以及创建实例的代码都改动。


 

那此时&#xff0c;Controller与Service层的代码是耦合的。

那为了解耦呢&#xff0c;我们需要为UserServiceA 与 UserServiceB 这两个不同的实现类&#xff0c;定义一个统一的接口&#xff0c;前端定义变量的时候&#xff0c;我们直接使用接口来声明就可以&#xff08;多态的体现&#xff09;。


 

具体代码为&#xff1a;

  • UserService接口&#xff1a;

public interface UserService {public List listUser();
}

  • UserServiceA实现类

public class UserServiceA implements UserService {private UserDao userDao &#61; new UserDaoA();public List listUser() {//调用 dao 层, 查询数据List userList &#61; userDao.listUser();//2. 对数据进行逻辑处理for (User user : userList) {Address address &#61; user.getAddress();address.setProvince(address.getProvince()&#43;" 省/市A");address.setCity(address.getCity()&#43;" 区/县A");user.setAddress(address);}return userList;}
}

  • UserServiceB 实现类

public class UserServiceA implements UserService {private UserDao userDao &#61; new UserDaoA();public List listUser() {//调用 dao 层, 查询数据List userList &#61; userDao.listUser();//2. 对数据进行逻辑处理for (User user : userList) {Address address &#61; user.getAddress();address.setProvince(address.getProvince()&#43;" 省/市B");address.setCity(address.getCity()&#43;" 区/县B");user.setAddress(address);}return userList;}
}

dao 层&#xff0c;为了更好的实现解耦&#xff0c;我们也可以为dao层提供一个接口 UserDao &#xff0c; 然后让实现类 UserDaoA 实现 UserDao。

首先&#xff0c;要想解耦&#xff0c;我们就不要在UserController中&#xff0c;直接去new service层的对象了&#xff0c;一旦我们new了&#xff0c;就耦合起来了。 那如果此时service层的实现类换了&#xff0c;换成UserServiceB了&#xff0c;我们就需要跟着修改UserController的代码。

如果我们直接删除掉后面的new对象的操作&#xff0c;此时如果程序直接运行&#xff0c;则会报错&#xff0c;报出空指针异常。

我们可以这样&#xff0c;可以将创建的实例对象&#xff0c;都存储在一个容器中&#xff0c;如果那么需要这个对象&#xff0c;就不用再去单独new对象了&#xff0c;直接在运行时通过容器提供就可以了。 比如&#xff1a;

我们可以将UserServiceA对象交给容器管理&#xff0c;UserController运行时需要userService&#xff0c;那么容器就给UserController提供这个userService对象。 此时如果UserService的实现类换了, 换成UserServiceB了, 我们只需要将UserServiceB产生的对象交给容器管理即可。

那这里呢&#xff0c;就涉及到两个过程&#xff1a;
 

1). 将对象交给容器管理的过程 , 称之为 控制反转。 Inversion Of Control&#xff0c;简称IOC。对象的创建控制权由程序自身转移到外部&#xff08;容器&#xff09;&#xff0c;这种思想称为控制反转。 而这个容器&#xff0c; 称之为IOC容器&#xff0c;或者Spring容器。
 

2). 应用程序运行时, 容器为其提供运行时所需要的资源, 这个过程我们称之为依赖注入。 Dependency Injection&#xff0c;简称DI
 

3). IOC容器中创建、管理的对象&#xff0c;称之为bean

3.3 IOC&DI

3.3.1 IOC&DI入门
 

  • Service层 及 Dao层的实现类&#xff0c;交给IOC容器管理。
     

在类上加上 &#64;Component 注解&#xff0c;就是将该类声明为IOC容器中的bean。

&#64;Component
public class UserServiceA implements UserService {&#64;Autowiredprivate UserDao userDao ;public List listUser() {//调用 dao 层, 查询数据List userList &#61; userDao.listUser();//2. 对数据进行逻辑处理for (User user : userList) {Address address &#61; user.getAddress();address.setProvince(address.getProvince()&#43;" 省/市A");address.setCity(address.getCity()&#43;" 区/县A");user.setAddress(address);}return userList;}
}

&#64;Component
public class UserDaoA implements UserDao {public List listUser() {//1. 从文件中查询数据String file &#61; UserController.class.getClassLoader().getResource("user.xml").getFile();List userList &#61; XmlParserUtils.parse(file);System.out.println(userList);return userList;}
}

  • 为Controller及Service注入运行时依赖的对象。

在成员变量上加上 &#64;Autowired 注解&#xff0c;表示在程序运行时&#xff0c;Springboot会自动的从IOC容器中找到UserService类型的bean对象&#xff0c;然后赋值给该变量。

&#64;RestController
public class UserController {&#64;Autowiredprivate UserService userService ;&#64;RequestMapping("/listUser")public Result listUser() {List userList &#61; userService.listUser();return Result.success(userList);}
}

  • 运行测试

运行引导类&#xff0c;启动完成之后&#xff0c;打开浏览器&#xff0c;输入&#xff1a;http://localhost:8080/user.html 访问

3.3.2 bean的声明
 

要把某个对象交给IOC容器管理&#xff0c;需要在对应的类上加上如下注解之一&#xff1a;

注解

说明

位置

&#64;Component

声明bean的基础注解

不属于以下三类时&#xff0c;用此注解

&#64;Controller

&#64;Component的衍生注解

标注在控制器类上

&#64;Service

&#64;Component的衍生注解

标注在业务类上

&#64;Repository

&#64;Component的衍生注解

标注在数据访问类上&#xff08;由于与mybatis整合&#xff0c;用的少&#xff09;

注意事项:

  • 声明bean的时候&#xff0c;可以通过value属性指定bean的名字&#xff0c;如果没有指定&#xff0c;默认为类名首字母小写。
  • 目前来说&#xff0c;我们使用以上四个注解都可以声明bean&#xff0c;但是在集成后端web开发之后&#xff0c;声明控制器bean只能用&#64;Controller。

3.3.3 组件扫描
 

1). 前面声明bean的四大注解&#xff0c;要想生效&#xff0c;还需要被组件扫描注解&#64;ComponentScan扫描。
 

2). &#64;ComponentScan注解虽然没有显式配置&#xff0c;但是实际上已经包含在了引导类声明注解 &#64;SpringBootApplication 中&#xff0c;默认扫描的范围是引导类所在包及其子包。
 

标准的目录结构&#xff0c;如下&#xff1a;
 

将我们定义的controller&#xff0c;service&#xff0c;dao这些包呢&#xff0c;都放在引导类所在包 com.itheima 的子包下&#xff0c;这样我们定义的bean就会被自动的扫描到。

3.3.4 依赖注入
 

使用&#64;Autowired 注解&#xff0c;表示在程序运行时&#xff0c;Springboot会自动的从IOC容器中找到UserService类型的bean对象&#xff0c;然后赋值给该变量。
 


 

但是需要注意&#xff1a;&#64;Autowired注解&#xff0c;默认是按照类型进行&#xff0c;如果存在多个相同类型的bean&#xff0c;将会报出如下错误&#xff1a;

我们可以通过如下几种方案来解决&#xff1a;
 

1). &#64;Primary 注解

当存在多个相同类型的Bean注入时&#xff0c;加上&#64;Primary注解&#xff0c;来确定默认的实现。

2). &#64;Qualifier 注解

可以通过&#64;Autowired &#xff0c;配合&#64;Qualifier 来指定我们当前要注入哪一个bean对象。 在&#64;Qualifier的value属性中&#xff0c;指定注入的bean的名称。

3). &#64;Resource注解

通过&#64;Resource注解&#xff0c;并指定其name属性&#xff0c;通过name指定要注入的bean的名称。这种方式呢&#xff0c;是按照bean的名称进行注入。

&#64;Autowird 与 &#64;Resource的区别&#xff1a;

  • &#64;Autowird 属于spring框架&#xff0c;默认按照bean的类型注入。 可以配合 &#64;Qualifier注解&#xff0c;实现按照名称注入。
  • &#64;Resource是JavaEE自带的注解&#xff0c;根据bean的名称进行注入的。

推荐阅读
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • Windows下配置PHP5.6的方法及注意事项
    本文介绍了在Windows系统下配置PHP5.6的步骤及注意事项,包括下载PHP5.6、解压并配置IIS、添加模块映射、测试等。同时提供了一些常见问题的解决方法,如下载缺失的msvcr110.dll文件等。通过本文的指导,读者可以轻松地在Windows系统下配置PHP5.6,并解决一些常见的配置问题。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 在springmvc框架中,前台ajax调用方法,对图片批量下载,如何弹出提示保存位置选框?Controller方法 ... [详细]
  • 网络请求模块选择——axios框架的基本使用和封装
    本文介绍了选择网络请求模块axios的原因,以及axios框架的基本使用和封装方法。包括发送并发请求的演示,全局配置的设置,创建axios实例的方法,拦截器的使用,以及如何封装和请求响应劫持等内容。 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了软件测试知识点之数据库压力测试方法小结相关的知识,希望对你有一定的参考价值。 ... [详细]
  • JavaWeb中读取文件资源的路径问题及解决方法
    在JavaWeb开发中,读取文件资源的路径是一个常见的问题。本文介绍了使用绝对路径和相对路径两种方法来解决这个问题,并给出了相应的代码示例。同时,还讨论了使用绝对路径的优缺点,以及如何正确使用相对路径来读取文件。通过本文的学习,读者可以掌握在JavaWeb中正确找到和读取文件资源的方法。 ... [详细]
author-avatar
魅由心生先_941
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有