前言
通过前面两篇,我们已经完成了 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("出现异常");
}
}
测试:
项目跑起来后,访问请求,查看控制台:
很明显,已经成功织入到业务中了。