前言
有一定開發經驗的同學對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