首页 > 编程语言 >聊聊那些年我们实现java AOP几种常见套路

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

时间:2023-05-10 10:14:31浏览次数:45  
标签:java class echo 聊聊 AOP new EchoService public

前言

有一定开发经验的同学对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

标签:java,class,echo,聊聊,AOP,new,EchoService,public
From: https://www.cnblogs.com/linyb-geek/p/17109279.html

相关文章

  • Java使用wkhtmltopdf实现HTML转pdf
    wkhtmltopdf设置全屏:wkhtmltopdf--disable-smart-shrinking--page-sizeA4-B0-L0-R0-T0test.htmloutput.pdf-B-T-R-L是有效果的,$snappy->setOption('margin-top','0mm');$snappy->setOption('margin-left','0mm'......
  • Java OOP 练习--银行客户管理
    AcountpublicclassAcount{privatedoublebalance;//余额//带参构造器publicAcount(doubleinitBalance){this.balance=initBalance;}//查询余额publicdoublegetBalance(){returnbalance;}//存钱......
  • JavaScript 和浏览器
    模块参见ES6部分DOM参见Web相关部分选择器document.querySelector()来获取网页的对应HTML元素document.querySelectorAll()来获取网页的所有对应HTML元素document.getElementById()根据ID获取元素document.getElementsByClassName()根据类名获取元素docum......
  • Java获取当前路径(Linux+Windows)
    Java获取当前路径(Linux+Windows)获取当前路径(兼容Linux、Windows):StringcurPath=System.getProperty("user.dir");log.info("===========当前路径===========curPath:{}",curPath);输出结果:===========当前路径===========curPath:/home/lizhm......
  • 优雅的操作文件:java.nio.file 库介绍
    概述在早期的Java版本中,文件IO操作功能一直相对较弱,主要存在以下问题:缺乏对现代文件系统的支持:只提供的基础的文件操作,不支持很多现代的文件系统API不够直观:文件操作的API设计相对较为复杂和冗长,使用体验感很差对于大文件处理和并发性能不够:简单的I/O模型,没有充分......
  • Java常用类
    字符串相关的类关于StringString类:代表字符串。Java程序中的所有字符串字面值(如"abc")都作为此类的实例实现String是一个final类,代表不可变的字符序列。字符串是常量,用双引号引起来标识,它们的值在创建之后不能更改。String对象的字符内容是存储在一个字符数组finalchar[]......
  • 实验四 Java图形界面与事件处理
    实验目的1.掌握Java语言中AWT和Swing组件的基本用法2.掌握Java语言中的事件处理方法3.掌握Java语言中事件源、监视器和处理事件的接口的概念图形用户界面设计程序(ArtFont.java)要求:设计一个文字字体设置窗体,在该窗体中可以设置要显示文字的字体内容,包括字体名称、......
  • Java程序设计-实验五 Java多线程程序设计
    目的1.掌握Runnable接口实现多线程的方法2.掌握Thread类实现多线程的用法3.掌握Java语言中多线程编程的基本方法1.线程接力(45分)要求:编写一个应用程序,除了主线程外,还有三个线程:first、second和third。first负责模拟一个红色的按钮从坐标(10,60)运动到(100,60);second负......
  • JAVA知识点总结1
    目录1.关键字2.数据类型3.运算符4.流程控制语句4.1ifelse4.2Scanner类从键盘获取数据4.3获取一个随机数4.4switch-case4.5for循环4.6while循环4.7do-while循环5.数组5.1一维数组的基本使用5.2二维数组的基本使用5.3数组的常见操作(特征值统计、......
  • JAVA的线程池随笔
    线程池基本概念概念:线程池主要是控制运行线程的数量,将待处理任务放到等待队列,然后创建线程执行这些任务。如果超过了最大线程数,则等待。优点:线程复用:不用一直new新线程,重复利用已经创建的线程来降低线程的创建和销毁开销,节省系统资源。提高响应速度:当任务达到时,不用创建新的......