Spring框架的AOP实现
前言
AOP(Aspect Orient Programming),翻译过来就是面向切面变成的意思。AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
AOP实现详细讲解
实现AOP的方法主要是通过生成动态代理来实现,而常规的动态代理是JDK动态代理和CGLIB动态代理,这个两者区别主要是CGLIB不需要接口就可以创建动态代理,因此因此我们采用CGLiB的方法来创建动态代理
我们将实现过程主要分为3部分
- 解决标记问题,定义横切逻辑的骨架
- 定义Aspect横切逻辑以及被代理方法的执行顺序
- 将横切逻辑织入到被代理的对象以及生成动态代理对象
1 解决标记问题,定义横切逻辑的骨架
回顾我们使用Spring框架的中定义切面类,我们需要定义一个@Aspect注解,因此我们遵循Spring 的原理。
解决标记问题,定义横切逻辑的骨架我们主要分为两步来完成
- 定义与横切逻辑相关的注解
- 定义供给外部使用的横切逻辑骨架
1.1 定义与横切逻辑相关的注解
我们同样定义一个@Aspect注解
/**
* 切面
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
Class<? extends Annotation> value();
}
1.2 定义供给外部使用的横切逻辑骨架
在这一步中我们定义了 横切逻辑的各种切入方法,通常有在之间注入的before,在之后切入的afterReturning,以及抛出异常的afterThrowing. 后面我们可以通过继承来重写这些切入方法
public abstract class DefaultAspect {
/**
* 事前拦截
*
* @param targetClass 被代理的目标类
* @param method 被代理的目标方法
* @param args 被代理的目标方法对应的参数列表
* @throws Throwable
*/
public void before(Class<?> targetClass, Method method, Object[] args) throws Throwable {
}
/**
* 事后拦截
*
* @param targetClass 被代理的目标类
* @param method 被代理的目标方法
* @param args 被代理的目标方法对应的参数列表
* @param returnValue 被代理目标方法的返回值
* @return
* @throws Throwable
*/
public Object afterReturning(Class<?> targetClass, Method method, Object[] args, Object returnValue) throws Throwable{
return returnValue;
}
/**
* 抛出异常后拦截
*
* @param targetClass
* @param method
* @param args
* @param e
* @throws Throwable
*/
public void afterThrowing(Class<?> targetClass, Method method, Object[] args, Throwable e) throws Throwable{
}
}
2 定义Aspect横切逻辑以及被代理方法的执行顺序
这一步需要干什么呢? 我们从第一步中得到了 切面类注解定义@Aspect以及对应的骨架切面逻辑,那么这一步我们主要实现代理方法的执行顺序
这里我们对这一步主要分为4个部分来完成:
- 定义MethodInterceptor的实现类
- 定义必须的成员变量 — 被代理的类,以及Aspect列表
- 按照Order对Aspect排序
- 按照横切逻辑以及被代理对象方法的定序执行
2.1 定义MethodInterceptor的实现类
MethodInterceptor作为AOP中需要的一个拦截器,我们可以用它来做我们的一个切入。
实现这个方法需要去重写intercept方法 同时这个方法中我们去实现
Object returnValue = methodProxy.invokeSuper(proxy, args);久相当于执行了代理方法,因此我们可以在这个方法的切面来实现我们的切面逻辑
public class AspectListExecutor implements MethodInterceptor {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object returnValue = methodProxy.invokeSuper(proxy, args);
return returnValue;
}
2.2 定义必须的成员变量 — 被代理的类,以及Aspect列表
public class AspectListExecutor implements MethodInterceptor {
//被代理的类
private Class<?> targetClass;
//切面集合
@Getter
private List<AspectInfo> sortedAspectInfoList;
public AspectListExecutor(Class<?> targetClass, List<AspectInfo> aspectInfoList){
this.targetClass = targetClass;
//根据Order对AspectInfo进行排序
this.sortedAspectInfoList = sortAspectInfoList(aspectInfoList);
}
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object returnValue = methodProxy.invokeSuper(proxy, args);
return returnValue;
}
}
2.3 按照Order对Aspect排序
我们正常的切面 肯定也是有一个顺序的,比如有日志1 日志2都想在 代理方法执行之前执行,那么你也需要对日志1 和 日志2 进行一个顺序的定义
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
//Order定义
public @interface Order {
/**
* 值越小,优先级越高
* @return
*/
int value();
}
这一步是对order进行排序为后面确定好 那些切面要先后执行 排序的话 根据order从小到大排序即可
其实就是多了一个对sortAspectInfoList的实现
public class AspectListExecutor implements MethodInterceptor {
//被代理的类
private Class<?> targetClass;
//切面集合
@Getter
private List<AspectInfo> sortedAspectInfoList;
public AspectListExecutor(Class<?> targetClass, List<AspectInfo> aspectInfoList){
this.targetClass = targetClass;
//根据Order对AspectInfo进行排序
this.sortedAspectInfoList = sortAspectInfoList(aspectInfoList);
}
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object returnValue = methodProxy.invokeSuper(proxy, args);
return returnValue;
}
}
/**
* 按照order的值进行升序排序,确保order值小的aspect先被织入
* @param aspectInfoList
* @return
*/
private List<AspectInfo> sortAspectInfoList(List<AspectInfo> aspectInfoList) {
Collections.sort(aspectInfoList, new Comparator<AspectInfo>() {
//
@Override
public int compare(AspectInfo o1, AspectInfo o2) {
return o1.getOrderIndex() - o2.getOrderIndex();
}
});
return aspectInfoList;
}
2.4 按照横切逻辑以及被代理对象方法的定序执行
这一步要显得麻烦很多,我们在intercept函数里面 不仅要要实现代理本身的方法,还要实现before,afterReturning,afterThrowing方法。
从这方法中可以看出来,我们以后只要调用这个Intercept方法,其实也就是调用代理方法,就可以实现对切面你的调用 和代理本身的方法的调用了
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object returnValue = null;
collectAccurateMatchedAspectList(method);
if(sortedAspectInfoList == null || sortedAspectInfoList.isEmpty()){
//执行被代理方法(即便没有织入逻辑也要执行)
returnValue = methodProxy.invokeSuper(proxy, args);
return returnValue;
}
//1. 按照order的顺序升序执行完所有Aspect的before方法
invokeBeforeAdvices(method, args);
try{
//2. 执行被代理类的方法
returnValue = methodProxy.invokeSuper(proxy, args);
//3. 如果被代理方法正常返回,则按照order的顺序降序执行完所有Aspect的afterReturning方法
returnValue = invokeAfterReturningAdvices(method, args, returnValue);
}catch (Exception e){
//4. 如果被代理方法抛出异常,则按照order的顺序降序执行完所有Aspect的afterThrowing方法
invokeAfterThrowingAdvice(method, args, e);
}
return returnValue;
}
3 将横切逻辑织入到被代理的对象以及生成动态代理对象
接下来是最后一步,将横切逻辑放到代理对象中,已经动态的生成代理对象,其实就是将我们织入了横切逻辑的对象,重新在装入BeanContainer这个容器类中。
因此这里我们也分为三步来完成:
- 获取所有的切面类
- 将切面类按照不同的织入目标进行切分
- 按照不同的注入目标分别按序织入到Aspect的逻辑
3.1 获取所有的切面类
这一步是非常容易的,我们对切面类都是进行了注解@Aspect标记的,那么我们只要将某个类上有Aspect注解就可以知道这是个切面类,在上一章节的IoC容器中 其实我们已经定义了根据注解来获取的方法这里就只是调用就好了。
Set<Class<?>> aspectSet = beanContainer.getClassesByAnnotation(Aspect.class);
3.2 将切面类按照不同的织入目标进行切分
这一步要麻烦一些,这我们主要是针对不同的切面目标来切分,为了后续的织入逻辑
//2 将切面类按照不同的织入目标进行划分
Map<Class<? extends Annotation>, List<AspectInfo>> categorizedMap = new HashMap<>();
if(ValidationUtil.isEmpty(aspectSet)) return;
for(Class<?> aspectClass : aspectSet){
//框架中一定要遵守给Aspect类添加@Aspect和@Order标签的规范,同时,必须继承自DefaultAspect.class
if(verifyAspect(aspectClass)){
//通过hashMap来进行切分归类
categorizeAspect(categorizedMap, aspectClass);
}else{
throw new RuntimeException("没有标记aspect,order 或者没有继承DefaultAspect");
}
}
3.3 按照不同的注入目标分别按序织入到Aspect的逻辑
这一步是将切面逻辑织入到对象中,并且更新我们我们IoC容器载体的数据,这一步也说明了我们需要先执行AOP功能 再去执行IOC功能
//3 按照不同的注入目标分别去按序执行aspect的逻辑
//if(ValidationUtil.isEmpty(categorizedMap)) return;
for(Class<? extends Annotation> category : categorizedMap.keySet()){
weaveByCategory(category, categorizedMap.get(category));
}
/**
* 根据织入目标进行织入
*
* @param category
* @param aspectInfoList
*/
private void weaveByCategory(Class<? extends Annotation> category, List<AspectInfo> aspectInfoList) {
//1. 获取被代理类的集合
Set<Class<?>> classSet = beanContainer.getClassesByAnnotation(category);
if (classSet == null || classSet.isEmpty()) {
return;
}
//2. 遍历被代理的类,分别为每个被代理类生成动态代理实例
for (Class<?> targetClass : classSet) {
AspectListExecutor aspectListExecutor = new AspectListExecutor(targetClass, aspectInfoList);
//创建动态代理对象
Object proxyBean = ProxyCreator.createProxy(targetClass, aspectListExecutor);
//3. 将动态代理对象实例添加到容器里,取代未被代理前的类实例 map这边是会覆盖的
beanContainer.addBean(targetClass, proxyBean);
}
}
总结
这样一个AOP容器就已经完成了创建了,如果后续想要执行自己的切入逻辑是否已经织入 可以执行这段代码
@Test
@DisplayName("织入通用测试逻辑:doAop")
public void doAopTest(){
BeanContainer beanContainer = BeanContainer.getInstance();
beanContainer.loadBeans("com.bimo");
new AspectWeaver().doAop1();
new DependencyInjector().doIoc();
MainPageController mainPageController = (MainPageController) beanContainer.getBean(MainPageController.class);
//这里会不仅仅会执行你的sayHi()而且你的切面方法也会打印
mainPageController.sayHi();
}
在了解AOP之前需要去了解IOC容器,可以参考上一篇博客 链接: Spring框架之IoC实现.
同时所有的源码将会在6月初在github公布,如果有兴趣可以去下载,到时候会公布链接