龙空技术网

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

linyb极客之路 189

前言:

如今看官们对“java的aop”大体比较注重,咱们都需要知道一些“java的aop”的相关内容。那么小编同时在网络上网罗了一些对于“java的aop””的相关资讯,希望姐妹们能喜欢,同学们一起来了解一下吧!

前言

有一定开发经验的同学对AOP应该很了解吧,如果不了解,可以先查看如下文章进行科普一下,再来阅读本文。

示例前置准备

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

1、在项目pom引入aop的GAV

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

2、编写业务服务

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

3、编写aspect切面

@Aspectpublic 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地址:

Codehaus的官方插件地址:

以及相应介绍:

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

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

而JavaPoet是JavaPoet 是生成 .java 源文件的 Java API,具体查看官方文档

或者查看此博文

不过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阶段的后置处理器阶段进行增强

示例

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

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

总结

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

demo链接

作者:linyb极客之路

链接:

来源:慕课网

本文原创发布于慕课网 ,转载请注明出处,谢谢合作

标签: #java的aop