理解Spring事务

事情源于一个Required New事务的需求,可能写代码时都遇到过这样的场景:一个比较重要的操作,需要在数据库保存日志或者流水之类的信息。写之前老大特地叮嘱,Required New方法和业务方法不要放在同一个类中,不然Required New事务会不生效,我不信邪,就试了下,emmmmmm,脸疼。网上搜了下,发现类似的问题很多,如一个类的两个方法,如果直接用没有事务控制的方法调用有@Transactional声明事务的方法,会不生效。然后查了些资料,发现了原因,记录分享下。

References:

测试和介绍的部分省略,直接说原理:
在开启了注解的声明式事务后,就意味着所有标记有@Transactional的方法应该在启动时被扫描并且变为有事务的方法。那么事务行为发生在什么地方呢?
举个栗子:

@Service
public class AccountServiceImpl  implements AccountService {
    @PersistenceContext
    private EntityManager entityManager;

    @Transactional
    public void create(Account account) {
    entityManager.persist(account);
    }
}

before-startup.png

在启动时,创建了一个新的代理类。代理类负责添加事务控制,如下所示:
Transactional-proxy-after-startup.png

生成的代理类是最重要的AccountService实现类,它添加了事务。
那么如何确信使用的是生成的代理而不是我们自己的实现类?有兴趣可以自己跑一下代码试试,这里简单的给出一个运行示例:

AccountService accountService = (AccountService) applicationContext.getBean(AccountService.class);
String accountServiceClassName = accountService.getClass().getName();
logger.info(accountServiceClassName);

输出如下:

INFO : transaction.TransactionProxyTest - $Proxy13

这个类是一个动态代理的类,是Spring使用JDK的反射API生成的(可以了解下java.lang.reflect.Proxy)。
应用关闭后,代理类就会被销毁并且你的磁盘上依然只有AccountService和AccountServiceImpl。

那么Spring如何做到装配代理而不是我们的实现类呢?
先看下这么一段代码:

@Controller
public class AccountController {
    private AccountService accountService;

    private void setAccountService(AccountService accountService) {
        this.accountService=accountService;
    }

    //…
}

proxy-and-target1.png

accountService属性是AccountService(接口)类型。变量依赖的是接口类型的AccountService,而不是具体的实现类。这样可以减少类之间的依赖。(最佳实践)

如上所示,AccountServiceImpl和生成的代理类都实现了AccountService接口。

  • 如果有代理,Spring会注入代理
  • 如果没有,Spring注入AccountServiceImpl的实例

上面是bean实现了接口的情况,Spring会和bean实现相同的接口,那如果bean没有实现任何接口呢?
Spring也能代理没有接口的bean,因为在许多情况下,实现接口并不是好的实践。
默认情况下,如果bean没有实现接口,Spring会使用技术继承:在启动时,创建一个新类,它继承自你的bean并添加相应的行为。
proxy-transaction-inheritance.png

为了生成这种代理类,Spring使用了一个叫做cglib的第三方库。(自行了解)

看下使用Java注解的方式:

@Configuration
public class JavaConfig {

    @Bean
    public AccountService accountService() {
        return new AccountServiceImpl((accountRepository());
    }

    @Bean
    public AccountRepository accountRepository() {
        // …
    }

}

每当需要注入“accountService”的bean实例时,Spring就会调用accountService()方法,然后方法会返回一个“new”出来的AccountService。如果有10个地方有这个依赖,这个方法就会被调用10次。

但是,不管Spring的配置是不是用注解的方式,每个bean都应该默认是单例。这是如何实现的呢?
下面这张图解释内部如何工作的:
java-config.png

图中可以看到,
- 如果scope是“singleton”,就检查是否有实例已经被创建了。
- 如果有,返回已经有的实例
- 如果没有,调用super.accountService()
所以是代理类处理了这些逻辑,将你的POJO转成了单例模式。

原理就是以上,所以现在看看问题就很清楚了,那么如何解决呢?
这里总结了三个方案:

  1. 放到一个新的类里(简单粗暴)
  2. 自己注入自己,比如:

    public class UserServiceImpl {
        @Autowired
        private UserService self;
        
    }

    上面的代码在4.3版本后可以使用,如果之前的版本需要注入ApplicationContext手动调用getBean()。但是这个方法有个缺陷,如果bean不是单例的可能会有意想不到的惊喜(bug>_<)。

  3. 代理方式换CGLIB(不知道就自行了解)

路漫漫其修远兮~~!

标签: none

添加新评论

ali-01.gifali-58.gifali-09.gifali-23.gifali-04.gifali-46.gifali-57.gifali-22.gifali-38.gifali-13.gifali-10.gifali-34.gifali-06.gifali-37.gifali-42.gifali-35.gifali-12.gifali-30.gifali-16.gifali-54.gifali-55.gifali-59.gif

加载中……