为Spring控制器添加@Transactional注解引发404错误的原因排查
问题根源
给控制器添加@Transactional后,Spring默认生成了JDK动态代理。JDK代理是基于接口实现的,代理类不会继承原控制器类上的@RestController、@RequestMapping等注解。而Spring MVC的AbstractHandlerMethodMapping在注册路由时,会通过isHandler方法检查Bean类是否带有@Controller/@RestController注解——代理类没有这些注解,因此无法通过检查,路由映射未被注册,最终所有请求返回404。
解决方案
1. 改用CGLIB代理(推荐)
CGLIB是基于类继承的代理方式,会完整保留原类的注解,让Spring MVC能正常识别控制器。
Java配置方式:
在事务配置类中开启proxyTargetClass参数:@Configuration @EnableTransactionManagement(proxyTargetClass = true) public class TransactionConfig { // 配置事务管理器 @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }XML配置方式:
在Spring配置文件中设置proxy-target-class="true":<tx:annotation-driven proxy-target-class="true"/> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
2. 迁移@Transactional到业务层(更符合架构规范)
事务注解本来就应该放在业务逻辑层(Service)而非控制器层,这是分层架构的基本规范。把@Transactional从控制器类上移到对应的Service方法或类上,既解决了404问题,也让代码结构更合理。
3. 自定义HandlerMapping的检测逻辑
如果必须使用JDK代理,可以自定义RequestMappingHandlerMapping,修改isHandler方法,让它检测代理背后的目标类是否带有控制器注解:
@Configuration public class WebMvcCustomConfig implements WebMvcConfigurer { @Bean public RequestMappingHandlerMapping requestMappingHandlerMapping() { return new RequestMappingHandlerMapping() { @Override protected boolean isHandler(Class<?> beanType) { // 获取代理对应的目标类 Class<?> targetClass = AopProxyUtils.getSingletonTargetClass(beanType); if (targetClass != null) { beanType = targetClass; } // 调用父类逻辑检查是否为控制器 return super.isHandler(beanType); } }; } }
4. 给控制器接口添加注解(不推荐)
如果控制器类实现了某个接口,可以在接口上添加@RestController和@RequestMapping注解,这样JDK代理类实现该接口后,就能被Spring MVC识别。但这种方式不符合常规编码规范,容易引起混淆,不建议使用。
内容的提问来源于stack exchange,提问作者Steve

