【问题标题】:Spring - How to cache in self-invocation with aspectJ?Spring - 如何使用 aspectJ 缓存自调用?
【发布时间】:2020-11-05 23:19:12
【问题描述】:

感谢您点击我的问题。 我想在自调用中调用缓存方法,所以需要使用AspectJ。 (缓存的配置没问题)

  1. 添加 AspectJ 依赖项
implementation 'org.springframework.boot:spring-boot-starter-aop'
  1. 将 @EnableCaching(mode = AdviceMode.ASPECTJ) 添加到我的 application.java
@EnableJpaAuditing
@EnableCaching(mode = AdviceMode.ASPECTJ) // <-- here 
@SpringBootApplication
public class DoctorAnswerApplication {

    public static void main(String[] args) {
        SpringApplication.run(DoctorAnswerApplication.class, args);
    }

}
  1. 我的服务.java
@Service
public class PredictionService {

    @Cacheable(value = "findCompletedRecordCache")
    public HealthCheckupRecord getRecordComplete(Long memberId, String checkupDate) {
        Optional<HealthCheckupRecord> recordCheckupData;
        recordCheckupData = healthCheckupRecordRepository.findByMemberIdAndCheckupDateAndStep(memberId, checkupDate, RecordStep.COMPLETE);

        return recordCheckupData.orElseThrow(NoSuchElementException::new);
    }
}
  1. 我的测试代码
    @Test
    public void getRecordCompleteCacheCreate() {
        // given
        Long memberId = (long)this.testUserId;
        List<HealthCheckupRecord> recordDesc = healthCheckupRecordRepository.findByMemberIdAndStepOrderByCheckupDateDesc(testUserId, RecordStep.COMPLETE);
        String checkupDate = recordDesc.get(0).getCheckupDate();
        String checkupDate2 = recordDesc.get(1).getCheckupDate();

        // when
        HealthCheckupRecord first = predictionService.getRecordComplete(memberId,checkupDate);
        HealthCheckupRecord second = predictionService.getRecordComplete(memberId,checkupDate);
        HealthCheckupRecord third = predictionService.getRecordComplete(memberId,checkupDate2);

        // then
        assertThat(first).isEqualTo(second);
        assertThat(first).isNotEqualTo(third);
    }

我没有...? 我没有开设任何与 aspectJ 相关的课程。 我认为 @EnableCaching(mode = AdviceMode.ASPECTJ) 使 @Cacheable 由 AspectJ 代替 Spring AOP(proxy) 工作。

【问题讨论】:

    标签: spring caching aspectj ehcache spring-aop


    【解决方案1】:

    你读过Javadoc for EnableCaching吗?

    请注意,如果 mode() 设置为AdviceMode.ASPECTJ,则proxyTargetClass() 属性的值将被忽略。另请注意,在这种情况下,spring-aspects 模块 JAR 必须存在于类路径中,编译时编织或加载时编织将方面应用于受影响的类。这种情况下不涉及代理;本地调用也会被拦截。

    所以请检查你是否

    1. 在类路径上有spring-aspects,并且
    2. 使用参数java -javaagent:/path/to/aspectjweaver.jar 启动您的应用程序。

    #2 有一个替代方案,但使用 Java 代理是最简单的。我不是 Spring 用户,所以我不是 Spring 配置方面的专家,但即使是像我这样的 Spring 菜鸟也能成功使用 Java 代理,所以请先试一试。

    【讨论】:

    • 感谢您的评论。我看到一个文件说 implementation 'org.springframework.boot:spring-boot-starter-aop' 包括 org.springframework:spring-aop 和 org.aspectj:aspectjweaver。我正在尝试从 ~aspectjweaver.jar 开始。我想我没有设置一些配置。
    • 请在您尝试后报告,以便我们一起关闭此问题。
    • 好的!我会回来的。
    • 你是终结者吗? ?
    【解决方案2】:

    感谢@kriegaex,他通过指出 spring-aspects 依赖和 load-time-weaving javaagent 要求来修复我。为了方便其他人,Spring Boot和Maven的配置sn-ps如下。

    (注意:最后,我觉得这一切(以及副作用)对于我的项目来说并不值得。请参阅我的其他答案,了解一个简单但有点难看的解决方法。)

    POM:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
    

    应用配置:

    @Configuration
    @EnableCaching(mode = AdviceMode.ASPECTJ)
    public class ApplicationConfig { ... }
    

    目标方法:

    @Cacheable(cacheNames = { "cache-name" })
    public Thingy fetchThingy(String identifier) { ... }
    

    编织机制:

    选项 1:加载时间编织(Spring 默认)

    使用 JVM javaagent 参数或添加到您的 servlet 容器库

    -javaagent:<path-to-jar>/aspectjweaver-<version>.jar
    

    选项 2:编译时间编织

    (这应该可行,但我发现缺少用于 Spring 缓存的连贯示例 - 请参阅下面的进一步阅读)

    使用 aspectj-maven-plugin:https://www.mojohaus.org/aspectj-maven-plugin/index.html

    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <version>1.11</version>
        <dependencies>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjrt</artifactId>
                <version>${aspectj.version}</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjtools</artifactId>
                <version>${aspectj.version}</version>
            </dependency>
        </dependencies>
        <executions>
            <execution>
                <goals>
                    <goal>compile</goal>
                    <goal>test-compile</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <outxml>true</outxml>
            <showWeaveInfo>false</showWeaveInfo>
            <Xlint>warning</Xlint>
            <verbose>false</verbose>
            <aspectLibraries>
                <aspectLibrary>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-aspects</artifactId>
                </aspectLibrary>
            </aspectLibraries>
            <complianceLevel>${java.version}</complianceLevel>
            <source>${java.version}</source>
            <target>${java.version}</target>
        </configuration>
    </plugin>
    

    出于参考/搜索目的,以下是引发这一切的错误:

    Caused by: java.io.FileNotFoundException: class path resource [org/springframework/cache/aspectj/AspectJCachingConfiguration.class] cannot be opened because it does not exist
    

    更多阅读:

    【讨论】:

    • 再次感谢您。阅读您的答案后,我创建了新项目进行测试。如果您很有趣并且有时间提供帮助,可以检查一下我的 git 吗? link - 'aspectj' 分支:我正在尝试应用 AspjectJ。但@Cacheable 不工作 - '缓存' 分支:在应用 AspectJ 之前。 @Cacheable 效果很好
    【解决方案3】:

    TL;DR:如果 AspectJ 让您头疼,而您除了解决 Spring Caching 自调用之外并不真正需要它,那么添加一个简单的“缓存委托”bean 实际上可能更清洁/更轻/更容易您的服务层可以重复使用。没有额外的依赖,没有性能影响,也没有意外的副作用来改变 spring 代理默认的工作方式。

    代码:

    @Component
    public class CacheDelegateImpl implements CacheDelegate {
        @Override @Cacheable(cacheNames = { "things" })
        public Object getTheThing(String id) { ... }
    }
    
    @Service
    public class ThingServiceImpl implements ThingService {
        @Resource
        private CacheDelegate cacheDelegate;
    
        public Object getTheThing(String id) {
            return cacheDelegate.getTheThing(id);
        }
    
        public Collection<Object> getAllTheThings() {
            return CollectionUtils.emptyIfNull(findAllTheIds())
                    .parallelStream()
                    .map(this::getTheThing)
                    .collect(Collectors.toSet());
        }
    }
    

    添加另一个答案,因为为了自己解决同样的问题,我最终改变了方向。 @kriegaex 和我之前提到了更直接的解决方案,但对于那些在您根本不需要 AspectJ 时遇到问题的人来说,这是一个不同的选择。

    对于我的项目,仅添加 AspectJ 以允许可缓存的相同 bean 引用是一场灾难,它导致了 10 个新问题,而不是一个简单(但令人讨厌)的问题。

    一个简短的非详尽纲要是:

    • 引入多个新依赖项
    • 将复杂的 POM 插件引入编译时编织(这对我来说从来没有完全正确)或将运行时字节编织 jar 编组到正确的位置
    • 为我们的所有部署添加运行时 javaagent JVM 参数
    • 在构建时或开始时(进行编织)的性能要差得多
    • AspectJ 在代码库的其他区域(我很乐意使用 Spring 代理)中的 Spring Transactional 注释上使用并失败
    • Java 版本控制问题
      • 不知何故依赖于古老的 Sun Microsystems tools.jar(在更高版本的 OpenJDK 中不存在)
    • 关于如何独立于 Spring Transactions 和/或没有使用 AspectJ 的成熟 AOP(我不需要/不需要)实现缓存用例的一般较差且分散的文档

    最后,我只是通过代理恢复到 Spring Caching,并引入了一个“缓存委托”,我的两个服务方法都可以引用它。这种解决方法不是最漂亮的,但对我来说,比我在真正不需要 AspectJ 时跳过的所有 AspectJ 箍更可取。我只想要无缝缓存和 DRY 服务代码,这种解决方法可以实现。

    【讨论】:

    • 感谢您分享您的经验和好文章。我通过代理使用 @EnableAspectJAutoProxy(exposeProxy = true) 注释而不是 AspectJ 应用了 Spring Cache(EhCache)。它可以直接访问类的AOP类(自调用)。我是 Spring 初学者,所以我只想学习和应用每种情况的最佳实践。没想到 AspectJ 出现了 10 次头疼?所以我会再次研究并阅读您链接的文章以备下次准备。
    • 对不起,迟到的评论,但请允许我说,我认为您的问题不是 AspectJ,或者在 Spring 中使用 AspectJ 非常困难(实际上使用 LTW 非常简单),但那可能你只是遇到了问题,因为你是第一次这样做。例如,tools.jar 问题可能是由于您使用了仍然依赖于 Java 8 的过时版本的 AspectJ Maven 插件,参考 tools.jar,但您使用了更新的 JDK 进行编译。使用更新的 Maven 插件,这将不是问题。
    • 实际上,加载时编织 (LTW) 根本不需要 AspectJ Maven 插件,仅用于编译时编织 (CTW)。也许您不小心将两者混合在一起,或者您在解决问题过程的不同迭代中都尝试了这两种方法。
    • @kriegaex 感谢您的 cmets。我确实提到有两种方法,CTW 或 LTW,两者的配置/插件不同。我没有同时做这两件事,但我在回答中都提到了。
    • 另外,我同意第一次这样做很困难,你可以通过足够的努力让它工作。我的意思是,即使它确实有效,这种取舍也不值得(对我而言)。除了使缓存更加无缝之外,我不需要它在我的代码中执行任何功能。配置很痛苦,性能也不是微不足道的。工具依赖来自 spring-boot BOM 中的一个部门。与它大相径庭,首先使用 spring-boot 会带来很大的好处。我会一直使用最新的插件。从那时起,他们可能已经继续前进了。
    猜你喜欢
    • 2012-01-15
    • 1970-01-01
    • 2014-08-06
    • 2017-07-22
    • 1970-01-01
    • 2020-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多