反射高級(jí)應(yīng)用:自定義 AOP 框架

上一篇文章詳細(xì)介紹了靜態(tài)代理和動(dòng)態(tài)代理的作用和實(shí)現(xiàn)方式,并介紹了動(dòng)態(tài)代理實(shí)現(xiàn)的兩種方式。熟練掌握反射技術(shù)是一個(gè)程序員走向高級(jí)的必備技能,今天我們來了解一下如何用反射來實(shí)現(xiàn)自定義的 AOP 代碼,結(jié)合配置文件的使用,完成一個(gè)反射高級(jí)應(yīng)用的實(shí)例,大家感受一下反射的魅力。

為了學(xué)習(xí)的連貫性,強(qiáng)烈建議大家先閱讀 代理設(shè)計(jì)模式與AOP 一文,然后再來閱讀下面的內(nèi)容。

一、如何統(tǒng)計(jì)方法執(zhí)行的時(shí)間?

在日常開發(fā)中,我們經(jīng)常會(huì)用到一個(gè)功能就是統(tǒng)計(jì)一個(gè)服務(wù)或者是一個(gè)方法的執(zhí)行時(shí)間,也就是說在進(jìn)入方法開始執(zhí)行到方法執(zhí)行完畢,總共耗費(fèi)的時(shí)間是多少,用來評(píng)估一個(gè)方法的執(zhí)行效率。

初級(jí)工程師 A 會(huì)說:“解決這個(gè)問題簡(jiǎn)單啊,在這個(gè)方法的開始加一行代碼記錄開始時(shí)間 beginTime,在方法的結(jié)尾加一行代碼記錄結(jié)束時(shí)間 endTime,結(jié)束時(shí)間 endTime 減去開始時(shí)間 beginTime 就是方法的執(zhí)行時(shí)間,最后打印這個(gè)時(shí)間差就好了呀!”。

中級(jí)工程師 B 聽了初級(jí)工程師 A 的解決方案后說:“你這個(gè)方案是能解決問題,但是我們系統(tǒng)里代碼幾萬行,方法那么多,按你這個(gè)思路實(shí)現(xiàn),不得累死大家??!你的建議不妥,我們應(yīng)該用代理模式來解決這個(gè)問題,寫一個(gè)代理類來統(tǒng)一代理這些方法的執(zhí)行時(shí)間的代碼,那個(gè) Spring 框架的 AOP 就能很好地解決這個(gè)問題啊!”。

高級(jí)工程師 C 點(diǎn)了點(diǎn)頭說:“小 B 同學(xué)說的沒錯(cuò),解決這個(gè)問題的首選方案還是要用 AOP 切面編程,Spring 框架確實(shí)可以完美地解決這個(gè)問題,你們知道 Spring AOP 是如何實(shí)現(xiàn)的嗎?如果沒有 Spring 框架,我們自己寫代碼的話,如何實(shí)現(xiàn)這個(gè)功能呢? ”。

A 和 B 頓時(shí)來了興趣,不約而同地看向高級(jí)工程師 C,異口同聲地說:“C 哥,那你給我們講講唄......”。

二、自定義 AOP 框架介紹

我們今天實(shí)現(xiàn)的這個(gè) AOP 框架主要的功能就是完成統(tǒng)計(jì)方法執(zhí)行的時(shí)間,用到的例子是上篇文章中的訂單服務(wù)、用戶服務(wù)、支付服務(wù)。

自定義 AOP 框架里有以下幾個(gè)部分:

1、Advice 接口:用于定義方法的功能增強(qiáng)的接口。 2、Advice 接口的實(shí)現(xiàn)類 ExecutionTimeAdvice:方法執(zhí)行時(shí)間統(tǒng)計(jì)功能增強(qiáng)的實(shí)現(xiàn)類。 3、BeanFactory 類:創(chuàng)建對(duì)象的工廠類,用它創(chuàng)建對(duì)象類似于用 new 關(guān)鍵字實(shí)例化一個(gè)對(duì)象。 4、BeanFactoryProxy 類:創(chuàng)建對(duì)象的工廠類 BeanFactory 的代理類,用它來代理具體類的對(duì)象方法的執(zhí)行,從而可以增強(qiáng)由它代理的類的方法的功能,比如實(shí)現(xiàn)方法執(zhí)行時(shí)間的統(tǒng)計(jì)。 5、config.properties 配置文件:用來配置實(shí)體類 bean 以及功能增強(qiáng)類 advice。

代碼結(jié)構(gòu)如下圖:

三、一步一步地實(shí)現(xiàn)自定義 AOP 框架

1、定義 Advice 接口

Advice 主要是用來定義方法的功能增強(qiáng)的接口,在本例的 Advice 接口中定義兩個(gè)方法 beforeMethod() 和 afterMethod(),分別表示方法執(zhí)行前做的事情和方法執(zhí)行后做的事情。

public interface Advice {
  public void beforeMethod(Method method);
  public void afterMethod(Method method);
}

2、定義Advice 接口的實(shí)現(xiàn)類 ExecutionTimeAdvice

ExecutionTimeAdvice 類實(shí)現(xiàn)了 Advice 接口,主要是用來定義統(tǒng)計(jì)方法執(zhí)行時(shí)間的功能增強(qiáng)的具體實(shí)現(xiàn)。

public class ExecutionTimeAdvice implements Advice {

  long beginTime = 0;
  
  public void beforeMethod(Method method) {
    beginTime = System.currentTimeMillis();
  }
  
  public void afterMethod(Method method) {
    long endTime = System.currentTimeMillis();
    System.out.println(method.getName() + " 方法運(yùn)行的時(shí)間為:" + (endTime - beginTime));
  }
}

3、定義 BeanFactory 類

BeanFactory 類用于根據(jù)配置文件里配置的具體的 bean 的名稱,來創(chuàng)建具體的對(duì)象。

public class BeanFactory {

  Properties prop = new Properties();

  public BeanFactory(InputStream is) throws IOException {
    if (is != null) {
      prop.load(is); // 加載配置文件
    }
  }

  /**
   * 獲取配置文件配置的具體對(duì)象的實(shí)例
   */
  public Object getBean(String beanName) throws Exception {
    String className = prop.getProperty(beanName);
    Class<?> clazz = Class.forName(className);
    Object bean = clazz.newInstance();
    if (bean instanceof BeanFactoryProxy) {
      // 如果是代理類,則根據(jù)配置文件的內(nèi)容設(shè)置被代理類和對(duì)應(yīng)的增強(qiáng),否則返回配置的bean的對(duì)象實(shí)例
      BeanFactoryProxy proxyFactoryBean = (BeanFactoryProxy) bean;
      Object target = Class.forName(prop.getProperty(beanName + ".target")).newInstance();
      Advice advice = (Advice) Class.forName(prop.getProperty(beanName + ".advice")).newInstance();
      proxyFactoryBean.setTarget(target); // 設(shè)置被代理類
      proxyFactoryBean.setAdvice(advice); // 設(shè)置功能增強(qiáng)
      return proxyFactoryBean.getProxy();
    }
    return bean;
  }
}

4、定義 BeanFactoryProxy 類

BeanFactoryProxy 類是 BeanFactory 類的代理類,用它來代理具體類的對(duì)象方法的執(zhí)行,從而可以增強(qiáng)由它代理的類的方法的功能,比如實(shí)現(xiàn)方法執(zhí)行時(shí)間的統(tǒng)計(jì)。

public class BeanFactoryProxy {

  private Object target; // 被代理類
  private Advice advice; // 功能增強(qiáng)

  /**
   * 通過動(dòng)態(tài)代理實(shí)現(xiàn)方法功能增強(qiáng)
   */
  public Object getProxy() {
    Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
        new InvocationHandler() {

          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            advice.beforeMethod(method); // 在方法執(zhí)行前調(diào)用
            Object retVal = method.invoke(target, args);
            advice.afterMethod(method); // 在方法執(zhí)行后調(diào)用
            return retVal;
          }
        });
    return proxy;
  }

  public Object getTarget() {
    return target;
  }

  public void setTarget(Object target) {
    this.target = target;
  }

  public Advice getAdvice() {
    return advice;
  }

  public void setAdvice(Advice advice) {
    this.advice = advice;
  }

}

5、定義 config.properties 配置文件

配置文件 config.properties 用來配置實(shí)體類 bean,功能增強(qiáng)類 advice,以及被代理類 target。

## 此配置表示beanName為order的Bean不使用方法的增強(qiáng)功能
order = com.jpm.reflection.aop.service.OrderService

## 此配置表示beanName為user的Bean,通過代理類實(shí)現(xiàn)方法的增強(qiáng)功能
user = com.jpm.reflection.aop.BeanFactoryProxy
user.advice = com.jpm.reflection.aop.ExecutionTimeAdvice
user.target = com.jpm.reflection.aop.service.UserService

## 此配置表示beanName為pay的Bean,通過代理類實(shí)現(xiàn)方法的增強(qiáng)功能
pay = com.jpm.reflection.aop.BeanFactoryProxy
pay.advice = com.jpm.reflection.aop.ExecutionTimeAdvice
pay.target = com.jpm.reflection.aop.service.WeChatPayService

## 此配置表示beanName為wechatpay的Bean不使用方法的增強(qiáng)功能
wechatpay = com.jpm.reflection.aop.service.WeChatPayService

四、感受自定義的 AOP 框架的魅力

通過以下的測(cè)試代碼,快來感受一下自定義的 AOP 框架的魅力吧:

public class TestAop {

  public static void main(String[] args) throws Exception {
    InputStream is = TestAop.class.getClassLoader()
        .getResourceAsStream("com//jpm//reflection//aop//config.properties");
    BeanFactory beanFactory = new BeanFactory(is);

    System.out.println("1、配置文件使用具體類OrderService,執(zhí)行OrderService的查詢方法和更新方法的結(jié)果:");
    DaoService order = (DaoService) beanFactory.getBean("order");
    order.query();
    order.update();

    System.out.println("2、配置文件使用代理類BeanFactoryProxy,執(zhí)行UserService的查詢方法和更新方法的結(jié)果:");
    DaoService user = (DaoService) beanFactory.getBean("user");
    user.query();
    user.update();

    System.out.println("3、配置文件配置使用代理類BeanFactoryProxy,執(zhí)行WeChatPayService的查詢方法和更新方法的結(jié)果:");
    PayService pay = (PayService) beanFactory.getBean("pay");
    pay.pay();

    System.out.println("4、配置文件使用具體類WeChatPayService,執(zhí)行WeChatPayService的查詢方法和更新方法的結(jié)果:");
    PayService weChatPay = (PayService) beanFactory.getBean("wechatpay");
    weChatPay.pay();
  }
}

運(yùn)行結(jié)果:

1、配置文件使用具體類OrderService,執(zhí)行OrderService的查詢方法和更新方法的結(jié)果:
OrderService.query()
OrderService.update()
2、配置文件使用代理類BeanFactoryProxy,執(zhí)行UserService的查詢方法和更新方法的結(jié)果:
UserService.query()
query 方法運(yùn)行的時(shí)間為:0
UserService.update()
update 方法運(yùn)行的時(shí)間為:0
3、配置文件配置使用代理類BeanFactoryProxy,執(zhí)行WeChatPayService的查詢方法和更新方法的結(jié)果:
WeChatPayService.pay()
pay 方法運(yùn)行的時(shí)間為:0
4、配置文件使用具體類WeChatPayService,執(zhí)行WeChatPayService的查詢方法和更新方法的結(jié)果:
WeChatPayService.pay()

通過以上的結(jié)果,我們可以看出,自己實(shí)現(xiàn)的 AOP 框架,完全可以達(dá)到根據(jù)配置文件來實(shí)現(xiàn)自由的切換哪些方法需要輸出執(zhí)行時(shí)間,哪些方法不需要輸出執(zhí)行時(shí)間,可以靈活的進(jìn)行配置。

通過以上的介紹,大家應(yīng)該感受到反射技術(shù)的強(qiáng)大的威力啦。其實(shí) AOP 的原理就是這么簡(jiǎn)單,關(guān)鍵的核心代碼也就是以上的代碼,希望大家可以熟練掌握。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容