程序员

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

2020-05-01  本文已影响0人  追梦人_奋斗青年

上一篇文章详细介绍了静态代理和动态代理的作用和实现方式,并介绍了动态代理实现的两种方式。熟练掌握反射技术是一个程序员走向高级的必备技能,今天我们来了解一下如何用反射来实现自定义的 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 的原理就是这么简单,关键的核心代码也就是以上的代码,希望大家可以熟练掌握。

上一篇下一篇

猜你喜欢

热点阅读