Spring测试上下文缓存+ AspectJ @Transactional + Ehcache的痛苦

您在使用AspectJ @Transactionals和Spring吗? 您是否有多个SessionFactory,也许一个用于嵌入式数据库进行单元测试,一个用于实际数据库进行集成测试? 您是否遇到这些例外之一?

org.springframework.transaction.CannotCreateTransactionException:无法打开Hibernate Session进行事务处理。 嵌套的异常是org.hibernate.service.UnknownServiceException:请求了未知的服务

要么

net.sf.ehcache.Cache.isKeyInCache(Cache.java:3068)的org.hibernate.cache.ehcache.internal.regions.EhcacheDataRegion.contains(EhcacheDataRegion.java:223)上的java.lang.NullPointerException

然后,您遇到了一个问题,其中多个缓存的应用程序上下文相互踩在一起。 这篇博客文章将描述一些解决我们遇到的问题的策略。

背景

Spring的Text Context框架默认尝试通过缓存容器来最小化spring容器必须启动的次数。 如果您正在运行全部使用相同配置的多个测试,则只需为所有测试创建一次容器,而无需在每次测试之前创建容器。 如果您要进行1000次测试,并且容器需要10到15秒的启动时间,那么构建/测试时间就会大为不同。

仅当每个人(您和您使用的所有库)都避免使用静态字段(全局状态)时,这才有效,不幸的是,在某些情况下,这是很难/不可能避免的,即使spring违反了此规定! 导致我们出现问题的几个地方:

  • Spring AspectJ @事务支持
  • EhCache缓存管理器

方面是设计上的单例。 Spring使用它来放置对BeanFactory和PlatformTransactionManager的引用。 如果您有多个带有各自“自己的” AnnotationTransactionAspect的容器,则它们实际上共享AnnotationTransactionAspect,而最后启动的容器是“赢家”,从而导致各种意外的难以调试的问题。

Ehcache在这里也很痛苦。 ehcache库维护它在VM中创建的所有缓存管理器的静态列表。 因此,如果要使用多个容器,它们将共享对同一缓存的引用。 Spring Test提供了一种机制来指示该测试已“污染”了容器并需要创建它。 这意味着在完成测试类后会破坏容器。 很好,但是如果您的容器具有其他容器共享的对象,则销毁该共享对象会破坏其他容器。

解决方案

最简单的解决方案是基本上完全禁用应用程序上下文缓存。 只需在每个测试上放置@DirtiesContext即可完成此操作,或者(最好)您可能应该使用超级类(“抽象测试夹具”)来组织您的测试,在这种情况下,只需在基类上添加@DirtiesContext。 不幸的是,您还失去了所有缓存优势,并且构建时间将增加。

弹簧容器没有“清理自身”的通用机制,因为跨容器共享状态肯定是一种反模式。 他们自己这样做(AnnotationTransactionAspect,EhCacheManagerFactoryBean.setShared(true)等),这表明他们可能应该添加一些支持。 如果要继续缓存,则第1步是确保您的代码中不使用任何“静态字段”单例。 还要确保将要写入的所有外部资源分开,以便多个容器可以共存于同一JVM中。

为了解决AspectJ问题,我发现的最佳解决方案是创建一个TestExecutionListener,以在测试执行之前“重置” AnnotationTransactionAspect以指向正确的bean工厂和PTM。 这种侦听器的代码在本要点中

然后,要使用侦听器,请将@TestListeners放在基类测试夹具上,以便所有测试都使用新的侦听器运行。 请注意,使用@TestListeners批注时,必须指定所有执行侦听器,包括现有的Spring侦听器。 要点有一个例子。

Ehcache的解决方法是不允许在容器之间共享CacheManager实例。 为此,您必须确保所有缓存管理器都有唯一的名称。 实际上,这很容易配置。

@org.springframework.context.annotation.Configuration
public class CacheBeans {

    private static final AtomicInteger cacheCounter = new AtomicInteger(0);
    
    @Bean
    public EhCacheManagerFactoryBean ecmfb() {
        EhCacheManagerFactoryBean ecmfb = new EhCacheManagerFactoryBean();
        // cannot share the cache managers
        ecmfb.setShared(false);
        // if you are using ehcache.xml on the classpath then there's nothing more to do than just make it 
        // a unique name.  If you are using a different config file then use ecmfb.setConfigLocation()
        ecmfb.setCacheManagerName("ehCache-" + cacheCounter.incrementAndGet());
        return ecmfb;
    }
    
    // more @Bean defs
}

相关问题

以下是一些涉及此问题的Spring jira问题的链接:

https://jira.spring.io/browse/SPR-6121

https://jira.spring.io/browse/SPR-6353

翻译自: https://www.javacodegeeks.com/2014/04/spring-test-context-caching-aspectj-transactional-ehcache-pain.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值