Spring:无法在类内使用this完成@Async、@Transactional、@Cacheable功能问题解决

问题

基于spring-aop的proxy实现,无法在类内使用this完成@Async、@Transactional、@Cacheable注解的功能。

原因

基于proxy的方式是在spring容器初始化过程中,通过继承或实现相应对象的类/接口,形成新的代理对象,并将原始对象进行包装,并在调用前后插入一些point,完成aop的功能,而this方式不会经过proxy,相当于原始对象的直接调用。

解决方案

解决方式,大致有以下几种:

  1. 不使用this,从spring容器上下文中获取被代理的对象,替换this
  2. 使用ThreadLocal方式,即Spring-aop提供的工具包:((xxService) AopContext.currentProxy()).yymethod(...)
  3. 使用aspectj,动态织入的方式,保持使用this.yymethod(...)的方式不变

前两种可查阅网上资料,说明有很多,本次主要展示第三种方式(能够保持this的使用方式):

  1. 配置各自的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);

        }

    }

  2. 具体方式:

    1. ltw(load time weaving)方式

      1. 启动参数增加:【本地调试模式需要在启动配置中填写正确的agent配置;运行环境中需编写dockerFile 】

        -javaagent:jarPath/org/aspectj/aspectjweaver/${version}/aspectjweaver-${version}.jar

      2. 依赖

        <!-- 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>

      3. 单测插件配置

        <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> 

    2. ctw(compile time weaving)方式:无需增加额外启动参数,修改后需要执行compile才能运行
      1. 依赖

        <!-- 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>

      2. 插件配置

        <!-- 用于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>
  3. 调用方式

    ...

        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);

        }

    ...

  4. 结果

    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的任何配置都无关,织入机制都是按自己的逻辑,只不过是编译器织入还是加载时动态织入,结果都是一样的,差别就是:

  1. ctw需要引入compile插件进行weave,且有lombok的项目需要使用lombok插件,否则两者不兼容,操作对象是java文件
  2. 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,如下:

<?xml version="1.0"?>

<!--

    AspectJ load-time weaving config file to install common Spring aspects.

-->

<aspectj>

  <!--

  <weaver options="-showWeaveInfo"/>

  -->

  <weaver>

    <include within="com.kk..*"/>

  </weaver>

  <aspects>

    <aspect name="com.yy.VVVAspect" />

  </aspects>

</aspectj>

(四)spring支持哪几种类型的动态weaver?

 

参考资料

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值