一、简述
Spring 基于注释的 MVC 框架简化了创建 RESTful Web 服务的过程。传统的 SpringMVC 控制器和 RESTful Web 服务控制器之间的关键区别在于:创建 HTTP 响应主体的方式。虽然传统的 MVC 控制器依赖于 View 技术,但 RESTful Web 服务控制器只返回对象,对象数据作为 JSON/XML 直接写入 HTTP 响应。以下步骤描述了典型的 SpringMVC REST 工作流:
2️⃣该请求被 DispatcherServlet 拦截,该服务器查找 Handler Mappings 及其类型。
- 应用程序上下文文件中定义的 Handler Mappings 部分告诉 DispatcherServlet 使用哪种策略根据传入请求查找控制器。
- SpringMVC 支持三种不同类型的映射请求 URI 到控制器:注释,名称约定和显式映射。
3️⃣请求由 Controller 处理,响应返回到 DispatcherServlet,然后 DispatcherServlet 将调度到视图。
二、使用@Controller
时需要用@ResponseBody
注释
Spring 3.x 或使用@Controller
情况下,在方法上使用@ResponseBody
注释时,Spring 会转换返回值并自动将其写入 HTTP 响应。Controller 类中每个方法都必须使用@ResponseBody
进行注释。Spring 有一个在后台注册的HttpMessageConverters
列表。HTTPMessageConverter
的职责是将请求主体转换为特定类并再次返回响应主体,具体取决于预定义的 mime 类型。每次发出请求命中@ResponseBody
时,Spring 都会遍历所有已注册的HTTPMessageConverters
,寻找符合给定 mime 类型和类的第一个,然后将其用于实际转换。
注意:
@ResponseBody
添加到返回值中的每个@RequestMapping
方法,Spring 将做两件事:
1️⃣将<context:component-scan>
和<mvc:annotation-driven/>
标记添加到 Spring 配置文件中。
- <context:component-scan>激活注释并扫描包以在应用程序上下文中查找和注册 bean。
- <mvc:annotation-driven/>如果 Jackson/JAXB 库在类路径上,则添加对读写 JSON/XML 的支持。
- 对于 JSON 格式,包括jackson-databind jar;对于 XML,包括项目类路径的jaxb-api-osgi jar。
2️⃣可在任何服务器(如Tomcat)上部署并运行应用程序。
-
http://localhost:8080/SpringRestControllerDemo/rest/Bob
并显示输出JSON; -
http://localhost:8080/SpringRestControllerDemo/rest/Bob.xml
输出XML
三、@RestController
相当于@ResponseBody + @Controller
Spring 4.0 引入了@RestController
,这是一个控制器的专用版本,它是一个方便的注释,除了自动添加@Controller
和@ResponseBody
注释之外没有其他作用。通过使用该注解对控制器类进行注释,不再需要将@ResponseBody
添加到所有请求映射方法中。@ResponseBody
注释默认处于活动状态。
使用@RestController
非常简单,这是从 Spring v4.0 开始创建 MVC RESTful Web 服务或基于 SpringBoot 2 的首选方法。
- 【前后端分离】
@RestController
是@Controller和@ResponseBody
的合集,表示这是个控制器 bean,并且是将函数的返回值直接填入 HTTP 响应体中,是 REST 风格的控制器。 - 【前后端不分离】单独使用
@Controller
不加@ResponseBody
的话一般使用在要返回一个视图的情况,这种情况属于比较传统的 SpringMVC 的应用。 - @Controller + @ResponseBody 返回 JSON 或 XML 形式数据。
四、区别
-
@RestController
注解相当于@ResponseBody
与@Controller
合在一起的作用。 - 如果只是使用
@RestController
注解 Controller,则 Controller 中的方法无法返回 jsp 页面,配置的视图解析器InternalResourceViewResolver不起作用,返回的内容就是 return 里的内容。例如:本来应该到 success.jsp 页面的,则其显示 success。 - 如果需要返回到指定页面,则需要用
@Controller
配合视图解析器InternalResourceViewResolver才行。 - 如果需要返回 JSON,XML 或自定义 mediaType 内容到页面,则需要在对应的方法上加上
@ResponseBody
注解。
总之,使用@Controller
注解在方法上,视图解析器可以解析 return 的 jsp、html 页面,并且跳转到相应页面。若要返回 json 等内容到页面,则需要加@ResponseBody
。使用@RestController
,返回 json 数据不需要在方法前面加@ResponseBody
,但使用@RestController
,就不能返回 jsp、html 页面,视图解析器无法解析。
五、Spring的Controller是单例的
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ScopeTestController {
private int num = 0;
@RequestMapping("/testScope")
public void testScope() {
System.out.println(++num);
}
@RequestMapping("/testScope2")
public void testScope2() {
System.out.println(++num);
}
}
首先访问 http://localhost:8080/testScope,得到的答案是 1;
然后再访问 http://localhost:8080/testScope2,得到的答案是 2。
得到的不同的值,这是线程不安全的。给 controller 增加作用域@Scope("prototype"):
@Controller
@Scope("prototype")
public class ScopeTestController {
private int num = 0;
@RequestMapping("/testScope")
public void testScope() {
System.out.println(++num);
}
@RequestMapping("/testScope2")
public void testScope2() {
System.out.println(++num);
}
}
首先访问 http://localhost:8080/testScope,得到的答案是 1;
然后再访问 http://localhost:8080/testScope2,得到的答案还是 1。
由此发现:单例 controller 类是不安全的,会导致属性重复使用。解决方案:
- 不要在 controller 中定义成员变量。
- 如若非要定义一个非静态成员变量,则通过@Scope("prototype"),将其设置为多例模式。
- 在 Controller 中使用 ThreadLocal 变量。