Transaction在Controller层的探索
一般开发中事务要求我们放在Service层,可是有些情况,我们可能会要求放在Controller层,你有没有碰到过这样的需求呢?那么放到Controller层事务会生效吗?会产生什么问题呢?下面一起来看看
I、透过现象看本质
- Controller层代码如下
@RestController @RequestMapping("/city") public class CityControllerImpl implements CityController { @Autowired private CityService cityService; @Override @RequestMapping(value = "getCity",method = RequestMethod.GET,produces = MediaType.APPLICATION_JSON_UTF8_VALUE) @Transcational public BaseResult<City> getCity(@RequestParam("id") Integer id) { City one = cityService.getOne(id); BaseResult<City> baseResult=new BaseResult<>(); baseResult.setData(one); return baseResult; } }
- 运行结果
- 对的,你没有看错,当Transactional加载Controller层时出现404异常
第二种情况
- Controller层代码如下
@RestController @RequestMapping("/city") public class CityControllerImpl { @Autowired private CityService cityService; @RequestMapping(value = "getCity",method = RequestMethod.GET,produces = MediaType.APPLICATION_JSON_UTF8_VALUE) @Transactional public BaseResult<City> getCity(@RequestParam("id") Integer id) { City one = cityService.getOne(id); BaseResult<City> baseResult=new BaseResult<>(); baseResult.setData(one); return baseResult; } }
- 跟上面的区别,就是没有实现CityController接口了,那么我们运行一下,会有什么结果呢?
- 运行结果如下:
{ data: null, message: null, status: 0 }
- 第二种情况居然没有啥问题,那么Transactional是否正常回滚呢?这里答案我直接告诉大家了,即使是换成有数据更改的接口,我们的事务是生效的。
- 下面我为大家看源码解释一下
- 笔者测试使用支持==JAX-RS 2.0==的 Resteasy 测试,发现是没有这个问题的,大家可以自测一下Jersey是不是存在这个问题,推断应该没有
II、熟悉本质解现象
可以看出,我们两个Controller的区别就是一个有实现接口,一个没有实现,为什么差别会这么大呢?
我们知道事务是基于代理实现的,目前Spring中有JDK动态代理和CGLIB代理两种代理,那么跟Spring选择的代理有没有关系呢?我们看一下Spring在代理类的时候选择使用何种代理的源代码。如下:
@Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } }
这是Spring创建代理比较核心的一段代码,在类 DefaultAopProxyFactory
中,不管加没有加接口,Spring看到了@Transactional
注解都会给我们的Controller注册为一个代理对象。注意:Spring并非对所有的Controller都会创建代理类,假如我们的Controller没有暴露任何切面,Spring并不会创建一个代理类
,这里可能大家会感到奇怪,我们这里打个TAG,文末讲解。
继续刚刚的话题,第一种情况,由于我们的Controller有接口,所以就走了JDK代理,相反第二种走了Cglib代理。OK, 我们的CityControllerImpl现在是一个代理类。那么为什么会发生404异常呢?
为什么Controller变成代理之后,就会404异常了,肯定跟我们的SpringMVC有关,我们看一下SpringMVC的核心类 AbstractHandlerMethodMapping
这个类可以绑定URL和需要执行处理器的哪个方法。这个抽象类实现了initializingBean
接口,其实主要的注册URL操作则是通过这个接口的afterPropertiesSet()接口方法来调用的。然后调用initHandlerMethods
方法进行绑定URL。方法详细如下:
protected void initHandlerMethods() { if (logger.isDebugEnabled()) { logger.