快速入门,HTTP请求响应、IOC & DI。本文介绍了Spring项目开发的入门知识。首先,介绍了Maven的安装和项目创建,包括依赖管理、依赖传递、生命周期等概念。接着,概述了Spring Boot的基本特性及项目创建方法。随后,阐述了HTTP协议的概念、请求和响应的具体内容,以及Tomcat的协议解析。最后,探讨了请求响应的架构,包括如何接收不同类型的请求参数以及响应数据的处理,并介绍了分层解耦的三层架构、控制反转(IOC)和依赖注入(DI)的基本原理。
Maven官方仓库:https://mvnrepository.com/
Spring官网:https://spring.io/
SpringBoot打包部署到服务器(宝塔面板):CSDN博文
1 Maven
1.1 介绍&安装
Maven(官网:http://maven.apache.org/)是apache旗下的一个开源项目,是一款用于管理和构建java项目的工具,它基于项目对象模型(POM)的概念,通过一小段描述信息来管理项目的构建。
作用
- 依赖管理:方便快捷的管理项目依赖的资源(jar包),避免版本冲突问题
- 统一项目结构:提供标准、统一的项目结构
- 标准化的项目构建流程:标准跨平台(Linux、Windows、MacOS)的自动化项目构建方式
仓库:用于存储资源,管理各种jar包
- 本地仓库:自己计算机上的一个目录
- 中央仓库:由Maven团队维护,全球唯一。仓库地址:https://repo1.maven.org/maven2/
- 远程仓库(私服):一般由公司团队搭建的私有仓库。
安装与配置
- 解压 apache-maven-3.6.1-bin.zip
- 配置本地仓库:修改
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>
- 配置阿里云私服:修改
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>
- 配置环境变量:MAVEN_HOME为maven的解压目录,并将其bin目录加入PATH环境变量
1.2 创建Maven项目
Maven坐标是资源的唯一标识,通过该坐标可以唯一定位资源位置。使用坐标来定义项目或引入项目中需要的依赖。
groupId
:定义当前Maven项目录属组织名称(通常是域名反写,例如:com.hyplus)artifactId
:定义当前Maven项目名称(通常是模块名称,例如order-service、goods-service)version
:定义当前项目版本号
1.3 依赖管理
依赖(Dependency)指当前项目运行所需要的jar包,一个项目中可以引入多个依赖。
1.3.1 配置依赖
直接去官方仓库搜索、复制依赖坐标
- 在 pom.xml 中编写
<dependencies>
标签 - 在
<dependencies>
标签中使用<dependency>
引入坐标 - 定义坐标的
groupld
、artifactld
、version
- 点击刷新按钮,引入最新加入的坐标,之后可在右侧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>
设置其作用范围:
- 主程序范围有效。(main文件夹范围内)
- 测试程序范围有效。(test文件夹范围内)
- 是否参与打包运行。(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个阶段)
- clean:移除上一次构建生成的文件
- compile: 编译项目源代码
- test:使用合适的单元测试框架运行测试(junit)
- package:将编译后的文件打包,如:jar、war等
- install:安装项目到本地仓库
2 SpringBootWeb入门
2.1 Spring概述
Spring(官网:https://spring.io)发展到今天已经形成了一种开发生态圈。Spring提供了若干个子项目,每个项目用于完成特定的功能。
Spring Boot可以帮助我们非常快速地构建应用程序、简化开发、提高效率。
Spring Boot项目的静态资源(html、css、js等前端资源)默认存放目录为:src/main/resources
下的static
或public
,或直接放于src/main/resources
2.2 创建SpringBoot项目
- 创建springboot工程,并勾选web开发相关依赖。
- 定义请求处理类RequestController, 添加方法
hello
,并添加注解 - 运行启动类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协议(Hyper Text Transfer Protocol,超文本传输协议)规定了浏览器和服务器之间数据传输的规则。
- 基于TCP协议:面向连接,安全。
- 基于请求-响应模型的:一次请求对应一次响应。
- 是无状态的协议:对于事务处理没有记忆能力(每次请求-响应都是独立的)。缺点是多次请求间不能共享数据,优点是速度快。
3.2 请求协议
请求格式:
- 请求行:请求数据第1行(请求方式、资源路径、协议)
- 请求头:第2行开始,格式
key: value
- 请求体:最后一部分,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 响应协议
响应格式:
- 响应行:响应数据第1行(协议、状态码、描述)
- 响应头:第2行开始,格式
key: value
- 响应体:最后一部分,存放响应数据(响应正文)
状态码大全:状态码大全
状态码分类 | 意义 | 说明 |
---|---|---|
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。
起步依赖:
- spring-boot-starter-web:包含了web应用开发所需要的常见依赖,包括tomcat
- spring-boot-starter-test:包含了单元测试所需要的常见依赖
org.springframework.boot spring-boot-starter-parent 3.2.5
4 请求响应
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参数:键名与形参对象属性名相同,定义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 三层架构
- Controller(控制层):接收前端发送的请求,对请求进行处理,并响应数据
- Service(业务逻辑层):处理具体的业务逻辑
- 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容器中创建、管理的对象
一般步骤
- IOC:Service层及Dao层的实现类,交给IOC容器管理——给实现类添加注释
@Component
或其衍生注解(详见5.2.2),使之成为IOC容器中的Bean - DI:为Controller及Service注入运行时所需的依赖的对象——给变量添加注释
@Autowired
,使得程序运行时IOC容器会提供该类型的Bean,并赋值给该变量 - 运行测试
解耦后的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
的区别:
@Autowired
是spring框架提供的注解,而@Resource
是JDK提供的注解。@Autowired
默认是按照类型注入,而@Resource
默认是按照名称注入。