概念:
AOP: Aspect Oriented Programming 面向切面編程绷落。
AOP是個概念,AspectJ 是它的一個具體實現(xiàn)。和Java配合使用瘾蛋。
AspectJ:核心是他的編譯器(ajc),就做了一件事矫限,講 AspectJ 的代碼在編譯期插入到目標程序中哺哼。運行時沒啥區(qū)別。ajc 會構建目標程序和AspectJ 代碼的聯(lián)系叼风,在編譯期將 AspectJ 代碼插入被切出的 PointCut中取董。達到AOP的目的。
我們采用 AspectJ 的方式來實現(xiàn) AOP无宿。
術語:
1茵汰、Advice:增強
也叫 通知。增強是織入目標類連接點的一段程序代碼
2孽鸡、JoinPoint:連接點
程序執(zhí)行的某個特點的位置蹂午,比如 類的初始化前后,方法的調用前后等等彬碱。具有邊界性質的點成為連接點豆胸。
3、PointCut:切入點
連接點 相當于 數據庫的記錄巷疼。 切入點 相當于 查詢條件晚胡。
切入點和連接點不是一一對應的,一個切入點可以匹配多個連接點
4、Aspect:切面
切面有切點和連接點組成
5估盘、Weaving:織入
將增強添加到目標類的具體連接點的過程瓷患。AOP 像一個織布機把目標類和增強縫在了一起
根據不同實現(xiàn)技術,三種織入的方式
- 編譯器織入遣妥,需要特殊的Java編譯器
- 類裝載期織入擅编,需要特殊的類加載器
- 動態(tài)代理織入,在運行期為目標類添加增強生成子類的方式
5燥透、Target:目標對象
增強邏輯的目標對象沙咏。
步驟:
1、首先定義一個表達式(PointCut)告訴程序我們要在哪里增加額外的操作班套。
通過這個表達式(PointCut)肢藐,獲得那些需要通知的方法(JoinPoint)。
2吱韭、我們要告訴程序這些方法(JointPoint)如何增強(Advice)
- 什么時候吆豹?執(zhí)行前?執(zhí)行后理盆?返回前痘煤?
- 額外具體操作是干甚么?
我們把這個兩個步驟定義到一個地方(Aspect)猿规。
涉及到的被修改的對象就是目標對象(Tatget)衷快。
完成了上面的所有動作,總成織入(Weaving)姨俩。
代碼敲起來
配置
1蘸拔、在應用build.gradle中
dependencies {
classpath 'org.aspectj:aspectjtools:1.8.10'
classpath 'org.aspectj:aspectjweaver:1.8.10'
}
2、在app build.gradle中
dependencies {
implementation 'org.aspectj:aspectjrt:1.8.10'
}
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
}
}
}
配置完畢环葵。
新建一個類调窍,添加注解@Aspect 說明這是一個切面。
然后創(chuàng)建我們的切點方法张遭。注解@PointCut
注意里面execution表達式,
.. 任意類型任意多個參數
- 包名通配符
execution(<修飾符模式>? <返回類型模式> <方法名模式>(<參數模式>) <異常模式>?)
根據需要創(chuàng)建以下注解方法
類型 | 描述 |
---|---|
Before | 前置通知, 在目標執(zhí)行之前執(zhí)行通知 |
After | 后置通知, 目標執(zhí)行后執(zhí)行通知 |
Around | 環(huán)繞通知, 在目標執(zhí)行中執(zhí)行通知, 控制目標執(zhí)行時機 |
AfterReturning | 后置返回通知, 目標返回時執(zhí)行通知 |
AfterThrowing | 異常通知, 目標拋出異常時執(zhí)行通知 |
@Aspect
public class TestAspect {
@Pointcut("execution(* com.ssy.qbd.MenuActivity.doSomeThing(..))")
public void pointcut() {
}
@Before("pointcut()")
public void before(JoinPoint point) {
Log.e("Mr.S", "@Before");
}
//注意 如果不調用joinPoint.proceed() 此方法不調用
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
return joinPoint.proceed();
}
@After("pointcut()")
public void after(JoinPoint joinPoint) {
Log.e("Mr.S", "@After");
}
@AfterReturning(value = "pointcut()", returning = "returnValue")
public void afterReeturning(JoinPoint joinPoint, Object returnValue) {
Log.e("Mr.S", "@AfterReturning" + "--" + returnValue);
}
@AfterThrowing(value = "pointcut()", throwing = "throwable")
public void afterThrowing(Throwable throwable) {
Log.e("Mr.S", "@AfterThrowing" + "--" + throwable.getMessage());
}
我們的目標對象
public String doSomeThing() {
Intent intent = new Intent(MenuActivity.this, HellowActivity.class);
startActivity(intent);
return "doSomeThing 執(zhí)行完畢";
}
執(zhí)行結果
2018-12-21 11:08:43.898 24567-24567/com.ssy.qbd E/Mr.S: @Before
2018-12-21 11:08:43.908 24567-24567/com.ssy.qbd E/Mr.S: @Around
2018-12-21 11:08:43.908 24567-24567/com.ssy.qbd E/Mr.S: @After
2018-12-21 11:08:43.908 24567-24567/com.ssy.qbd E/Mr.S: @AfterReturning--doSomeThing 執(zhí)行完畢
實踐完畢邓萨,那么這個面向切面編程,到底是用在什么情況呢菊卷?這幾個方法的實際運用又是如何呢缔恳?我如何控制多個方法呢?不能一個方法一個切面吧的烁?
相信大家會有很多疑問褐耳。
那我們就根據實際情況來,靈活運用我們的 AOP渴庆。
舉例一:
某日,
老大說:S啊,咱們的產品要升級了襟雷,要增加權限控制了刃滓,對于沒有登錄的用戶,嚴格控制耸弄,比如評論必須登錄咧虎,不能匿名了,點贊也要先登錄 计呈、看新聞也要先登錄 砰诵。。捌显。
我:WTF茁彭?我挨個給你加嗎?
if (isLogin()) {
Intent intent = new Intent(MenuActivity.this, HellowActivity.class);
}
哇 好煩啊~~有沒有簡單的扶歪,優(yōu)美的理肺,偷懶的做法呢?
老大說:你試試 AOP善镰。用過你就知道什么是偷懶妹萨,什么優(yōu)美了。
我:好 我試試炫欺。
先寫個注解,標注一下
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckLogin {
}
然后就是在我們的方法上添加注解
@CheckLogin
public String doSomeThing() {
Intent intent = new Intent(MenuActivity.this, HellowActivity.class);
startActivity(intent);
return "doSomeThing 執(zhí)行完畢";
}
最后在around 方法里進行我們的操作
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
CheckLogin checkLogin = signature.getMethod().getAnnotation(CheckLogin.class);
if (checkLogin != null) {
if (isLogin()) {
Log.e("Mr.S", "@Around" + "--" + "登錄成功");
return joinPoint.proceed();
} else {
Log.e("Mr.S", "@Around" + "--" + "請登錄");
Login((Context) joinPoint.getThis());
return null;
}
}
return joinPoint.proceed();
}
執(zhí)行結果:
- 未登錄
2018-12-21 15:20:30.199 29343-29343/com.ssy.qbd E/Mr.S: @Before
2018-12-21 15:20:30.200 29343-29343/com.ssy.qbd E/Mr.S: @Around--請登錄
2018-12-21 15:20:30.200 29343-29343/com.ssy.qbd E/Mr.S: Login--登錄頁面
2018-12-21 15:20:30.200 29343-29343/com.ssy.qbd E/Mr.S: @After
2018-12-21 15:20:30.200 29343-29343/com.ssy.qbd E/Mr.S: @AfterReturning--null
- 已登錄
2018-12-21 15:21:55.669 29463-29463/com.ssy.qbd E/Mr.S: @Before
2018-12-21 15:21:55.671 29463-29463/com.ssy.qbd E/Mr.S: @Around--登錄成功
2018-12-21 15:21:55.691 29463-29463/com.ssy.qbd E/Mr.S: @After
2018-12-21 15:21:55.691 29463-29463/com.ssy.qbd E/Mr.S: @AfterReturning--doSomeThing 執(zhí)行完畢
我們會發(fā)現(xiàn)乎完,我們可以對這個方法進行任意的操作,執(zhí)行前后加邏輯品洛,也可以屏蔽這個方法树姨,換成咱們的新方法。但是在我們的原方法那里是不用改變的毫别,我們可以更改邏輯娃弓,增加或者減少我們的功能。只要一個注解@CheckLogin岛宦,真實太方便了台丛。
這個例子其實還不算太變態(tài),如果是讓我們把所有的操作都加上日志怎么辦砾肺?我們是不是要在自己的代碼里挽霉,各種更改,這個侵入性太強了变汪,稍有不注意 bug 就鋪天蓋地侠坎。所以面向切面編程的方式。我們會更加輕松的裙盾。
只要是涉及統(tǒng)一處理的地方实胸,AOP基本都是一個利器他嫡。程序員追求的就是一個懶。能省事當然要省事了庐完。
當然 AspectJ 的真正實力遠比我們展現(xiàn)的要強大得多钢属,認識他,運用它门躯,你就會喜歡上它淆党。
參考:
http://www.reibang.com/p/aa1112dbebc7
https://www.cnblogs.com/weizhxa/p/8567942.html