Spring - 如何使用 aspectJ 缓存自调用?
Posted
技术标签:
【中文标题】Spring - 如何使用 aspectJ 缓存自调用?【英文标题】:Spring - How to cache in self-invocation with aspectJ? 【发布时间】:2020-11-05 23:19:12 【问题描述】:感谢您点击我的问题。 我想在自调用中调用缓存方法,所以需要使用AspectJ。 (缓存的配置没问题)
-
添加 AspectJ 依赖项
implementation 'org.springframework.boot:spring-boot-starter-aop'
-
将 @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);
-
我的服务.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);
-
我的测试代码
@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) 工作。
【问题讨论】:
【参考方案1】:你读过Javadoc for EnableCaching
吗?
请注意,如果 mode() 设置为
AdviceMode.ASPECTJ
,则proxyTargetClass()
属性的值将被忽略。另请注意,在这种情况下,spring-aspects
模块 JAR 必须存在于类路径中,编译时编织或加载时编织将方面应用于受影响的类。这种情况下不涉及代理;本地调用也会被拦截。
所以请检查你是否
-
在类路径上有
spring-aspects
,并且
使用参数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
更多阅读:
AOP 和 Spring:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop AspectJ 教程(Baeldung):https://www.baeldung.com/aspectj 编译时编织与加载时编织:https://***.com/a/23042793/631272 春季简报中的 CTW 与 LTW:https://***.com/a/41370471/631272 CTW 与 LTW 教程:https://satenblog.wordpress.com/2017/09/22/spring-aspectj-compile-time-weaving/ 让 CTW 在 Eclipse M2e 中工作:https://***.com/a/19616845/631272 CTW 和 Java 11 问题(可能是我与之斗争的一部分):https://www.geekyhacker.com/2020/03/28/how-to-configure-aspectj-in-spring-boot/【讨论】:
再次感谢您。阅读您的答案后,我创建了新项目进行测试。如果您很有趣并且有时间提供帮助,可以检查一下我的 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 会带来很大的好处。我会一直使用最新的插件。从那时起,他们可能已经继续前进了。以上是关于Spring - 如何使用 aspectJ 缓存自调用?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 JUnit 测试 Spring Boot aspectj 方法?
如果没有AspectJ,Spring中如何使用SpringAOP@Transactional?