Android中的AOP

在上一篇
使用自定義注解實(shí)現(xiàn)MVP中Model和View的注入
中畏浆,使用了自定義的方式進(jìn)行依賴注入這一篇我們將繼續(xù)對(duì)注解進(jìn)行深入了解税稼。在日常的開發(fā)過程中烦秩,我們經(jīng)常會(huì)在同一個(gè)地方使用到相同的代碼,以往我們的處理方式是可以將其進(jìn)行一個(gè)封裝郎仆,然后在
不同的地方進(jìn)行調(diào)用這樣確實(shí)也很方便只祠,但是還有另外的方式,就是自定義注解實(shí)現(xiàn)AOP扰肌。

需求:在開發(fā)過程中有很多頁(yè)面需要判斷登錄抛寝,實(shí)現(xiàn)這樣一個(gè)功能,能夠在不同需要實(shí)現(xiàn)的地方進(jìn)行登錄的校驗(yàn)曙旭!

AOP

AOPAspect Oriented Program的首字母縮寫AOP盗舰,其意是面向切面編程),其實(shí)很多前端的開發(fā)可能都沒有聽說過這個(gè)桂躏,但是對(duì)于
后端的小伙伴來說這個(gè)是在是太熟悉了钻趋,因?yàn)楹芏鄷r(shí)候他們就靠這個(gè)來進(jìn)行Log的打印。

那么AOP到底是什么呢?

AOP定義

先看定義:運(yùn)行時(shí)沼头,動(dòng)態(tài)地將代碼切入到類的指定方法爷绘、指定位置上的編程思想

在解釋AOP之前书劝,首先得說說和面向切面編程相對(duì)的另一個(gè)編程思想:面向?qū)ο缶幊蹋?code>OOP。在面向?qū)ο蟮乃枷胫型林粒覀円浴耙磺薪詫?duì)象”為原則购对,為不同的對(duì)象賦予不同的
功能,在需要使用到的時(shí)候陶因,我們就對(duì)實(shí)例化對(duì)象骡苞,然后調(diào)用其功能,這樣降低了代碼的復(fù)雜度楷扬,使類可重用解幽。

但是在使用的過程中,會(huì)出現(xiàn)這么一種情況烘苹,類A和類B躲株,都需要進(jìn)行實(shí)現(xiàn)一個(gè)功能(比如:是否登錄的判斷),以往我們的做法很簡(jiǎn)單镣衡,
將這個(gè)登錄判斷的功能寫在一個(gè)類中(這里命名為C)霜定,然后在各自的引用的地方調(diào)用這個(gè)類的方法,確實(shí)這樣是解決了這個(gè)問題廊鸥,但是
這樣卻使A,B 兩個(gè)類與C類之間就會(huì)有耦合望浩。有沒有什么辦法,能讓我們?cè)谛枰臅r(shí)候惰说,隨意地加入代碼呢磨德?
為了解決這樣的問題就出現(xiàn)了面向切面編程的思想,即是:這種在運(yùn)行時(shí)吆视,動(dòng)態(tài)地將代碼切入到類的指定方法典挑、指定位置上的編程思想就是面向切面的編程

AOP和OOP之間的關(guān)系

AOP的實(shí)際操作是將幾個(gè)類之間共有的功能單獨(dú)出來,然后在這幾個(gè)需要的時(shí)候進(jìn)行切入揩环,改變其本來的運(yùn)行方式搔弄。這樣分析下來,我們可以
得出一個(gè)結(jié)論丰滑,即是:面向切面編程(AOP)其實(shí)是面向?qū)ο缶幊蹋?code>OOP)的一個(gè)補(bǔ)充。

加入AspectJ

AspectJ AspectJ實(shí)際上是對(duì)AOP編程思想的一個(gè)實(shí)現(xiàn)倒庵。

  • 在項(xiàng)目的gradle文件下加入:

     dependencies {
             classpath 'com.android.tools.build:gradle:3.0.0'
             classpath 'org.aspectj:aspectjtools:1.8.9'
             classpath 'org.aspectj:aspectjweaver:1.8.9'
    
             // NOTE: Do not place your application dependencies here; they belong
             // in the individual module build.gradle files
         }
    
  • 在app的gradle文件下加入:

    1. 引入aspectjtools

      import org.aspectj.bridge.IMessage
      import org.aspectj.bridge.MessageHandler
      import org.aspectj.tools.ajc.Main
      
    2. 導(dǎo)入第三方包

      compile 'org.aspectj:aspectjrt:1.8.9'
      
  1. 使用AspectJ編譯器ajc

使用ajc會(huì)對(duì)所有受 aspect 影響的類進(jìn)行織入褒墨,這樣才能使我們的Aspect

//獲取 log實(shí)例
final def log = project.logger
//獲取variants
final def variants = project.android.applicationVariants
variants.all { variant ->
    if (!variant.buildType.isDebuggable()) {
        log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
        return;
    }

    //編譯時(shí)做如下處理
    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;
            }
        }
    }
}      

至此,我們就將AspectJ的準(zhǔn)備工作做好了擎宝,那么接下來就是使用了

在Android中使用AOP

先來介紹幾個(gè)概念:

  • Pointcut:切入點(diǎn)郁妈,就是在程序運(yùn)行過程中,在何處注入我們想運(yùn)行的特定代碼绍申。
    注意:這里的何處噩咪,并不是真正意義上的具體位置顾彰,而是可切入的范圍,比如整個(gè)包下面所有類及所有方法胃碾,或者某個(gè)類下面的所有方法涨享。
  • Joint point:連接點(diǎn),程序中可能作為代碼注入目標(biāo)的特定的點(diǎn)仆百,所以此處才是執(zhí)行注入的具體的位置厕隧。
  • Advice: 通知,即是在程序運(yùn)行過程中俄周,當(dāng)執(zhí)行到切點(diǎn)位置時(shí)吁讨,執(zhí)行注入到class文件中什么樣的代碼,
    比較常用的類型是before峦朗,around建丧,after。從字面上面我們就可以看出其意波势,
    就是在目標(biāo)方法執(zhí)行之前茶鹃,執(zhí)行之時(shí)替代目標(biāo)方法,執(zhí)行之后的代碼艰亮。
  • Aspect: 切面闭翩,其實(shí)就是PointcutAdvice的組合,所以如上可以總結(jié)為在何處做什么迄埃。

創(chuàng)建@CheckLogin注解

可能有人會(huì)問:為什么是創(chuàng)建注解呢疗韵?不能是其的什么類或者對(duì)象么?
AOP本來就是為了解決耦合才進(jìn)行使用的侄非,如果使用其他的蕉汪,或讓AspectJ與其耦合,那我們使用AOP干什么呢逞怨?


@Retention(RetentionPolicy.RUNTIME) //保留到源碼中者疤,同時(shí)也保留到class中,最后加載到虛擬機(jī)中
@Target({ElementType.METHOD,ElementType.CONSTRUCTOR}) //可以注解在方法或構(gòu)造上
public @interface CheckLogin {
}

在上次的講解中已經(jīng)提到元注解@Retention,表示注解的表示方式叠赦,這里再回顧一下:

  • SOURCE:只保留在源碼中驹马,不保留在class中,同時(shí)也不加載到虛擬機(jī)中
  • CLASS:保留在源碼中除秀,同時(shí)也保留到class中糯累,但是不加載到虛擬機(jī)中
  • RUNTIME:保留到源碼中,同時(shí)也保留到class中册踩,最后加載到虛擬機(jī)中

@Target 這個(gè)注解表示注解的作用范圍泳姐,主要有如下:

  • ElementType.FIELD 注解作用于變量
  • ElementType.METHOD 注解作用于方法
  • ElementType.PARAMETER 注解作用于參數(shù)
  • ElementType.CONSTRUCTOR 注解作用于構(gòu)造方法
  • ElementType.LOCAL_VARIABLE 注解作用于局部變量
  • ElementType.PACKAGE 注解作用于包

所以如上的CheckLogin表示將注解可以注入到構(gòu)造方法和其他方法上,并且保留到源碼中暂吉,同時(shí)也保留到class中胖秒,最后加載到虛擬機(jī)中缎患。

創(chuàng)建Aspect類

到此,才是我們這章的重點(diǎn)阎肝,就是怎么構(gòu)建一個(gè)Aspect類,這里以CheckLoginAspectJ為例挤渔。

@Aspect
public class CheckLoginAspectJ {
    private static final String TAG = "CheckLogin";

    /**
     * 找到處理的切點(diǎn)
     * * *(..)  可以處理CheckLogin這個(gè)類所有的方法
     */
    @Pointcut("execution(@com.yw.android.aoptest.aop.CheckLogin  * *(..))")
    public void executionCheckLogin() {

    }

    /**
     * 處理切面
     *
     * @param joinPoint
     * @return
     */
    @Around("executionCheckLogin()")
    public Object checkLogin(ProceedingJoinPoint joinPoint) throws Throwable {
        Log.i(TAG, "checkLogin: ");
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        CheckLogin checkLogin = signature.getMethod().getAnnotation(CheckLogin.class);
        if (checkLogin != null) {
            Context context = (Context) joinPoint.getThis();
            if (BaseApplication.isLogin) {
                Log.i(TAG, "checkLogin: 登錄成功 ");
                return joinPoint.proceed();
            } else {
                Log.i(TAG, "checkLogin: 請(qǐng)登錄");
                Toast.makeText(context, "請(qǐng)登錄", Toast.LENGTH_SHORT).show();
                Intent intent = new Intent(context, LoginActivity.class);
                context.startActivity(intent);
                return null;
            }
        }
        return joinPoint.proceed();
    }

@Pointcut說明

在上方代碼Pointcut之后緊跟了一個(gè)execution的表達(dá)式,這個(gè)就代表切入點(diǎn)的位置盗痒,也就是我們上述的何處

解釋一下execution的用法:

execution僅僅是AOP中pointcut expression表達(dá)式中的一種蚂蕴。其他還有如下這幾種:

  • args():用于匹配當(dāng)前執(zhí)行的方法傳入的參數(shù)為指定類型的執(zhí)行方法
  • @args():用于匹配當(dāng)前執(zhí)行的方法傳入的參數(shù)持有指定注解的執(zhí)行
  • execution():用于匹配方法執(zhí)行的連接點(diǎn)
  • this():用于匹配當(dāng)前AOP代理對(duì)象類型的執(zhí)行方法;注意是AOP代理對(duì)象的類型匹配俯邓,這樣就可能包括引入接口也類型匹配
  • target():用于匹配當(dāng)前目標(biāo)對(duì)象類型的執(zhí)行方法骡楼;注意是目標(biāo)對(duì)象的類型匹配,這樣就不包括引入接口也類型匹配
  • @target():用于匹配當(dāng)前目標(biāo)對(duì)象類型的執(zhí)行方法稽鞭,其中目標(biāo)對(duì)象持有指定的注解鸟整;
  • within():用于匹配指定類型內(nèi)的方法執(zhí)行
  • @within():用于匹配所有持有指定注解類型內(nèi)的方法;
  • @annotation:用于匹配當(dāng)前執(zhí)行方法持有指定注解的方法

這里重點(diǎn)解釋一下execution,因?yàn)樵谖覀兊娜粘J褂弥校?code>execution是最多的朦蕴。

類型匹配語(yǔ)法

  • *:匹配任何數(shù)量字符,即是全部篮条;
  • ..:匹配任何數(shù)量字符的重復(fù),如在類型模式中匹配任何數(shù)量子包吩抓;而在方法參數(shù)模式中匹配任何數(shù)量參數(shù)涉茧。
  • +:匹配指定類型的子類型;僅能作為后綴放在類型模式后邊疹娶。
  • ():表示方法沒有任何參數(shù)
  • (..):表示匹配接受任意個(gè)參數(shù)的方法
//匹配String類型
java.lang.String
//匹配java包下任何子包的String類型
java.*.String
//匹配java包及任何子包下的任何類型
java..*

execution表達(dá)式

execution的表達(dá)式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)

  • modifiers-pattern:修飾符匹配伴栓,如publicprivate雨饺、protect钳垮,可選。
  • ret-type-pattern:返回類型匹配额港,必填饺窿。
  • declaring-type-pattern:聲明類型匹配,可選移斩。
  • name-pattern(param-pattern):
    • name-pattern:方法名匹配肚医,必填
    • param-pattern:方法參數(shù)匹配,必填
  • throws-pattern:異常匹配叹哭,可選忍宋。

至此,我們可以知道风罩,上述中代碼代表的匹配意思了

"execution(@com.yw.android.aoptest.aop.CheckLogin  * *(..))"

返回類型:com.yw.android.aoptest.aop.CheckLogin;
聲明類型: * ,表示任何
方法名: *,任何方法
參數(shù):(..),任意個(gè)參數(shù)

即是:匹配com.yw.android.aoptest.aop.CheckLogin類下的所有聲明和所以任意參數(shù)方法。

@Advice說明

@Around("executionCheckLogin()")
    public Object checkLogin(ProceedingJoinPoint joinPoint) throws Throwable {
        ...
    }

在上述代碼中我們使用的是@Around,這個(gè)也是很常用的舵稠。

@Around("executionCheckLogin()")將切面表達(dá)式與通知進(jìn)行綁定超升,使用我們的代碼注入在使用@CheckLogin的地方生效
,其中參數(shù)是上面切面的方法名入宦。

而在方法中參數(shù)就是JoinPoint,常用的也就是這個(gè)ProceedingJoinPoint

JoinPoint

public interface JoinPoint {
    String toString();         //連接點(diǎn)所在位置的相關(guān)信息
    String toShortString();     //連接點(diǎn)所在位置的簡(jiǎn)短相關(guān)信息
    String toLongString();     //連接點(diǎn)所在位置的全部相關(guān)信息
    Object getThis();         //返回AOP代理對(duì)象
    Object getTarget();       //返回目標(biāo)對(duì)象
    Object[] getArgs();       //返回被通知方法參數(shù)列表
    Signature getSignature();  //返回當(dāng)前連接點(diǎn)簽名
    SourceLocation getSourceLocation();//返回連接點(diǎn)方法所在類文件中的位置
    String getKind();        //連接點(diǎn)類型
    StaticPart getStaticPart(); //返回連接點(diǎn)靜態(tài)部分
}

ProceedingJoinPoint

ProceedingJoinPoint繼承了JoinPoint

public interface ProceedingJoinPoint extends JoinPoint {
    public Object proceed() throws Throwable;
    public Object proceed(Object[] args) throws Throwable;
}

使用proceed()方法來執(zhí)行目標(biāo)方法,即是被@CheckLogin注解的方法,我們?cè)賮砜纯次覀兊姆椒?/p>

@Around("executionCheckLogin()")
    public Object checkLogin(ProceedingJoinPoint joinPoint) throws Throwable {
        Log.i(TAG, "checkLogin: ");
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        CheckLogin checkLogin = signature.getMethod().getAnnotation(CheckLogin.class);
        if (checkLogin != null) {
            Context context = (Context) joinPoint.getThis();
            if (BaseApplication.isLogin) {
                Log.i(TAG, "checkLogin: 登錄成功 ");
                return joinPoint.proceed();
            } else {
                Log.i(TAG, "checkLogin: 請(qǐng)登錄");
                Toast.makeText(context, "請(qǐng)登錄", Toast.LENGTH_SHORT).show();
                Intent intent = new Intent(context, LoginActivity.class);
                context.startActivity(intent);
                return null;
            }
        }
        return joinPoint.proceed();
    }

  1. 先獲取一個(gè)方法前面對(duì)象MethodSignature室琢,這個(gè)對(duì)象有兩個(gè)方法:
public interface MethodSignature extends CodeSignature {
    Class getReturnType();      /* name is consistent with reflection API */
    Method getMethod();
}

一個(gè)是獲取目標(biāo)方法的返回類型乾闰,一個(gè)是目標(biāo)方法的Methond對(duì)象。
然后通過:

signature.getMethod().getAnnotation(CheckLogin.class);

就可以獲取目標(biāo)方法的注解盈滴,如果注解實(shí)例不為空涯肩,說明加了CheckLogin注解。

Context context = (Context) joinPoint.getThis();

通過上述方法巢钓,可以獲取目標(biāo)方法所在類的對(duì)象病苗,但是這里強(qiáng)轉(zhuǎn)成了Context,也就是說症汹,改注解只能在有上下文的類里使用硫朦。
然后通過登錄的標(biāo)志進(jìn)行判斷,是讓目標(biāo)方法繼續(xù)執(zhí)行背镇,還是跳轉(zhuǎn)至登錄咬展。

簡(jiǎn)單測(cè)試

private Button btnAop;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    btnAop = (Button) findViewById(R.id.btn_aop);
    btnAop.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
             onAop();
        }
    });
}

@CheckLogin
public void onAop(){
    Log.d("tag","執(zhí)行方法參數(shù)");
}
  1. 設(shè)置登錄標(biāo)志為未登錄:
I/CheckLogin: checkLogin:
I/CheckLogin: checkLogin: 請(qǐng)登錄

檢測(cè)出未登錄,跳轉(zhuǎn)到了登錄界面

  1. 設(shè)置登錄標(biāo)志為已登錄:
I/CheckLogin: checkLogin:
I/CheckLogin: checkLogin: 登錄成功
D/tag: 執(zhí)行方法參數(shù)

檢測(cè)出已登錄瞒斩,執(zhí)行目標(biāo)方法破婆。

總結(jié)

AOP的使用不光在檢測(cè)登錄,還有其他的一些用處:

  • 打印日志胸囱,在需要打印日志的地方加上這樣的方式沃粗,就可以打印日志壁榕,是不是比寫一個(gè)打印方法簡(jiǎn)單多了
  • 緩存,假設(shè)目標(biāo)方法是個(gè)數(shù)據(jù)請(qǐng)求,那么是不是可以在目標(biāo)方法執(zhí)行之后殃姓,進(jìn)行緩存
  • 數(shù)據(jù)校驗(yàn),我們的代碼中很多地方都會(huì)去校驗(yàn)數(shù)據(jù)对供,那么自定義一個(gè)AOP逾苫,然后傳入你需要注解的對(duì)象進(jìn)行校驗(yàn)。

這樣的方式應(yīng)該還有很多柬帕,只是現(xiàn)在還沒有用到哟忍,希望大家可以多多提出自己的想法。

查看項(xiàng)目陷寝,請(qǐng)戳這里

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锅很,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子凤跑,更是在濱河造成了極大的恐慌爆安,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仔引,死亡現(xiàn)場(chǎng)離奇詭異扔仓,居然都是意外死亡褐奥,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門翘簇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撬码,“玉大人,你說我怎么就攤上這事版保∥匦Γ” “怎么了?”我有些...
    開封第一講書人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵彻犁,是天一觀的道長(zhǎng)叫胁。 經(jīng)常有香客問我,道長(zhǎng)袖裕,這世上最難降的妖魔是什么曹抬? 我笑而不...
    開封第一講書人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮急鳄,結(jié)果婚禮上谤民,老公的妹妹穿的比我還像新娘。我一直安慰自己疾宏,他們只是感情好张足,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著坎藐,像睡著了一般为牍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上岩馍,一...
    開封第一講書人閱讀 51,604評(píng)論 1 305
  • 那天碉咆,我揣著相機(jī)與錄音,去河邊找鬼蛀恩。 笑死疫铜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的双谆。 我是一名探鬼主播壳咕,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼顽馋!你這毒婦竟也來了谓厘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤寸谜,失蹤者是張志新(化名)和其女友劉穎竟稳,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡住练,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年地啰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了愁拭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讲逛。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖岭埠,靈堂內(nèi)的尸體忽然破棺而出盏混,到底是詐尸還是另有隱情,我是刑警寧澤惜论,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布许赃,位于F島的核電站,受9級(jí)特大地震影響馆类,放射性物質(zhì)發(fā)生泄漏混聊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一乾巧、第九天 我趴在偏房一處隱蔽的房頂上張望句喜。 院中可真熱鬧,春花似錦沟于、人聲如沸咳胃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)展懈。三九已至,卻和暖如春供璧,著一層夾襖步出監(jiān)牢的瞬間存崖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工睡毒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留来惧,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓吕嘀,卻偏偏與公主長(zhǎng)得像违寞,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子偶房,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

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

  • 來源:知乎 欲眼熊貓 面向切面編程(AOP是Aspect Oriented Program的首字母縮寫) 趁曼,我...
    wenld_閱讀 3,299評(píng)論 4 13
  • Android 中的 AOP 編程 原文鏈接 : Aspect Oriented Programming in A...
    mao眼閱讀 18,424評(píng)論 19 82
  • 引言 之前有個(gè)做Java Web的師兄就跟我提過,我一直以為這是Java Web的特產(chǎn)棕洋,也就是一個(gè)叫做Spring...
    Android開發(fā)哥閱讀 1,494評(píng)論 1 7
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理挡闰,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,657評(píng)論 18 139
  • AOP實(shí)現(xiàn)可分為兩類(按AOP框架修改源代碼的時(shí)機(jī)): 靜態(tài)AOP實(shí)現(xiàn):AOP框架在編譯階段對(duì)程序進(jìn)行修改,即實(shí)現(xiàn)...
    數(shù)獨(dú)題閱讀 2,320評(píng)論 0 22