Spring AOP 剖析(5)

2023-11-14

在动态代理 和 CGLIB 的支持下, Spring AOP 框架的实现经过了两代。

 

从 Spring AOP 框架第一次发布,到 Spring 2.0 发布之前的 AOP 实现,是 Spring 第一代 AOP 实现。

 

Spring 2.0 发布后的 AOP 实现是第二代。

 

但是,Spring AOP 的底层实现机制一直没有变,唯一改变的,是各种 AOP 概念实现的表现形式以及 Spring AOP  的使用


方式。

 

 

Spring AOP 的底层实现机制

 

 

1. Spring AOP 中的 Joinpoint

 

Spring AOP 中,仅支持方法级别的 Joinpoint ,更确切的说,只支持方法执行 (Method Execution )类型的 Joinpoint

 

虽然 Spring AOP 仅提供方法拦截,但是实际的开发过程中,这已经可以满足 80% 的开发需求了。Spring AOP 之所以

 

如此,主要有以下几个原因。

 

a.  Spring AOP 要提供一个简单而强大的 AOP 框架,并不想因大而全使得框架本身过于臃肿。能够仅付出 20% 的

 

努力,就能够得到 80% 的回报。否则,事倍功半,并不是想看到的结果。

 

b.  对于类中属性 (Field )级别的 Joinpoint ,如果提供这个级别的拦截,那么就破坏了面向对象的封装,而且,完全

 

可以通过 setter 和 getter 方法的拦截达到同样的目的。

 

c.   如果应用需求非常特殊,完全超出了 Spring AOP 提供的那 80% 的需求支持,可以求助于现有其他 AOP 实现产品,

 

如 AspectJ。 目前看来, AspectJ 是 Java 平台对 AOP 支持最完善的产品,同时,Spring AOP 也提供了对 Aspect

 

的支持。

 

 

 

2.  Spring AOP 中的 Pointcut

 

Spring 中以接口 org.springframework.aop.Pointcut 作为其 AOP 框架中的所有 Pointcut 的最顶层抽象。

 

package org.springframework.aop;

public interface Pointcut {

	ClassFilter getClassFilter();

	MethodMatcher getMethodMatcher();

	Pointcut TRUE = TruePointcut.INSTANCE;
}

 

ClassFilter  和  MethodMatcher 分别用于匹配被执行织入操作的对象以及相应的方法。

 

 

a.  ClassFilter

 

ClassFilter 接口的作用是对 Joinpoint 所处的对象进行 Class 级别的类型匹配。

 

package org.springframework.aop;

public interface ClassFilter {

	boolean matches(Class<?> clazz);

	ClassFilter TRUE = TrueClassFilter.INSTANCE;
}

 

当织入的目标对象的 Class 类型与 Pointcut 所规定的类型相符时,matchers 方法将会返回 true,否则返回 false。

 

即意味着不会对这个类型的目标对象进行织入操作。比如,如果仅希望对系统中的 Foo 类型的对象执行织入,则可以

 

package prx.aop.proxy;

import org.springframework.aop.ClassFilter;

public class FooClassFilter implements ClassFilter{

	public boolean matches(Class<?> clazz) {
		return Foo.class.equals(clazz);
	}

}

 

如果类型对所捕捉的 Joinpoint 无所谓,那么 Pointcut 中使用的 ClassFilter 可以直接使用

 

ClassFilter TRUE = TrueClassFilter.INSTANCE 。

 

当 Pointcut 中返回的 ClassFilter 类型为该类型实例时,Pointcut 的匹配将会针对系统中所有类的实例。

 


b.  MethodMatcher

 

MethodMatcher 接口的作用是描述 Pointcut 中 Method Execution 的 Joinpoint 的集合。即对象中的方法是否匹配

 

该 Pointcut 的而需要拦截。也就是 Spring 主要支持的 方法级别的拦截 的依据。

 

package org.springframework.aop;

import java.lang.reflect.Method;


public interface MethodMatcher {

	boolean matches(Method method, Class<?> targetClass);

	boolean isRuntime();

	boolean matches(Method method, Class<?> targetClass, Object[] args);

	MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

}
 

MethodMatcher 通过重载,定义了两个 matches 方法,而这两个方法的分界线就是 isRuntime 方法。

 

两个 mathcers 方法的区别在于:

 

在进行方法拦截的时候,可以选择忽略方法执行时的传入参数,也可以每次都检查方法执行时的传入参数。

 

 

比如:现在要对 登录方法 login(String username, String passwod) 进行拦截

 

1. 只想在 login 方法之前插入计数功能,那么 login 方法的参数对于 Joinpoint 捕捉就是可以忽略的。

 

2. 在用户登录的时候对某个用户做单独处理(拒绝登录 或 给予特殊权限),那么方法的参数在匹配 Joinpoint 时必须要考虑到

 

(1).  StaticMethodMatcher

 

前一种情况下, isRuntime 返回 false , 表示不会考虑具体 Joinpoint 的方法参数, 这种类型的 MethodMatcher

 

称之为 StaticMethodMatcher。因为不用每次都检查参数,那么对于同样的类型的方法匹配结果,就可以在框架内部

 

缓存以提高性能。isRuntime 方法返回 false 表明当前的 MethodMatcher 为 StaticMethodMatcher 的时候, 只有

 

boolean matches(Method method, Class<?> targetClass);

 方法将被执行, 它的匹配结果将会成为其所属的 Pointcut 主要依据。

 

package org.springframework.aop.support;

import java.lang.reflect.Method;

import org.springframework.aop.MethodMatcher;

public abstract class StaticMethodMatcher implements MethodMatcher {

	public final boolean isRuntime() {
		return false;
	}

	public final boolean matches(Method method, Class<?> targetClass, Object[] args) {
		// should never be invoked because isRuntime() returns false
		throw new UnsupportedOperationException("Illegal MethodMatcher usage");
	}

}
 

(2). DynamicMethodMatcher

 

当 isRuntime 方法返回 true 时, 表明 MethodMatcher 将会每次都对方法调用的参数进行匹配检查,这种类型的

 

MethodMatcher 称之为 DynamicMethodMatcher。 因为每次都要对方法参数进行检查,无法对匹配结果进行缓存,

 

所以,匹配效率相对 StatisMethodMatcher 来说要差。

 

大部分情况下, StaticMethodMatcher 已经够用了,最好避免使用 DynamicMethodMatcher 类型。

 

如果一个 MethodMatcher 为 DynamicMethodMatcher , 那么只有 isRuntime 返回 true, 而且

 

matchers(Method method, Class targetClass) 也返回 true 的时候, 三个参数的 matchers 方法将被执行,进行

 

进一步检查匹配条件。否则不会执行 三个参数的 matchers 方法,直接返回 false 了。

 

package org.springframework.aop.support;

import java.lang.reflect.Method;

import org.springframework.aop.MethodMatcher;

public abstract class DynamicMethodMatcher implements MethodMatcher {

	public final boolean isRuntime() {
		return true;
	}

	/**
	 * Can override to add preconditions for dynamic matching. This implementation
	 * always returns true.
	 */
	public boolean matches(Method method, Class<?> targetClass) {
		return true;
	}

}
 

在 MethodMatcher 类型的基础上, Pointcut 可以分为两类, 即 StaticMethodMatcherPointcut 和 DynamicMethodMatcherPointcut。

 

因为 StaticMethodMatcherPointcut 具有明显的性能优势, 所以, Spring 为其提供了更多的支持。

 

Spring 中 Pointcut  局部类结构图

 


 

部分资料说明  AbstractRegexpMethodPointcut 还有个 子类  Perl5RegexpMethodPointcut,但是我在

 

Spring 3.0.5 包中没有看到此类。

 

从上图中看出 Spring 提供了集中常见的 Pointcut 实现 (浅红色的类图 )。


1.  NameMatchMethodPointcut

 

最简单的 Pointcut 实现,根据自身指定的一组方法名称与 Joinpoint 处的方法的名称进行匹配,支持“*”通配符实现简单

 

的模糊匹配。

 

NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();

pointcut.setMappedName("methodName");

pointcut.setMappedNames(new String[]{"methodName1", "methodName2"});

pointcut.setMappedNames(new String[]{"method*", "*Name", "method*Num"});

 

但是, NameMatchMethodPointcut 无法对重载的方法名进行匹配, 因为它仅对方法名匹配,不考虑参数信息。

 


2.  JdkRegexpMethodPointcut

 

StaticMethodMatcherPointcut 中正则表达式的分支实现,基于 JDK1.4 之后引入的 JDK 标准正则表达式。

 

JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();

pointcut.setPattern(".*method.*");

pointcut.setPatterns(new String[]{".*method.*", ".*name.*"});

 

注意:使用正则表达式来匹配对应的 Joinpoint 所处的方法时, 正则表达式的匹配模式必须以匹配整个方法签名的形式


指定,而不能像 NameMatchMethodPointcut 那样仅给出匹配的方法名称。

 

 

package prx.aop.proxy;

public class Foo {
	public void doSomething() {
		
	}
}

 

如果使用正则表达式  .*doS.* 则会匹配 Foo 的 doSomething  方法, 即完整签名:

 

prx.aop.proxy.Foo.doSomething  。 但是如果 Pointcut 使用 doS.* 作为匹配的正则表达式模式,就无法捕捉到

 

Foo 的 doSomething 方法的执行。

 


3.  AnnotationMatchingPointcut

 

根据目标对象中是否存在指定类型的注解来匹配 Joinpoint , 只能使用在 JDK5 或更高版本中, 因为注解是 JDK5

 

发布后才有的。

 

AnnotationMatchingPointcut 根据目标对象中是否存在指定类型的注解来匹配 Joinpoint ,要使用该类型的 Pointcut

 

首先需要声明相应的注解。

 

例如:

 

package prx.aop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassLevelAnnotation {

}

 

package prx.aop.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodLevelAnnotation {

}

 

注解定义中的 @Target 指定注解可以标注的类型。 ClassLevelAnnotation用于类层次,MethodLevelAnnotation

 

用于方法层次。

 

不同的 AnnotationMatchingPointcut 的定义方式会产生不同的匹配行为。

 

//仅指定类级别的注解, 标注了 ClassLevelAnnotation 注解的类中的所有方法执行的时候,将全部匹配。
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class);

//还可以使用静态方法创建 pointcut 实例
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);


//仅指定方法级别的注解,标注了 MethodLeavelAnnotaion 注解的方法(忽略类匹配)都将匹配
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(MethodLevelAnnotation.class);


//同时限定类级别和方法级别的注解,只有标注了 ClassLevelAnnotation 的类中 同时标注了 MethodLevelAnnotation 的方法才会匹配
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class, MethodLevelAnnotation.class);

 

 讲了这么多,比较空泛了, 来个简单的实际例子看下: (注解的定义延用上面的代码)

 

package prx.aop.annotation;

@ClassLevelAnnotation
public class TargetObject {

	@MethodLevelAnnotation
	public void method1() {
		System.out.println("target : method1");
	}
	
	public void method2() {
		System.out.println("target : method2");
	}
}
package prx.aop.annotation;

import java.lang.reflect.Method;

import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;

public class Client {

	public static void main(String[] args) {	
		//pointcut 定义, 匹配方式可以按上面的说明修改,  这里是注解类的所有方法都匹配
		AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);
		
		// advice 定义, 根据前面的介绍知道 这个是 横切逻辑的定义, 这里是 方法执行前插入横切逻辑
		BeforeAdvice advice = new MethodBeforeAdvice() {
			public void before(Method method, Object[] args, Object target) throws Throwable {
				System.out.println(target.getClass().getSimpleName() + ":" + method.getName() + " - before logic ");
			}	
		};
		
		// Spring 中的 Aspect , pointcut 和 advice 的封装类
		DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
		advisor.setPointcut(pointcut);
		advisor.setAdvice(advice);
		
		// Spring 基本织入器 weaving 和 weaver
		ProxyFactory weaver = new ProxyFactory();
		weaver.setTarget(new TargetObject());	//指定代理目标对象
		weaver.addAdvisor(advisor);				//指定 Aspect
		
		Object proxyObject = weaver.getProxy();	//生成代理对象 (这里没接口, Spring 使用 CGLIB 创建子类)
		
		((TargetObject) proxyObject).method1();
		((TargetObject) proxyObject).method2();
	}
}
 

 


4.   ComposablePointcut

 

ComposablePointcut 是 Spring AOP 提供的可以进行 Pointcut 逻辑运算的 Pointcut 实现, 它可以进行 Pointcut

 

之间的 “并” 以及 “交” 运算。

 

ComposablePointcut pointcut1 = new ComposablePointcut(classFilter1, methodMatcher1);
ComposablePointcut pointcut2 = new ComposablePointcut(classFilter2, methodMatcher2);

//求并集
ComposablePointcut unitedPointcut 		= pointcut1.union(pointcut2);
//求交集
ComposablePointcut intersectionPointcut	= pointcut1.intersection(unitedPointcut);

assertEquals(pointcut1, intersectionPointcut);

 

同时, Spring AOP 还提供了 工具类: org.springframework.aop.support.Pointcuts

 

Pointcut pointcut1 = ...;
Pointcut pointcut2 = ...;

//求并集
Pointcut unitedPointcut = Pointcuts.union(pointcut1, pointcut2);
//求交集
Pointcut intersectionPointcut = Pointcuts.intersection(pointcut1, unitedPointcut);

assertEquals(pointcut1, intersectionPointcut);
 


5.   ControlFlowPointcut

 

非常有个性的 Pointcut 类型, 不是很常用。 指定只有当 Joinpoint 指定的某个方法 在 某个特定的 类中被调用时,才

 

对其进行拦截。而一般情况是,Joinpoint  指定的方法,无论被谁调用,都会被拦截。

 

package prx.aop.controlflow;

public class TargetObject {

	public void method1() {
		System.out.println("TargetObject : method1");
	}
}

 

package prx.aop.controlflow;

public class TargetCaller {
	
	private TargetObject target;
	
	public void callMethod() {
		target.method1();
	}
	
	public void setTarget(TargetObject target) {
		this.target = target;
	}
}

 

package prx.aop.controlflow;

import java.lang.reflect.Method;

import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.ControlFlowPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;

public class Client {

	public static void main(String[] args) {	
		//该 Pointcut 表示 Joinpoint 指定的方法 只有在 TargetCaller 类中被调用,才能拦截织入横切逻辑
		ControlFlowPointcut pointcut = new ControlFlowPointcut(TargetCaller.class);
		
		// advice 定义, 根据前面的介绍知道 这个是 横切逻辑的定义, 这里是 方法执行前插入横切逻辑
		BeforeAdvice advice = new MethodBeforeAdvice() {
			public void before(Method method, Object[] args, Object target) throws Throwable {
				System.out.println(target.getClass().getSimpleName() + ":" + method.getName() + " - before logic ");
			}	
		};
		
		// Spring 中的 Aspect , pointcut 和 advice 的封装类
		DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
		advisor.setPointcut(pointcut);
		advisor.setAdvice(advice);
		
		// Spring 基本织入器 weaving 和 weaver
		ProxyFactory weaver = new ProxyFactory();
		weaver.setTarget(new TargetObject());	//指定代理目标对象
		weaver.addAdvisor(advisor);				//指定 Aspect
		
		Object proxyObject = weaver.getProxy();	//生成代理对象 (这里没接口, Spring 使用 CGLIB 创建子类)
	
		//直接调用 method1 不会触发横切逻辑执行
		((TargetObject) proxyObject).method1();

		System.out.println("-----------------");
		
		//advice的逻辑在这里才能被触发执行
		//因为 TargetCaller 的 callMethod() 将调用 method1
		TargetCaller caller = new TargetCaller();
		caller.setTarget((TargetObject)proxyObject);
		caller.callMethod();

	}
}

 

如果在 ControlFlowPointcut 的构造方法中单独指定 Class 类型的参数,如上面的例子,那么 ControlFlowPointcut

 

将尝试匹配指定的 Class 中声明的所有方法,跟目标对象的 Joinpoint 处的方法流程组合。 所以,如果是想要做到

 

“只有 TargetCaller 类的 callMethod 方法调用 TargetObject.method1() 才拦截,而 TargetCaller 的其他方法

 

全都忽略” 的话,可以在构造时,传入第二个参数

 

ControlFlowPointcut pointcut = new ControlFlowPointcut(TargetCaller.class, "callMethod");

 

因为 ControlFlowPointcut 类型的 Pointcut 需要在运行期间检查程序的调用栈,而且每次方法调用都需要检查,所以


性能比较差,应该尽量避免使用。

 

 

太长了。。。下回待续。O(∩_∩)O~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring AOP 剖析(5) 的相关文章

随机推荐

  • 如何对List(Map)进行Stream排序

    public void sortLastOnlineList CloudRInfoPo cloudRInfoPo List
  • 【JAVA】使用intellij IDEA将项目打包为jar包

    当你有一个能正常编译的项目 以springboot为例 有两步步骤 打包配置 打包 一 打包配置 1 点击右上角快捷按钮 文件 gt 项目结构 打开项目结构设置 2 项目结构 gt Artifacts 如图所示选择 3 在Create JA
  • DS18B20温度传感器模块介绍及与USART HMI通信

    一 DS18B20温度传感器 DS18B20是常用的数字温度传感器 其输出的是数字信号 具有体积小 硬件开销低 抗干扰能力强 精度高的特点 DS18B20数字温度传感器接线方便 封装成后可应用于多种场合 如管道式 螺纹式 磁铁吸附式 不锈钢
  • 算法分析与设计编程题 动态规划

    矩阵连乘 题目描述 解题代码 void printOptimalParens vector
  • INSTANCE 2022数据集

    论文链接 https arxiv org abs 2301 03281 数据集链接 Home Grand Challenge github baseline GitHub PerceptionComputingLab INSTANCE202
  • 陷波器介绍_50Hz工频信号陷波器设计

    文章目录 学习目标 基本概念 基本原理 参数的具体计算及选择 具体计算 陷波器的意义 学习目标 了解陷波器的基本概念 掌握50HZ工频陷波器的基本电路图 基本概念 提示 这里可以添加要学的内容 例如 1 陷波器的基本概念 陷波器是一种谐振电
  • Java中的 + 运算符和 += 运算符

    通过几个小实例来理解Java中的 运算符 首先运算符都要从左往右进行计算 字符串旁边的 号的含义是拼接 字符旁边的 号的含义是ASCII码相加 System out println a System out println a 1 a是ch
  • 第十章 Oracle恢复内部原理(各式各样的恢复特性)

    10 1 并行恢复 v7 1 并行恢复的目标是用计算和I O的并行机制减少崩溃恢复 单实例恢复和介质恢复的时间 当多个磁盘上多个数据文件同时进行恢复时能有效的降低恢复时间 10 1 1 并行恢复架构 并行恢复分区做两件事 1 读重做日志 2
  • spring BeanCreationException

    一般出现这样的错误 日志打印很长 稍微不注释加上失去耐心 就会查询问题很久 spring一般报错误都是由上到下的进行提示 比如a调用b b调用c c调用d d有问题 一般看到的日志时a b c d这样大概的顺序描述 例如 Caused by
  • pip install -r requirements.txt出现错误时........

    只需要把pip install r requirements txt后加入替换网址即可 安装需要的库的时候建议使用清华源 这样更快 使用下面的指令就可以使用清华源下载了 pip install r requirements txt i ht
  • Django 快速搭建博客 第三节(数据库表设计)

    上一节我们已经能在pycharm下新建了blog app了 这个时候 我们需要设计一下博客的数据库设计 关于数据库表的设计 作为新手的我们并不需要要求懂太多稍微的懂一些也就可以了 毕竟数据库也是需要有一定的功底的 这里我们依据博客学习 将博
  • 多线程操作同一个变量

    在java线程并发处理中 有一个关键字volatile的使用目前存在很大的混淆 以为使用这个关键字 在进行多线程并发处理的时候就可以万事大吉 Java语言是支持多线程的 为了解决线程并发的问题 在语言内部引入了 同步块 和 volatile
  • Python算法工程师:心中无码便是高清,马赛克“脑补”算法 PULSE

    1 万恶马赛克 万恶的马赛克 是阻碍人类进步的绊脚石 马赛克 脑补 算法 PULSE 助你图片模糊变高清 这是杜克大学近期的一项研究 将模糊人脸秒变高清 PULSE 算法目前只支持人脸的马赛克 去除 因为训练数据都是人脸 也就是说 脑补 其
  • 华为X系列服务器,华为X系列高密服务器产品介绍.pptx

    华为X系列高密服务器产品介绍 目标 华为高密服务器总览X6000服务器介绍X8000服务器介绍 计算面临的挑战 云计算 IT面临的挑战 华为服务器家族 华为高密服务器总览X6000服务器介绍X6000服务器简介X6000服务器硬件结构X60
  • MySQL 视图(详解)

    文章目录 一 视图概念 使用视图的原因 二 创建视图 1 基本语法 2 创建基于单表的视图 实例 1 实例 2 3 创建基于多表的视图 实例 3 4 查询视图 实例 4 三 查看视图 1 查询表 包括view 2 查询视图 四 修改视图 1
  • 【Node】使用Node.js连接数据库时报错客户端不支持服务器请求的身份验证协议

    使用Node js连接数据库时报错 Error ER NOT SUPPORTED AUTH MODE Client does not support authentication protocol requested by server c
  • 嗯… 无法访问此页面 www.bing.com 花了太长时间进行响应解决办法

    从昨天开始 Microsoft Edge浏览器在搜索栏输入中文后就无法响应 但是网络连接是好的 防火墙也没有设置过 问题见下图 点击运行Windows网络诊断 如下图 检测完成后 只是说你的计算机配置似乎是正确的 但该设备或资源 www b
  • 微信小程序animation动画,微信小程序animation动画无限循环播放

    需求是酱紫的 页面顶部的喇叭通知 内容不固定 宽度不固定 就是做走马灯 轮播 效果 从左到右的走马灯 轮播 每播放一遍暂停 1500ms 2000ms 刚开始想的是 css 的 position relative animation 如果宽
  • 自定义一个VideoCapturer(WebRTC)用于获取大疆无人机实时视频

    WebRTC做大疆无人机直播 大疆带屏遥控器有直播功能 用的是rtmp 但是延时有点大 所以在遥控器里安装自己的软件 用webrtc来做一个无人机视频实时传输 需要自定义一个VideoCapturer来获取无人机视频封装成便于webrtc使
  • Spring AOP 剖析(5)

    在动态代理 和 CGLIB 的支持下 Spring AOP 框架的实现经过了两代 从 Spring AOP 框架第一次发布 到 Spring 2 0 发布之前的 AOP 实现 是 Spring 第一代 AOP 实现 Spring 2 0 发