TOC
KINA

KINA-0

Start having fun with KINA right now!

Spring Boot开发(1):Web快速入门 + Spring基础

快速入门,HTTP请求响应、IOC & DI。本文介绍了Spring项目开发的入门知识。首先,介绍了Maven的安装和项目创建,包括依赖管理、依赖传递、生命周期等概念。接着,概述了Spring Boot的基本特性及项目创建方法。随后,阐述了HTTP协议的概念、请求和响应的具体内容,以及Tomcat的协议解析。最后,探讨了请求响应的架构,包括如何接收不同类型的请求参数以及响应数据的处理,并介绍了分层解耦的三层架构、控制反转(IOC)和依赖注入(DI)的基本原理。

Maven官方仓库:https://mvnrepository.com/
Spring官网:https://spring.io/
SpringBoot打包部署到服务器(宝塔面板):CSDN博文

Web流程
ssm

1 Maven

1.1 介绍&安装

Maven(官网:http://maven.apache.org/)是apache旗下的一个开源项目,是一款用于管理和构建java项目的工具,它基于项目对象模型(POM)的概念,通过一小段描述信息来管理项目的构建。

作用

  1. 依赖管理:方便快捷的管理项目依赖的资源(jar包),避免版本冲突问题
  2. 统一项目结构:提供标准、统一的项目结构

统一项目结构

  1. 标准化的项目构建流程:标准跨平台(Linux、Windows、MacOS)的自动化项目构建方式

标准化的项目构建流程

仓库:用于存储资源,管理各种jar包

  • 本地仓库:自己计算机上的一个目录
  • 中央仓库:由Maven团队维护,全球唯一。仓库地址:https://repo1.maven.org/maven2/
  • 远程仓库(私服):一般由公司团队搭建的私有仓库。

仓库

安装与配置

  1. 解压 apache-maven-3.6.1-bin.zip
  2. 配置本地仓库:修改apache-maven-3.6.1-bin/conf/settings.xml中的<localRepository>为一个指定目录
<!-- apache-maven-3.6.1-bin/conf/settings.xml -->
<localRepository>/Users/example/develop/apache-maven-3.9.5/mvn_repo</localRepository>
  1. 配置阿里云私服:修改apache-maven-3.6.1-bin/conf/settings.xml中的<mirrors>标签,为其添加如下子标签
<!-- apache-maven-3.6.1-bin/conf/settings.xml -->
<mirror>
    <id>alimaven</id>
    <name>aliyun maven</name>
    <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
    <mirrorOf>central</mirrorOf>
</mirror>
  1. 配置环境变量:MAVEN_HOME为maven的解压目录,并将其bin目录加入PATH环境变量

1.2 创建Maven项目

创建Maven项目1

创建Maven项目2

Maven坐标是资源的唯一标识,通过该坐标可以唯一定位资源位置。使用坐标来定义项目或引入项目中需要的依赖

  • groupId:定义当前Maven项目录属组织名称(通常是域名反写,例如:com.hyplus)
  • artifactId:定义当前Maven项目名称(通常是模块名称,例如order-service、goods-service)
  • version:定义当前项目版本号

Maven项目界面

1.3 依赖管理

依赖(Dependency)指当前项目运行所需要的jar包,一个项目中可以引入多个依赖。

1.3.1 配置依赖

直接去官方仓库搜索、复制依赖坐标

  1. 在 pom.xml 中编写<dependencies>标签
  2. <dependencies>标签中使用<dependency>引入坐标
  3. 定义坐标的groupldartifactldversion
  4. 点击刷新按钮,引入最新加入的坐标,之后可在右侧Maven面板
<!-- pom.xml -->
<dependencies>
    <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

1.3.2 依赖传递

直接依赖:在当前项目中通过依赖配置建立的依赖关系

间接依赖:被依赖的资源如果依赖其他资源,当前项目间接依赖其他资源

依赖

右键选 Diagrams > Show Diagrams... 可直接以图表形式查看当前依赖信息

查看当前依赖信息

排除依赖:主动断开依赖的资源(<exclusionsl>),被排除的资源无需指定版本

排除依赖

<!-- pom.xml -->
<dependencies>
    <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.10</version>
        <exclusions>    <!-- 排除依赖(仅供示例) -->
            <exclusion>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-core</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

1.3.3 依赖范围

依赖的jar包,默认情况下可以在任何地方使用。可以通过<scope></scope>设置其作用范围:

  1. 主程序范围有效。(main文件夹范围内)
  2. 测试程序范围有效。(test文件夹范围内)
  3. 是否参与打包运行。(package指令范围内)
scope值 主程序 测试程序 打包(运行) 范例
compile(默认) Y Y Y log4j
test - Y - junit
provided Y Y - servlet-api
runtime - Y Y jdbc驱动

1.3.4 生命周期

Maven的生命周期:对所有的maven项目构建过程进行抽象和统一。有3套相互独立的生命周期:

  • clean: 清理工作。
  • default:核心工作,如:编译、测试、打包、安装、部署等。
  • site:生成报告、发布站点等。

每套生命周期包含一些阶段(phase),阶段是有顺序的,后面的阶段依赖于前面的阶段——在同一套生命周期中,当运行后面的阶段时,前面的阶段都会运行。(主要关注以下5个阶段)

生命周期

  1. clean:移除上一次构建生成的文件
  2. compile: 编译项目源代码
  3. test:使用合适的单元测试框架运行测试(junit
  4. package:将编译后的文件打包,如:jar、war等
  5. install:安装项目到本地仓库

生命周期2


2 SpringBootWeb入门

2.1 Spring概述

Spring(官网:https://spring.io)发展到今天已经形成了一种开发生态圈。Spring提供了若干个子项目,每个项目用于完成特定的功能。

Spring

Spring Boot可以帮助我们非常快速地构建应用程序、简化开发、提高效率。

Spring Boot项目的静态资源(html、css、js等前端资源)默认存放目录为:src/main/resources下的staticpublic,或直接放于src/main/resources

2.2 创建SpringBoot项目

  1. 创建springboot工程,并勾选web开发相关依赖。
  2. 定义请求处理类RequestController, 添加方法hello,并添加注解
  3. 运行启动类SpringbootWebQuickstartApplication,测试
/* RequestController.class */
package com.hyplus.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

// 请求处理类
@RestController
public class RequestController {
    @RequestMapping("/hello")
    public String hello() {
        System.out.println("Hello World!");
        return "Hello World!";
    }
}

给类添加注释@Slf4j 自动创建日志对象成员,方法内用log.info("查询全部部门数据");输出日志信息。


3 HTTP协议

3.1 概述

HTTP

HTTP协议(Hyper Text Transfer Protocol,超文本传输协议)规定了浏览器和服务器之间数据传输的规则。

  1. 基于TCP协议:面向连接,安全。
  2. 基于请求-响应模型的:一次请求对应一次响应。
  3. 无状态的协议:对于事务处理没有记忆能力(每次请求-响应都是独立的)。缺点是多次请求间不能共享数据,优点是速度快。

协议

3.2 请求协议

request

请求格式:

  1. 请求行:请求数据第1行(请求方式、资源路径、协议)
  2. 请求头:第2行开始,格式key: value
  3. 请求体:最后一部分,POST请求存放请求参数

请求方式-GET:请求参数在请求行中,没有请求体,如:/brand/EindA11?name=OPO&status=1。GET请求大小是有限制的。
请求方式-POST:请求参数在请求体中。POST请求大小是没有限制的。

@RequestMapping对应于各种请求方式的衍生注解为@GetMapping@PostMapping ...

常见的请求头 含义
Host 请求的主机名
User-Agent 浏览器版本
Accept 表示浏览器能接收的资源类型,如text/*image/*,或者*/*表示所有
Accept-Language 表示浏览器偏好的语言,服务器可以据此返回不同语言的网页
Accept-Encoding 表示浏览器可以支持的压缩类型,例如gzip, deflate等
Content-Type 请求主体的数据类型
Content-Length 请求主体的大小(单位:字节)

3.3 响应协议

response

响应格式:

  1. 响应行:响应数据第1行(协议、状态码、描述)
  2. 响应头:第2行开始,格式key: value
  3. 响应体:最后一部分,存放响应数据(响应正文)

状态码大全:状态码大全

状态码分类 意义 说明
1xx 响应中 临时状态码,表示请求已经接收,告诉客户端应该继续请求或者如果它已经完成则忽略它
2xx 成功 表示请求已经被成功接收,处理已完成
200 OK:客户端请求成功,即处理成功,这是我们最想看到的状态码
3xx 重定向 重定向到其他地方;让客户端再发起一次请求以完成整个处理
4xx 客户端错误 处理发生错误,责任在客户端。如:请求了不存在的资源、客户端未被授权、禁止访问等
404 Not Found:请求资源不存在,一般是URL输入有误,或者网站资源被则除了
5xx 服务器错误 处理发生错误,贵任在服务端。如:程序抛出异常等
500 Internal Server Error:服务器发生不可预期的错误,服务器出异常了
常见的响应头 说明
Content-Type 表示该响应内容的类型,例如text/html、application/json
Content-Length 表示该响应内容的长度(字节数)
Content-Encoding 表示该响应压缩算法,例如gzip
Cache-Control 指示客户端应如何缓存,例如max-age=300表示可以最多缓存300秒
Set-Cookie 告诉浏览器为当前页面所在的域设置cookie

3.4 协议解析、Tomcat

web服务器是一个软件程序,对HTTP协议的操作进行封装,使得程序员不必直接对协议进行操作,让Web开发更加便捷。主要功能是“提供网上信息浏览服务”。

Tomcat(官网:https://tomcat.apache.org/)是Apache 软件基金会一个核心项目,是一个开源免费的轻量级Web服务器,支持Servlet、JSP等少量JavaEE规范,因此Tomcat也被称为Web容器、Servlet容器。Servlet程序需要依赖于Tomcat才能运行。

Java语言三大分支:JavaSE、JavaME、JavaEE
JavaEE(Java Enterprise Edition,Java企业版)指Java企业级开发的技术规范总和,包含13项技术规范:JDBC、JNDI、EJB、RMI.JSP、Servlet、XML、JMS、Java IDL、JTS、JTA、JavaMail、JAF

SpringBoot内嵌了Tomcat。

Tomcat

起步依赖

  • spring-boot-starter-web:包含了web应用开发所需要的常见依赖,包括tomcat
  • spring-boot-starter-test:包含了单元测试所需要的常见依赖

   
      org.springframework.boot
      spring-boot-starter-parent
      3.2.5
       

4 请求响应

Servlet
Servlet

4.1 架构概述

架构概述

请求(HttpServletRequest):获取请求效据
响应(HttpServletResponse):设置响应数据

BS架构(Browser/Server,浏览器/服务器架构模式):客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。维护方便,体验一般。
CS架构(Client/Server,客户端/服务器架构模式):需要单独安装客户端。开发、维护麻烦,体验不错。

4.2 接收请求

Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件,常用于进行接口测试。
同类工具:Apipost、Apifox

4.2.1 简单参数

http://localhost:8080/simpleParam?name=age为例——

原始方式:在原始的web程序中,获取请求参数,需要通过HttpServletRequest对象手动获取。

/*  RequestController.java */
@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";
}

简单参数:参数名与形参变量名相同,定义形参即可接收参数。

/* RequestController.java */
@RequestMapping("/simpleParam")
public String simpleParam(String name, Integer age) {   // SpringBoot方式会自动进行类型转换
    System.out.println(name + " : " + age);
    return "OK";
}

如果方法形参名称与请求参数名称不匹配则默认接收到null
可以使用注解@RequestParam完成映射,其中的required属性默认为true,表示该请求参数必须传递。若该参数可选则可设为false,此时对应的形参默认赋null

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

4.2.2 实体参数

简单实体对象:同简单参数,请求参数名与形参对象属性名相同,定义POJO(Plain Old Java Object)接收即可

/* RequestController.java */
@RequestMapping("/simplePojo")
public String simplePojo(User user) {
    System.out.println(user);   // 要打印成员的值需重写自定义类的toString()方法,此处略
    return "OK";
}
/* User.java */
public class User {     // POJO
    private String name;
    private Integer age;
}

复杂实体对象:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套PQJO属性参数。
如下所示,GET请求可为GET http://localhost:8080/complexPojo?name=Akira&age=2024&address.province=浙江&address.city=杭州,POST请求的请求体同理。

/* RequestController.java */
@RequestMapping("/complexPojo")
public String complexPojo(User user) {
    System.out.println(user);
    return "OK";
}
/* User.java */
public class User {
    private String name;
    private Integer age;
    private Address address;
}
/* Address.java */
public class Address {
    private String province;
    private String city;
}

4.2.3 数组集合参数

http://localhost:8080/arrayParam?hobby=python&hobby=cpp为例——

数组参数:请求参数名与形参数组名称相同且请求参数为多个,定义数组类型形参即可接收参数。

/* RequestController.java */
@RequestMapping("/arrayParam")
public String arrayParam(String[] hobby) {      // 或为可变参数(String... hobby)
    System.out.println(Arrays.toString(hobby));
    return "OK";
}

集合参数:请求参数名与形参集合名称相同且请求参数为多个,用@RequestParam绑定参数关系。

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

4.2.4 日期时间参数

日期参数:使用@DateTimeFormat注解完成日期参数格式转换。

LocalDateTime类包含"年月日"、"时分秒",LocalDate类只包含"年月日"。

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

4.2.5 JSON参数

JSON数据需放在请求体中传输,故必须用POST请求。

JSON参数

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

/* RequestController.java */
@RequestMapping("/jsonParam")
public String jsonParam(@RequestBody User user) {
    System.out.println(user);
    return "OK";
}
/* User.java */
public class User {
    private String name;
    private Integer age;
    private Address address;
}
/* Address.java */
public class Address {
    private String province;
    private String city;
}

4.2.6 路径参数

路径参数:通过请求URL直接传递参数(如http://localhost:8080/path/100)。使用{...}来标识该路径参数,需要使用@PathVariable获取路径参数

/* RequestController.java */
@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";
}

4.3 响应数据

响应注解 @ResponseBody

  • 类型:方法注解、类注解
  • 位置:controller方法/类上
  • 作用:将方法返回值直接响应,如果返回值类型是实体对象/集合,将会转换为JSON格式响应
  • 说明:@RestController = @Controller + @ResponseBody
/* RestController.class 源码 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented     // 前3个为元注解(修饰注解的注解)
@Controller
@ResponseBody
public @interface RestController {
    @AliasFor(
        annotation = Controller.class
    )
    String value() default "";
}

三种功能接口:


/* ResponseController.java */ 
@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;  // 传回JSON格式数据
    }

    @RequestMapping("/listAddr")
    public List
listAddr() { List
list = new ArrayList<>(); Address addr1 = new Address(); addr1.setProvince("浙江"); addr1.setCity("杭州"); Address addr2 = new Address(); addr2.setProvince("陕西"); addr2.setCity("西安"); Collections.addAll(list, addr1, addr2); return list; // 传回JSON格式数组 } } ```

统一响应结果,可以设置一个实体对象Result类进行接收

/* Result.java */
public class Result {
    private Integer code;   // 响应码:1代表成功,0代表失败
    private String msg;     // 提示信息
    private Object data;    // 返回的数据

    /* 省略Conductors、Getters and Setters、toString()重写 */

    // 响应成功
    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);
    }
}

则上述功能接口可改写为:

/* ResponseController.java */
@RequestMapping("/hello")
public Result hello() {
    System.out.println("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 addr1 = new Address();
    addr1.setProvince("浙江");
    addr1.setCity("杭州");

    Address addr2 = new Address();
    addr2.setProvince("陕西");
    addr2.setCity("西安");

    Collections.addAll(list, addr1, addr2);
    return Result.success(list);
}

案例:解析并处理xml中的数据
解析并处理emp.xml中的Emp类数据(仅供示例,稍后优化)


/* EmpController */
@RestController
public class EmpController {
  @RequestMapping("/listEmp")
  public Result list() {
      // 1. 数据访问:加载 emp.xml,并解析 emp.xml中的数据
      String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
      List empList = XmlParserUtils.parse(file, Emp.class);
      // 2. 逻辑处理:对员工信息中的gender,job字般进行处理
      empList.stream().forEach(emp -> {
          String gender= emp.getGender();     // gender: 1男,2女
          if ("1".equals(gender)) {
              emp.setGender("男");
          } else if ("2".equals(gender)) {
              emp.setGender("女");
          }
          String job = emp.getJob();          // job: 1讲师,2班主任,3就业指导
          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);```

5 分层解耦

传统未拆分的写法,复用性差,难以维护。解耦使得每一层职能单一,可以增强复用性,便于维护,利于拓展。

5.1 三层架构

  1. Controller(控制层):接收前端发送的请求,对请求进行处理,并响应数据
  2. Service(业务逻辑层):处理具体的业务逻辑
  3. Dao(Data Access Object,数据访问层,持久层):负责数据访问操作,包括数据的增、删、改、查

三层架构

根据三层架构初步改写4.3案例

Dao层:

/* dao.EmpDao.java */
public interface EmpDao {
    public List<Emp> listEmp();       // 获取员工列表数据
}

/* dao.impl.EmpDaoA.java */
public class EmpDaoA implements EmpDao {
    // 数据访问:加载 emp.xml,并解析 emp.xml中的数据
    @Override
    public List<Emp> listEmp() {
        String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
        List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
        return empList;
    }
}

Service层:

/* service.EmpService.java */
public interface EmpService {
    public List<Emp> listEmp();       // 获取员工列表数据
}
/* service.impl.EmpServiceA.java */
public class EmpServiceA implements EmpService {
    private EmpDao empDao = new EmpDaoA();      // 存在耦合

    // 逻辑处理:对员工信息中的gender,job字般进行处理
    @Override
    public List<Emp> listEmp() {
        List<Emp> empList = empDao.listEmp(); // 调用Dao层的listEmp()方法获取列表
        empList.stream().forEach(emp -> {
            String gender= emp.getGender();
            if ("1".equals(gender)) {
                emp.setGender("男");
            } else if ("2".equals(gender)) {
                emp.setGender("女");
            }
            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层:

/* RestController.java */
@RestController
public class EmpController {
    private EmpService empService = new EmpServiceA();  // 存在耦合

    @RequestMapping("/listEmp")
    public Result list() {
        List<Emp> empList = empService.listEmp(); // 调用Service,获取数据
        return Result.success(empList);     // 响应数据
    }
}

5.2 IOC & DI

5.2.1 解耦入门

内聚:软件中各个功能模块内部的功能联系。
耦合:衡量软件中各个层/模块之间的依赖、关联的程度。
软件设计原则:高内聚低耦合

要解除5.1代码中Controller层与Service层的耦合,可以设置一个容器用来存储Service层接口各种实现类的对象(控制反转),当Controller层运行时,Service层去容器中查找对应的实现类的对象(依赖注入)。同理也能实现Service层与Dao层解耦。

控制反转(Inversion of Control,IOC):对象的创建控制权由程序自身转移到外部(容器)
依赖注入(Dependency Injection,DI):容器为应用程序提供运行时,所依赖的资源,称之为依赖注入
Bean对象:IOC容器中创建、管理的对象

一般步骤

  1. IOC:Service层及Dao层的实现类,交给IOC容器管理——给实现类添加注释@Component或其衍生注解(详见5.2.2),使之成为IOC容器中的Bean
  2. DI:为Controller及Service注入运行时所需的依赖的对象——给变量添加注释@Autowired,使得程序运行时IOC容器会提供该类型的Bean,并赋值给该变量
  3. 运行测试

解耦后的5.1代码如下
Dao层:

/* dao.impl.EmpDaoA.java */
@Repository("daoA")
public class EmpDaoA implements EmpDao {
    // 数据访问:加载 emp.xml,并解析 emp.xml中的数据
    @Override
    public List<Emp> listEmp() {
        String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
        List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
        return empList;
    }
}

Service层:

/* service.impl.EmpServiceA.java */
@Service
public class EmpServiceA implements EmpService {
    @Autowired
    private EmpDao empDao;

    @Override
    public List<Emp> listEmp() {
        List<Emp> empList = empDao.listEmp();
        empList.stream().forEach(emp -> {
            String gender= emp.getGender();
            if ("1".equals(gender)) {
                emp.setGender("男");
            } else if ("2".equals(gender)) {
                emp.setGender("女");
            }
            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层:

/* RestController.java */
@RestController
public class EmpController {
    @Autowired
    private EmpService empService;

    @RequestMapping("/listEmp")
    public Result list() {
        List<Emp> empList = empService.listEmp();
        return Result.success(empList);
    }
}

若要切换实现类(例如从EmpServiceA改为EmpServiceB),则只需取消原来的实现类(EmpServiceA)的@Component注解,给切换后的实现类(EmpServiceB)打上该注解即可(双方同时带着该注解会报错,解决方法详见5.2.3),其他部分都无需任何变动。

5.2.2 IOC声明Bean

Bean的声明:要把某个对象交给IOC容器管理,需要在对应的类上加上如下注解之一。Bean的名字默认为首字母小写的类名,可通过注解的value属性设定。

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

Bean组件扫描:上述声明bean的四大注解,要想生效,还需要被组件扫描注解@ComponentScan扫描。该注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解@SpringBootApplication中,默认扫猫的范围是启动类所在包及其子包。
因此为避免声明注解无法被扫描,建议将所有组件放在启动类所在包的子包下,规范编码。

5.2.3 DI自动装配

注解@Autowired(自动装配)默认按类型(接口)装配,若存在多个相同类型的Bean将会报错。通常有以下几种解决方法

  • @Primary:给实现类追加该注解,让带有该注解的Bean优先装配
@Primary
@Service
public class EmpServiceA implements EmpService { ... }
  • @Qualifier:配合@Autowired变量加上该注解,指定要注入的Bean的类型
@RestController
public class EmpController {
    @Autowired
    @Qualifier("empServiceA")
    private EmpService empService;
    // ...
}
  • @Resource:直接给变量加上该注解,指定要注入的Bean的名称
@RestController
public class EmpController {
    @Resource(name = "empServiceB")
    private EmpService empService;
    // ...
}

@Resource@Autowired的区别:

  1. @Autowiredspring框架提供的注解,而@ResourceJDK提供的注解。
  2. @Autowired默认是按照类型注入,而@Resource默认是按照名称注入。

发表评论