Android面向切面編程(AOP)

一验夯、簡述

1魁蒜、AOP的概念

如果你用java做過后臺(tái)開發(fā),那么你一定知道AOP這個(gè)概念烁挟。如果不知道也無妨婴洼,套用百度百科的介紹,也能讓你明白這玩意是干什么的:

AOP為Aspect Oriented Programming的縮寫撼嗓,意為:面向切面編程柬采,通過預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)。AOP是OOP的延續(xù)静稻,是軟件開發(fā)中的一個(gè)熱點(diǎn)警没,也是Spring框架中的一個(gè)重要內(nèi)容,是函數(shù)式編程的一種衍生范型振湾。利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離杀迹,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性,同時(shí)提高了開發(fā)的效率树酪。

2浅碾、項(xiàng)目場(chǎng)景

項(xiàng)目開發(fā)過程中,可能會(huì)有這樣的需求续语,需要我們?cè)诜椒▓?zhí)行完成后垂谢,記錄日志(后臺(tái)開發(fā)中比較常見~),或是計(jì)算這個(gè)方法的執(zhí)行時(shí)間疮茄,在不使用AOP的情況下滥朱,我們可以在方法最后調(diào)用另一個(gè)專門記錄日志的方法,或是在方法體的首尾分別獲取時(shí)間力试,然后通過計(jì)算時(shí)間差來計(jì)算整個(gè)方法執(zhí)行所消耗的時(shí)間徙邻,這樣也可以完成需求。那如果不只一個(gè)方法要這么玩怎么辦畸裳?每個(gè)方法都寫上一段相同的代碼嗎缰犁?后期處理邏輯變了要怎么辦?最后老板說這功能不要了我們還得一個(gè)個(gè)刪除怖糊?

很明顯帅容,這是不可能的,我們不僅僅是代碼的搬運(yùn)工伍伤,我們還是有思考能力的軟件開發(fā)工程師并徘。這么low的做法絕對(duì)不干,這種問題我們完全可以用AOP來解決扰魂,不就是在方法前和方法后插入一段代碼嗎饮亏?AOP分分鐘搞定。

3阅爽、AOP的實(shí)現(xiàn)方式

要注意了,AOP僅僅只是個(gè)概念荐开,實(shí)現(xiàn)它的方式(工具和庫)有以下幾種:

  • AspectJ: 一個(gè) JavaTM 語言的面向切面編程的無縫擴(kuò)展(適用Android)付翁。
  • Javassist for Android: 用于字節(jié)碼操作的知名 java 類庫 Javassist 的 Android 平臺(tái)移植版。
  • DexMaker: Dalvik 虛擬機(jī)上晃听,在編譯期或者運(yùn)行時(shí)生成代碼的 Java API百侧。
  • ASMDEX: 一個(gè)類似 ASM 的字節(jié)碼操作庫,運(yùn)行在Android平臺(tái)能扒,操作Dex字節(jié)碼佣渴。

本篇的主角就是AspectJ,下面就來看看AspectJ方式的AOP如何在Android開發(fā)中進(jìn)行使用吧初斑。

二辛润、AspectJ的引入

對(duì)于eclipse與Android Studio的引入是不一樣的,本篇只介紹Android Studio如何引入AspectJ见秤,eclipse請(qǐng)自行百度砂竖。Android Studio需要在app模塊的build.gradle文件中引入真椿,總共分為3個(gè)步驟:

1)添加核心依賴

dependencies {
    ...
    compile 'org.aspectj:aspectjrt:1.8.9'
}

2)編寫gradle編譯腳本

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.aspectj:aspectjtools:1.8.9'
        classpath 'org.aspectj:aspectjweaver:1.8.9'
    }
}

AspectJ需要依賴maven倉庫。

3)添加gradle任務(wù)

dependencies {
    ...
}
// 貼上面那段沒用的代碼是為了說明:下面的任務(wù)代碼與dependencies同級(jí)

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;
            }
        }
    }
}

直接粘貼到build.gradle文件的末尾即可乎澄,不要嵌套在別的指令中突硝。

三、AOP的基本知識(shí)

在使用AspectJ之前置济,還是需要先介紹下AOP的基本知識(shí)解恰,熟悉的看官可以跳過這部分。

1浙于、AOP術(shù)語

  1. 通知护盈、增強(qiáng)處理(Advice):就是你想要的功能,也就是上面說的日志路媚、耗時(shí)計(jì)算等黄琼。
  2. 連接點(diǎn)(JoinPoint):允許你通知(Advice)的地方,那可就真多了整慎,基本每個(gè)方法的前脏款、后(兩者都有也行),或拋出異常是時(shí)都可以是連接點(diǎn)(spring只支持方法連接點(diǎn))裤园。AspectJ還可以讓你在構(gòu)造器或?qū)傩宰⑷霑r(shí)都行撤师,不過一般情況下不會(huì)這么做,只要記住拧揽,和方法有關(guān)的前前后后都是連接點(diǎn)剃盾。
  3. 切入點(diǎn)(Pointcut):上面說的連接點(diǎn)的基礎(chǔ)上,來定義切入點(diǎn)淤袜,你的一個(gè)類里痒谴,有15個(gè)方法,那就有十幾個(gè)連接點(diǎn)了對(duì)吧铡羡,但是你并不想在所有方法附件都使用通知(使用叫織入积蔚,下面再說),你只是想讓其中幾個(gè)烦周,在調(diào)用這幾個(gè)方法之前尽爆、之后或者拋出異常時(shí)干點(diǎn)什么,那么就用切入點(diǎn)來定義這幾個(gè)方法读慎,讓切點(diǎn)來篩選連接點(diǎn)漱贱,選中那幾個(gè)你想要的方法。
  4. 切面(Aspect):切面是通知和切入點(diǎn)的結(jié)合∝参現(xiàn)在發(fā)現(xiàn)了吧幅狮,沒連接點(diǎn)什么事,連接點(diǎn)就是為了讓你好理解切點(diǎn)搞出來的,明白這個(gè)概念就行了彪笼。通知說明了干什么和什么時(shí)候干(什么時(shí)候通過before钻注,after,around等AOP注解就能知道)配猫,而切入點(diǎn)說明了在哪干(指定到底是哪個(gè)方法)幅恋,這就是一個(gè)完整的切面定義。
  5. 織入(weaving) 把切面應(yīng)用到目標(biāo)對(duì)象來創(chuàng)建新的代理對(duì)象的過程泵肄。

上述術(shù)語的解釋引用自《AOP中的概念通知捆交、切點(diǎn)、切面》這篇文章腐巢,作者的描述非常直白品追,很容易理解,點(diǎn)個(gè)贊冯丙。

2肉瓦、AOP注解與使用

  • @Aspect:聲明切面,標(biāo)記類
  • @Pointcut(切點(diǎn)表達(dá)式):定義切點(diǎn)胃惜,標(biāo)記方法
  • @Before(切點(diǎn)表達(dá)式):前置通知泞莉,切點(diǎn)之前執(zhí)行
  • @Around(切點(diǎn)表達(dá)式):環(huán)繞通知,切點(diǎn)前后執(zhí)行
  • @After(切點(diǎn)表達(dá)式):后置通知船殉,切點(diǎn)之后執(zhí)行
  • @AfterReturning(切點(diǎn)表達(dá)式):返回通知鲫趁,切點(diǎn)方法返回結(jié)果之后執(zhí)行
  • @AfterThrowing(切點(diǎn)表達(dá)式):異常通知,切點(diǎn)拋出異常時(shí)執(zhí)行

@Pointcut利虫、@Before挨厚、@Around、@After糠惫、@AfterReturning疫剃、@AfterThrowing需要在切面類中使用,即在使用@Aspect的類中硼讽。

1)切點(diǎn)表達(dá)式是什么慌申?

這就是切點(diǎn)表達(dá)式:execution (* com.lqr..*.*(..))。切點(diǎn)表達(dá)式的組成如下:

execution(<修飾符模式>? <返回類型模式> <方法名模式>(<參數(shù)模式>) <異常模式>?)

除了返回類型模式理郑、方法名模式和參數(shù)模式外,其它項(xiàng)都是可選的咨油。

修飾符模式指的是public您炉、private、protected役电,異常模式指的是NullPointException等赚爵。

對(duì)于切點(diǎn)表達(dá)式的理解不是本篇重點(diǎn),下面列出幾個(gè)例子說明一下就好了:

@Before("execution(public * *(..))")
public void before(JoinPoint point) {
    System.out.println("CSDN_LQR");
}

匹配所有public方法,在方法執(zhí)行之前打印"CSDN_LQR"冀膝。

@Around("execution(* *to(..))")
public void around(ProceedingJoinPoint joinPoint) {
    System.out.println("CSDN");
    joinPoint.proceed();
    System.out.println("LQR");
}

匹配所有以"to"結(jié)尾的方法唁奢,在方法執(zhí)行之前打印"CSDN",在方法執(zhí)行之后打印"LQR"窝剖。

@After("execution(* com.lqr..*to(..))")
public void after(JoinPoint point) {
    System.out.println("CSDN_LQR");
}

匹配com.lqr包下及其子包中以"to"結(jié)尾的方法麻掸,在方法執(zhí)行之后打印"CSDN_LQR"。

@AfterReturning("execution(int com.lqr.*(..))")
public void afterReturning(JoinPoint point, Object returnValue) {
    System.out.println("CSDN_LQR");
}

匹配com.lqr包下所有返回類型是int的方法赐纱,在方法返回結(jié)果之后打印"CSDN_LQR"脊奋。

@AfterThrowing(value = "execution(* com.lqr..*(..))", throwing = "ex")
public void afterThrowing(Throwable ex) {
    System.out.println("ex = " + ex.getMessage());
}

匹配com.lqr包及其子包中的所有方法,當(dāng)方法拋出異常時(shí)疙描,打印"ex = 報(bào)錯(cuò)信息"诚隙。

2)@Pointcut的使用

@Pointcut是專門用來定義切點(diǎn)的,讓切點(diǎn)表達(dá)式可以復(fù)用起胰。

你可能需要在切點(diǎn)執(zhí)行之前和切點(diǎn)報(bào)出異常時(shí)做些動(dòng)作(如:出錯(cuò)時(shí)記錄日志)久又,可以這么做:

@Before("execution(* com.lqr..*(..))")
public void before(JoinPoint point) {
    System.out.println("CSDN_LQR");
}

@AfterThrowing(value = "execution(* com.lqr..*(..))", throwing = "ex")
public void afterThrowing(Throwable ex) {
    System.out.println("記錄日志");
}

可以看到,表達(dá)式是一樣的效五,那要怎么重用這個(gè)表達(dá)式呢地消?這就需要用到@Pointcut注解了,@Pointcut注解是注解在一個(gè)空方法上的火俄,如:

@Pointcut("execution(* com.lqr..*(..))")
public void pointcut() {}

這時(shí)犯建,"pointcut()"就等價(jià)于"execution(* com.lqr..*(..))",那么上面的代碼就可以這么改了:

@Before("pointcut()")
public void before(JoinPoint point) {
    System.out.println("CSDN_LQR");
}

@AfterThrowing(value = "pointcut()", throwing = "ex")
public void afterThrowing(Throwable ex) {
    System.out.println("記錄日志");
}

四瓜客、實(shí)戰(zhàn)

經(jīng)過上面的學(xué)習(xí)适瓦,下面是時(shí)候?qū)崙?zhàn)一下了,這里我們來一個(gè)簡單的例子谱仪。

1玻熙、切點(diǎn)

這是界面上一個(gè)按鈕的點(diǎn)擊事件,就是一個(gè)簡單的方法而已疯攒,我們拿它來試刀嗦随。

public void test(View view) {
    System.out.println("Hello, I am CSDN_LQR");
}

2、切面類

要織入一段代碼到目標(biāo)類方法的前前后后敬尺,必須要有一個(gè)切面類枚尼,下面就是切面類的代碼:

@Aspect
public class TestAnnoAspect {

    @Pointcut("execution(* com.lqr.androidaopdemo.MainActivity.test(..))")
    public void pointcut() {

    }    

    @Before("pointcut()")
    public void before(JoinPoint point) {
        System.out.println("@Before");
    }

    @Around("pointcut()")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("@Around");
    }

    @After("pointcut()")
    public void after(JoinPoint point) {
        System.out.println("@After");
    }

    @AfterReturning("pointcut()")
    public void afterReturning(JoinPoint point, Object returnValue) {
        System.out.println("@AfterReturning");
    }

    @AfterThrowing(value = "pointcut()", throwing = "ex")
    public void afterThrowing(Throwable ex) {
        System.out.println("@afterThrowing");
        System.out.println("ex = " + ex.getMessage());
    }
}

3、各通知的執(zhí)行結(jié)果

先來試試看砂吞,這幾個(gè)注解的執(zhí)行結(jié)果如何署恍。

不對(duì)啊,按鈕的點(diǎn)擊事件中有打印"Hello, I am CSDN_LQR"的蜻直,這里沒有盯质,怎么肥事袁串?

這里因?yàn)锧Around環(huán)繞通知會(huì)攔截原方法內(nèi)容的執(zhí)行,我們需要手動(dòng)放行才可以呼巷。代碼修改如下:

@Around("pointcut()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("@Around");
    joinPoint.proceed();// 目標(biāo)方法執(zhí)行完畢
}

也不對(duì)啊囱修,少了一個(gè)@AfterThrowing通知。這個(gè)通知只有在切點(diǎn)拋出異常時(shí)才會(huì)執(zhí)行王悍,我們可以讓代碼出現(xiàn)一個(gè)簡單的運(yùn)行時(shí)異常:

public void test(View view) {
    System.out.println("Hello, I am CSDN_LQR");
    int a = 1 / 0;
}

這下@AfterThrowing通知確實(shí)被調(diào)用了破镰,而且也打印出了錯(cuò)誤信息(divide by zero)。但@AfterReturning通知反而不執(zhí)行了配名,原因很簡單啤咽,都拋出異常了,切點(diǎn)肯定是不能返回結(jié)果的渠脉。也就是說:@AfterThrowing通知與@AfterReturning通知是沖突的宇整,在同個(gè)切點(diǎn)上不可能同時(shí)出現(xiàn)。

4芋膘、方法耗時(shí)計(jì)算的實(shí)現(xiàn)

因?yàn)锧Around是環(huán)繞通知鳞青,可以在切點(diǎn)的前后分別執(zhí)行一些操作,AspectJ為了能肯定操作是在切點(diǎn)前還是在切點(diǎn)后为朋,所以在@Around通知中需要手動(dòng)執(zhí)行joinPoint.proceed()來確定切點(diǎn)已經(jīng)執(zhí)行臂拓,故在joinPoint.proceed()之前的代碼會(huì)在切點(diǎn)執(zhí)行前執(zhí)行,在joinPoint.proceed()之后的代碼會(huì)切點(diǎn)執(zhí)行后執(zhí)行习寸。于是胶惰,方法耗時(shí)計(jì)算的實(shí)現(xiàn)就是這么簡單:

@Around("pointcut()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
    long beginTime = SystemClock.currentThreadTimeMillis();
    joinPoint.proceed();
    long endTime = SystemClock.currentThreadTimeMillis();
    long dx = endTime - beginTime;
    System.out.println("耗時(shí):" + dx + "ms");
}

5、JoinPoint的作用

發(fā)現(xiàn)沒有霞溪,上面所有的通知都會(huì)至少攜帶一個(gè)JointPoint參數(shù)孵滞,這個(gè)參數(shù)包含了切點(diǎn)的所有信息,下面就結(jié)合按鈕的點(diǎn)擊事件方法test()來解釋joinPoint能獲取到的方法信息有哪些:

MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String name = signature.getName(); // 方法名:test
Method method = signature.getMethod(); // 方法:public void com.lqr.androidaopdemo.MainActivity.test(android.view.View)
Class returnType = signature.getReturnType(); // 返回值類型:void
Class declaringType = signature.getDeclaringType(); // 方法所在類名:MainActivity
String[] parameterNames = signature.getParameterNames(); // 參數(shù)名:view
Class[] parameterTypes = signature.getParameterTypes(); // 參數(shù)類型:View

6鸯匹、注解切點(diǎn)

前面的切點(diǎn)表達(dá)式結(jié)構(gòu)是這樣的:

execution(<修飾符模式>? <返回類型模式> <方法名模式>(<參數(shù)模式>) <異常模式>?)

但實(shí)際上坊饶,上面的切點(diǎn)表達(dá)式結(jié)構(gòu)并不完整,應(yīng)該是這樣的:

execution(<@注解類型模式>? <修飾符模式>? <返回類型模式> <方法名模式>(<參數(shù)模式>) <異常模式>?)

這就意味著殴蓬,切點(diǎn)可以用注解來標(biāo)記了匿级。

1)自定義注解

如果用注解來標(biāo)記切點(diǎn),一般會(huì)使用自定義注解染厅,方便我們拓展痘绎。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnoTrace {
    String value();
    int type();
}
  • @Target(ElementType.METHOD):表示該注解只能注解在方法上。如果想類和方法都可以用肖粮,那可以這么寫:@Target({ElementType.METHOD,ElementType.TYPE})简逮,依此類推。
  • @Retention(RetentionPolicy.RUNTIME):表示該注解在程序運(yùn)行時(shí)是可見的(還有SOURCE尿赚、CLASS分別指定注解對(duì)于那個(gè)級(jí)別是可見的散庶,一般都是用RUNTIME)。

其中的value和type是自己拓展的屬性凌净,方便存儲(chǔ)一些額外的信息悲龟。

2)使用自定義注解標(biāo)記切點(diǎn)

這個(gè)自定義注解只能注解在方法上(構(gòu)造方法除外,構(gòu)造方法也叫構(gòu)造器冰寻,需要使用ElementType.CONSTRUCTOR)须教,像平常使用其它注解一樣使用它即可:

@TestAnnoTrace(value = "lqr_test", type = 1)
public void test(View view) {
    System.out.println("Hello, I am CSDN_LQR");
}

3)注解的切點(diǎn)表達(dá)式

既然用注解來標(biāo)記切點(diǎn),那么切點(diǎn)表達(dá)式肯定是有所不同的斩芭,要這么寫:

@Pointcut("execution(@com.lqr.androidaopdemo.TestAnnoTrace * *(..))")
public void pointcut() {}

切點(diǎn)表達(dá)式使用注解轻腺,一定是@+注解全路徑,如:@com.lqr.androidaopdemo.TestAnnoTrace划乖。

親測(cè)可用 贬养,不貼圖了。

4)獲取注解屬性值

上面在編寫自定義注解時(shí)就聲明了兩個(gè)屬性琴庵,分別是value和type误算,而且在使用該注解時(shí)也都為之賦值了,那怎么在通知中獲取這兩個(gè)屬性值呢迷殿?還記得JoinPoint這個(gè)參數(shù)吧儿礼,它就可以獲取到注解中的屬性值,如下所示:

MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 通過Method對(duì)象得到切點(diǎn)上的注解
TestAnnoTrace annotation = method.getAnnotation(TestAnnoTrace.class);
String value = annotation.value();
int type = annotation.type();

最后貼下Demo地址

https://github.com/GitLqr/AndroidAopDemo

歡迎關(guān)注微信公眾號(hào):全棧行動(dòng)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末庆寺,一起剝皮案震驚了整個(gè)濱河市蚊夫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌懦尝,老刑警劉巖知纷,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異导披,居然都是意外死亡屈扎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門撩匕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鹰晨,“玉大人,你說我怎么就攤上這事止毕∧@” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵扁凛,是天一觀的道長忍疾。 經(jīng)常有香客問我,道長谨朝,這世上最難降的妖魔是什么卤妒? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任甥绿,我火速辦了婚禮,結(jié)果婚禮上则披,老公的妹妹穿的比我還像新娘共缕。我一直安慰自己,他們只是感情好士复,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布图谷。 她就那樣靜靜地躺著,像睡著了一般阱洪。 火紅的嫁衣襯著肌膚如雪便贵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天冗荸,我揣著相機(jī)與錄音承璃,去河邊找鬼。 笑死俏竞,一個(gè)胖子當(dāng)著我的面吹牛绸硕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播魂毁,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼玻佩,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了席楚?” 一聲冷哼從身側(cè)響起咬崔,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎烦秩,沒想到半個(gè)月后垮斯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡只祠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年兜蠕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抛寝。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡熊杨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出盗舰,到底是詐尸還是另有隱情晶府,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布钻趋,位于F島的核電站川陆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蛮位。R本人自食惡果不足惜较沪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一鳞绕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧尸曼,春花似錦猾昆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽楷扬。三九已至解幽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間烘苹,已是汗流浹背躲株。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留镣衡,地道東北人霜定。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像廊鸥,于是被迫代替她去往敵國和親望浩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容

  • 本章內(nèi)容: 面向切面編程的基本原理 通過POJO創(chuàng)建切面 使用@AspectJ注解 為AspectJ切面注入依賴 ...
    謝隨安閱讀 3,127評(píng)論 0 9
  • WHY 如果說OOP(面向?qū)ο蟮某绦蛟O(shè)計(jì))的主要思想是將問題歸分到相應(yīng)的對(duì)象(類)中去實(shí)現(xiàn),再把相關(guān)類模塊化使得模...
    野生大P閱讀 944評(píng)論 0 8
  • 歡迎閱讀系列文章 Android aop切點(diǎn)表達(dá)式(execution)Android aop Advice(通知...
    嘮嗑008閱讀 20,542評(píng)論 7 88
  • 因?yàn)楣ぷ餍枨蠖杷担约喝チ私庖幌耡op并做下的記錄磨德,當(dāng)然大部分都是參考他人博客以及官方文檔。 目錄 [關(guān)于 AOP](...
    forip閱讀 2,267評(píng)論 1 20
  • 在看51822的DFU文章時(shí)看到了內(nèi)部有種比較特殊的寄存器吆视,避免自己在忘記典挑,記錄下來加深印象。根據(jù)nRF51 Se...
    zldiy閱讀 1,471評(píng)論 0 0