目录

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 {

@Override

public 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 安装

百度网盘 请输入提取码百度网盘为您提供文件的网络备份、同步和分享服务。空间大、速度快、安全稳固,支持教育网加速,支持手机端。注册使用百度网盘即可享受免费存储空间https://pan.baidu.com/s/1L834nG-YcHfB8fM2wOhOGA?pwd=5kc2

双击资料中的 “1.postman/Postman-win64-8.3.1-Setup.exe” 即可自动安装。

界面介绍:

如果我们需要将测试的请求信息保存下来,就需要创建一个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;

}

@Override

public String toString() {

return "User{" +

"name='" + name + '\'' +

", age=" + age +

'}';

}

}

lombok使用

在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和equals

public 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;

}

@Override

public 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;

}

@Override

public 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 = new User();

user.setName("Tom");

user.setAge(10);

List userList = new ArrayList<>();

userList.add(user);

return userList;

}

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

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

2.2 @ResponseBody

名称:@ResponseBody类型:方法注解、类注解位置:SpringMVC控制器方法上/类上作用:将当前方法返回值直接返回,如果是 实体/集合 转换为JSON返回

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

2.3 统一响应结果  

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

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

A. 当前请求是成功,还是失败。

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

C . 返回的数据。  

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

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(){

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 需求说明  

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

需求:加载并解析xml文件,完成省、市数据转换,并完成页面展示。

2.4.2 准备工作

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

在SpringBoot项目中,静态资源可以存放的目录:

"classpath:/META-INF/resources/"

"classpath:/resources/"

"classpath:/static/"

"classpath:/public/"

classpath:

代表的是类路径,在maven的项目中,其实指的就是 src/main/resources 或者 src/main/java,但是java目录是存放java代码的,所以相关的配置文件及静态资源文档,就放在 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;

/**

* @author HuanLe

* @version 1.0

*/

@RestController

public class UserController {

@RequestMapping("/listUser")

public Result listUser() {

//1. 获取数据

String file = ClassLoader.getSystemResource("user.xml").getFile();

//调用工具类去解析

List list = XmlParserUtils.parse(file);

//2.处理数据(省后面加 省/市 , 市加 市/区)

for (User user : list) {

//获取用户的地址

Address address = user.getAddress();

//省

address.setProvince(address.getProvince() + "省/市");

address.setCity(address.getCity() + "市/区");

}

//3.将数据封装到Result对象中,返回

return Result.success(list);

}

}

2.4.4 测试  

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

http://localhost:8080/user.html

2.4.5 问题分析

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

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

3. 分层解耦

3.1 三层架构

3.1.1 介绍

那其实我们上述案例的处理逻辑呢,从组成上看可以分为三个部分:

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

按照上述的三个组成部分,在我们的业务开发中呢,按照这三个部分,我们将代码分为三层:  

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

3.1.2 代码拆分

1). UserController

接收前端发送的请求,对请求进行处理,并响应数据

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;

/**

* @author HuanLe

* @version 1.0

*/

@RestController

public class UserController {

private UserServiceA userService = new UserServiceA();

@RequestMapping("/listUser")

public Result listUser() throws Exception {

// 调用service, 获取数据

List userList = 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;

/**

* @author HuanLe

* @version 1.0

*/

public class UserServiceA {

private UserDaoA userDao = new UserDaoA();

public List listUser() throws Exception {

//调用 dao 层, 查询数据

List userList = userDao.listUser();

//2. 对数据进行逻辑处理 - 对地址 address 中的 province 属性后, 添加上 "省/市"

for (User user : userList) {

Address address = user.getAddress();

address.setProvince(address.getProvince()+" 省/市");

address.setCity(address.getCity()+" 市/区");

user.setAddress(address);

}

return userList;

}

}

3). UserDaoA  

负责数据的访问操作,包含数据的增、删、改、查

import com.itheima.controller.UserController;

import com.itheima.domain.User;

import com.itheima.utils.XmlParserUtils;

import java.util.List;

/**

* @author HuanLe

* @version 1.0

*/

public class UserDaoA {

public List listUser() throws Exception {

//1. 从文件中查询数据

String file = UserController.class.getClassLoader().getResource("user.xml").getFile();

List userList = XmlParserUtils.parse(file);

System.out.println(userList);

return userList;

}

}

3.2 分层解耦

3.2.1 耦合问题

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

1).内聚:软件中各个功能模块内部的功能联系。  

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

3).软件设计原则:高内聚低耦合。  

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

 

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

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

3.2.2 解耦思路

 

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

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

 

那此时,Controller与Service层的代码是耦合的。

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

 

具体代码为:

UserService接口:

public interface UserService {

public List listUser();

}

UserServiceA实现类

public class UserServiceA implements UserService {

private UserDao userDao = new UserDaoA();

public List listUser() {

//调用 dao 层, 查询数据

List userList = userDao.listUser();

//2. 对数据进行逻辑处理

for (User user : userList) {

Address address = user.getAddress();

address.setProvince(address.getProvince()+" 省/市A");

address.setCity(address.getCity()+" 区/县A");

user.setAddress(address);

}

return userList;

}

}

UserServiceB 实现类

public class UserServiceA implements UserService {

private UserDao userDao = new UserDaoA();

public List listUser() {

//调用 dao 层, 查询数据

List userList = userDao.listUser();

//2. 对数据进行逻辑处理

for (User user : userList) {

Address address = user.getAddress();

address.setProvince(address.getProvince()+" 省/市B");

address.setCity(address.getCity()+" 区/县B");

user.setAddress(address);

}

return userList;

}

}

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

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

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

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

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

那这里呢,就涉及到两个过程:  

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

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

3). IOC容器中创建、管理的对象,称之为bean。

3.3 IOC&DI

3.3.1 IOC&DI入门  

Service层 及 Dao层的实现类,交给IOC容器管理。  

在类上加上 @Component 注解,就是将该类声明为IOC容器中的bean。

@Component

public class UserServiceA implements UserService {

@Autowired

private UserDao userDao ;

public List listUser() {

//调用 dao 层, 查询数据

List userList = userDao.listUser();

//2. 对数据进行逻辑处理

for (User user : userList) {

Address address = user.getAddress();

address.setProvince(address.getProvince()+" 省/市A");

address.setCity(address.getCity()+" 区/县A");

user.setAddress(address);

}

return userList;

}

}

@Component

public class UserDaoA implements UserDao {

public List listUser() {

//1. 从文件中查询数据

String file = UserController.class.getClassLoader().getResource("user.xml").getFile();

List userList = XmlParserUtils.parse(file);

System.out.println(userList);

return userList;

}

}

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

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

@RestController

public class UserController {

@Autowired

private UserService userService ;

@RequestMapping("/listUser")

public Result listUser() {

List userList = userService.listUser();

return Result.success(userList);

}

}

运行测试

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

3.3.2 bean的声明  

要把某个对象交给IOC容器管理,需要在对应的类上加上如下注解之一:

注解 说明 位置 @Component 声明bean的基础注解 不属于以下三类时,用此注解 @Controller @Component的衍生注解 标注在控制器类上 @Service @Component的衍生注解 标注在业务类上 @Repository @Component的衍生注解 标注在数据访问类上(由于与mybatis整合,用的少)

注意事项:

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

3.3.3 组件扫描  

1). 前面声明bean的四大注解,要想生效,还需要被组件扫描注解@ComponentScan扫描。  

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

标准的目录结构,如下:  

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

3.3.4 依赖注入  

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

 

但是需要注意:@Autowired注解,默认是按照类型进行,如果存在多个相同类型的bean,将会报出如下错误:

我们可以通过如下几种方案来解决:  

1). @Primary 注解

当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现。

2). @Qualifier 注解

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

3). @Resource注解

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

@Autowird 与 @Resource的区别:

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

参考阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: