关于java:聊聊那些年我们实现java-AOP几种常见套路

27次阅读

共计 6033 个字符,预计需要花费 16 分钟才能阅读完成。

前言

有肯定开发教训的同学对 AOP 应该很理解吧,如果不理解,能够先查看如下文章进行科普一下 https://baike.baidu.com/item/AOP/1332219?fr=aladdin,再来浏览本文。

示例前置筹备

注: 本示例基于 springboot 进行演示

1、在我的项目 pom 引入 aop 的 GAV

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

2、编写业务服务

@Service
public class EchoService {

    @CostTimeRecoder
    public void echo(String message){System.out.println("echo ->" + message);
    }

}

3、编写 aspect 切面

@Aspect
public class EchoAspect {@Before(value = "execution(* com.github.lybgeek.aop.service.EchoService.echo(..))")
    public void before(JoinPoint joinPoint){System.out.println("USE AOP BY ASPECT WITH ARGS:" + Arrays.toString(joinPoint.getArgs()));

    }
}

实现 AOP 的常见套路

1、在编译期阶段实现 AOP

办法一:通过 aspectj-maven-plugin 插件在编译期进行织入

在我的项目的 pom 引入如下内容

<build>
  <plugins>
    <plugin>
      <artifactId>maven-surefire-plugin</artifactId>
      <version>2.22.2</version>
    </plugin>
    <plugin>
      <groupId>com.nickwongdev</groupId>
      <artifactId>aspectj-maven-plugin</artifactId>
      <version>1.12.6</version>
      <configuration>
        <complianceLevel>${java.version}</complianceLevel>
        <source>${java.version}</source>
        <target>${java.version}</target>
        <encoding>${project.encoding}</encoding>
      </configuration>
      <executions>
        <execution>
          <goals>
            <goal>compile</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
<dependencies>
  <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.5</version>
  </dependency>
</dependencies>

通过执行如下 maven 命令,进行我的项目编译

mvn clean compile

执行测试类

public class AspectMavenPluginMainTest {public static void main(String[] args) {EchoService.echo("aspectMavenPlugin");
    }
}

发现切面曾经执行。咱们在查看下生成的 EchoService.class 文件有没有产生什么变动

public class EchoService {public EchoService() { }

    public static final void echo(String message) {JoinPoint var1 = Factory.makeJP(ajc$tjp_0, (Object)null, (Object)null, message);
        EchoAspect.aspectOf().before(var1);
        System.out.println("echo ->" + message);
    }

    static {ajc$preClinit();
    }
}

发现多了一些切面的内容。

注: 本示例利用他人从新封装的插件,而非 Codehaus 的官网提供的插件,Codehaus 的官网提供的插件只能反对 JDK8(蕴含 JDK8)以下的版本,而本示例的插件能够反对到 JDK13

本示例的插件 github 地址:https://github.com/nickwongdev/aspectj-maven-plugin

Codehaus 的官网插件地址:https://github.com/mojohaus/aspectj-maven-plugin
以及相应介绍:https://www.mojohaus.org/aspectj-maven-plugin/index.html

办法二:利用 APT + JavaPoet 在编译期实现切面逻辑

如果对于 APT 不理解的小伙伴,能够查看我之前的文章聊聊如何使用 JAVA 注解处理器(APT)

而 JavaPoet 是 JavaPoet 是生成 .java 源文件的 Java API,具体查看官网文档
https://github.com/square/javapoet
或者查看此博文
https://weilu.blog.csdn.net/article/details/112429217

不过 JavaPoet 只能生产新的代码,无奈对原有的代码进行批改。因而在演示此办法时,本文就通过生成一个继承 EchoService 的子类,来实现 AOP 性能

生成的子类如下

public final class LybGeekEchoServiceCostTimeRecord extends EchoService {public LybGeekEchoServiceCostTimeRecord() { }

    public final void echo(String message) {long startTime = System.currentTimeMillis();
        super.echo(message);
        long costTime = System.currentTimeMillis() - startTime;
        System.out.println("costTime :" + costTime + "ms");
    }
    }

注: 因为 JavaPoet 是通过生成新代码,而非进行在源代码进行插桩,因而也不是很合乎咱们我要求

办法三:利用 APT+AST 在编译期进行织入

AST 形象语法树,能够在编译期对字节码进行批改,达到插桩的成果。因之前我有写过一篇文章
聊聊如何通过 APT+AST 来实现 AOP 性能

本示例就不贴相应的代码了

2、在 JVM 进行类加载时进行 AOP

外围是用利用 aspectjweaver 在 JVM 进行类加载时进行织入。具体实现步骤如下

1、在我的项目的 POM 引入 aspectjweaver GAV

<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>

2、创立切面类和须要被织入的指标类

即示例前置筹备的内容

3、在 src/main/resource 目录下创立 META-INF/aop.xml 文件

<aspectj>
    <weaver options="-XnoInline -Xset:weaveJavaxPackages=true -Xlint:ignore -verbose -XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMessageHandler">
        <!-- 在编织时导入切面类和须要被切入的指标类 -->
        <include within="com.github.lybgeek.aop.aspect.EchoAspect"/>
        <include within="com.github.lybgeek.aop.service.EchoService"/>
    </weaver>
    <aspects>
        <!-- 指定切面类 -->
        <aspect name="com.github.lybgeek.aop.aspect.EchoAspect"/>
    </aspects>
</aspectj>

4、指定 VM 参数

-javaagent:aspectjweaver.jar 的门路
示例:-javaagent:D:\repository\org\aspectj\aspectjweaver\1.9.5\aspectjweaver-1.9.5.jar

5、测试

public class AspectjweaverMainTest {public static void main(String[] args) {EchoService echoService = new EchoService();
        echoService.echo("Aspectjweaver");
    }
}

查看控制台

3、在运行时进行 AOP

咱们以 spring aop 为例

1、手动代理(间接应用底层 API)

次要是利用 AspectJProxyFactory、ProxyFactoryBean、ProxyFactory

public class AopApiTest {

    @Test
    public void testAopByAspectJProxyFactory(){AspectJProxyFactory aspectJProxyFactory = new AspectJProxyFactory(new EchoService());
        aspectJProxyFactory.addAspect(EchoAspect.class);
        EchoService echoService = aspectJProxyFactory.getProxy();
        echoService.echo("AspectJProxyFactory");
    }

    @Test
    public void testAopByProxyFactoryBean(){ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        proxyFactoryBean.setTarget(new EchoService());

        AspectJExpressionPointcutAdvisor aspectJExpressionPointcutAdvisor = new AspectJExpressionPointcutAdvisor();
        aspectJExpressionPointcutAdvisor.setExpression("execution(* com.github.lybgeek.aop.service.EchoService.echo(..))");
        aspectJExpressionPointcutAdvisor.setAdvice((MethodBeforeAdvice) (method, args, target) -> System.out.println("USE AOP BY ASPECT WITH ARGS:" + Arrays.toString(args)));
        proxyFactoryBean.addAdvisor(aspectJExpressionPointcutAdvisor);

        EchoService echoService = (EchoService) proxyFactoryBean.getObject();
        echoService.echo("ProxyFactoryBean");

    }

    @Test
    public void testAopByProxyFactory(){ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(new EchoService());

        AspectJExpressionPointcutAdvisor aspectJExpressionPointcutAdvisor = new AspectJExpressionPointcutAdvisor();
        aspectJExpressionPointcutAdvisor.setExpression("execution(* com.github.lybgeek.aop.service.EchoService.echo(..))");
        aspectJExpressionPointcutAdvisor.setAdvice((MethodBeforeAdvice) (method, args, target) -> System.out.println("USE AOP BY ASPECT WITH ARGS:" + Arrays.toString(args)));
        proxyFactory.addAdvisor(aspectJExpressionPointcutAdvisor);

        EchoService echoService = (EchoService) proxyFactory.getProxy();
        echoService.echo("ProxyFactory");



    }

2、主动代理

这个是咱们平时用得最多的。主动代理常见实现伎俩就是在 spring bean ioc 阶段的后置处理器阶段进行加强

示例

@Configuration
public class AopConfig {

    @Bean
    public EchoAspect echoAspect(){return new EchoAspect();
    }


}

因为主动代理太常见了,java 开发必备技能,就不多做介绍了

总结

本文次要从编译期,JVM 加载器期、运行期这三个环节,来讲述如何进行 AOP。如果对性能有强烈要求的话,举荐在编译期或者 JVM 加载期进行织入。如果想对办法修饰符为 final、static、private 进行织入,也能够思考在编译期进行实现。不过在编译期或者 JVM 加载期进行织入有个弊病就是,呈现问题不好排查。如果不是对性能有极致要求的话,举荐在运行时,进行 AOP 进行切入,次要是呈现问题,绝对好排查。有时候基于业务角度而非技术角度,进行衡量,可能会得出意想不到的成果

demo 链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-aop

正文完
 0