1. Spring AOP实现机制

Spring采用动态代理机制和字节码生成技术实现AOP。与最初的AspectJ采用编译器将横切逻辑织入目标对象不同,动态代理机制和字节码生成都是在运行期间为目标对象生成一个代理对象,而将横切逻辑织入到这个代理对象中,系统最终使用的是织入了横切逻辑的代理对象,而不是真正的目标对象。

1.1 动态代理

动态代理机制的实现主要由一个类和一个接口组成,即java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。下面让我们看一下怎么用动态代理来实现“request服务时间控制”功能。


public class RequestCtrlInvocationHandler implements InvocationHandler {
private static final Log logger = LogFactory.getLog(RequestCtrlInvocationHandler.class);
// 将被目标对象通过构造方法加入到代理对象中
private Object target;
public RequestCtrlInvocationHandler(Object target) {
this.target = target;
} public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
if (method.getName().equals("request")) {
TimeOfDay startTime = new TimeOfDay(0, 0, 0);
TimeOfDay endTime = new TimeOfDay(5, 59, 59);
TimeOfDay currentTime = new TimeOfDay();
if (currentTime.isAfter(startTime) && currentTime.isBefore(endTime)) {
logger.warn("service is not available now.");
return null;
}
return method.invoke(target, args);
}
return null;
}
}

然后我们就可以使用Proxy类根据RequestCtrlInvocationHandler的逻辑,为ISubject和IRequestable两种类型生成相应的代理对象实例。

//第一个参数是目标对象的类加载器,第二个参数是需要增强的接口,这里可直接使用ISubject,最后一个参数是横切逻辑的类,并且需要持有一个目标对象实例
ISubject subject = (ISubject)Proxy.newProxyInstance(ProxyRunner.class.getClassLoader(), new Class[]{ISubject.class}, new RequestCtrlInvocationHandler(new SubjectImpl()));
subject.request();

即使还有更多的目标对象类型,只要它们的织入的横切逻辑都相同,用RequestCtrlInvocationHandler一个类并通过Proxy为它们生成相应的动态代理实例就可以满足要求。当Proxy动态生成的代理对象上相应的接口方法被调用时,对应的InvocationHandler就会拦截相应的方法调用,并进行相应处理。

1.2 动态字节码生成

动态代理虽好,但是只能对实现了相应接口的类使用,如果某个类没有实现任何的接口,就无法使用动态代理为其生成相应的动态代理对象。这时候就轮到我们的动态字节码生成出场了。

使用动态字节码生成技术扩展对象行为的原理,我们可以为其生成相应的子类,而子类可以通过重写来扩展父类的行为,只要将横切逻辑的实现放到子类中,然后让系统使用扩展后的目标对象的子类,就可以达到与代理模式同样的效果了。

但是,使用继承的方式来扩展对象定义,要借助于CGLIB这样的动态字节码生成库,在系统运行期间动态地为目标对象生成相应的扩展子类。

2. Spring AOP中的概念实体

2.1 Jointpoint

我们在前面提到,AOP的Jointpoint可以有许多类型,但是在Spring AOP中,仅支持方法级别的Jointpoint。更确切地说,只支持方法执行类型的Jointpoint。

2.2 Pointcut

Spring以接口定义org.springframework.aop.Pointcut作为其AOP框架中所有Pointcut的最顶层抽象,该接口定义了两个方法用来帮助捕捉系统中的相应Jointpoint,并提供了一个TruePointcut类型实例(何为捕捉?如果我寻找到某个目标对象符合Pointcut的定义,则对其进行织入)。如果Pointcut类型为TruePointcut,默认会对系统中的所有对象,以及对象上所有被支持的Jointpoint进行匹配。接口定义如下所示:

public interface Pointcut {
Pointcut TRUE = TruePointcut.INSTANCE;
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}

ClassFilterMethodMatcher分别用于匹配将被执行织入操作的对象以及相应的方法。ClassFilter的作用是对Jointpoint所处的对象进行Class级别的类型匹配。其定义如下:

public interface ClassFilter {
ClassFilter TRUE = TrueClassFilter.INSTANCE;
boolean matches(Class<?> var1);
}

当织入的目标对象的Class类型与Pointcut规定的类型相符时返回true,反之为false,即意味着不会对这个类型的对象做织入操作。举个栗子,我们仅希望对系统中Foo类型的类执行织入,则可以如下定义ClassFilter

public class FooClassFilter {
public boolean matches(Class clazz) {
return Foo.class.equals(clazz);
}
}

相比于ClassFilter的简单定义,MethodMatcher则要复杂地多。MathodMatcher实现的就是方法级别的拦截,定义如下:

public interface MethodMatcher {
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
boolean matches(Method var1, Class<?> var2);
boolean isRuntime();
boolean matches(Method var1, Class<?> var2, Object... var3);
}

MethodMatcher通过重载,定义了两个matches方法,而这两个方法的分界线是isRuntime()方法。在对对象具体方法进行拦截的时候,可以忽略调用者传入的参数,也可以检查这些方法的调用参数,以强化拦截条件。举个例子:如果只想在login方法之前插入计数功能,那么login方法的参数对于Jointpoint捕捉就是可以忽略的;如果想在用户登陆的时候对某个用户进行单独的处理,如不让其登陆或者给予特殊权限,那么这个方法的参数就是在匹配Jointpoint的时候必须要考虑的。

在前一种情况下,isRuntime()返回false,表示不会考虑具体Jointpoint的方法参数,这种类型的MethodMatcher成为StaticMethodMatcher。因为不用每次都检查参数,对于同样类型的方法匹配结果,就可以在框架内部缓存以提高性能,Spring对该种类型的Pointcut提供了更多的支持。后一种情况下,isRuntime()返回true,表明该MethodMatcher将会每次都对方法调用的参数进行匹配检查,称之为DynamicMathodMatcher

常见的Pointcut有NameMatchMethodPointcutJdkRegexMethodPointAnnotationMatchingPointcut,在此简要介绍一下NameMatchMethodPointcut

NameMatchMethodPointcut是最简单的Pointcut实现,可以根据自身指定的一组方法名称与Jointpoint处方法的方法名称进行匹配,比如:

NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedName("matches");
// 或者传入多个方法名
pointcut.setMappedName(new String[]{"matches", "isRunTime"});

我们来看一下NameMatchMethodPointcut内部匹配方法matches()的实现:

    // method是各个类中,用来被比较匹配的方法对象,如果与我们传入的将要拦截的方法名相同或满足通配符,则匹配成功
public boolean matches(Method method, Class<?> targetClass) {
// mappedNames是我们传入的需要匹配的方法名
Iterator var3 = this.mappedNames.iterator();
String mappedName;
do {
if (!var3.hasNext()) {
return false;
}
mappedName = (String)var3.next();
// equals()方法表示字符串匹配,后面的isMatch()方法表示用通配符进行模糊匹配
} while(!mappedName.equals(method.getName()) && !this.isMatch(method.getName(), mappedName));
return true;
}

2.3 Advice

Advice实现了将被织入到Pointcut规定到Jointpoint处的横切逻辑。在Spring中,Advice按照其自身实例(instance)能否在目标对象类的所有实例中共享这一标准,可以划分为两大类,即per-class类型和per-instance

2.3.1 per-class类型的Advice

per-class类型的Advice是指,该类型的Advice的实例可以在目标对象类的所有实例之间共享。这种类型的Advice通只是提供方法拦截的功能,不会为目标对象类保存任何状态或者添加新的特性。

  • Before Advice:Before Advice所实现的横切逻辑将在相应的Jointpoint之前执行。在Spring中,我们通常只需要实现org.springframework.aop.MethodBeforeAdvice接口即可。该接口定义如下:
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method var1, Object[] var2, Object var3) throws Throwable;
}

我们可以使用Before Advice进行整个系统的某些初始化资源或者其他一些准备性的工作。比如,假设我们的系统需要在某些指定位置生成一些数据文件,创建之前,我们需要检查这些指定位置是否存在,不存在则需要去创建它们。为了避免不必要的代码散落,我们可以为系统中相应目标类提供一个Before Advice,对文件系统的指定路径进行统一的检查或者初始化。代码如下:

public class ResourceSetupBeforeAdvice implements MethodBeforeAdvice {
private Resource resource;
public ResourceSetupBeforeAdvice(Resource resource) {
this.resoource = resource;
}
public void before(Method method, Object[] args, Object target) throws Throwable {
if (!resource.exists()) {
FileUtils.forceMkdir(resouce.getFile());
}
}
}
  • ThrowsAdvice:该接口并未定义任何方法,但是我们的方法定义需要遵循如下规则
void afterThrowing([Method, args, target], ThrowableSubclass)

ThrowAdvice通常用于对系统中特定的异常情况进行监控,以统一的方式对所发生的异常进行处理。

  • AfterReturningAdvice:只有在方法正常返回的情况下,AfterReturningAdvice才会执行,所有并不能用来处理资源清理之类的工作。另外,虽然AfterReturningAdvice可以访问到方法的返回值,但是它并不能改变返回值。其定义如下:
public interface AfterReturningAdvice extends AfterAdvice {
void afterReturning(Object var1, Method var2, Object[] var3, Object var4) throws Throwable;
}
  • AroundAdvice:Spring中没有直接定义对应Around Service的实现接口,而是直接采用AOP Alliance的标准接口,org.aopalliance.intercept.MethodInterceptor,该接口定义如下:
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation var1) throws Throwable;
}

之前提到的几种Advice能完成的事情,对于MethodInterceptor都不在话下!口说无凭,我们来看一个栗子吧!

// 简单的检测系统某些方法的执行性能
public class PerformanceMethodInterceptor implements MethodInterceptor {
private final Log logger = LogFactory.getLog(this.getClass());
// 通过MethodInvocation参数,我们可以控制对相应Jointpoint的拦截行为
public Object invoke(MethodInvocation invocation) throws Throwable {
StopWatch watch = new StopWatch();
try {
watch.start();
// 调用proceed方法可以让程序执行继续沿调用链执行
return invocation.proceed();
} catch (Exception e) {
//TODO: handle exception
} finally {
watch.stop();
if (logger.isInfoEnabled()) {
logger.info(watch.toString());
}
}
}
}

通过MethodInvocation参数,我们可以控制对相应Jointpoint的拦截行为。通过调用MethodIvocationproceed()方法,可以让程序继续沿着调用链传播。如果我们在某一个MethodInterceptor没有调用proceed()方法,那么程序将会在当前MethodInterceptor处短路,同一Jointpoint处的其他MethodInterceptor以及Jointpoint处的方法逻辑将都不会执行!正如在PerformanceMethodInterceptor看到的那样,我们可以在proceed()方法的前后加入相应的逻辑,甚至可以捕获proceed()抛出的异常。现在,你是否理解了为什么MethodInterceptor可以完成其他类型Advice可以完成的任务了?

我们可以直接通过编程的方式来使用该类,如下所示:

PerformanceMethodInterceptor interceptor = new PerformanceMethodInterceptor();
...
// 可以将其添加到相应的Aspect中使用

既然我们使用了Spring框架并且这些Advice实现都是普通的POJO,更多时候会将其集成到IoC容器中,如下:

<bean id="performanceInterceptor" class="...PerformanceMethodInterceptor"/>

2.3.2 per-instance类型的Advice

与per-class类型的Advice不同,per-instance类型的Advice不会再目标类所有对象实例之间共享,而是会为不同的实例对象保存它们各自的状态和相关逻辑。就拿上班族为例,如果员工是一类人的话,那么公司的每一类员工就是员工类的不同对象实例。每个员工上班之前,公司设置了一个per-class类型的Advice进行“上班活动”的一个拦截,即打卡机,所有员工共用一个打卡机。当每个员工进入各自的位置之后,他们就会使用各自的电脑进行工作,而他们各自的电脑就好像per-instance类型的Advice一样,每个电脑保存了每个员工自己的资料。在Spring中,Introduction就是唯一的一种per-instance类型的Advice。以后会专门写一篇文章讲关于Introduction(逃。

2.4 Spring AOP中的Aspect

我们在之前提到过,AOP的概念中,一个Aspect是由多个Pointcut和Advice组成的概念实体。但是在Spring中,Aspect被称为Advisor,它通常只持有一个Pointcut和一个Advice。Spring中的Advisor实现方式可以简单划分为两个分支。一个分支以PointcutAdvisor接口为首,另一个分支以IntroductionAdvisor为首。下图是Advisor家族的UML类图。

2.4.1 PointcutAdvisor家族

实际上,org.springframework.aop.PointcutAdvisor才是真正定义一个Pointcut和一个Advice的Advisor,大部分的Advisor实现全都是PointcutAdvisor的部下。下面我们来看几个常用的PointcutAdvisor实现。

  • DefaultPointcutAdvisor:它是最通用的Advisor类型,除了不能为其指定Introduction类型的Advice之外,剩下任何类型的Pointcut和Advice都可以通过DefaultPointcutAdvisor来使用。我们可以在构造DefaultPointcutAdvisor的时候,就明确指定属于当前DefaultPointcutAdvisor实例的Advisor实例的Point和Advice,也可以在该实例构造完成后再通过set方法设置。代码如下:
Pointcut pointcut = ...;// 任何Pointcut类型
Advice advice = ...;
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
// 或者
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setPointcut(pointcut);
advisor.setAdvice(advice);

但是这个代码不是让你在实际环境中这么用的,如果在Spring的XML配置文件中,我们需要这么做:

<bean id="pointcut" class="...">
...
</bean>
<bean id="advice" class="...">
...
</bean>
<bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut" ref="pointcut"/>
<property name="advice" ref="advice">
</bean>
  • NameMatchMethodPointcutAdvisor是细化后的DefaultPointcutAdvisor,它限定了自身可以使用的Pointcut类型为NameMatchMethodPointcut,并且外部不可更改。关于这个类型的Pointcut上面有介绍,在此不做赘述。可以通过该Advisor公开的setMappedName()setMappedNames()方法设置将被拦截的方法名称(实际上是在操作持有的NameMatchMethodPointcut实例)。
  • RegexMethodPointcutAdvisor:也限定了自身可以使用的Pointcut类型,即只能通过正则表达式为其设置相应的Pointcut。

2.4.2 IntroductionAdvisor分支

IntroductionAdvisorPointcutAdvisor最本质的区别是IntroductionAdvisor只能应用于类级别的拦截,以及只能使用Introduction型的Advice。它只有一个默认的实现类DefaultIntroductionAdvisor,其继承关系也在上面的UML类图中提到。

2.4.3 Ordered的作用

大多数时候,我们的系统中都会有多个横切关注点需要处理,那么系统中就会有多个Advisor存在。当其中某些Advisor的Point匹配了同一个Jointpoint的时候,就会在这同一个Jointpoint处执行多个Advice的逻辑。Spring在处理同一Jointpoint处的多个Advisor时,实际上会按照指定的顺序和优先级来执行它们,如果我们不明确指定各个Advisor的执行顺序,那么Spring会按照他们的声明顺序来应用它们。那么我们如何指定执行的顺序或优先级呢?

在Spring框架中,我们可以让相应的Advisor以及其他顺序紧要的bean实现org.springframework.core.Ordered接口来明确指定相应的顺序号。不过,在UML图中可以看到,各个Advisor的实现类已经默认实现了Ordered接口,我们唯一需要做的就是在配置的时候指定顺序号。如下代码所示:

<bean id="xxxxAdvisor" class="xxxx">
<property name="order" value="1"/>
</bean>

参考资料:《Spring揭秘》王福强

Spring中的AOP(一)的更多相关文章

  1. Spring中的AOP

    什么是AOP? (以下内容来自百度百科) 面向切面编程(也叫面向方面编程):Aspect Oriented Programming(AOP),通过预编译方式和运行期动态代理实现程序功能的统一维护的一种 ...

  2. Spring中关于AOP的实践之概念

    一.什么是AOP AOP:也称作面向切面编程 在分享几个概念执行我想先举个栗子(可能例子举得并不是特别恰当): 1.假如路人A走在大街上,被一群坏人绑架了: 2.警察叔叔接到报警迅速展开行动:收集情报 ...

  3. Spring中的AOP 专题

    Caused by: java.lang.IllegalArgumentException: ProceedingJoinPoint is only supported for around advi ...

  4. spring中的AOP 以及各种通知 配置

    理解了前面动态代理对象的原理之后,其实还是有很多不足之处,因为如果在项目中有20多个类,每个类有100多个方法都需要判断是不是要开事务,那么方法调用那里会相当麻烦. spring中的AOP很好地解决了 ...

  5. Spring学习笔记(四)—— Spring中的AOP

    一.AOP概述 AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善.O ...

  6. 2018.12.24 Spring中的aop演示(也就是运用aop技术实现代理模式)

    Aop的最大意义是:在不改变原来代码的前提下,也不对源代码做任何协议接口要求.而实现了类似插件的方式,来修改源代码,给源代码插入新的执行代码. 1.spring中的aop演示 aop:面向方面编程.不 ...

  7. JavaWeb_(Spring框架)认识Spring中的aop

    1.aop思想介绍(面向切面编程):将纵向重复代码,横向抽取解决,简称:横切 2.Spring中的aop:无需我们自己写动态代理的代码,spring可以将容器中管理对象生成动态代理对象,前提是我们对他 ...

  8. (五)Spring 中的 aop

    目录 文章目录 AOP概念 AOP原理 AOP术语 **`Spring`** 中的 **`aop`** 的操作 使用 `AspectJ` 实现 `aop` 的两种方式 AOP概念 浅理解 aop :面 ...

  9. Spring 中基于 AOP 的 @AspectJ

    Spring 中基于 AOP 的 @AspectJ @AspectJ 作为通过 Java 5 注释注释的普通的 Java 类,它指的是声明 aspects 的一种风格. 通过在你的基于架构的 XML ...

  10. Spring 中基于 AOP 的 XML架构

    Spring 中基于 AOP 的 XML架构 为了使用 aop 命名空间标签,你需要导入 spring-aop j架构,如下所述: <?xml version="1.0" e ...

随机推荐

  1. 上传应用至Google Play 后被重新签名,怎么获取最新的签名信息

    基本签名信息在Google Play 上都能查看到. 快速解决Google+登录和facebook登录的办法: 不用改包名重新创建应用,不用重新打包,不要删除自己的keystore文件,不要重新创建k ...

  2. & 加密

    接口参数中sign加密方式: 1. 签名算法使用SHA256: 2. 服务方和消费方都需要校验签名: 3. 签名生成步骤: 第一步,设所有发送或者接收到的数据为集合M1,将集合M1内非空参数值的参数按 ...

  3. StackOverflow 创始人关于如何高效编程的清单.md

    这是 StackOverflow 联合创始人 Jeff Atwood 注释的十戒.程序员普遍有很强的自尊心,都应该看看本文,打印下来时刻提醒自己. "无我编程"发生在开发阶段,表现 ...

  4. Chisel3 - Tutorial - VendingMachine

    https://mp.weixin.qq.com/s/tDpUe9yhwC-2c1VqisFzMw   演示如何使用状态机.   参考链接: https://github.com/ucb-bar/ch ...

  5. Java实现 LeetCode 670 最大交换(暴力)

    670. 最大交换 给定一个非负整数,你至多可以交换一次数字中的任意两位.返回你能得到的最大值. 示例 1 : 输入: 2736 输出: 7236 解释: 交换数字2和数字7. 示例 2 : 输入: ...

  6. Java实现 LeetCode 508 出现次数最多的子树元素和

    508. 出现次数最多的子树元素和 给出二叉树的根,找出出现次数最多的子树元素和.一个结点的子树元素和定义为以该结点为根的二叉树上所有结点的元素之和(包括结点本身).然后求出出现次数最多的子树元素和. ...

  7. Java实现 LeetCode 10 正则表达式匹配

    10. 正则表达式匹配 给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配. '.' 匹配任意单个字符 '*' 匹配零个或多个前面的那一个元素 所谓匹配, ...

  8. Java实现 蓝桥杯VIP 算法提高 change

    算法提高 change 时间限制:1.0s 内存限制:256.0MB 问题描述 数组A中共有n个元素,初始全为0.你可以对数组进行两种操作:1.将数组中的一个元素加1:2.将数组中所有元素乘2.求将数 ...

  9. Java实现旅行商问题

    1 问题描述 何为旅行商问题?按照非专业的说法,这个问题要求找出一条n个给定的城市间的最短路径,使我们在回到触发的城市之前,对每个城市都只访问一次.这样该问题就可以表述为求一个图的最短哈密顿回路的问题 ...

  10. 解Bug之路-记一次存储故障的排查过程

    解Bug之路-记一次存储故障的排查过程 高可用真是一丝细节都不得马虎.平时跑的好好的系统,在相应硬件出现故障时就会引发出潜在的Bug.偏偏这些故障在应用层的表现稀奇古怪,很难让人联想到是硬件出了问题, ...