控制器声明 (Declaration)
你可以通过在 Servlet 的 WebApplicationContext 中使用标准的 Spring Bean 定义来定义控制器 Bean。@Controller 刻板注解(Stereotype)允许自动检测,这与 Spring 对类路径中 @Component 类的通用支持是一致的。它同时也作为被注解类的标识,表明其作为 Web 组件的角色。
启用自动检测
要启用此类 @Controller Bean 的自动检测,你可以将组件扫描添加到 Java 配置中:
@Configuration
@ComponentScan("org.example.web")
public class WebConfiguration {
// ...
}@Configuration
@ComponentScan("org.example.web")
class WebConfiguration {
// ...
}<beans ...>
<context:component-scan base-package="org.example.web"/>
</beans>@RestController 是一个组合注解,它本身被 @Controller 和 @ResponseBody 元注解,表示该控制器的每个方法都会继承类型级别的 @ResponseBody 注解,因此会直接写入响应体,而不是进行视图解析。
AOP 代理
在某些情况下,你可能需要在运行时用 AOP 代理装饰控制器。例如,如果你选择在控制器上直接使用 @Transactional 注解。对于控制器,我们建议使用基于类的代理。当注解直接在控制器上时,这通常是自动发生的。
如果控制器实现了一个接口,并且需要 AOP 代理,你可能需要显式配置基于类的代理。例如:
- 使用
@EnableTransactionManagement(proxyTargetClass = true)。 - 使用
<tx:annotation-driven proxy-target-class="true"/>。
注意
从 6.0 版本开始,在使用接口代理时,Spring MVC 不再仅基于接口上的类型级 @RequestMapping 注解来检测控制器。请启用基于类的代理,或者接口本身也必须具有 @Controller 注解。
补充教学
1. 扫描路径的考量
为什么文档中特意提到 base-package="org.example.web"? 这是为了分层扫描。
- Servlet 上下文(Web):通常只扫描
@Controller、WebMvcConfigurer等 Web 相关的组件。 - 根上下文(Root):扫描
@Service、@Repository等业务和持久层组件。 这样做可以避免在 Web 层和业务层之间产生 Bean 的重复加载或循环依赖。
2. 为什么 @RestController 更好写?
在没有 @RestController 时,如果你写一个 REST 接口,你需要在每个方法上都写 @ResponseBody。 组合注解极大地减少了样板代码。它明确了“数据即响应”的职责。
3. AOP 代理的“坑”:proxy-target-class
如果你在 Controller 上使用了 Spring Security 的注解(如 @PreAuthorize)或者 @Transactional,Spring 会创建一个代理对象。
- JDK 动态代理:要求 Controller 必须实现接口。
- CGLIB 代理(基于类的代理):不需要接口。 推荐:在现代开发中(尤其是 Spring Boot 中),默认就是使用 CGLIB 代理。这避免了因缺少接口而导致的拦截失效或类型转换错误。