SpringAop源码解析

Posted by 皇甫嗷嗷叫 on 09-11,2020

一、拉拉家常

又是一周过去了,不知上周发的关于Spring循环依赖使用的三级缓存你们掌握到什么样子了呢?这周又是一篇深度好文(自夸一下),作者每天下班肝了好几天赶出来的文章,有没有很感动,哈哈(疯狂暗示点个赞)!相信有读者看到了昨天我发的朋友圈,有关Spring AOP的源码级别的注释,那么这个也是本篇文章的主体,不用多说涉及到源码,肯定是一个长文,希望大家有所收获!

说到Spring Aop无论是面试还是开发都是绕不过的一个坎 ,相信不少同学工作中也是经常性的使用AOP去搞一些日志啦权限啦或者校验之类的开发,但是实际上不少同学开发过程中基本都是去网上找一篇帖子,施展CV大法,然后改改就用到生产了!对原理也是基本不了解,让我感触最深的一个事就是,面试的一个小伙子,当我问到AOP使用的是那种代理模式时,面试者给我一个很轻蔑的眼神,来了句使用的Cglib 动态代理,哎,也不怪我筛选掉你不是!

开篇一问:Spring Aop使用的是那种动态代理模式呢?相信你会在图中找到答案!

image-20200808231235002

没错,两种模式都会使用,当存在接口的时候是jdk动态代理,不存在接口的时候是cglib动态代理!对应的实现类是:CglibAopProxyJdkDynamicAopProxy,后续文章会给出介绍!

本篇文章呢,还是会和上一篇文章Spring如何解决循环依赖一样,先自己实现一个Aop动态代理,然后看Spring是如何实现的!

二、自己尝试实现一下

那么,我们再实现AOP的时候,应该怎么去考虑呢?

首先我们肯定要先找到所有的切面,就是我们加了@Aspect注解的那个方法,找到之后,我们再创建bean的时候,先判断是否符合切面,是否需要代理,需要代理就走代理的逻辑,不需要就走自己的逻辑!

1.定义通知类型

首先,我们先定义一个类似于通知类型的注解(对应Spring的前置通知、后置通知等),这里以环绕通知为例,定义一个注解:

/**
 * 模拟一个环绕通知
 * @author huangfu
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAround {
	/**
	 * 要拦截的携带什么注解的方法
	 * @return 返回这个注解的class
	 */
	Class<? extends Annotation> targetClass();
}

里面的属性就是切点,这里以注解为例,意思就是,只要你方法上加了 targetClass设置的注解,那么就会被AOP拦截!

2.定义一个代理链的类

注意:想直接看源码的请跳到第三章!!自己实现的逻辑不短!!!

注意:想直接看源码的请跳到第三章!!自己实现的逻辑不短!!!

注意:想直接看源码的请跳到第三章!!自己实现的逻辑不短!!!

这个类是干嘛的,Spring中采用责任链的设计模式,设计一个方法对应多个切点,这里我们也采用这种设计模式!

package simulation.aop.system;

import lombok.Data;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 代理调用信息  切点代理方法
 * @author huangfu
 */
@Data
public class ProxyChain {
	/**
	 * 切点的方法
	 */
	private Method method;
	/**
	 * 切点的对象
	 */
	private Object target;
	/**
	 * 切点的参数
	 */
	private Object[] args;

	public ProxyChain(Method method, Object target, Object... args) {
		this.method = method;
		this.target = target;
		this.args = args;
	}

	public Object invoker() throws InvocationTargetException, IllegalAccessException {
		method.setAccessible(true);
		return method.invoke(target, args);
	}
}

3.定义通知里面的上下文对象参数

什么是上下文参数呢?以Spring为例,假设我们开发一个环绕通知,环绕通知里面的方法参数叫做ProceedingJoinPoint,那么我们也定义一个类似这样的方法,咱们叫做MyProceedingJoinPoint

package simulation.aop.system;

import lombok.Data;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * @author huangfu
 */
@Data
public class MyProceedingJoinPoint {
	/**
	 * 目标方法的参数
	 */
	private Object[] args;
	/**
	 * 目标对象
	 */
	private Object target;
	/**
	 * 目标方法
	 */
	private Method method;
	/**
	 * 存在的调用链
	 */
	private List<ProxyChain> proxyChains = new ArrayList<>(8);
	/**
	 * 当前调用链的指针位置
	 */
	private int chainsIndex = 0;


	public Object proceed(Object[] args) throws InvocationTargetException, IllegalAccessException {
		return method.invoke(target,args);
	}

	public Object proceed() throws InvocationTargetException, IllegalAccessException {
		return method.invoke(target);
	}
}

4.开发一个jdk动态代理所需要的回调方法(InvocationHandler)

熟悉jdk动态代理的小伙伴基本应该知道这个是干嘛的把,代理对象最终执行的逻辑是这个类里面的invoker方法,不熟悉的小伙伴希望去恶补一下再回来,最起码要知道它的使用方式!

package simulation.aop.system;

import org.springframework.util.CollectionUtils;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 动态代理执行器
 *
 * @author huangfu
 */
public class MyJdkDynamicAopProxy implements InvocationHandler, Serializable {
	int chainsIndex = 0;
	Object target;

	private final BeanUtil beanUtil;

	public MyJdkDynamicAopProxy(BeanUtil beanUtil, Object target) {
		this.beanUtil = beanUtil;
		this.target = target;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

		Method targetMethod = target.getClass()
            			.getMethod(method.getName(), method.getParameterTypes());
		Annotation[] targetAnnotations = targetMethod.getDeclaredAnnotations();
		//没注解  不代理
		if (targetAnnotations.length <= 0) {
			return method.invoke(target, args);
		}
		List<ProxyChain> proxyChains = new ArrayList<>(8);
		//获取这个方法的所有的注解,获取所以配置注解的切面方法
		Arrays.stream(targetAnnotations).forEach(annotation -> {
			Class<? extends Annotation> annotationClass = annotation.annotationType();
			if (beanUtil.proxyRule.containsKey(annotationClass.getName())) {
				proxyChains.addAll(beanUtil.proxyRule.get(annotationClass.getName()));
			}
		});
		//若拦截规则为空就直接执行就行了
		if (CollectionUtils.isEmpty(proxyChains)) {
			return method.invoke(target, args);
		}
		//当调用链执行完了就代表拦截方法全部执行完了,此时就可以回调自己的方法了!
		if (chainsIndex == proxyChains.size()) {
			return method.invoke(target, args);
		}
		//构建参数
		MyProceedingJoinPoint myProceedingJoinPoint = new MyProceedingJoinPoint();
		myProceedingJoinPoint.setArgs(args);
		myProceedingJoinPoint.setMethod(method);
		myProceedingJoinPoint.setProxyChains(proxyChains);
		myProceedingJoinPoint.setTarget(proxy);

		ProxyChain proxyChain = proxyChains.get(chainsIndex++);
		myProceedingJoinPoint.setChainsIndex(chainsIndex);
		//设置对应执行链节点的参数
		proxyChain.setArgs(new Object[]{myProceedingJoinPoint});
		//执行该节点对应的方法
		return proxyChain.invoker();
	}
}

5.开发一个使用的工具类

对应Spring的bean工厂,当然里面大部分和本篇无关的逻辑都被我简化写死了,读者只需要关注和本篇文章有关的内容就好了!

package simulation.aop.system;

import simulation.aop.my.MyAspect;
import simulation.aop.my.TestServiceImpl;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 模拟AOP实现
 *
 * @author huangfu
 */
@SuppressWarnings("all")
public class BeanUtil {
	/**
	 * 假设这是单例池   模仿Spring,存放已经弄好的对象
	 */
	public final Map<String, Object> beanCache = new ConcurrentHashMap<>(8);

	/**
	 * 假设这是bd map 存放bean的信息 这里相对Spring进行简化了,只存储类对象足够我们使用了
	 */
	public final Map<String, Class> beanClassCache = new ConcurrentHashMap<String, Class>(8);
	/**
	 * 假设这是bean容器里面存储切面的缓存  存储的是切面的缓存
	 */
	public final List<Class> aspectjClassCache = new ArrayList<>(8);

	/**
	 * 拦截规则  存储注解对应的规则链
	 */
	public final Map<String, List<ProxyChain>> proxyRule = new ConcurrentHashMap<>();

	/**
	 * 我们假设Spring完成了基础扫描步骤,已经将bd存放再了容器里面
	 * 假设假设哈
	 */
	public BeanUtil() throws InstantiationException, IllegalAccessException {
		beanClassCache.put("testService", TestServiceImpl.class);
		aspectjClassCache.add(MyAspect.class);
		//解析切面
		parseAspectjClass();
	}


	/**
	 * 初始化bean
	 */
	public void initBean() {
		beanClassCache.forEach((key, value) -> {
			//判断是否有需要代理的方法
			try {
				//创建所有的类 转换为bean
				createBean(key, value, hasProxy(value));
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (InstantiationException e) {
				e.printStackTrace();
			}

		});
	}

	/**
	 * 创建bean
	 *
	 * @param beanName bean的名称
	 * @param classes  bean的类对象
	 * @param isProxy  是否需要代理
	 */
	public void createBean(String beanName, Class classes, boolean isProxy) throws IllegalAccessException, InstantiationException {
		Class<?>[] interfaces = getInterfaces(classes);
		Object target = classes.newInstance();
		//需要被代理 这里没有模仿cglib而是使用的jdk动态代理 所以必须需要接口 Spring是两种接口混合使用的
		if (isProxy && interfaces != null && interfaces.length > 0) {
			//创建jdk动态代理的对象
			MyJdkDynamicAopProxy myJdkDynamicAopProxy = new MyJdkDynamicAopProxy(this, target);
			//返回代理对象
			target = Proxy.newProxyInstance(BeanUtil.class.getClassLoader(), interfaces, myJdkDynamicAopProxy);
			//存储再缓存池
			beanCache.put(beanName, target);
		} else {
			//不需要代理也存储再缓存池
			beanCache.put(beanName, target);
		}
	}

	/**
	 * 获取bean对象
	 *
	 * @param beanName beanName
	 * @param <T>
	 * @return
	 */
	public <T> T getBean(String beanName) {
		return (T) beanCache.get(beanName);
	}


	/**
	 * 获取对应类的接口
	 *
	 * @param classes 要获取的类对象
	 * @return 该类对象的接口
	 */
	private Class<?>[] getInterfaces(Class classes) {
		return classes.getInterfaces();
	}

	/**
	 * 内部是否存在需要被拦截的方法对象
	 *
	 * @param targetClass 目标类对象
	 * @return 返回是否需要代理
	 */
	private boolean hasProxy(Class targetClass) {
		//获取所有的方法
		Method[] declaredMethods = targetClass.getDeclaredMethods();
		for (Method method : Arrays.asList(declaredMethods)) {
			//获取方法上的注解
			Annotation[] declaredAnnotations = method.getDeclaredAnnotations();
			//当方法存在注释的时候 开始判断是否方法需要被代理
			if (declaredAnnotations != null && declaredAnnotations.length > 0) {
				for (Annotation annotation : Arrays.asList(declaredAnnotations)) {
					//如果解析规则存在这个注解就返回true
					if (proxyRule.containsKey(annotation.annotationType().getName())) {
						return true;
					}
				}
			}
		}
		return false;
	}

	/**
	 * 解析切面类
	 */
	private void parseAspectjClass()  throws IllegalAccessException, InstantiationException {
		for (Class aClass : aspectjClassCache) {
			//获取切面的所有方法
			Method[] declaredMethods = aClass.getDeclaredMethods();
			for (Method method : Arrays.asList(declaredMethods)) {
				//寻找携带MyAround注解的切面方法
				MyAround myAroundAnntation = method.getAnnotation(MyAround.class);
				if (myAroundAnntation != null) {
					//拿到切点标志 也就是未来切点的方法
					Class<? extends Annotation> targetClassAnnotation 
                        						= myAroundAnntation.targetClass();
					//实例化切面
					Object proxyTarget = aClass.newInstance();
					//创建调用链实体
					ProxyChain proxyChain = new ProxyChain(method, proxyTarget);
					//构建对应规则的调用链
					String classAnnotationName = targetClassAnnotation.getName();
					if (proxyRule.containsKey(classAnnotationName)) {
						proxyRule.get(classAnnotationName).add(proxyChain);
					} else {
						List<ProxyChain> proxyChains = new ArrayList<>();
						proxyChains.add(proxyChain);
						proxyRule.put(classAnnotationName, proxyChains);
					}
				}
			}
		}
	}


}

具体的逻辑介绍,看文中注释,此时我们的AOP就开发完了,赶紧,去测一下!

6.开发一个通知注解

这个注解是为了标识那些方法需要被拦截的!

package simulation.aop.system;

import java.lang.annotation.*;

/**
 * 模拟AOP通过注解方式添加拦截器
 * @author huangfu
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAopAnnotation {
}

7.开发一个切面

package simulation.aop.my;

import simulation.aop.system.MyAopAnnotation;
import simulation.aop.system.MyAround;
import simulation.aop.system.MyProceedingJoinPoint;

import java.lang.reflect.InvocationTargetException;

/**
 * 定义一个切面
 * @author huangfu
 */
public class MyAspect {

	/**
	 * 拦截所有方法上携带  MyAopAnnotation 注解的方法
	 * @param joinPoint
	 * @return
	 * @throws InvocationTargetException
	 * @throws IllegalAccessException
	 */
	@MyAround(targetClass = MyAopAnnotation.class)
	public Object testAspect(MyProceedingJoinPoint joinPoint) throws InvocationTargetException, IllegalAccessException {
		long startTime = System.currentTimeMillis();
		//方法放行
		Object proceed = joinPoint.proceed(joinPoint.getArgs());
		long endTime = System.currentTimeMillis();
		System.out.println("总共用时:"+(endTime - startTime));
		return proceed;
	}


	/**
	 * 拦截所有方法上携带  MyAopAnnotation 注解的方法
	 * @param joinPoint
	 * @return
	 * @throws InvocationTargetException
	 * @throws IllegalAccessException
	 */
	@MyAround(targetClass = MyAopAnnotation.class)
	public Object testAspect2(MyProceedingJoinPoint joinPoint) throws InvocationTargetException, IllegalAccessException {
		long startTime = System.currentTimeMillis();
		//方法放行
		Object proceed = joinPoint.proceed(joinPoint.getArgs());
		long endTime = System.currentTimeMillis();
		System.out.println("总共用时:"+(endTime - startTime));
		return proceed;
	}
}

8.开发一个接口和一个实现类

package simulation.aop.my;

/**
 * @author huangfu
 */
public interface TestService {
	/**
	 * 打印一句话  拦截方法
	 * @param msg 返回信息
	 */
	String print(String msg) throws InterruptedException;
	/**
	* 普通方法 不拦截
	**/
	void sendUser();
}
package simulation.aop.my;

import simulation.aop.system.MyAopAnnotation;

/**
 * 实现类
 * @author huangfu
 */
public class TestServiceImpl implements TestService {

	@MyAopAnnotation
	@Override
	public String print(String msg) throws InterruptedException {
		System.out.println("我执行了,参数是:"+msg);
		Thread.sleep(5000);
		return msg;
	}

	@Override
	public void sendUser() {
		System.out.println("----发送信息---");
	}
}

9.最终测试

package simulation.aop;

import simulation.aop.my.TestService;
import simulation.aop.system.BeanUtil;

public class TestMyProxy {

	public static void main(String[] args) throws IllegalAccessException, InstantiationException, InterruptedException {
		BeanUtil beanUtil = new BeanUtil();
		beanUtil.initBean();
		TestService testService = beanUtil.getBean("testService");
		System.out.println(testService.print("wqewqeqw"));
		testService.sendUser();
	}
}

10.结果

image-20200808234158861

当然,肯定是成功的,但是写到这我慌了,为什么?现在就已经超过1w字了,源码部分还一个没动,天哪,我得去前面加个:不想看自己实现部分就跳过的提示!

三、Spring AOP源码学习

上篇文章,对有关Spring实例化,自动注入属性做了很详细的介绍有兴趣可以到【万字长文,助你深度遨游Spring循环依赖源码实现!】查看,本篇文章对它实例化等操作直接略过,先看一张图,找到入口方法,我们好继续看源码: