【SpringBootWeb开发】《一篇带你入门Web后端开发》
文章目录
- SpringBootWeb
- 一、HTTP协议
- (1)HTTP请求协议的结构
- 1. 请求行(Request Line)
- 2. 请求头(Request Headers)
- 3. 请求体(Request Body)
- (2)HTTP响应协议的结构
- 1.状态行(Status Line)
- 2.响应头(Response Headers)
- 3.响应体(Response Body)
- (3)协议解析
- 二、Tomcat介绍
- (1)简介:
- (2)基本使用
- 三、请求响应
- *BS架构流程解析
- (1)请求参数
- (2)响应数据
- (2)统一响应结果:
- 四、分层解耦
- (1)三层架构
- (2)IOC&DI入门
- (4)IOC详解
- (5)DI详解
SpringBootWeb
在创建springboot工程的时候勾选springweb选项;
一、HTTP协议
**概念:**Hyper Text Transfer Protocol,超文本传输协议,规定了浏览器和服务器之间的数据传输规则。
特点:
- 基于TCP协议:面向连接,安全
- 基于请求-响应模型的:一次请求对对应一次响应
- HTTP协议是无状态的协议:对于事务处理没有记忆能力。每次请求-响应都是独立的。
- 缺点:多次请求间不能共享数据。
- 优点:速度快
(1)HTTP请求协议的结构
一个完整的 HTTP 请求由三部分组成:请求行(Request Line)、请求头(Request Headers)、请求体(Request Body)(可选)。
第一行为请求行,其余为请求头,图中没有请求体,若有会出现在最后一行
1. 请求行(Request Line)
定义请求的方法、URL 和协议版本,格式为:
请求方法 URL 协议版本
- 请求方法:表示对资源的操作,如GET/POST;
- URL:请求的资源路径(如
/index.html
); - 协议版本:如
HTTP/1.1
(主流版本)、HTTP/2
、HTTP/3
。
示例:
GET /api/users HTTP/1.1
2. 请求头(Request Headers)
由一系列 “键值对” 组成,用于描述请求的附加信息(如客户端类型、数据格式等),每行格式为键: 值
,最后以空行结束。
常见请求头说明:
头部字段 | 作用 |
---|---|
Host | 服务器的域名或 IP(如Host: www.example.com ,HTTP/1.1 必填) |
User-Agent | 客户端(通常是浏览器)信息(如浏览器类型:Mozilla/5.0 (Windows NT 10.0; ...) ) |
Accept | 客户端(通常是浏览器)可接受的响应数据格式(如Accept: text/* 或者 */*表示所有 ) |
Accept-Language | 客户端(通常是浏览器)偏好的语言,服务器可以据此返回不同语言的网页 |
Accept-Encoding | 客户端(通常是浏览器)可以支持的压缩类型,例如gzip,defate等 |
Content-Type | 请求体的数据类型(如application/x-www-form-urlencoded 、multipart/form-data ) |
Connection | 是否保持连接(如Connection: keep-alive ,HTTP/1.1 默认持久连接) |
Content-Type | 请求主体的数据类型 |
Content-Length | 请求主体的大小(单位:字节) |
Cookie | 客户端存储的 Cookie 信息,用于保持会话 |
Authorization | 身份验证信息(如Bearer token ) |
3. 请求体(Request Body)
可选部分,用于携带请求的具体数据(如表单提交、API 参数等),仅在需要向服务器发送数据时存在(如POST
、PUT
方法)。
常见数据格式:
application/x-www-form-urlencoded
:表单默认格式(如name=xxx&age=20
);application/json
:JSON 格式(如{"name": "xxx", "age": 20}
);multipart/form-data
:用于上传文件(包含二进制数据);text/plain
:纯文本。
(2)HTTP响应协议的结构
HTTP 响应是服务器收到客户端请求后返回的信息,其结构与请求相对应,同样遵循严格的格式规范。
一个完整的 HTTP 响应由三部分组成:状态行(Status Line)、响应头(Response Headers)、响应体(Response Body)(可选)。
1.状态行(Status Line)
状态行位于响应的第一行,用于告知客户端请求的处理结果,格式为:
协议版本 状态码 状态描述
- 协议版本:与请求中的版本对应(如
HTTP/1.1
、HTTP/2
); - 状态码:3 位数字,表示请求的处理状态(核心部分,见下文分类);
- 状态描述:对状态码的简短文字说明(如
OK
、Not Found
)。
示例:
HTTP/1.1 200 OK
2.响应头(Response Headers)
由一系列 “键值对” 组成,用于向客户端传递服务器的附加信息(如响应数据类型、缓存策略、服务器信息等),格式为键: 值
;
每行一个,最后以空行与响应体分隔。
常见响应头说明:
头部字段 | 作用 |
---|---|
Server | 服务器软件信息(如Server: Nginx/1.21.0 ) |
Content-Type | 响应体的数据类型及编码(如text/html; charset=UTF-8 、application/json ) |
Content-Length | 响应体的字节长度(帮助客户端判断数据是否完整接收) |
Content-Encoding | 表示响应压缩算法,例如gzip |
Date | 服务器生成响应的时间(如Date: Wed, 27 Aug 2025 12:00:00 GMT ) |
Cache-Control | 缓存策略(如Cache-Control: max-age=3600 表示缓存 1 小时) |
Set-Cookie | 服务器向客户端设置 Cookie(如Set-Cookie: sessionId=abc123; Path=/; HttpOnly ) |
Location | 用于重定向(配合 3xx 状态码,指定跳转的 URL,如Location: https://example.com/login ) |
Access-Control-Allow-Origin | 跨域资源共享(CORS)配置,允许指定域名访问(如Access-Control-Allow-Origin: * 表示允许所有域名) |
3.响应体(Response Body)
响应体是服务器返回的实际数据内容,可选(如HEAD
请求的响应无体),其格式由Content-Type
头指定,常见类型包括:
- 文本类:HTML(
text/html
)、CSS(text/css
)、JavaScript(application/javascript
)、纯文本(text/plain
); - 数据类:JSON(
application/json
)、XML(application/xml
); - 二进制类:图片(
image/jpeg
、image/png
)、文件(application/octet-stream
)等。
示例:
-
当Content-Type: text/html时,响应体可能是:
<html><body><h1>Hello World</h1></body> </html>
-
当Content-Type: application/json时,响应体可能是:
{"name": "Alice", "age": 25}
状态码:
状态码 | 英文描述 | 解释 |
---|---|---|
200 | OK | 客户端请求成功,即处理成功,这是我们最想看到的状态码 |
302 | Found | 指示所请求的资源已移动到由 Location 响应头给定的 URL,浏览器会自动重新访问到这个页面 |
304 | Not Modified | 告诉客户端,你请求的资源至上次取得后,服务端并未更改,你直接用你本地缓存吧。隐式重定向 |
400 | Bad Request | 客户端请求有语法错误,不能被服务器所理解 |
403 | Forbidden | 服务器收到请求,但是拒绝提供服务,比如:没有权限访问相关资源 |
404 | Not Found | 请求资源不存在,一般是 URL 输入有误,或者网站资源被删除了 |
405 | Method Not Allowed | 请求方式有误,比如应该用 GET 请求方式的资源,用了 POST |
428 | Precondition Required | 服务器要求有条件的请求,告诉客户端要想访问该资源,必须携带特定的请求头 |
429 | Too Many Requests | 指示用户在给定时间内发送了太多请求(“限速”),配合 Retry - After (多长时间后可以请求) 响应头一起使用 |
431 | Request Header Fields Too Large | 请求头太大,服务器不愿意处理请求,因为它的头部字段太大。请求可以在减少请求头域的大小后重新提交。 |
500 | Internal Server Error | 服务器发生不可预期的错误。服务器出异常了,赶紧看日志去吧 |
503 | Service Unavailable | 服务器尚未准备好处理请求,服务器刚刚启动,还未初始化好 |
(3)协议解析
**概念:**Hyper Text Transfer Protocol,超文本传输协议,规定了浏览器和服务器之间的数据传输规则。
对http协议进行解析是比较繁琐的事情,这时候就有封装了解析http协议的代码的web服务器,接收客户端的请求并返回相应的资源。
web服务器:是一个软件程序,对HTTP协议的操作进行封装,使得程序员不必直接对协议进行操作,让web开发更加便捷。
主要功能是“提供网上信息浏览服务”。
Tomcat就是一款Web服务器
二、Tomcat介绍
(1)简介:
Tomcat是Apache软件基金会一个核心项目,是一个开源免费的轻量级Web服务器,支持Servlet/JSP少量JavaEE规范。
JavaEE:Java EnterPrise Edition,java企业版。指java企业级开发的技术规范总和。
包含13项目技术规范:JDBC、JNDI、EJB、RMI、JSP、Servlet、XML、JMS、Java IDL、JTS、JTA、JavaMail、JAF
Tomcat也称为Web容器、Servlet容器。Servlet程序需要依赖于Tomcat才能运行
官网:https://tomcat/apache.org/
(2)基本使用
1.目录结构:
2.指令操作
#启动
bin/startup.bat
#停止
bin/shutdown.bat
#部署:应用复制到webapps目录
3.Windows下载安装
配置端口:
- 配置Tomcat端口号(conf/server.xml)
<Connecetor port="8080" protocol="HTTP/1.1"connectionTimeout="20000"redirectPort="8443" />
这样的tomcat为外置的tomcat;
在springboot项目中,有内嵌的tomcat;
三、请求响应
请求响应:
- 请求(HttpServletRequest):获取请求数据
- 响应(HttpServletResponse):设置响应数据
请求 是浏览器(客户端)发给 Web 服务器的「数据指令」
网络架构模式:
-
BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。
-
CS架构:Client/Server,客户端/服务器架构模式。
BS架构(维护方便,体验一般)像京东,淘宝,天猫等
CS架构(开发、维护麻烦 体验不错)像微信,百度网盘等
网络架构模式是指在网络设计中,为满足不同规模和需求的通信需求而采用的结构化设计方法。常见的网络架构模式包括C/S架构、B/S架构以及分层网络模型。
BS架构流程如图:
*BS架构流程解析
- 请求发起:浏览器(如 Chrome)通过 HTTP 协议,向 Web 服务器( Tomcat)发送请求。
- 前端控制器(也叫核心控制器)分发:Web 服务器中的
DispatcherServlet
(Spring MVC 的前端控制器,负责统一接收请求并分发)接收到请求后,会根据请求的信息(如 URL、请求方式等),将请求分发给对应的XxxController
(控制器类)。 - 响应返回:
XxxController
处理完请求后,生成响应结果,再通过DispatcherServlet
将响应返回给浏览器,浏览器最终展示响应内容。
Tomcat 的本质:作为 Servlet 容器,它只能直接识别和处理符合 Servlet 规范的组件(即实现了 Servlet 接口的类),无法直接识别 Spring 中的 Controller。
DispatcherServletSpring MVC 框架中的核心组件, 实现了 Servlet 接口,它会拦截所有进入 Spring MVC 应用程序的 HTTP 请求,并将这些请求分发给相应的处理器(如 Controller)来进行处理,处理完成后再负责将处理器返回的结果进行适当处理并响应给客户端。
如果没有DispatcherServlet,会①失去统一的请求入口,代码冗余度剧增② Controller 与 Servlet 容器强耦合
(1)请求参数
1.概念
请求参数是客户端(如浏览器、App)发起 HTTP 请求时,主动传递给服务器的数据;服务器需遵循 HTTP 协议约定的格式(或框架适配的规则),通过解析请求内容来接收、使用这些参数,以执行对应的业务逻辑。
2.参数详解
常见参数类型有:1.简单参数,2.实体参数,3.数组集合参数,4.日期参数,5.JSON参数,6.路径参数。
①客户端发起 HTTP 请求时:
不同参数类型的核心区别在于 传递位置 和 格式:
- 简单参数、实体参数、数组 / 集合参数:多通过 query 或表单请求体(
application/x-www-form-urlencoded
)传递; - JSON 参数:通过请求体(
application/json
)传递; - 日期参数:需指定格式解析;
- 路径参数:嵌在 URL 路径中,用于标识资源。
②Web服务器接收请求:
在原始的web程序中,获取请求参数,需要通过HttpServerletRequest对象手动获取。
//假设访问URL:http://localhost:8080/paramDemo?username=张三&age=20@RequestMapping("/paramDemo")
public String simpleParam(HttpServletRequest request){String username = req.getParameter("username");String ageStr = req.getParameter("age");int age = Interger.parsenInt(ageStr);system.out.println(username+" "+age);return "OK";}
而Spring 框架通过 @RequestParam
、@RequestBody
、@PathVariable
等注解简化了参数接收,开发者只需根据参数类型选择对应方式即可。
如下:
(1)简单参数
①请求参数名与形参名相同
简单参数:定义形参即可接收参数。
//假设访问URL:http://localhost:8080/getSimplepara?name=张三&age=20@RequestMapping("/user")
public String getSimpleParam(String name, int age ) {System.out.println(name+" "+age);return "OK";
}
②请求参数名与形参变量名不相同
使用@RequestParam完成映射。
@RequestMapping("/user")
public String getSimpleParam(@RequestParam(name="name")String username, int age ) {System.out.println(name+" "+age);return "OK";
}
- @RequestParam的属性
属性名 | 类型 | 作用 | 默认值 |
---|---|---|---|
name /value | String | 指定请求参数的名称(解决参数名不一致问题) | 空字符串 |
required | boolean | 表示该参数是否必须传递(不传递会报错) | true |
defaultValue | String | 当参数未传递时,使用的默认值(自动转换为参数类型) | 空字符串 |
注意事项:
name
和value
是等效的(@RequestParam(value = "user_name")
与@RequestParam(name = "user_name")
一样)。defaultValue
优先级高于required
:如果设置了默认值,required
会被自动视为false
(即使显式写required=true
也无效)。- 默认值是字符串,但会自动转换为参数类型(如
defaultValue = "18"
可赋值给Integer age
)。
(2)实体参数
请求参数名与形参对象属性名相同,即可直接通过POJO接收
//假设访问URL:http://localhost:8080/user?name=李四&age=25@RequestMapping("/user")
public String saveUser(User user) { // 直接用实体类接收,自动匹配属性system.out.println(user);return "OK";
}
//user实体类
public class User {private String name;private Integer age;
}
@RequestParam
无法直接处理这种 “对象级别的参数绑定”。
所以通常不加。
(3)数组/集合参数
①数组
请求参数名与形参数组名称相同且请求参数为多个,定义数组类型形参即可接收参数
//假设访问URLhttp://localhost:8080/hobby?hobby=篮球&hobby=足球&hobby=游泳@RequestMapping("/hobby")
public String getHobbyArray(String[] hobby) {system.out.println(Arrays.toString(hobby));return "OK";
}
②集合
请求参数名与形参数组名称相同且请求参数为多个,当需要集合的时候就需要@RequestParam映射
//假设访问URLhttp://localhost:8080/hobby?hobby=篮球&hobby=足球&hobby=游泳@RequestMapping("/hobby")
public String getHobbyArray(@RequestParam List<String> hobby) {system.out.println(hobby);return "OK";
}
(4)日期参数
日期参数:使用@DateTimeFormat注解完成日期参数格式转换
//假设访问URLhttp://localhost:8080/time?updateTime=2023/10/01 12:00:00@GetMapping("/time")
public String getDate(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime updateTime) {system.out.println(updateTime);return "OK";
}
(5)JSON参数
需要使用@RequestBody标识
JSON数据:JSON数据键名与形参对象属性名相同,定义POJO类型参数即可接收参数,需要使用@RequestBody标识。
//放在请求体(Body)中,请求头 Content-Type 为 application/json@PostMapping("/user/json")
public String getJsonParam(@RequestBody User user) {system.out.println(user);return "OK";
}
请求体(json格式):
{"name": "王五","age": 30,"address": {"city": "北京","street": "长安街"}
}
需要java实体类接收
public class User{private String name;private Integer age;private Address address;
}
public class Address{private String province;private String city;
}
(6)路径参数
需要使用@PathVariable 获取路径参数
路径参数:通过请求URL直接传递参数,使用{…}来标识该路径参数,需要使用@PathVariable获取路径参数
//假设访问URLhttp://localhost:8080/user/100@GetMapping("/user/{id}")
public String getPathParam(@PathVariable Integer id) {system.out.println(id);return "OK";
}
(2)响应数据
响应数据就是return返回的部分。需要将类用注解@ResponseBody标注后,才会返回。
在 Spring 框架中,@ResponseBody
注解的作用与 HTTP 响应的处理机制密切相关,理解它的必要性需要结合 Spring MVC 的工作原理:
-
默认响应机制:
当控制器方法没有标注@ResponseBody
时,Spring 会默认将方法返回值视为 “视图名称”,然后通过视图解析器(ViewResolver)寻找对应的视图文件(如 JSP、HTML 等)进行渲染后返回。 通俗来说就是:Spring 默认认为控制器方法返回的是 “页面名称”(比如返回 “login” 就去找 login.html)。但我们现在要返回数据(比如用户信息、列表),这时候就需要
@ResponseBody
告诉 Spring:“别找页面了,我给你的是要直接返回的数据”。 -
数据响应需求:
在前后端分离架构中,我们需要直接返回 JSON/XML 等数据格式,而非视图。当加上@ResponseBody
后,Spring 会自动把你的对象(比如 User 类)转换成前端能看懂的格式(通常是 JSON)。 -
注解的作用:
- 告诉 Spring:“不要把返回值当作视图名处理”
- 而是将返回值通过消息转换器(HttpMessageConverter)序列化为指定格式(默认 JSON)
- 直接写入 HTTP 响应体(Response Body)中
@Controller
public class UserController {@GetMapping("/user")@ResponseBody // 必须添加,否则返回 "user" 字符串对应的页面路径public User getUser() {return new User("张三", 20); // 自动转为 JSON}
}
将方法返回值直接响应,如果返回值类型是 实体对象/集合 ,将会转化为JSON格式响应
说明:@RestController = @Controller + @ResponseBody,所以只需要写@RestController即可
@ResponseBody是方法注解和类注解,用在类或方法上
(2)统一响应结果:
由于不同的类,返回数据格式不同,我们可以规范统一响应结果。
创建以一个返回类,专门用于返回:
public class Result{//响应码,200表示成功,500表示失败(自定义)private Integer code;//提示信息private String msg;//返回的数据private Object data;//....
}
之后的controller都返回这个类即可,只需要往里面插入对应的数据。
四、分层解耦
“分层解耦” 是后端开发中非常重要的设计思想,核心目标是通过合理的层次划分,将复杂系统拆分成职责单一、低耦合的模块,从而提高代码的可维护性、可扩展性和复用性。
“分层” 是将系统按功能职责划分为不同的层次,每个层次只专注于解决某一类问题,并且遵循 “上层依赖下层,下层不依赖上层” 的原则。
“解耦” 是降低模块之间的依赖关系,让每个模块可以独立修改、替换,而不影响其他模块。
内聚:软件中各个功能模块内部的功能联系。
耦合:衡量软件中各个层/模块之间的依赖、关联的程度。
软件设计原则是:高内聚低耦合
(1)三层架构
三层架构解决后端处理逻辑中 “业务逻辑与数据访问混杂导致难以维护” 的问题
三层具体是:
- controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据。
- service:业务逻辑层,处理具体的业务逻辑。
- dao:数据访问层(Data Access Object)(持久层),负责数据访问操作,包括数据的增删查改。
(2)IOC&DI入门
假设我们定义了一个serivice接口,他有两个实现类ServiceA和ServiceB,我们正在使用ServiceA,当我们修改ServiceA为ServiceB时,我们在Controller层也需要跟着修改Service对象;这时候两层是耦合的;
解耦方法就是:将Server对象A或B放入一个容器中,当需要一个Server对象的时候,就去该容器中找;
这里就引入了两个概念:控制反转(IOC)和 依赖注入(DI)
控制反转:Inversion Of Control,简称**IOC。对象的创建控制权由程序自身**转移到外部(容器),这种思维称为控制反转。
依赖注入:Dependency Injection,简称**DI**。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。
Bean 对象:是 “被容器管理的对象”(如 Service、Dao、Controller 等),它们是应用程序的核心组件(包含业务逻辑、数据访问等功能)。
简单来说:Bean 是被管理的对象,IoC 是管理这些 Bean 的思想,而 DI 是实现 IoC 的主要手段。
(4)IOC详解
对象的创建控制权由程序自身转移到外部(容器),这种思维称为控制反转
1.bean对象的声明
要把某个对象交给IOC容器管理,需要在对应的类上加上如下注解之一,这也就是Bean的声明:
注解 | 说明 | 位置 |
---|---|---|
@Component | 声明bean的基本注解 | 不属于以下三类时,用此注解 |
@Controller | @Component的衍生注解 | 标注在控制器类上 |
@Service | @Component的衍生注解 | 标注在业务类上 |
@Repository | @Component的衍生注解 | 标注在数据访问类上(由于与mybatis整合,用的少) |
作用是将类标记为 “需要被 Spring 容器管理的 Bean”
后三个注解的作用与@Component一样,标注对应的业务层,实现依赖注入;@Component则常用于工具类中;
@Repository 除了标识 Bean 外,还会被 Spring 的异常转换机制识别,自动将数据库操作的异常转换为 Spring 统一的 DataAccessException。
注意事项:
- 声明bean的时候,可以通过value属性指定bean的名字,如果没有指定,默认为类名首字母小写。
- 声明以上四个注解都可以声明bean,但是在springboot集成web开发中,声明控制器bean只能用@Controller
2.Bean的组件扫描
前面声明bean的四大注解,要想生效,还需要被组件扫描注解@ComponentScan扫描。
@ComponentScan注解虽然没有显示配置,但是实际上已经包含在了启动类声明注解@SpringBootApplication中,默认扫描的范围是启动类所在包及其子包
所以包应该按照规范放入;
如果dao包放在了启动类的外面会识别不到,需要手动通过@ComponentScan注解扫描
(5)DI详解
@Autowired
和 @Component
是 Spring 中两个核心注解,但作用和使用场景完全不同,核心区别在于:@Autowired
负责 “依赖注入”,@Component
负责 “定义 Bean”。
@Autowired:默认按照类型自动装配。
1.同类型bean存在多个解决方法
(1)使用注解@Primary
作用:给 “同类型的多个 Bean” 打一个 “首选” 标记,Spring 优先选加了 @Primary
的 Bean。
用法(示例):
假设有 UserService
接口,和两个实现类 UserServiceImplA
、UserServiceImplB
,都被扫描成 Bean。
// 给 UserServiceImplA 加 @Primary,标记为“首选”
@Service
@Primary
public class UserServiceImplA implements UserService { ... }@Service
public class UserServiceImplB implements UserService { ... }// 注入时,Spring 会优先选 UserServiceImplA
@Service
public class OrderService {@Autowiredprivate UserService userService; // 实际注入的是 UserServiceImplA
}
注意:同类型 Bean 里,@Primary
最多只能有一个,否则又会冲突。
(2)@Qualifier (“bean 的名称”)
作用:@Qualifier
配合 @Autowired
,按 “Bean 的名称(ID)” 精确匹配,绕过 “按类型” 的歧义。
用法(示例):
Bean 的名称默认是类名首字母小写(比如 UserServiceImplA
→ userServiceImplA
),也可以通过 @Service("自定义名")
指定。
@Service("serviceA") // 显式指定 Bean 名称为 "serviceA"
public class UserServiceImplA implements UserService { ... }@Service("serviceB")
public class UserServiceImplB implements UserService { ... }// 注入时,用 @Qualifier 指定名称
@Service
public class OrderService {@Autowired@Qualifier("serviceA") // 精确选 "serviceA" 这个 Beanprivate UserService userService;
}
关键点:@Qualifier
的值必须和 Bean 的名称完全一致,否则找不到。
(3)@Resource (name=“bean 的名称”)
作用:这是 Java 标准注解(JSR-250),不是 Spring 专属,按 “名称(name)” 匹配 Bean,功能类似 @Autowired + @Qualifier
。
用法(示例):
@Service("serviceA")
public class UserServiceImplA implements UserService { ... }@Service
public class OrderService {// 直接用 @Resource 指定名称@Resource(name = "serviceA") private UserService userService;
}
和 @Autowired + @Qualifier
的区别:
@Resource
是 Java 标准,兼容性更好(换框架可能还能用);@Autowired
是 Spring 专属。@Resource
默认先按名称匹配,匹配不到再按类型;@Autowired
默认严格按类型,配合@Qualifier
才按名称。
2.@Resource 与 @Autowired区别
① @Autowired 是spring架构提供的注解,而@Resource是JDK提供的注解。
② @Autowired按照类型注入,而@Resource默认是按照名称注入。