引言
相信很多做過Web的同學(xué)對AspectJ都不陌生,Spring的AOP就是基于它而來的括蝠。如果說平常我們隨便寫寫程序的時候斑胜,基本也不會用到它控淡,需要調(diào)試的話無非就是多加一個System.out.printfln()或者Log.d()嫌吠。但是由于基于面向?qū)ο蟮墓逃腥毕荩瑢?dǎo)致很多同模塊掺炭、同一水平上的工作要在許多類中重復(fù)出現(xiàn)辫诅。比如說:輸出日志,監(jiān)控方法執(zhí)行時間竹伸,修改程序運行時的參數(shù)等等這樣的事情泥栖,其實它們的代碼都是可以重用的。
如果在一個大型的項目當(dāng)中勋篓,使用手動修改源碼的方式來達到調(diào)試吧享、監(jiān)控的目的,第一譬嚣,需要插入許多重復(fù)代碼(打印日志钢颂,監(jiān)控方法執(zhí)行時間),代碼無法復(fù)用拜银;第二殊鞭,修改的成本太高,處處需要手動修改(分分鐘累死尼桶、眼花)操灿。
OOP: 面向?qū)ο蟀阉械氖挛锒籍?dāng)做對象看待,因此每一個對象都有自己的生命周期泵督,都是一個封裝的整體趾盐。每一個對象都有自己的一套垂直的系列方法和屬性,使得我們使用對象的時候不需要太多的關(guān)系它的內(nèi)部細節(jié)和實現(xiàn)過程小腊,只需要關(guān)注輸入和輸出救鲤,這跟我們的思維方式非常相近,極大的降低了我們的編寫代碼成本(而不像C那樣讓人頭痛V雀浴)本缠。但在現(xiàn)實世界中,并不是所有問題都能完美得劃分到模塊中入问。舉個最簡單而又常見的例子:現(xiàn)在想為每個模塊加上日志功能丹锹,要求模塊運行時候能輸出日志。在不知道AOP的情況下芬失,一般的處理都是:先設(shè)計一個日志輸出模塊楣黍,這個模塊提供日志輸出API,比如Android中的Log類麸折。然后锡凝,其他模塊需要輸出日志的時候調(diào)用Log類的幾個函數(shù),比如e(TAG,…)垢啼,w(TAG,…)窜锯,d(TAG,…)张肾,i(TAG,…)等。
AOP:
OOP
固然開啟另一個編程時代锚扎,但是久而久之也顯露了它的缺點吞瞪,最明顯的一點就是它無法橫向切割某一類方法、屬性驾孔,當(dāng)我們需要了解某一類方法芍秆、某一類屬性的信息時,就必須要在每一個類的方法里面(即便他們是同樣的方法翠勉,只因是不同的類所以不同)添加監(jiān)控代碼妖啥,在代碼量龐大的情況下,這是一個不可取的方法对碌。因此荆虱,AOP編產(chǎn)生了,基于AOP的編程可以讓我們橫向的切割某一類方法和屬性(不需要關(guān)心他是什么類別P嗝恰)怀读,AOP并不是與OOP對立的,而是為了彌補OOP的不足骑脱,因為有了AOP我們的調(diào)試和監(jiān)控就變得簡單清晰菜枷。
1.AspectJ介紹
1.1 AspectJ只是一個代碼編譯器
AspectJ 意思就是Java的Aspect,Java的AOP叁丧。它其實不是一個新的語言啤誊,它就是一個代碼編譯器(ajc,后面以此代替)歹袁,在Java編譯器的基礎(chǔ)上增加了一些它自己的關(guān)鍵字識別和編譯方法坷衍。因此寝优,ajc也可以編譯Java代碼条舔。它在編譯期將開發(fā)者編寫的Aspect程序編織到目標程序中,對目標程序作了重構(gòu)乏矾,目的就是建立目標程序與Aspect程序的連接(耦合孟抗,獲得對方的引用(獲得的是聲明類型,不是運行時類型)和上下文信息)钻心,從而達到AOP的目的(這里在編譯期還是修改了原來程序的代碼凄硼,但是是ajc替我們做的)。
1.2 AspectJ是用來做AOP編程的
Cross-cutting concerns(橫切關(guān)注點): 盡管面向?qū)ο竽P椭写蠖鄶?shù)類會實現(xiàn)單一特定的功能捷沸,但通常也會開放一些通用的附屬功能給其他類摊沉。例如,我們希望在數(shù)據(jù)訪問層中的類中添加日志痒给,同時也希望當(dāng)UI層中一個線程進入或者退出調(diào)用一個方法時添加日志说墨。盡管每個類都有一個區(qū)別于其他類的主要功能骏全,但在代碼里,仍然經(jīng)常需要添加一些相同的附屬功能尼斧。
Advice(通知): 注入到class文件中的代碼姜贡。典型的 Advice 類型有 before、after 和 around棺棵,分別表示在目標方法執(zhí)行之前楼咳、執(zhí)行后和完全替代目標方法執(zhí)行的代碼。 除了在方法中注入代碼烛恤,也可能會對代碼做其他修改母怜,比如在一個class中增加字段或者接口。
Joint point(連接點): 程序中可能作為代碼注入目標的特定的點缚柏,例如一個方法調(diào)用或者方法入口糙申。
Pointcut(切入點): 告訴代碼注入工具,在何處注入一段特定代碼的表達式船惨。例如柜裸,在哪些 joint points 應(yīng)用一個特定的 Advice。切入點可以選擇唯一一個粱锐,比如執(zhí)行某一個方法疙挺,也可以有多個選擇,比如怜浅,標記了一個定義成@DebguTrace 的自定義注解的所有方法铐然。
Aspect(切面):Pointcut 和 Advice 的組合看做切面。例如恶座,我們在應(yīng)用中通過定義一個 pointcut 和給定恰當(dāng)?shù)腶dvice搀暑,添加一個日志切面。
Weaving(織入): 注入代碼(advices)到目標位置(joint points)的過程跨琳。
下面這張圖簡要總結(jié)了一下上述這些概念自点。
傳統(tǒng)編程:逐個插入驗證用戶模塊
AOP方案:關(guān)注點聚焦
1.3、為什么要用AspectJ脉让?
- 非侵入式監(jiān)控: 支持編譯期和加載時代碼注入桂敛,可以在不修監(jiān)控目標的情況下監(jiān)控其運行,截獲某類方法溅潜,甚至可以修改其參數(shù)和運行軌跡术唬!
- 易于使用: 它就是Java,只要會Java就可以用它滚澜。
- 功能強大粗仓,可拓展性高: 它就是一個編譯器+一個庫,可以讓開發(fā)者最大限度的發(fā)揮,實現(xiàn)形形色色的AOP程序借浊!
2眶掌、下載AspectJ相關(guān)資源與build.gradle配置
2.1、下載地址
下載aspectj的地址http://www.eclipse.org/aspectj/downloads.php
2.2巴碗、解壓aspectj jar包得到aspectjrt.jar
2.3朴爬、build.gradle配置
參考build.gradle aspectJ 寫法 http://fernandocejas.com/2014/08/03/aspect-oriented-programming-in-android/
根目錄中build.gradle配置:
buildscript {
repositories {
mavenCentral()
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
classpath 'org.aspectj:aspectjtools:1.8.13'
classpath 'org.aspectj:aspectjweaver:1.8.13'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
modules中build.gradle配置:
apply plugin: 'com.android.application'
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.haocai.aopdemo"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
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;
}
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testImplementation 'junit:junit:4.12'
compile files('libs/aspectjrt.jar')
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
// compile 'org.aspectj:aspectjrt:1.8.+'
}
注意:
dependencies 不要忘記添加 compile files('libs/aspectjrt.jar') ,aspectjrt.jar就是上一步解壓得到的文件橡淆,放到libs文件夾下
3召噩、示例程序
創(chuàng)建注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface BehaviorTrace {
String value();
int type();
}
Aspect 類
/**
* Created by Xionghu on 2018/1/23.
* Desc: 切面
* 你想要切下來的部分(代碼邏輯功能重復(fù)模塊)
*/
@Aspect
public class BehaviorAspect {
private static final String TAG = "MainAspect";
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 根據(jù)切點 切成什么樣子
*
*/
@Pointcut("execution(@com.haocai.aopdemo.BehaviorTrace * *(..))")
public void annoBehavior() {
}
/**
* 切成什么樣子之后,怎么去處理
*
*/
@Around("annoBehavior()")
public Object dealPoint(ProceedingJoinPoint point) throws Throwable{
//方法執(zhí)行前
MethodSignature methodSignature = (MethodSignature)point.getSignature();
BehaviorTrace behaviorTrace = methodSignature.getMethod().getAnnotation(BehaviorTrace.class);
String contentType = behaviorTrace.value();
int type = behaviorTrace.type();
Log.i(TAG,contentType+"使用時間: "+simpleDateFormat.format(new Date()));
long beagin=System.currentTimeMillis();
//方法執(zhí)行時
Object object = null;
try{
object = point.proceed();
}catch (Exception e){
e.printStackTrace();
}
//方法執(zhí)行完成
Log.i(TAG,"消耗時間:"+(System.currentTimeMillis()-beagin)+"ms");
return object;
}
}
調(diào)用主程序
package com.haocai.aopdemo;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Main";
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 搖一搖的模塊
*
* @param view
*/
@BehaviorTrace(value = "搖一搖",type = 1)
public void mShake(View view)
{
//搖一搖的代碼邏輯
{
SystemClock.sleep(3000);
Log.i(TAG," 搖到一個紅包");
}
}
/**
* 語音的模塊
*
* @param view
*/
@BehaviorTrace(value = "語音:",type = 1)
public void mAudio(View view)
{
//語音代碼邏輯
{
SystemClock.sleep(3000);
Log.i(TAG,"發(fā)語音:我要到一個紅包啦");
}
}
/**
* 打字模塊
*
* @param view
*/
@BehaviorTrace(value = "打字:",type = 1)
public void mText(View view)
{
//打字模塊邏輯
{
SystemClock.sleep(3000);
Log.i(TAG,"打字邏輯逸爵,我搖到了一個大紅包");
}
}
// /**
// * 搖一搖的模塊
// *
// * @param view
// */
// @BehaviorTrace(value = "搖一搖",type = 1)
// public void mShake(View view)
// {
// SystemClock.sleep(3000);
// Log.i(TAG," 搖到一個嫩模: 約不約");
// }
//
// /**
// * 搖一搖的模塊
// *
// * @param view
// */
// public void mShake(View view)
// {
//
// long beagin=System.currentTimeMillis();
// Log.i(TAG,"搖一搖: 使用時間: "+simpleDateFormat.format(new Date()));
// //搖一搖的代碼邏輯
// {
// SystemClock.sleep(3000);
//
// Log.i(TAG," 搖到一個紅包");
//
// }
// //事件統(tǒng)計邏輯
// Log.i(TAG,"消耗時間: "+(System.currentTimeMillis()-beagin)+"ms");
//
// }
// /**
// * 語音的模塊
// *
// * @param view
// */
// public void mAudio(View view)
// {
// long beagin=System.currentTimeMillis();
// Log.i(TAG,"語音: 使用時間: "+simpleDateFormat.format(new Date()));
// //語音代碼邏輯
// {
// SystemClock.sleep(3000);
//
// Log.i(TAG,"發(fā)語音:我要到一個紅包啦");
//
// }
// //事件統(tǒng)計邏輯
// Log.i(TAG,"消耗時間: "+(System.currentTimeMillis()-beagin)+"ms");
// }
//
// /**
// * 打字模塊
// *
// * @param view
// */
// public void mText(View view)
// {
// //統(tǒng)計用戶行為 的邏輯
// Log.i(TAG,"文字: 使用時間: "+simpleDateFormat.format(new Date()));
// long beagin=System.currentTimeMillis();
//
// //打字模塊邏輯
// {
// SystemClock.sleep(3000);
// Log.i(TAG,"打字邏輯具滴,我搖到了一個大紅包");
//
// }
// //事件統(tǒng)計邏輯
// Log.i(TAG,"消耗時間: "+(System.currentTimeMillis()-beagin)+"ms");
// }
}
注意:下面注釋部分為傳統(tǒng)寫法
運行結(jié)果
01-23 19:39:09.579 13051-13051/com.haocai.aopdemo I/MainAspect: 搖一搖使用時間: 2018-01-23 19:39:09
01-23 19:39:12.589 13051-13051/com.haocai.aopdemo I/Main: 搖到一個紅包
01-23 19:39:12.589 13051-13051/com.haocai.aopdemo I/MainAspect: 消耗時間:3001ms
01-23 19:39:12.589 13051-13051/com.haocai.aopdemo I/MainAspect: 語音:使用時間: 2018-01-23 19:39:12
01-23 19:39:15.599 13051-13051/com.haocai.aopdemo I/Main: 發(fā)語音:我要到一個紅包啦
01-23 19:39:15.599 13051-13051/com.haocai.aopdemo I/MainAspect: 消耗時間:3000ms
01-23 19:39:15.599 13051-13051/com.haocai.aopdemo I/MainAspect: 打字:使用時間: 2018-01-23 19:39:15
01-23 19:39:18.609 13051-13051/com.haocai.aopdemo I/Main: 打字邏輯,我搖到了一個大紅包
01-23 19:39:18.609 13051-13051/com.haocai.aopdemo I/MainAspect: 消耗時間:3000ms