AOP(面向切面編程)的概念如今已經應用的很是普遍了,下面是從百度百科上摘抄的一段解釋,比較淺顯易懂java
在軟件業,AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,經過預編譯方式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP能夠對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度下降,提升程序的可重用性,同時提升了開發的效率。git
AOP 是一種編程思想,可是它的實現方式有不少,好比:Spring、AspectJ、JavaAssist、ASM 等。因爲我是作 Android 開發的,因此會用 Android 中的一些例子。github
那 ASM 是什麼呢?這兒有一篇介紹 ASM 的文章,寫的不錯 AOP 的利器:ASM 3.0 介紹,摘抄其中一段:編程
ASM 是一個 Java 字節碼操控框架。它能被用來動態生成類或者加強既有類的功能。ASM 能夠直接產生二進制 class 文件,也能夠在類被加載入 Java 虛擬機以前動態改變類行爲。Java class 被存儲在嚴格格式定義的 .class 文件裏,這些類文件擁有足夠的元數據來解析類中的全部元素:類名稱、方法、屬性以及 Java 字節碼(指令)。ASM 從類文件中讀入信息後,可以改變類行爲,分析類信息,甚至可以根據用戶要求生成新類。bash
簡單點說,經過 javac 將 .java 文件編譯成 .class 文件,.class 文件中的內容雖然不一樣,可是它們都具備相同的格式,ASM 經過使用訪問者(visitor)模式,按照 .class 文件特有的格式從頭至尾掃描一遍 .class 文件中的內容,在掃描的過程當中,就能夠對 .class 文件作一些操做了,有點黑科技的感受數據結構
提到 Java 字節碼,可能不少人都不是很熟悉,大概都知道使用 javac 能夠將 .java 文件編譯成 .class 文件,.class 文件中存放的就是該 .java 文件對應的字節碼內容,好比以下一段 Demo.java 代碼很簡單:app
package com.lijiankun24.classpractice;
public class Demo {
private int m;
public int inc() {
return m + 1;
}
}
複製代碼
經過 javac 編譯生成對應的 Demo.class 文件,使用純文本文件打開 Demo.class,其中的內容是以 8 位字節爲基礎單位的二進制流,表面來看就是由十六進制符號組成的,這一段十六進制符號組成的長串是遵照 Java 虛擬機規範的框架
cafe babe 0000 0034 0013 0a00 0400 0f09
0003 0010 0700 1107 0012 0100 016d 0100
0149 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 0369 6e63
0100 0328 2949 0100 0a53 6f75 7263 6546
696c 6501 0009 4465 6d6f 2e6a 6176 610c
0007 0008 0c00 0500 0601 0004 4465 6d6f
0100 106a 6176 612f 6c61 6e67 2f4f 626a
6563 7400 2100 0300 0400 0000 0100 0200
0500 0600 0000 0200 0100 0700 0800 0100
0900 0000 1d00 0100 0100 0000 052a b700
01b1 0000 0001 000a 0000 0006 0001 0000
0001 0001 000b 000c 0001 0009 0000 001f
0002 0001 0000 0007 2ab4 0002 0460 ac00
0000 0100 0a00 0000 0600 0100 0000 0600
0100 0d00 0000 0200 0e
複製代碼
若是再使用 javap -verbose Demo.class
查看該 Demo.class 中的內容,以下圖所示 koa
從上圖中,咱們能夠看到,.class 文件中主要有常量池、字段表、方法表和屬性表等內容。如何從以 8 位字節爲基礎單位的二進制流中分析出常量池、方法表的內容呢?在這篇文章中有詳細的介紹 認識 .class 文件的字節碼結構 ,這篇文章以一個簡單的例子,手把手的分析十六進制符合表示的 .class 文件maven
上面一小節介紹了 .class 文件的結構,可是 .class 文件是靜態的,它最終是會被虛擬機加載才能執行的,那麼問題來了,.class 文件是何時會被加載呢?
通常來講,一個 .class 文件就包含一個 Java 類,.class 文件和 Java 類是息息相關的。要說 .class 文件的加載時機,就不得不提到 Java 類的生命週期了。想必你們都知道,Java 類的生命週期包含加載
、驗證
、準備
、解析
、初始化
、使用
、卸載
七個步驟,在 Java 虛擬機規範中並無規定 Java 類的加載
時機,可是卻規定了 Java 類 初始化
的時機,而加載
又必定是在初始化的前面,因此也能夠說是間接地規定了 .class 文件的加載
的時機。
有五種狀況,是必須初始化
一個類的,這五種狀況被稱爲對 Java 類的主動引用
,除了 主動引用
以外,其餘的對 Java 類的引用稱爲 被動引用
。
上面也提到了 Java 類的生命週期總共分爲加載
、驗證
、準備
、解析
、初始化
、使用
、卸載
,其中最重要的是前五個步驟加載
、驗證
、準備
、解析
、初始化
,那在這五個步驟中都發生了什麼事情呢?
舉一個簡單的例子,以下所示。下面的 Constant
類中,有一個靜態 static
代碼塊,和一個靜態 static
變量, 是何時給 value 賦值的呢?何時會執行 static 代碼塊呢?答案是在類的 初始化
階段。
public class Constant {
static {
System.out.println("Constant init!");
}
public static String value = "lijiankun24!";
}
複製代碼
在 Java 類中,若是有靜態 static
代碼塊、靜態 static
變量的話,編譯器會爲這個類自動生成一個類構造器
(注意,不是實例構造器
),在 類構造器
中會執行靜態 static
代碼塊,初始化靜態 static
變量,類構造器
就是在類的 初始化
階段執行的
提到 Java 類的加載,就不得不提及 Java 中的類加載器 ClassLoader 了,雙親委派模型及其好處也是必需要清楚的。
上面只是粗略的介紹,更多想了解五種主動引用
、類的生命週期、類構造器、類加載器、雙親委派模型,若是想了解的更詳細,請看這篇文章 理解 JVM 中的類加載機制
Java 內存模型中,很是重要的一個區域就是 Java 虛擬機棧。Java 中每個方法執行的時候都會在 Java 虛擬機棧中壓入一個棧幀
,方法執行完成以後,也會將該棧幀出棧。 棧幀
中最主要的是局部變量表
、操做數棧
這兩個概念,在執行一個 Java 方法的字節碼時,其實就是調用 Java 字節碼指令操縱局部變量表
、操做數棧
,最後將執行的結果返回。若是想學習 Java 字節碼指令的話,推薦一篇文章。
除了方法的執行過程,還須要瞭解一下 Java 中的方法調用
。方法調用就是指經過 .class 文件中方法的符號引用,確認方法的直接引用的過程,這個過程有可能發生在加載階段,也有可能發生在運行階段。 有一些方法是在加載階段就已經肯定了方法的直接引用,好比:靜態方法、私有方法、實例構造器方法,這類方法的調用稱爲 解析
;除了解析
,方法的 靜態分派
也是在加載階段就肯定了方法的直接引用,這類方法常見的就是 重載
的方法。 有一些方法是在運行階段確認方法的直接引用的,好比:重寫
的方法,調用重寫
的方法時,須要具體到對象的實際類型,因此須要特定的 Java 字節碼 invokevirtual
去肯定合適的方法。
Java 虛擬機是基於棧的解釋執行的,這裏所說的棧
就是 Java 虛擬機棧,解釋執行時相對於編譯執行而言的,解釋執行就是指:代碼經過編譯生成字節碼指令集以後,經過解釋器解釋執行的。這個不用瞭解的太深,明白這幾個定義就好
上面介紹了 Java 虛擬機棧中的 棧幀
、方法調用
、解析
、靜態分派
、動態分派
和 Java 虛擬機基於棧的解釋執行,詳細的內容能夠參考 虛擬機字節碼執行引擎。
ASM 庫是一款基於 Java 字節碼層面的代碼分析和修改工具,那 ASM 和訪問者模式有什麼關係呢?訪問者模式主要用於修改和操做一些數據結構比較穩定的數據,經過前面的學習,咱們知道 .class 文件的結構是固定的,主要有常量池、字段表、方法表、屬性表等內容,經過使用訪問者模式在掃描 .class 文件中各個表的內容時,就能夠修改這些內容了。在學習 ASM 以前,能夠經過這篇文章學習一下訪問者模式訪問者模式和 ASM。
ASM 能夠直接生產二進制的 .class 文件,也能夠在類被加載入 JVM 以前動態修改類行爲。ASM 庫的介紹和使用 文章介紹了 ASM 庫的結構和幾個重要的 Core Api,包括 ClassVisitor、ClassReader、ClassWriter、MethodVisitor 和 AdviceAdapter 等,而且經過兩個簡單的例子,分別介紹瞭如何修改 Java 類中方法的字節碼和修改屬性的字節碼。
在剛開始使用的時候,可能對字節碼的執行不是很清楚,使用 ASM 會比較困難,ASM 官方也提供了一個幫助工具 ASMifier,咱們能夠先寫出目標代碼,而後經過 javac 編譯成 .class 文件,而後經過 ASMifier 分析此 .class 文件就能夠獲得須要插入的代碼對應的 ASM 代碼了。
上面提到的內容,ASM 庫的 Core Api 和 ASMifier 的使用具體請參閱這篇文章ASM 庫的介紹和使用 。
最後,學習完理論知識之後,爲了練手,寫了一個小項目,使用自定義 Gradle 插件 + ASM 的方式實現了和 JakeWharton 的 hugo 庫一樣的功能的庫,叫作 Koala,將特定註解的方法的傳入參數、返回結果和執行時間打印到 Logcat 中,方便開發調試。
在項目工程的 build.gradle
中添加以下代碼:
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "gradle.plugin.com.lijiankun24:buildSrc:1.1.1"
}
}
複製代碼
在須要使用的 module 中的 build.gradle 中添加以下代碼:
apply plugin: "com.lijiankun24.koala-plugin"
複製代碼
Gradle:
compile 'com.lijiankun24:koala:1.1.2'
複製代碼
Maven:
<dependency>
<groupId>com.lijiankun24</groupId>
<artifactId>koala</artifactId>
<version>1.1.2</version>
<type>pom</type>
</dependency>
複製代碼
使用起來仍是很是簡單的,在 Java 的方法上添加 @KoalaLog
註解,以下所示:
@KoalaLog
public String getName(String first, String last) {
SystemClock.sleep(15); // Don't ever really do this!
return first + " " + last;
}
複製代碼
當上述方法被調用的時候,Logcat 中的輸出以下所示:
09-04 20:51:38.008 12076-12076/com.lijiankun24.practicedemo I/0KoalaLog: ┌───────────────────────────────────------───────────────────────────────────------
09-04 20:51:38.008 12076-12076/com.lijiankun24.practicedemo I/1KoalaLog: │ The class's name: com.lijiankun24.practicedemo.MainActivity 09-04 20:51:38.008 12076-12076/com.lijiankun24.practicedemo I/2KoalaLog: │ The method's name: getName(java.lang.String, java.lang.String)
09-04 20:51:38.008 12076-12076/com.lijiankun24.practicedemo I/3KoalaLog: │ The arguments: [li, jiankun]
09-04 20:51:38.008 12076-12076/com.lijiankun24.practicedemo I/4KoalaLog: │ The result: li jiankun
09-04 20:51:38.008 12076-12076/com.lijiankun24.practicedemo I/5KoalaLog: │ The cost time: 15ms
09-04 20:51:38.008 12076-12076/com.lijiankun24.practicedemo I/6KoalaLog: └───────────────────────────────────------───────────────────────────────────------
複製代碼
-keep class com.lijiankun24.koala.** { *; }
複製代碼
歡迎 star 和 fork Koala,也歡迎點贊和收藏