反射高级应用:自定义 AOP 框架

上一篇文章详细介绍了静态代理和动态代理的作用和实现方式,并介绍了动态代理实现的两种方式。熟练掌握反射技术是一个程序员走向高级的必备技能,今天我们来了解一下如何用反射来实现自定义的 AOP 代码,结合配置文件的使用,完成一个反射高级应用的实例,大家感受一下反射的魅力。

为了学习的连贯性,强烈建议大家先阅读 代理设计模式与AOP 一文,然后再来阅读下面的内容。

一、如何统计方法执行的时间?

在日常开发中,我们经常会用到一个功能就是统计一个服务或者是一个方法的执行时间,也就是说在进入方法开始执行到方法执行完毕,总共耗费的时间是多少,用来评估一个方法的执行效率。

初级工程师 A 会说:“解决这个问题简单啊,在这个方法的开始加一行代码记录开始时间 beginTime,在方法的结尾加一行代码记录结束时间 endTime,结束时间 endTime 减去开始时间 beginTime 就是方法的执行时间,最后打印这个时间差就好了呀!”。

中级工程师 B 听了初级工程师 A 的解决方案后说:“你这个方案是能解决问题,但是我们系统里代码几万行,方法那么多,按你这个思路实现,不得累死大家啊!你的建议不妥,我们应该用代理模式来解决这个问题,写一个代理类来统一代理这些方法的执行时间的代码,那个 Spring 框架的 AOP 就能很好地解决这个问题啊!”。

高级工程师 C 点了点头说:“小 B 同学说的没错,解决这个问题的首选方案还是要用 AOP 切面编程,Spring 框架确实可以完美地解决这个问题,你们知道 Spring AOP 是如何实现的吗?如果没有 Spring 框架,我们自己写代码的话,如何实现这个功能呢? ”。

A 和 B 顿时来了兴趣,不约而同地看向高级工程师 C,异口同声地说:“C 哥,那你给我们讲讲呗......”。

二、自定义 AOP 框架介绍

我们今天实现的这个 AOP 框架主要的功能就是完成统计方法执行的时间,用到的例子是上篇文章中的订单服务、用户服务、支付服务。

自定义 AOP 框架里有以下几个部分:

1、Advice 接口:用于定义方法的功能增强的接口。 2、Advice 接口的实现类 ExecutionTimeAdvice:方法执行时间统计功能增强的实现类。 3、BeanFactory 类:创建对象的工厂类,用它创建对象类似于用 new 关键字实例化一个对象。 4、BeanFactoryProxy 类:创建对象的工厂类 BeanFactory 的代理类,用它来代理具体类的对象方法的执行,从而可以增强由它代理的类的方法的功能,比如实现方法执行时间的统计。 5、config.properties 配置文件:用来配置实体类 bean 以及功能增强类 advice。

代码结构如下图:

三、一步一步地实现自定义 AOP 框架

1、定义 Advice 接口

Advice 主要是用来定义方法的功能增强的接口,在本例的 Advice 接口中定义两个方法 beforeMethod() 和 afterMethod(),分别表示方法执行前做的事情和方法执行后做的事情。

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

2、定义Advice 接口的实现类 ExecutionTimeAdvice

ExecutionTimeAdvice 类实现了 Advice 接口,主要是用来定义统计方法执行时间的功能增强的具体实现。

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() + " 方法运行的时间为:" + (endTime - beginTime));
  }
}

3、定义 BeanFactory 类

BeanFactory 类用于根据配置文件里配置的具体的 bean 的名称,来创建具体的对象。

public class BeanFactory {

  Properties prop = new Properties();

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

  /**
   * 获取配置文件配置的具体对象的实例
   */
  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) {
      // 如果是代理类,则根据配置文件的内容设置被代理类和对应的增强,否则返回配置的bean的对象实例
      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); // 设置被代理类
      proxyFactoryBean.setAdvice(advice); // 设置功能增强
      return proxyFactoryBean.getProxy();
    }
    return bean;
  }
}

4、定义 BeanFactoryProxy 类

BeanFactoryProxy 类是 BeanFactory 类的代理类,用它来代理具体类的对象方法的执行,从而可以增强由它代理的类的方法的功能,比如实现方法执行时间的统计。

public class BeanFactoryProxy {

  private Object target; // 被代理类
  private Advice advice; // 功能增强

  /**
   * 通过动态代理实现方法功能增强
   */
  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); // 在方法执行前调用
            Object retVal = method.invoke(target, args);
            advice.afterMethod(method); // 在方法执行后调用
            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 用来配置实体类 bean,功能增强类 advice,以及被代理类 target。

## 此配置表示beanName为order的Bean不使用方法的增强功能
order = com.jpm.reflection.aop.service.OrderService

## 此配置表示beanName为user的Bean,通过代理类实现方法的增强功能
user = com.jpm.reflection.aop.BeanFactoryProxy
user.advice = com.jpm.reflection.aop.ExecutionTimeAdvice
user.target = com.jpm.reflection.aop.service.UserService

## 此配置表示beanName为pay的Bean,通过代理类实现方法的增强功能
pay = com.jpm.reflection.aop.BeanFactoryProxy
pay.advice = com.jpm.reflection.aop.ExecutionTimeAdvice
pay.target = com.jpm.reflection.aop.service.WeChatPayService

## 此配置表示beanName为wechatpay的Bean不使用方法的增强功能
wechatpay = com.jpm.reflection.aop.service.WeChatPayService

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

通过以下的测试代码,快来感受一下自定义的 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,执行OrderService的查询方法和更新方法的结果:");
    DaoService order = (DaoService) beanFactory.getBean("order");
    order.query();
    order.update();

    System.out.println("2、配置文件使用代理类BeanFactoryProxy,执行UserService的查询方法和更新方法的结果:");
    DaoService user = (DaoService) beanFactory.getBean("user");
    user.query();
    user.update();

    System.out.println("3、配置文件配置使用代理类BeanFactoryProxy,执行WeChatPayService的查询方法和更新方法的结果:");
    PayService pay = (PayService) beanFactory.getBean("pay");
    pay.pay();

    System.out.println("4、配置文件使用具体类WeChatPayService,执行WeChatPayService的查询方法和更新方法的结果:");
    PayService weChatPay = (PayService) beanFactory.getBean("wechatpay");
    weChatPay.pay();
  }
}

运行结果:

1、配置文件使用具体类OrderService,执行OrderService的查询方法和更新方法的结果:
OrderService.query()
OrderService.update()
2、配置文件使用代理类BeanFactoryProxy,执行UserService的查询方法和更新方法的结果:
UserService.query()
query 方法运行的时间为:0
UserService.update()
update 方法运行的时间为:0
3、配置文件配置使用代理类BeanFactoryProxy,执行WeChatPayService的查询方法和更新方法的结果:
WeChatPayService.pay()
pay 方法运行的时间为:0
4、配置文件使用具体类WeChatPayService,执行WeChatPayService的查询方法和更新方法的结果:
WeChatPayService.pay()

通过以上的结果,我们可以看出,自己实现的 AOP 框架,完全可以达到根据配置文件来实现自由的切换哪些方法需要输出执行时间,哪些方法不需要输出执行时间,可以灵活的进行配置。

通过以上的介绍,大家应该感受到反射技术的强大的威力啦。其实 AOP 的原理就是这么简单,关键的核心代码也就是以上的代码,希望大家可以熟练掌握。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342