关于java:如何实现一个简易版的-Spring-如何实现-AOP下

5次阅读

共计 9169 个字符,预计需要花费 23 分钟才能阅读完成。

前言

后面两篇 如何实现 AOP(上)、如何实现 AOP(中)做了一些 AOP 的外围基础知识简要介绍,本文进入到了实战环节了,去实现一个基于 XML 配置的简易版 AOP,尽管是简易版的然而 麻雀虽小五脏俱全 ,一些外围的性能都会实现,通过实现这个简易版的 AOP,置信你会对 AOP 有深刻的了解,不止知其然,还能知其所以然。AOP 的顶层接口标准和底层依赖根底组件都是由一个叫 AOP Alliance 的组织制订的,咱们常常听到的 AspectJASMCGLIB 就是其中被治理的一些我的项目,须要明确的一点是,在 Spring 中只是应用了 AspectJ 的外围概念和外围类,并不是像 AspectJ 那样在编译期实现的 AOP,而是在 运行期。话不多说,上面开始进入主题。

解析 XML 中的 pointcut 定义及办法解析

假如有一个 OrderService 类(P.S. 这里的 @Component 是我自定义的注解,详见 这篇),其中有一个下单的办法 placeOrder(),咱们想实现的成果是想给这个 placeOrder() 办法加上 数据库事务,即执行办法之前开启事务,执行过程中产生异样回滚事务,失常执行实现提交事务。OrderService 类的代码如下:

/**
 * @author mghio
 * @since 2021-06-06
 */
@Component(value = "orderService")
public class OrderService {public void placeOrder() {System.out.println("place order");
  }

}  

很显著,这里的 pointcut 就是 placeOrder() 办法,在 XML 配置文件中的配置如下:

<aop:pointcut id="placeOrder" expression="execution(* cn.mghio.service.version5.*.placeOrder(..))"/>

咱们须要一个类去表白这个概念,pointcut 要实现的性能是给定一个类的办法,判断是否匹配配置文件中给定的表达式。总的来看 pointcut 办法匹配器 匹配表达式 两局部组成,办法匹配器能够有各种不同的实现,所以是一个接口,pointcut 同样也能够基于多种不同技术实现,故也是一个接口,默认是基于 AspectJ 实现的,类图构造如下:

实现类 AspectJExpressionPointcut 是基于 AspectJ 实现的,办法的匹配过程是委托给 AspectJ 中的 PointcutExpression 来判断给定的办法是否匹配表达式,该类的外围实现如下:

/**
 * @author mghio
 * @since 2021-06-06
 */
public class AspectJExpressionPointcut implements Pointcut, MethodMatcher {private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();

  static {SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
  }

  private String expression;
  private ClassLoader pointcutClassLoader;
  private PointcutExpression pointcutExpression;

  @Override
  public MethodMatcher getMethodMatcher() {return this;}

  @Override
  public String getExpression() {return expression;}

  @Override
  public boolean matches(Method method) {checkReadyToMatch();

    ShadowMatch shadowMatch = getShadowMatch(method);
    return shadowMatch.alwaysMatches();}

  private void checkReadyToMatch() {if (Objects.isNull(getExpression())) {throw new IllegalArgumentException("Must set property'expression'before attempting to match");
    }
    if (Objects.isNull(this.pointcutExpression)) {this.pointcutClassLoader = ClassUtils.getDefaultClassLoader();
      this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
    }
  }

  private PointcutExpression buildPointcutExpression(ClassLoader classLoader) {
    PointcutParser pointcutParser = PointcutParser
        .getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(SUPPORTED_PRIMITIVES, classLoader);
    return pointcutParser.parsePointcutExpression(replaceBooleanOperators(getExpression()));
  }

  private String replaceBooleanOperators(String pcExpr) {String result = StringUtils.replace(pcExpr, "and", "&&");
    result = StringUtils.replace(result, "or", "||");
    result = StringUtils.replace(result, "not", "!");
    return result;
  }

  private ShadowMatch getShadowMatch(Method method) {
    ShadowMatch shadowMatch;
    try {shadowMatch = this.pointcutExpression.matchesMethodExecution(method);
    } catch (Exception e) {throw new RuntimeException("not implemented yet");
    }
    return shadowMatch;
  }

  // omit other setter、getter ...

}

到这里就实现了给定一个类的办法,判断是否匹配配置文件中给定的表达式的性能。再来看如下的一个残缺的 AOP 配置:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.e3.org/2001/XMLSchema-instance"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/beans/spring-context.xsd">

  <context:scann-package base-package="cn.mghio.service.version5,cn.mghio.dao.version5" />

  <bean id="tx" class="cn.mghio.tx.TransactionManager"/>

  <aop:config>
    <aop:aspect ref="tx">
      <aop:pointcut id="placeOrder" expression="execution(* cn.mghio.service.version5.*.placeOrder(..))"/>
      <aop:before pointcut-ref="placeOrder" method="start"/>
      <aop:after-returning pointcut-ref="placeOrder" method="commit"/>
      <aop:after-throwing pointcut-ref="placeOrder" method="rollback"/>
    </aop:aspect>
  </aop:config>
</beans>

在实现各种 XXXAdvice 之前须要定位到这个 Method,比方以上配置文件中的 startcommitrollback 等办法,为了达到这个指标咱们还须要实现的性能就是依据一个 Bean 名称(比方这里的 tx)定位到指定的 Method,而后通过反射调用这个定位到的办法。实际上也比较简单,这个类命名为 MethodLocatingFactory,依据其性能能够定义出指标 Bean 的名称 targetBeanName、须要定位的办法名称 methodName 以及定位实现后失去的办法 method 这三个属性,整体类图构造如下所示:

依据名称和类型定位到办法次要是在 setBeanFactory() 办法中实现的,前提是对应的指标 Bean 名称和办法名称要设置实现,办法定位的类 MethodLocatingFactory 类的代码如下所示:

/**
 * @author mghio
 * @since 2021-06-06
 */
public class MethodLocatingFactory implements FactoryBean<Method>, BeanFactoryAware {

  private String targetBeanName;

  private String methodName;

  private Method method;

  public void setTargetBeanName(String targetBeanName) {this.targetBeanName = targetBeanName;}

  public void setMethodName(String methodName) {this.methodName = methodName;}

  @Override
  public void setBeanFactory(BeanFactory beanFactory) {if (!StringUtils.hasText(this.targetBeanName)) {throw new IllegalArgumentException("Property'targetBeanName'is required");
    }
    if (!StringUtils.hasText(this.methodName)) {throw new IllegalArgumentException("Property'methodName'is required");
    }

    Class<?> beanClass = beanFactory.getType(this.targetBeanName);
    if (Objects.isNull(beanClass)) {throw new IllegalArgumentException("Can't determine type of bean with name '" + this.targetBeanName);
    }

    this.method = BeanUtils.resolveSignature(this.methodName, beanClass);
    if (Objects.isNull(this.method)) {throw new IllegalArgumentException("Unable to locate method [" + this.methodName + "] on bean ["
          + this.targetBeanName + "]");
    }
  }

  @Override
  public Method getObject() {return this.method;}

  @Override
  public Class<?> getObjectType() {return Method.class;}
}

实现各种不同类型的 Advice

各种不同类型的 AdviceBeforeAdviceAfterAdvice 等)指标都是须要在指定对象的指定办法执行前后按指定秩序执行一些操作(称之为 拦截器 ),比方以上示例中的一种执行秩序为:BeforeAdvice -> placeOrder -> AfterAdvice。这里的一个关键问题就是 如何去实现依照指定秩序的链式调用?,这里先卖个关子,这个问题先放一放等下再介绍具体实现,先来看看要如何定义各种不同类型的 Advice,咱们的 Advice 定义都是扩大自 AOP Alliance 定义的 MethodInterceptor 接口,Advice 局部的外围类图如下:

其实到这里如果有了后面两篇文章(如何实现 AOP(上)、如何实现 AOP(中))的根底了,实现起来就绝对比较简单了,就是在办法执行之前、之后以及产生异样时调用一些特定的办法即可,AbstractAspectJAdvice 类定义了一下公共的属性和办法,外围实现源码如下:

/**
 * @author mghio
 * @since 2021-06-06
 */
public abstract class AbstractAspectJAdvice implements Advice {

  protected Method adviceMethod;
  protected AspectJExpressionPointcut pc;
  protected AopInstanceFactory adviceObjectFactory;

  public AbstractAspectJAdvice(Method adviceMethod, AspectJExpressionPointcut pc, AopInstanceFactory adviceObjectFactory) {
    this.adviceMethod = adviceMethod;
    this.pc = pc;
    this.adviceObjectFactory = adviceObjectFactory;
  }

  @Override
  public Pointcut getPointcut() {return pc;}

  protected void invokeAdviceMethod() throws Throwable {adviceMethod.invoke(adviceObjectFactory.getAspectInstance());
  }

  public Object getAdviceInstance() throws Exception {return adviceObjectFactory.getAspectInstance();
  }

  // omit getter ...

}

有了这个公共形象父类之后其它几个 Advice 的实现就很简略了,AspectJBeforeAdvice 就是在执行拦挡办法之前调用,外围源码如下:

/**
 * @author mghio
 * @since 2021-06-06
 */
public class AspectJBeforeAdvice extends AbstractAspectJAdvice {

  // omit constructor ...

  @Override
  public Object invoke(MethodInvocation mi) throws Throwable {this.invokeAdviceMethod();
    return mi.proceed();}
}

同理,AspectJAfterReturningAdvice 就是在办法失常执行完结后调用,外围源码如下:

/**
 * @author mghio
 * @since 2021-06-06
 */
public class AspectJAfterReturningAdvice extends AbstractAspectJAdvice {

  // omit constructor ...

  @Override
  public Object invoke(MethodInvocation mi) throws Throwable {Object result = mi.proceed();
    this.invokeAdviceMethod();
    return result;
  }
}

剩下的 AspectJAfterThrowingAdvice 想必你曾经猜到了,没错,就是在办法执行过程中产生异样时调用,对应 Java 的异样机制也就是在 try{...}catch{...}catch 中调用,外围源码如下:

/**
 * @author mghio
 * @since 2021-06-06
 */
public class AspectJAfterThrowingAdvice extends AbstractAspectJAdvice {

  // omit constructor ...

  @Override
  public Object invoke(MethodInvocation mi) throws Throwable {
    try {return mi.proceed();
    } catch (Throwable t) {this.invokeAdviceMethod();
      throw t;
    }
  }
}

咱们反对的三种不同的 Advice 曾经定义好了,接下来就是如何组装调用的问题了,同时也解决了 如何去实现依照指定秩序的链式调用?的问题,这里的办法调用咱们也是扩大 AOP Alliance 定义的标准,即办法调用 MethodInvocation 接口。

因为这里的办法调用是基于反射实现的,将该类命名为 ReflectiveMethodInvocation,要应用反射来调用办法,很显然须要晓得指标对象 targetObjecttargetMethod 以及办法参数列表 arguments 等参数,当然还有咱们的拦截器列表(也就是上文定义的 Adviceinterceptors,因为这个是一个相似 自调用的过程,为了判断是否曾经执行实现所有拦截器,还须要记录以后调用拦截器的下标地位 currentInterceptorIndex,当 currentInterceptorIndex 等于 interceptors.size() - 1 时示意所有拦截器都已调用实现,再调用咱们的理论办法即可。外围的类图如下:

其中类 ReflectiveMethodInvocation 的外围源码实现如下,强烈建议大家将 proceed() 办法联合上问定义的几个 Advice 类一起看:

/**
 * @author mghio
 * @since 2021-04-05
 */
public class ReflectiveMethodInvocation implements MethodInvocation {

  protected final Object targetObject;
  protected final Method targetMethod;
  protected Object[] arguments;
  protected final List<MethodInterceptor> interceptors;
  private int currentInterceptorIndex = -1;

  public ReflectiveMethodInvocation(Object targetObject, Method targetMethod,
      Object[] arguments, List<MethodInterceptor> interceptors) {
    this.targetObject = targetObject;
    this.targetMethod = targetMethod;
    this.arguments = arguments;
    this.interceptors = interceptors;
  }

  @Override
  public Object proceed() throws Throwable {
    // all interceptors have been called.
    if (this.currentInterceptorIndex == interceptors.size() - 1) {return invokeJoinpoint();
    }

    this.currentInterceptorIndex++;
    MethodInterceptor methodInterceptor = this.interceptors.get(this.currentInterceptorIndex);
    return methodInterceptor.invoke(this);
  }

  private Object invokeJoinpoint() throws Throwable {return this.targetMethod.invoke(this.targetObject, this.arguments);
  }

  // omit other method ...

}

至此,各种不同类型的 Advice 的外围实现曾经介绍结束,原本打算在这边介绍完 AOP 剩下局部的实现的,然而鉴于文章长度太长,还是放到下一次再开一篇来介绍吧。

总结

本文次要介绍了 AOPXML 配置的 pointcut 解析实现、办法匹配定位以及各种不同类型的 Advice 的实现,特地是 Advice 的实现局部,倡议本人入手实现一版,这样印象会更加粗浅,另源码已上传至 GitHub,可自行下载参考,有任何问题请留言交换探讨。

正文完
 0