前言
有肯定开发教训的同学对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、编写业务服务
@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地址: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阶段的后置处理器阶段进行加强
示例
@Configurationpublic 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