如何实现一个简易版的 Spring - 如何实现 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 实现的,类图结构如下:

pointcut-implemented.png

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

/**

  • @author mghio
  • @since 2021-06-06
    */
    public class AspectJExpressionPointcut implements Pointcut, MethodMatcher {

private static final Set 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" ?>

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

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>

在实现各种 XXXAdvice 之前需要定位到这个 Method,比如以上配置文件中的 start、commit、rollback 等方法,为了达到这个目标我们还需要实现的功能就是根据一个 Bean 名称(比如这里的 tx)定位到指定的 Method,然后通过反射调用这个定位到的方法。实际上也比较简单,这个类命名为 MethodLocatingFactory,根据其功能可以定义出目标 Bean 的名称 targetBeanName、需要定位的方法名称 methodName 以及定位完成后得到的方法 method 这三个属性,整体类图结构如下所示:

method-locating.png

根据名称和类型定位到方法主要是在 setBeanFactory() 方法中完成的,前提是对应的目标 Bean 名称和方法名称要设置完成,方法定位的类 MethodLocatingFactory 类的代码如下所示:

/**

  • @author mghio
  • @since 2021-06-06
    */
    public class MethodLocatingFactory implements FactoryBean, 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
各种不同类型的 Advice(BeforeAdvice、AfterAdvice 等)目标都是需要在指定对象的指定方法执行前后按指定次序执行一些操作(称之为 拦截器),比如以上示例中的一种执行次序为:BeforeAdvice -> placeOrder -> AfterAdvice。这里的一个关键问题就是如何去实现按照指定次序的链式调用?,这里先卖个关子,这个问题先放一放等下再介绍具体实现,先来看看要如何定义各种不同类型的 Advice,我们的 Advice 定义都是扩展自 AOP Alliance 定义的 MethodInterceptor 接口,Advice 部分的核心类图如下:

advice-implemented.png

其实到这里如果有了前面两篇文章(如何实现 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;
}
}
}
USB Microphone https://www.soft-voice.com/
Wooden Speakers https://www.zeshuiplatform.com/
亚马逊测评 www.yisuping.cn
深圳网站建设www.sz886.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值