一喇喉、AOP
全稱“Aspect Oriented Programming”,面向切面編程硅堆,由于面向?qū)ο蟮乃枷胍蟾邇?nèi)聚,低耦合的風(fēng)格姓蜂,使模塊代碼間的可見性變差萎羔,對(duì)于埋點(diǎn)液走,日志輸出等需求,就會(huì)變的十分復(fù)雜贾陷,如果手動(dòng)編寫代碼缘眶,入侵性很大,不利于擴(kuò)展髓废,AOP應(yīng)運(yùn)而生巷懈。
二、使用場(chǎng)景
當(dāng)我們需要在某個(gè)方法運(yùn)行前和運(yùn)行后做一些處理時(shí)慌洪,便可使用AOP技術(shù)顶燕。具體有:
- 統(tǒng)計(jì)埋點(diǎn)
- 日志打印/打點(diǎn)
- 數(shù)據(jù)校驗(yàn)
- 行為攔截
- 性能監(jiān)控
- 動(dòng)態(tài)權(quán)限控制
***三、環(huán)境配置
我們以Android工程+官方的AspectJ為例
1.項(xiàng)目根目錄build.gradle配置
dependencies {
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
2.具體使用AspectJ的工程build.gradle中配置
dependencies {
implementation 'org.aspectj:aspectjrt:1.8.13'
}
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
final def log = project.logger
final def variants = project.android.applicationVariants
variants.all { variant ->
if (!variant.buildType.isDebuggable()) {
log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
return;
}
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.8",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
log.debug "ajc args: " + Arrays.toString(args)
MessageHandler handler = new MessageHandler(true);
new Main().run(args, handler);
for (IMessage message : handler.getMessages(null, true)) {
switch (message.getKind()) {
case IMessage.ABORT:
case IMessage.ERROR:
case IMessage.FAIL:
log.error message.message, message.thrown
break;
case IMessage.WARNING:
log.warn message.message, message.thrown
break;
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}
以上配置完成后冈爹,便可以項(xiàng)目中進(jìn)行使用了涌攻,當(dāng)然,除了官方提供的频伤,還有其他三方的aspectJ框架供我們使用恳谎,如https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx,其配置與上面的略有不同憋肖,開發(fā)者根據(jù)自己選擇的具體框架進(jìn)行相應(yīng)的配置
三惠爽、代碼實(shí)例
public class AspectJActivity extends AppCompatActivity {
private String TAG = "AspectJActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aspect);
Log.i(TAG,hashCode()+"");
aspectTest();//以普通方法進(jìn)行aspectJ處理
aspectTestAnn();//以注解方式進(jìn)行
}
public void aspectTest() {
Log.i(TAG, "aspect test");
}
@AspectAnn
public void aspectTestAnn() {
Log.i(TAG, "aspect by annoation test");
}
}
要求,在運(yùn)行aspectTest()前先打印一個(gè)信息瞬哼,在運(yùn)行aspectTestAnn后也打印一個(gè)信息婚肆,兩者最大的區(qū)別在于,前者使用類名和方法名來實(shí)現(xiàn)切面坐慰,而后者則使用注解來實(shí)現(xiàn)较性,在實(shí)際開發(fā)中,用注解來實(shí)現(xiàn)切面的場(chǎng)景更多一些结胀。
自定義注解AspectAnn
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AspectAnn {
}
關(guān)鍵的切面處理類 AspectUtil
@Aspect
public class AspectUtil {
@Pointcut
("execution(* com.game.xiangxuemytest.aspectj.AspectJActivity.aspectTest(..))")
public void pointActionMethod() {}
@Before("pointActionMethod()")
public void testBefore(){
Log.i("AspectJActivity","AspectUtil method1 Before");
}
// @Pointcut
// ("@annotation(com.game.xiangxuemytest.aspectj.AspectAnn)")
// public void pointActionMethod2(AspectAnn ann) {
//
// }
@Pointcut
("execution(@com.game.xiangxuemytest.aspectj.AspectAnn * *(..))")
public void pointActionMethod2() {
}
@After("pointActionMethod2()")
public void testAfter(JoinPoint point) {
Log.i("AspectJActivity", "AspectUtil method2 After");
}
}
@Aspect 標(biāo)志切面的處理類
@Pointcut標(biāo)志切點(diǎn)是誰赞咙,后面跟符合切點(diǎn)的規(guī)則。
1> 包名+類名+方法名來確定切點(diǎn)規(guī)則:
pointActionMethod()上糟港,com.game.xiangxuemytest.aspectj.AspectJActivity.aspectTest(..)攀操。其中第一個(gè) * 號(hào)表示返回值可為任意類型,括號(hào)內(nèi)表示參數(shù)列表秸抚, .. 表示匹配任意個(gè)參數(shù)速和,參數(shù)類型為任何類型
2>以注解來確定切點(diǎn)規(guī)則的:
方法 pointActionMethod2()歹垫,@com.game.xiangxuemytest.aspectj.AspectAnn * (..)),注意兩個(gè) 之間有個(gè)空格颠放,和方式1略有不同排惨。
@Before
切入代碼運(yùn)行在目標(biāo)代碼前,同理還有 @After,@Around等碰凶,表示目標(biāo)后暮芭,及目標(biāo)前+后。其目的表示切入點(diǎn)運(yùn)行的代碼欲低。同時(shí)辕宏,由于可以存在多個(gè)切入點(diǎn),所以該注解后要跟上切入點(diǎn)的方法名砾莱,
execution瑞筐,這個(gè)我理解的為實(shí)現(xiàn)切入的方式吧。另外還有一個(gè)call恤磷,隨后我們可以根據(jù).class文件看一下兩者的區(qū)別。
運(yùn)行結(jié)果:
AspectUtil method1 Before
aspect test
aspect by annoation test
AspectUtil method2 After
四野宜、實(shí)現(xiàn)原理
通過Gradle Transform扫步,在class文件生成后至dex文件生成前,遍歷并匹配所有符合AspectJ文件中聲明的切點(diǎn)匈子,更改我們?cè)械?class文件河胎,將事先聲明好的代碼在切點(diǎn)前后織入。因此虎敦,這個(gè)增加我們的編譯時(shí)間游岳,下面來看一下上述代碼的最終的.class文件
為了方便后面的問題,我們以截圖的方式顯示其徙,注意紅框部分胚迫。從.class文件可以看到,我們?cè)械姆椒ㄇ盎蚝笸倌牵黾恿诵碌拇a访锻。從而實(shí)現(xiàn)了我們想要的切面功能。
五闹获、call與execution
上面我們說了期犬,這兩個(gè)會(huì)影響切面實(shí)現(xiàn)的方式,我們把execution改為call后避诽,看一下其.class
比較兩者龟虎,可以發(fā)現(xiàn),call的方式沙庐,不會(huì)改變?cè)械拇a鲤妥,而是在目標(biāo)方法的調(diào)用前后佳吞,進(jìn)行了相應(yīng)的新代碼的插入。
以上是個(gè)人對(duì)AspecJ的一些入門理解旭斥,希望能對(duì)小伙伴們有所幫助容达。