问题
基于spring-aop的proxy实现,无法在类内使用this完成@Async、@Transactional、@Cacheable注解的功能。
原因
基于proxy的方式是在spring容器初始化过程中,通过继承或实现相应对象的类/接口,形成新的代理对象,并将原始对象进行包装,并在调用前后插入一些point,完成aop的功能,而this方式不会经过proxy,相当于原始对象的直接调用。
解决方案
解决方式,大致有以下几种:
- 不使用this,从spring容器上下文中获取被代理的对象,替换this
- 使用ThreadLocal方式,即Spring-aop提供的工具包:((xxService) AopContext.currentProxy()).yymethod(...)
- 使用aspectj,动态织入的方式,保持使用this.yymethod(...)的方式不变
前两种可查阅网上资料,说明有很多,本次主要展示第三种方式(能够保持this的使用方式):
-
配置各自的mode为aspectJ
@EnableAsync
(mode = AdviceMode.ASPECTJ)
@EnableCaching
(mode = AdviceMode.ASPECTJ)
@EnableTransactionManagement
(mode = AdviceMode.ASPECTJ)
@SpringBootApplication
public
class
StdDemoApplication {
public
static
void
main(String[] args) {
SpringApplication.run(StdDemoApplication.
class
, args);
}
}
-
具体方式:
-
ltw(load time weaving)方式
-
启动参数增加:【本地调试模式需要在启动配置中填写正确的agent配置;运行环境中需编写dockerFile 】
-javaagent:jarPath/org/aspectj/aspectjweaver/${version}/aspectjweaver-${version}.jar
-
依赖
<!-- Include AspectJ runtime and weaving libraries -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
-
单测插件配置
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
-javaagent:
"${settings.localRepository}"
/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar
</argLine>
<useSystemClassLoader>
true
</useSystemClassLoader>
<forkMode>always</forkMode>
</configuration>
</plugin>
-
- ctw(compile time weaving)方式:无需增加额外启动参数,修改后需要执行compile才能运行
-
依赖
<!-- Include AspectJ runtime and weaving libraries -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
-
插件配置
<!-- 用于LTW,agent模式下:单测保证 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
-javaagent:"${settings.localRepository}"/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar
</argLine>
<useSystemClassLoader>true</useSystemClassLoader>
<forkMode>always</forkMode>
</configuration>
</plugin>
<!--使用aspect插件编译时,需要额外保证lombok的注解被正常编译-->
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.20.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>delombok</goal>
</goals>
</execution>
</executions>
<configuration>
<addOutputDirectory>false</addOutputDirectory>
<sourceDirectory>src/main/java</sourceDirectory>
</configuration>
</plugin>
<!-- 用于CTW,会对aspect(aop.xml)中定义的标记进行无差别wave -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.11</version>
<configuration>
<proc>none</proc>
<Xlint>ignore</Xlint>
<forceAjcCompile>true</forceAjcCompile>
<complianceLevel>${java.version}</complianceLevel>
<source>${java.version}</source>
<target>${java.version}</target>
<showWeaveInfo>true</showWeaveInfo>
<sources/>
<testSources/>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<id>compile-sources</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<weaveDirectories>
<weaveDirectory>${project.build.outputDirectory}</weaveDirectory>
</weaveDirectories>
</configuration>
</execution>
<execution>
<id>compile-test-sources</id>
<goals>
<goal>test-compile</goal>
</goals>
<configuration>
<weaveDirectories>
<weaveDirectory>${project.build.testOutputDirectory}</weaveDirectory>
</weaveDirectories>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>${java.version}</version>
<scope>system</scope>
<systemPath>${project.basedir}/pom.xml</systemPath>
</dependency>
</dependencies>
</plugin>
-
-
-
调用方式
...
public
String testThisAsync(String param)
throws
Exception {
this
.asyncFunction(param);
// 保持内部调用
System.out.println(
"service:async function inner invoke, param:"
+ param);
return
RandomStringUtils.random(
10
);
}
@Async
// ("threadPoolTaskExecutor")
public
void
asyncFunction(String param)
throws
InterruptedException {
Thread.sleep(
2000
);
System.out.println(
"service:async function have been invoked, param:"
+ param);
}
...
-
结果
service:async function inner invoke, param:
66
#可见优先执行了本函数体的下一步,并未等待
@Async
标注的方法
service:async function have been invoked, param:
66
总结
以上方式是基于aspectJ的ltw(load time weaving)也就是加载时对字节码进行重新编写,带来一个弊端就是需要在启动时增加agent,用于支持weave操作。
疑问解答
(一)疑问:为何不使用ctw(compile time weaving),编译期进行重新编写?原因是无法使用spring的控制,这样会对标记的class进行无差别weave,若想要使用 @EnableCache 、@EnableTransactionManagement、@EnableAsync等运行时决定策略,需要使用ltw。该说法不对,ctw或者ltw,本质上与Spring的任何配置都无关,织入机制都是按自己的逻辑,只不过是编译器织入还是加载时动态织入,结果都是一样的,差别就是:
- ctw需要引入compile插件进行weave,且有lombok的项目需要使用lombok插件,否则两者不兼容,操作对象是java文件
- ltw需要在启动时增加aspectweaver的agent,用于在启动后进行weave,操作对象是class文件
运行机制一样:均是先加载classpath/META-INF/aop.xml(任意多个),将aspect及其他配置拿出,对符合的class(ltw)/java(ctw)文件进行weave
所以,既可以使用ctw,也可以使用ltw
(二)疑问:Spring的@EnableAsync在mode=AspectJ模式下,是如何影响最终行为的?简单看下源码即可得知,该模式下,Spring会将单例AnnotationAsyncExecutionAspect拿出,设置executor和exceptionHandler,仅此而已,如果不设置,那仍按同步方式就行了(原来这么取巧!!)
(三)疑问:使用了该模式,如何控制扫描范围?使用aop.xml,如下:
|
(四)spring支持哪几种类型的动态weaver?
参考资料
- https://stackoverflow.com/questions/48492162/using-aspectj-ltw-to-allow-spring-proxy-functionalty-on-self-invocation-of-non-p
- https://www.cnblogs.com/takumicx/p/10150344.html
- https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop
- https://blog.mythsman.com/post/5d301cf2976abc05b34546be/
- https://chaoshuaizhang.github.io/2018/06/09/java/AOP%E7%9A%84%E4%B8%80%E4%BA%9B%E4%BD%BF%E7%94%A8%E4%B8%8E%E7%90%86%E8%A7%A3/
- https://www.eclipse.org/aspectj/doc/released/pdguide/index.html