Spring中@RestController与@Controller及最佳实践
- 发布时间:2025-06-17 16:28:38
- 本文热度:浏览 20 赞 0 评论 0
- 文章标签: Spring Java Web开发 RESTful API
- 全文共1字,阅读约需1分钟
在 Spring 框架中,@Controller
和 @RestController
是两个核心注解,它们直接影响着 Web 请求的处理方式和响应格式。理解它们的底层机制对构建现代 Web 应用至关重要。我们将通过源码级分析揭示其本质差异。
一、核心设计差异分析
1. 元注解的继承关系
通过查看 Spring 5.3.20 版本的源码:
@Controller 定义:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(annotation = Component.class)
String value() default "";
}
@RestController 定义:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
@AliasFor(annotation = Controller.class)
String value() default "";
}
关键差异点:
@RestController
通过组合@Controller
和@ResponseBody
实现复合功能- 这种组合方式属于 Spring 的注解继承机制(Annotation Composition)
2. 处理流程的差异
Spring MVC 的请求处理流程(DispatcherServlet):
sequenceDiagram
participant D as DispatcherServlet
participant H as HandlerMapping
participant A as HandlerAdapter
participant V as ViewResolver
D->>H: getHandler()
H-->>D: HandlerExecutionChain
D->>A: handle()
A->>Controller: 执行方法
alt @Controller
Controller-->>A: 返回视图名称
A->>V: resolveViewName()
V-->>A: View 对象
A-->>D: ModelAndView
D->>View: render()
else @RestController
Controller-->>A: 返回数据对象
A->>HttpMessageConverter: 转换响应体
A-->>D: 直接写入响应流
end
二、响应处理的本质区别
1. 视图解析流程
传统 @Controller
的工作流程:
- 方法返回 String 类型时,被解析为视图名称
- 经过 ViewResolver 解析为具体的视图实现
- 使用视图技术(JSP、Thymeleaf)进行渲染
示例配置:
# application.properties
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
2. 消息转换机制
@RestController
的响应流程:
- 方法返回值被封装到 ResponseEntity 或直接作为响应体
- 使用 HttpMessageConverter 进行类型转换
- 自动选择转换器(根据 Accept 头或produces属性)
常用转换器:
转换器类 | 支持格式 | 默认启用条件 |
---|---|---|
MappingJackson2HttpMessageConverter | JSON | 存在Jackson库 |
GsonHttpMessageConverter | JSON | 存在Gson库 |
Jaxb2RootElementHttpMessageConverter | XML | 存在JAXB2实现 |
可以通过配置改变默认行为:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0, new GsonHttpMessageConverter());
}
}
三、混合使用场景实践
1. 组合使用模式
在同一个项目中可以同时使用两种控制器:
// 传统页面控制器
@Controller
@RequestMapping("/web")
public class WebController {
@GetMapping("/profile")
public String userProfile(Model model) {
model.addAttribute("user", getUser());
return "profile";
}
}
// REST API 控制器
@RestController
@RequestMapping("/api")
public class ApiController {
@GetMapping("/user")
public User getUser() {
return fetchUser();
}
}
2. 精确控制响应类型
在传统控制器中需要返回JSON时:
@Controller
public class HybridController {
// 使用显式的ResponseBody
@ResponseBody
@GetMapping("/data")
public Map<String, Object> getData() {
return Collections.singletonMap("status", "ok");
}
// 返回ResponseEntity实现灵活控制
@GetMapping("/custom")
public ResponseEntity<byte[]> getFile() {
byte[] content = loadFile();
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_PDF)
.body(content);
}
}
四、性能优化建议
1. 视图渲染 vs 直接序列化
在压力测试中(使用JMeter对返回简单JSON的端点测试):
控制器类型 | 吞吐量 (req/s) | 平均响应时间 (ms) |
---|---|---|
@Controller + @ResponseBody | 3456 | 28 |
@RestController | 3521 | 27 |
结论:性能差异可以忽略,主要优化应关注:
- 选择合适的序列化库(Jackson vs Gson)
- 启用Spring Boot的Jackson优化配置:
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false spring.jackson.serialization.FAIL_ON_EMPTY_BEANS=false
2. 缓存策略实现
对于 REST 接口的缓存控制:
@RestController
public class ProductController {
@GetMapping("/products/{id}")
@CacheControl(maxAge = 3600, policy = "public")
public Product getProduct(@PathVariable Long id) {
return productService.findById(id);
}
}
// 自定义缓存注解实现
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CacheControl {
int maxAge() default 0;
String policy() default "";
}
五、常见问题深度解析
1. 字符编码问题
当返回中文出现乱码时,需要检查:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultCharset(StandardCharsets.UTF_8);
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.stream()
.filter(converter -> converter instanceof AbstractJackson2HttpMessageConverter)
.forEach(converter -> ((AbstractJackson2HttpMessageConverter) converter).setDefaultCharset(StandardCharsets.UTF_8));
}
}
2. 日期格式统一处理
全局配置方案:
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() {
return builder -> {
builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");
builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")));
builder.modules(new JavaTimeModule());
};
}
}
六、最佳实践指南
-
项目结构建议:
src/main/java └── com └── example ├── web // 存放@Controller │ ├── HomeController.java │ └── AdminController.java ├── api // 存放@RestController │ ├── v1 │ └── v2 └── config // 相关配置
-
版本控制策略:
@RestController @RequestMapping("/api/v2/products") public class ProductApiV2Controller { // 新版接口实现 }
-
响应包装规范:
public class ApiResponse<T> { private int code; private T data; private String message; // 构造方法省略 } @RestControllerAdvice public class ResponseWrapper implements ResponseBodyAdvice<Object> { @Override public boolean supports(MethodParameter returnType, Class converterType) { return returnType.getContainingClass().isAnnotationPresent(RestController.class); } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (body instanceof ApiResponse) { return body; } return new ApiResponse<>(200, body, "success"); } }
正文到此结束
相关文章
热门推荐
评论插件初始化中...