深入挖掘Spring系列 -- 实现一个简易版本的aop

简介: 深入挖掘Spring系列 -- 实现一个简易版本的aop

在正式进行aop模块的介绍之前,我们需要先弄懂一些基本的术语概念。


在软件业,AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。


AOP可以用于解决什么问题?



代码复用,公共函数抽取,简化开发,业务之间的解耦;最典型的例子就是日志功能,因为日志功能往往横跨系统中的每个业务模块,使用 AOP 可以很好的将日志功能抽离出来。


目前已有的几款AOP框架技术:


  • AspectJ:基于源代码和字节码解包的一个编织器,用户需要使用的时候用到一门新的语言,因此Spring的Aop对AspectJ进行了一次封装。
  • AspectWerkz:AOP 框架,使用字节码动态编织器和 XML 配置
  • JBoss-AOP:基于拦截器和元数据的 AOP 框架,运行在 JBoss 应用服务器上,以及 AOP 中用到的一些相关技术实现
  • Javassist:Java 字节码操作类库,JBoss 的一个子项目


AOP基本概念



在介绍AOP技术之前,我们先来理清几个基本概念点:


Aspect(切面)

可以理解为是将业务代码抽出来的一个类,例如:


@Aspect
public class LogAspect {
  /** ... 这里面是相关方法的部分 省略大部分内容 ... **/
}
复制代码


JoinPoint(连接点)

拦截点其实可以理解为下边这个参数:


网络异常,图片无法展示
|


Spring框架目前只支持方法级别的拦截,其实aop的连接点还可以有多种方式,例如说参数,构造函数等等。


PointCut(切入点)


可以理解为对各个连接点进行拦截对一个定义。


网络异常,图片无法展示
|


Advice(通知)


指拦截到连接点之后需要执行的代码。

分为了前置,后置,异常,环绕,最终

具体表现如下:


网络异常,图片无法展示
|


目标对象


代理的目标对象


织入(weave)


将切面应用到目标对象,并且导致代理对象创建的过程。weave是一个操作过程。


引入(introduction)


在不修改代码的前提下,引入可以在运行的时候动态天津一些方法或者字段。


Cglib如何实现接口调用的代理



首先我们定义一个基本的业务代码对象:


package org.idea.spring.aop.cglib;
/**
 * @Author linhao
 * @Date created in 3:56 下午 2021/5/6
 */
public class MyBis {
    void doBus(){
        System.out.println("this is do bis");
    }
}
复制代码


接着是目标对象的拦截:


package org.idea.spring.aop.cglib;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
 * 整体的一个调用流程其实就是:
 * @Author linhao
 * @Date created in 3:57 下午 2021/5/6
 */
public class TargetInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("==== intercept before ====");
        //从代理实例的方法调用返回的值。
        Object result = methodProxy.invokeSuper(o,objects);
        System.out.println("==== intercept after ====");
        return result;
    }
}
复制代码


最后是测试代码的执行。


package org.idea.spring.aop.cglib;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
/**
 * @Author linhao
 * @Date created in 3:59 下午 2021/5/6
 */
public class TestCglib {
    public static void main(String[] args) throws InterruptedException {
        //将cglib生成的字节码文件存放到这个目录下边,查看下会有什么东西
    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/Users/idea/IdeaProjects/framework-project/spring-framework/spring-core/spring-aop/cglib-class");
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MyBis.class);
        enhancer.setCallback(new TargetInterceptor());
        MyBis myBis = (MyBis) enhancer.create();
        myBis.doBus();
    }
}
复制代码


执行结果:


网络异常,图片无法展示
|


上边代码中的TargetInterceptor中的intercept方法会在目标函数被调用之后自动进行回调操作,从而实现代理调用的效果。


cglib和jdk代理


cglib的代理模式和jdk实现的代理技术本质还是会有较大差异性,jdk要求被代理对象需要实现jdk内部的InvocationHandler接口才能进行接口回调操作,但是cglib对是否实现接口方面没有强制要求,而且其性能也比JDK自带的代理要高效许多。


cglib代理的原理


关于cglib的原理我只能简单地介绍一下,仔细看了下里面的内容点实在是太多了,如果一下子深入挖掘容易掉进坑,所以这里打算用些大白话的方式来介绍好了。


cglib实现代理的基本思路


1.对被调用对象进行一层包装,并且对方法建立索引。


2.当调用目标方法的时候,通过索引值去寻找并调用函数。

这里面详细细节点可以参考这篇博客:

www.cnblogs.com/cruze/p/386…


根据这篇博客介绍的思路,我自己也简单实现了一个cglib类似的代理工具。代码地址见文末


难点:


如何给方法建立索引?如何根据索引调用函数?


这里贴出我自己的一些思考和输出。


对调用对方法名称取出hashcode,然后通过switch关键字判断需要调用对函数名称:


网络异常,图片无法展示
|


使用起来差不多,不过很多细节方面没有做得特别完善:


网络异常,图片无法展示
|


使用cglib实现代理功能,主要目的就是希望在执行某些函数之前去调用某些方法。为了实现这种方式,其实借助反射也是可以达成目的的。但是反射在多次调用的时候性能开销比较大。cglib在这块所做的优化主要是对调用方法做了一次索引的包装,生产新的字节码,实现性能上的提升。


相关实现对代码仓库地址可以见文末。


Cglib底层是如何生成字节码文件的



ASM


对于需要手动操纵字节码的需求,可以使用ASM,它可以直接生产 .class字节码文件,也可以在类被加载入JVM之前动态修改类行为。ASM的应用场景有AOP(Cglib就是基于ASM)、热部署、修改其他jar包中的类等。


整体的操作流程图如下所示:


网络异常,图片无法展示
|


先通过ClassReader读取编译好的.class文件


其通过访问者模式(Visitor)对字节码进行修改,常见的Visitor类有:对方法进行修改的MethodVisitor,或者对变量进行修改的FieldVisitor等


通过ClassWriter重新构建编译修改后的字节码文件、或者将修改后的字节码文件输出到文件中


如何自己实现一个简单版本的AOP



首先需要定义相关的注解:


package org.idea.spring.aop.version1.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @Author linhao
 * @Date created in 3:49 下午 2021/5/6
 */
@Retention(value = RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Pointcut {
    String value() default "";
}
复制代码


package org.idea.spring.aop.version1.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @Author linhao
 * @Date created in 3:42 下午 2021/5/6
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Before {
    String value();
}
复制代码


package org.idea.spring.aop.version1.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @Author linhao
 * @Date created in 3:43 下午 2021/5/6
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface After {
    String value();
}
复制代码


package org.idea.spring.aop.version1.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @Author linhao
 * @Date created in 3:41 下午 2021/5/6
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Aspect {
    String value() default "";
}
复制代码


接下来为自己定义的这些注解添砖加瓦,组合使用到一个Aspect的切面当中去:


package org.idea.spring.aop.version1.aspect;
import org.idea.spring.aop.version1.annotation.After;
import org.idea.spring.aop.version1.annotation.Aspect;
import org.idea.spring.aop.version1.annotation.Before;
import org.idea.spring.aop.version1.annotation.Pointcut;
import java.lang.reflect.Method;
/**
 * 切面
 *
 * @Author linhao
 * @Date created in 3:43 下午 2021/5/6
 */
@Aspect
public class MyAspect {
    @Pointcut("org.idea.spring.aop.version1.test.*.*(..)")
    public void pointCut(){
    }
    @Before("pointCut()")
    public void doBefore(Method method, Object object){
        System.out.println("doBefore");
    }
    @After("pointCut()")
    public void doAfter(Method method, Object object){
        System.out.println("doAfter");
    }
}
复制代码


同时补充一个测试使用的方法


package org.idea.spring.aop.version1.test;
/**
 * @Author linhao
 * @Date created in 3:44 下午 2021/5/6
 */
public class TestMethod {
    public void doTest(){
        System.out.println("do test");
    }
}
复制代码


最后是一个核型的AspectLoader加载器代码


package org.idea.spring.aop.version1;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.idea.spring.aop.version1.annotation.After;
import org.idea.spring.aop.version1.annotation.Aspect;
import org.idea.spring.aop.version1.annotation.Before;
import org.idea.spring.aop.version1.annotation.Pointcut;
import org.idea.spring.aop.version1.test.TestMethod;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * @Author linhao
 * @Date created in 3:51 下午 2021/5/6
 */
public class AspectLoader {
    /**
     * 配置扫描aop的aspect基础包路径
     */
    public static final String PACKAGE_NAME = "org.idea.spring.aop";
    /**
     * 模拟ioc容器
     */
    public Map<String, Object> beanContainer = new HashMap<>();
    public AspectLoader() {
        this.beanContainer.put("TestMethod", new TestMethod());
    }
    public static void main(String[] args) {
        AspectLoader aspectLoader = new AspectLoader();
        aspectLoader.init();
        TestMethod testMethod = (TestMethod) aspectLoader.beanContainer.get("TestMethod");
        testMethod.doTest();
    }
    /**
     * 初始化aop的配置相关
     */
    private void init() {
        try {
            //获取切面点aspect
            List<Class> targetsWithAspectJAnnotationList = this.getAspectClass();
            for (Class targetsWithAspectJAnnotation : targetsWithAspectJAnnotationList) {
                Method beforeMethod = this.getBeforeMethod(targetsWithAspectJAnnotation);
                Pointcut pointcut = (Pointcut) this.getMethodAnnotation(targetsWithAspectJAnnotation, Pointcut.class);
                Method afterMethod = this.getAfterMethod(targetsWithAspectJAnnotation);
                List<Class> classList = this.getClassFromPackage(AspectLoader.class, pointcut.value().substring(0, pointcut.value().indexOf("*") - 1));
                for (Class sourceClass : classList) {
                    Object aspectObject = targetsWithAspectJAnnotation.newInstance();
                    Enhancer enhancer = new Enhancer();
                    enhancer.setSuperclass(sourceClass);
                    enhancer.setCallback(new MethodInterceptor() {
                        @Override
                        public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                            beforeMethod.invoke(aspectObject, method, obj);
                            methodProxy.invokeSuper(obj, objects);
                            afterMethod.invoke(aspectObject,method,obj);
                            return obj;
                        }
                    });
                    Object proxyObj = enhancer.create();
                    this.beanContainer.put(sourceClass.getSimpleName(), proxyObj);
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
    private List<Class> getAspectClass() throws ClassNotFoundException, IOException {
        final ClassPath classPath = ClassPath.from(AspectLoader.class.getClassLoader());
        List<Class> aspectClass = new ArrayList<>();
        ImmutableSet<ClassPath.ClassInfo> clazz = classPath.getAllClasses();
        List<ClassPath.ClassInfo> list = clazz.asList();
        for (ClassPath.ClassInfo classInfo : list) {
            if (classInfo.getName() != null && classInfo.getPackageName().contains(PACKAGE_NAME)) {
                Class clazzTemp = Class.forName(classInfo.getName());
                if (clazzTemp.isAnnotationPresent(Aspect.class)) {
                    aspectClass.add(clazzTemp);
                }
            }
        }
        return aspectClass;
    }
    /**
     * 获取指定包名下边的所有类
     *
     * @param source
     * @param packageName
     * @return
     * @throws Exception
     */
    private List<Class> getClassFromPackage(Class source, String packageName) {
        List<Class> classList = new ArrayList<>();
        final ClassPath classPath;
        try {
            classPath = ClassPath.from(source.getClassLoader());
            ImmutableSet<ClassPath.ClassInfo> clazz = classPath.getAllClasses();
            List<ClassPath.ClassInfo> list = clazz.asList();
            for (ClassPath.ClassInfo classInfo : list) {
                if (classInfo.getName() != null && classInfo.getPackageName().contains(packageName)) {
                    classList.add(Class.forName(classInfo.getName()));
                }
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return classList;
    }
    private Annotation getMethodAnnotation(Class source, Class annotationClass) {
        Method[] methods = source.getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(annotationClass)) {
                Annotation[] beforeArr = method.getAnnotationsByType(annotationClass);
                if (beforeArr.length > 0) {
                    return beforeArr[0];
                }
            }
        }
        return null;
    }
    private Method getBeforeMethod(Class source) {
        Method[] methods = source.getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(Before.class)) {
                return method;
            }
        }
        return null;
    }
    private Method getAfterMethod(Class source) {
        Method[] methods = source.getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(After.class)) {
                return method;
            }
        }
        return null;
    }
}


目录
相关文章
|
1月前
|
XML Java 开发者
Spring Boot中的AOP实现
Spring AOP(面向切面编程)允许开发者在不修改原有业务逻辑的情况下增强功能,基于代理模式拦截和增强方法调用。Spring Boot通过集成Spring AOP和AspectJ简化了AOP的使用,只需添加依赖并定义切面类。关键概念包括切面、通知和切点。切面类使用`@Aspect`和`@Component`注解标注,通知定义切面行为,切点定义应用位置。Spring Boot自动检测并创建代理对象,支持JDK动态代理和CGLIB代理。通过源码分析可深入了解其实现细节,优化应用功能。
|
14天前
|
XML Java 测试技术
Spring AOP—通知类型 和 切入点表达式 万字详解(通俗易懂)
Spring 第五节 AOP——切入点表达式 万字详解!
73 25
|
14天前
|
XML 安全 Java
Spring AOP—深入动态代理 万字详解(通俗易懂)
Spring 第四节 AOP——动态代理 万字详解!
66 24
|
1月前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
81 8
|
3月前
|
XML Java 数据安全/隐私保护
Spring Aop该如何使用
本文介绍了AOP(面向切面编程)的基本概念和术语,并通过具体业务场景演示了如何在Spring框架中使用Spring AOP。文章详细解释了切面、连接点、通知、切点等关键术语,并提供了完整的示例代码,帮助读者轻松理解和应用Spring AOP。
Spring Aop该如何使用
|
3月前
|
监控 安全 Java
什么是AOP?如何与Spring Boot一起使用?
什么是AOP?如何与Spring Boot一起使用?
111 5
|
3月前
|
Java 开发者 Spring
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
103 8
|
3月前
|
Java 开发者 Spring
Spring AOP 底层原理技术分享
Spring AOP(面向切面编程)是Spring框架中一个强大的功能,它允许开发者在不修改业务逻辑代码的情况下,增加额外的功能,如日志记录、事务管理等。本文将深入探讨Spring AOP的底层原理,包括其核心概念、实现方式以及如何与Spring框架协同工作。
|
3月前
|
XML 监控 安全
深入调查研究Spring AOP
【11月更文挑战第15天】
62 5
|
3月前
|
Java 开发者 Spring
Spring AOP深度解析:探秘动态代理与增强逻辑
Spring框架中的AOP(Aspect-Oriented Programming,面向切面编程)功能为开发者提供了一种强大的工具,用以将横切关注点(如日志、事务管理等)与业务逻辑分离。本文将深入探讨Spring AOP的底层原理,包括动态代理机制和增强逻辑的实现。
72 4