Controller中为什么不能写@Transactional

1.背景

Controller指SpringMVC项目中用于定义接口信息的类,该类一般会被@Controller或@RestController等SpringMVC相关注解标记;
@Transactional指spring-tx包中定义的事务注解,被该注解标记的方法或类将成为一个整体,“同进同退”;
在开发过程中,注解是我们的神兵利器,但如果不恰当的使用将会造成严重的问题。

2.问题
* 在Controller层的接口定义处添加@Transactional
* 对Controller层进行统一代理,添加公共校验等拦截
* 添加过@Transactional的Controller接口失效,请求返回404

3.解决方案
方案一:调整@Transactional到service层
方案二:添加cglib依赖,指定强制使用cglib代理

4.问题分析
* 从@Transactional入手分析;Spring在扫描bean 的时候,发现某些类或方法上添加了事务注解,就会生成该类的代理类,并赋予代理类事务的相关逻辑,从而达到事务效果;我们在实际调用中,调用的是对应的代理类而非其本身。

* 从Controller层代理入手分析;通过相关工具类例如BeanNameAutoProxyCreater对controller进行统一代理,并插入统一的校验逻辑,达到快速开发的目的;同样的在实际调用中,我们调用到的也是其对应的代理类而非其本身。

*从代理方面入手分析;SpringAOP部分主要使用JDK动态代理或cglib代理;默认情况下,如果被代理的目标对象实现了至少一个接口,则会使用jdk动态代理,否则会通过cglib创建代理类,但也可以通过设置强制使用cglib进行代理操作。

*结合以上分析,404的Controller被代理了两次,因controller没有实现接口,所以第一次代理一定是cglib代理;因设置强制使用cglib代理可解决404问题反推,第二次代理一定是jdk动态代理;结合以上推测,第二次jdk动态代理时可能导致@RequestMapping等注解失效,最终造成404问题。

5.相关问题
本次问题的引发原因是对现有工程新增统一验证逻辑导致,同步发现的问题还有Controller中的自动注入失败,导致执行service中逻辑时报404;原因时Controller中方法为private,private的方法不会被代理,导致引用的service属性没有完成注入。

6.开发规范
* Controller中不要添加@Transactional等注解,该类注解一律放到service中;
* Controller中被定义为接口的方法不要定义为private

7.加buffer

public static void main(String[] args){
    //生成代理类的class文件到工程根目录
    System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
  //生成cglib代理的class文件到指定目录
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"e:\\cglib");
    SpringApplication.run(Application.class);
}

发表回复

您的电子邮箱地址不会被公开。

19 − 2 =