Spring框架实现之AOP

Spring框架的AOP实现文章目录Spring框架的AOP实现前言AOP实现详细讲解1 解决标记问题,定义横切逻辑的骨架1.1 定义与横切逻辑相关的注解1.2 定义供给外部使用的横切逻辑骨架2 定义Aspect横切逻辑以及被代理方法的执行顺序2.1 定义MethodInterceptor的实现类2.2 定义必须的成员变量 --- 被代理的类,以及Aspect列表2.3 按照Order对Aspect排序2.4 按照横切逻辑以及被代理对象方法的定序执行3 将横切逻辑织入到被代理的对象以及生成动态代理对象
摘要由CSDN通过智能技术生成

Spring框架的AOP实现


前言

AOP(Aspect Orient Programming),翻译过来就是面向切面变成的意思。AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。

AOP实现详细讲解

实现AOP的方法主要是通过生成动态代理来实现,而常规的动态代理是JDK动态代理和CGLIB动态代理,这个两者区别主要是CGLIB不需要接口就可以创建动态代理,因此因此我们采用CGLiB的方法来创建动态代理
我们将实现过程主要分为3部分

  1. 解决标记问题,定义横切逻辑的骨架
  2. 定义Aspect横切逻辑以及被代理方法的执行顺序
  3. 将横切逻辑织入到被代理的对象以及生成动态代理对象

1 解决标记问题,定义横切逻辑的骨架

回顾我们使用Spring框架的中定义切面类,我们需要定义一个@Aspect注解,因此我们遵循Spring 的原理。
解决标记问题,定义横切逻辑的骨架我们主要分为两步来完成

  1. 定义与横切逻辑相关的注解
  2. 定义供给外部使用的横切逻辑骨架
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个部分来完成:

  1. 定义MethodInterceptor的实现类
  2. 定义必须的成员变量 — 被代理的类,以及Aspect列表
  3. 按照Order对Aspect排序
  4. 按照横切逻辑以及被代理对象方法的定序执行
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这个容器类中。
因此这里我们也分为三步来完成:

  1. 获取所有的切面类
  2. 将切面类按照不同的织入目标进行切分
  3. 按照不同的注入目标分别按序织入到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公布,如果有兴趣可以去下载,到时候会公布链接

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值