Spring 深度学习 — 高仿 Spring(AOP)

前言

通过前面两篇,我们已经完成了 IOC/DI、MVC的简单设计,这一篇来学习 AOP 的设计思想。

一、AOP 顶层设计

aop不用过多解释,面向切面编程,特指的是一个面,针对某一个面进行相关处理,如我们常见的日志切面、事务切面等,在开发工作中也经常见到。下面我们来模仿 Spring,写自己的aop 切面。

基本配置

首先,来定义配置类。在使用 aop 的时候,通过会定义好切面的一些属性,比如 pointCut、before、after... 等:

@Data
public class AopConfig {

	private String pointCut;
	private String aspectClass;
	private String aspectBefore;
	private String aspectAfter;
	private String aspectAfterThrow;
	private String aspectAfterThrowingName;
}

代理类配置

通过前面的 IOC 部分,知道了 bean 的初始化过程是利用反射将class 进行了实例化保存起来,执行的时候,也是通过反射进行执行的,那么好像跟 aop 没有任何关系?是的,没错,如果不启用切面的话,确实没有关系,但是,当我们启动切面编程的时候(xml配置中,会有一个对应的配置,在 springboot 中也有一个注解,好像是@EnableAspect,具体记不清了),这个时候,就需要让它们之间发生关系,所以aop 中使用了代理类,目前是两种方式:JDK 动态代理和 CgLib动态代理。

 这里,以 JDK 的动态代理为例:

public class JdkDynamicAopProxy implements InvocationHandler {
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		return null;
	}
}

aop 配置解析类

在 IOC 实例化的时候,判断当前实例化的 bean 是否满足 aop 的切面,是否需要织入 aop 相关的业务,所以需要来解析我们配置好的aop 配置:

public class AdvisedSupport {

	/**
	 * aop 的配置
	 */
	private AopConfig config;
	/**
	 * bean instance
	 */
	private Object target;
	/**
	 * bean class
	 */
	private Class targetClass;
	/**
	 * pointcut 正则
	 */
	private Pattern pointCutClassPattern;

	public AdvisedSupport(AopConfig config) {
		this.config = config;
	}
}

定义通知类

满足条件的bean,在执行的时候,通过代理类进行反射执行,执行时,会拿到对应的通知类,进行相关的切面消息通知:

@Data
public class Advice {

	/**
	 * 通知 instance
	 */
	private Object aspect;
	/**
	 * 通知 method
	 */
	private Method adviceMethod;
	/**
	 * 通知 异常
	 */
	private String throwName;

	public Advice(Object aspect, Method adviceMethod) {
		this.aspect = aspect;
		this.adviceMethod = adviceMethod;
	}
}

二、AOP 与 IOC挂钩

上面说到了,aop真正切入的时机,就是在 IOC bean 实例化的时候,那么我们来加入 aop 相关的业务:

private Object instantiateBean(String beanName, BeanDefinition beanDefinition) {
	String className = beanDefinition.getBeanClassName();
	Object instance = null;
	try {
		// 先从缓存中取,如果不存在,再进行实例化
		if (this.factoryBeanObjectCache.containsKey(beanName)) {
			instance = this.factoryBeanObjectCache.get(beanName);
		} else {
			Class<?> clazz = Class.forName(className);
			instance = clazz.newInstance();
			//==================AOP开始=========================
			//如果满足条件,就直接返回Proxy对象
			//1、加载AOP的配置文件
			AdvisedSupport config = instantionAopConfig(beanDefinition);
			config.setTargetClass(clazz);
			config.setTarget(instance);
			//判断规则,要不要生成代理类,如果要就覆盖原生对象
			//如果不要就不做任何处理,返回原生对象
			if(config.pointCutMath()){
				instance = new JdkDynamicAopProxy(config).getProxy();
			}
			//===================AOP结束========================
			this.factoryBeanObjectCache.put(beanName, instance);
		}
	} catch (Exception e) {
		e.printStackTrace();
	}
	return instance;
}

读取在配置文件中配置好的内容:

private AdvisedSupport instantionAopConfig(BeanDefinition beanDefinition) {
	AopConfig config = new AopConfig();
	config.setPointCut(this.reader.getConfig().getProperty("pointCut"));
	config.setAspectClass(this.reader.getConfig().getProperty("aspectClass"));
	config.setAspectBefore(this.reader.getConfig().getProperty("aspectBefore"));
	config.setAspectAfter(this.reader.getConfig().getProperty("aspectAfter"));
	config.setAspectAfterThrow(this.reader.getConfig().getProperty("aspectAfterThrow"));
	config.setAspectAfterThrowingName(this.reader.getConfig().getProperty("aspectAfterThrowingName"));
	return new AdvisedSupport(config);
}

完善后的 AdvisedSupport 类:

public class AdvisedSupport {

	/**
	 * aop 的配置
	 */
	private AopConfig config;
	/**
	 * bean instance
	 */
	private Object target;
	/**
	 * bean class
	 */
	private Class targetClass;
	/**
	 * pointcut 正则
	 */
	private Pattern pointCutClassPattern;

	private Map<Method, Map<String, Advice>> methodCache;

	public AdvisedSupport(AopConfig config) {
		this.config = config;
	}

	public void setTarget(Object target) {
		this.target = target;
	}

	public void setTargetClass(Class targetClass) {
		this.targetClass = targetClass;
		parse();
	}

	/**
	 * 给ApplicationContext首先IoC中的对象初始化时调用,决定要不要生成代理类的逻辑
	 *
	 * @return
	 */
	public boolean pointCutMath() {
		return pointCutClassPattern.matcher(this.targetClass.toString()).matches();
	}

	/**
	 * 解析配置文件的方法
	 */
	private void parse() {
		//把Spring的Excpress变成Java能够识别的正则表达式
		String pointCut = config.getPointCut()
				.replaceAll("\\.", "\\\\.")
				.replaceAll("\\\\.\\*", ".*")
				.replaceAll("\\(", "\\\\(")
				.replaceAll("\\)", "\\\\)");
		//保存专门匹配Class的正则
		String pointCutForClassRegex = pointCut.substring(0, pointCut.lastIndexOf("\\(") - 4);
		pointCutClassPattern = Pattern.compile("class " + pointCutForClassRegex.substring(pointCutForClassRegex.lastIndexOf(" ") + 1));
		//享元的共享池
		methodCache = new HashMap<Method, Map<String, Advice>>();
		//保存专门匹配方法的正则
		Pattern pointCutPattern = Pattern.compile(pointCut);
		try {
			Class aspectClass = Class.forName(this.config.getAspectClass());
			Map<String, Method> aspectMethods = new HashMap<String, Method>();
			for (Method method : aspectClass.getMethods()) {
				aspectMethods.put(method.getName(), method);
			}

			for (Method method : this.targetClass.getMethods()) {
				String methodString = method.toString();
				if (methodString.contains("throws")) {
					methodString = methodString.substring(0, methodString.lastIndexOf("throws")).trim();
				}

				Matcher matcher = pointCutPattern.matcher(methodString);
				if (matcher.matches()) {
					Map<String, Advice> advices = new HashMap<String, Advice>();
					if (!(null == config.getAspectBefore() || "".equals(config.getAspectBefore()))) {
						advices.put("before", new Advice(aspectClass.newInstance(), aspectMethods.get(config.getAspectBefore())));
					}
					if (!(null == config.getAspectAfter() || "".equals(config.getAspectAfter()))) {
						advices.put("after", new Advice(aspectClass.newInstance(), aspectMethods.get(config.getAspectAfter())));
					}
					if (!(null == config.getAspectAfterThrow() || "".equals(config.getAspectAfterThrow()))) {
						Advice advice = new Advice(aspectClass.newInstance(), aspectMethods.get(config.getAspectAfterThrow()));
						advice.setThrowName(config.getAspectAfterThrowingName());
						advices.put("afterThrow", advice);
					}
					//跟目标代理类的业务方法和Advices建立一对多个关联关系,以便在Porxy类中获得
					methodCache.put(method, advices);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public Class getTargetClass() {
		return targetClass;
	}

	public Object getTarget() {
		return target;
	}

	/**
	 * 根据一个目标代理类的方法,获得其对应的通知
	 *
	 * @param method
	 * @param o
	 * @author: <a href="568227120@qq.com">heliang.wang</a>
	 * @date: 2022/5/27 4:19 下午
	 * @return: java.util.Map<java.lang.String, com.mmt.imitate.spring.framework.aop.aspect.Advice>
	 */
	public Map<String, Advice> getAdvices(Method method, Object o) throws Exception {
		//享元设计模式的应用
		Map<String, Advice> cache = methodCache.get(method);
		if (null == cache) {
			Method m = targetClass.getMethod(method.getName(), method.getParameterTypes());
			cache = methodCache.get(m);
			this.methodCache.put(m, cache);
		}
		return cache;
	}

}

完善后的 JdkDynamicAopProxy 类:

public class JdkDynamicAopProxy implements InvocationHandler {

	private AdvisedSupport config;

	public JdkDynamicAopProxy(AdvisedSupport config) {
		this.config = config;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Map<String, Advice> advices = config.getAdvices(method, null);

		Object returnValue;
		try {
			// 反射执行 织入的代理类 before
			invokeAdivce(advices.get("before"));
			// 反射执行 业务方法
			returnValue = method.invoke(this.config.getTarget(), args);
			// 反射执行 织入的代理类 after
			invokeAdivce(advices.get("after"));
		} catch (Exception e) {
			// 反射执行 织入的代理类 afterThrow
			invokeAdivce(advices.get("afterThrow"));
			throw e;
		}
		return returnValue;
	}

	/**
	 * 执行代理方法
	 *
	 * @param advice
	 * @author: <a href="568227120@qq.com">heliang.wang</a>
	 * @date: 2022/5/27 4:20 下午
	 * @return: void
	 */
	private void invokeAdivce(Advice advice) {
		try {
			advice.getAdviceMethod().invoke(advice.getAspect());
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 生成代理类
	 *
	 * @param
	 * @author: <a href="568227120@qq.com">heliang.wang</a>
	 * @date: 2022/5/27 4:18 下午
	 * @return: java.lang.Object
	 */
	public Object getProxy() {
		return Proxy.newProxyInstance(this.getClass().getClassLoader(), this.config.getTargetClass().getInterfaces(), this);
	}
}

三、织入业务

        下面来测一把,试试我们自己写的 aop 是否生效了,以一个日志切面为例子:

在 application.properties 文件,加入 aop 的相关配置:

# aop 切面
pointCut=public .* com.mmt.imitate.demo.service..*Service..*(.*)
#aop 织入的业务类
aspectClass=com.mmt.imitate.demo.aspect.LogAspect
# 执行业务之前
aspectBefore=before
# 执行业务之后
aspectAfter=after
# 抛出异常时的处理
aspectAfterThrow=afterThrowing
# 异常类
aspectAfterThrowingName=java.lang.Exception

 定义日志切面类:

@Slf4j
public class LogAspect {

	/**
	 * 在调用一个方法之前,执行before方法
	 *
	 * @param
	 * @author: <a href="568227120@qq.com">heliang.wang</a>
	 * @date: 2022/5/27 4:26 下午
	 * @return: void
	 */
	public void before() {
		log.info("Invoker Before Method!!!");
	}

	/**
	 * 在调用一个方法之后,执行after方法
	 */
	public void after() {
		log.info("Invoker After Method!!!");
	}

	/**
	 * 在抛出异常之后,执行afterThrowing方法
	 */
	public void afterThrowing() {
		log.info("出现异常");
	}
}

测试:

项目跑起来后,访问请求,查看控制台:

很明显,已经成功织入到业务中了。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值