百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

Spring容器AOP原理解析(spring容器的作用)

lipiwang 2025-03-24 17:53 57 浏览 0 评论

前言


Spring Framework是一个轻量级控制反转(IOC)和面向切面(AOP)的容器框架。IOC和AOP是Spring的两大核心思想。Spring容器IOC解析一文中聊了IOC的思想和原理,那么本篇我们来探究下AOP的思想及实现原理。

AOP和OOP概念


AOP(
Aspect-OrientedProgramming)面向切面编程,它将程序逻辑分解为所谓关注点的不同部分。跨越应用程序多个点的功能称为跨领域问题,这些跨领域问题在概念上与应用程序的业务逻辑分离。有许多常见的好例子,如日志记录,审计,声明式事务,安全性,缓存等。

OOP(Object-Oriented Programming)面向对象编程中模块化的关键单元是类,而在AOP中,模块化单元是切面。如果说依赖注入(IOC)可以将应用程序对象相互分离,那么AOP可以将交叉问题与它们所影响的对象分离。


AOP思想



AOP可以说是OOP的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为时,OOP则显得无能为力。OOP允许定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平的散步到所有对象层次中,而与它所散步到的对象的核心功能毫无关系。这种散布在各处的无关的代码被称为横切代码。在OOP设计中,导致了大量的代码重复。而AOP技术恰恰相反,它将那些影响多个类的公共行为封装到一个重用模块即切面。减少重复代码、降低模块间的耦合度。用一句话概括AOP编程思想,即横向重复,纵向抽取。

AOP编程示意图


AOP的实现原理




通俗点讲IOC负责将对象动态的注入到容器。而 AOP实现的就是让容器中的对象都享有容器中的公共服务。那么容器是怎么做到的呢?那就是动态代理。即:AOP实现原理是动态代理。

动态代理其实不是什么新鲜的东西,学过设计模式的同学都应该知道代理模式,代理模式是一种静态代理。

静态代理类图


图中HelloImplProxy是代理类,需要我们Coding,在编译期间就生成的。

而动态代理就是用反射和动态编译将代理模式变成动态的。原理跟动态注入一样,代理模式在编译的时候就已经确定代理类将要代理谁,而动态代理在运行的时候才知道自己要代理谁。

动态代理类的生成依赖通过字节码动态生成加载技术,在运行时生成加载类。即,代理类的字节码将在运行时生成并载入当前代理的ClassLoader。

Spring的动态代理有两种:一种是JDK的动态代理;另一种是CGLib(CodeGeneration Library)的动态代理。

一、JDK动态代理

我们先来看JDK的动态代理。在看JDK动态代理实现之前,我们思考一下如何实现?

按照代理模式的实现方式,肯定是用一个代理类,让它也实现Hello接口,然后在其内部声明一个HelloImpl,然后分别调用sayHello()方法,并在调用前后加上我们需要的其它操作。但是这样显然是写死的,如何做到动态呢?我们知道,要实现代理,那么我们的代理类跟被代理类实现同一接口,但是动态代理的话我们根本不知道我们将要代理谁?也就不知道要实现哪个接口,那么要怎么办?我们只有知道要代理谁以后,才能给出相应的代理类,那么我们何不等知道要代理谁以后再去生成一个代理类呢?想到这里,我们好像想到了解决的办法,就是动态生成代理类!这时候反射有了用武之地,我们可以写一个方法来接收被代理类,这样我们就可以通过反射知道它的一切信息。


而JDK的动态代理主要就是通过反射跟动态编译来实现的,下面通过代码来看如何实现的。示例:

//被代理接口  
public interface Hello {
    void sayHello();
}


//被代理类  
public class HelloImpl implements Hello {
    @Override
    public void sayHello() {
        System.out.println("Hello Lucky!");
    }
}


//Invocationhandler类
public class HelloInvocationHandler implements InvocationHandler {


    private Object subject;


    public HelloProxy(Object subject){
        this.subject = subject;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before invoke "+ method.getName());
        method.invoke(subject,args);
        System.out.println("After invoke "+ method.getName());
        return null;
    }
}


//客户端测试类
public class Client {
    public static void main(String[] args) {
        HelloImpl helloImpl = new HelloImpl();
        InvocationHandler handler = new HelloInvocationHandler(helloImpl);
        Hello hello = (Hello) Proxy.newProxyInstance(handler.getClass().getClassLoader(),helloImpl.getClass().getInterfaces(),handler);
        hello.sayHello();
    }
}


//运行结果
Before invoke sayHello
Hello Lucky!
After invoke sayHello

JDK动态代理的两个核心分别是InvocationHandler和Proxy,

下面我们从源码角度分析一下这个过程的实现原理,先看InvocationHandler类
InvocationHandler是一个接口,只有一个方法:

public interface InvocationHandler {    
  public Object invoke(Object proxy, Methodmethod, Object[] args) throws Throwable;
}

在定义增强逻辑类时,需要实现该接口,并在invoke方法里实现增强逻辑和对真实对象方法的调用。

对于invoke方法的三个参数,proxy表示代理对象,method表示真实对象的方法,args表示真实对象方法的参数,这里也可以看出对真实对象方法的调用是通过反射来实现的。

增强逻辑定义好了以后,我们需要一个代理对象来执行,先看如何产生这个代理对象。代理对象的产生是通过调用Proxy类的静态方法:newProxyInstance,以下源码分析部分篇幅原因仅截取部分代码片段。

public static ObjectnewProxyInstance(ClassLoader loader,
                                         Class[] interfaces,
                                          InvocationHandler h)
       throws IllegalArgumentException
       //该方法需要三个参数:ClassLoader确保返回的代理对象和真实对象由同一个类加载器加载,interfaces用于定义代理类应该实现的方法,invocationHandler用于定义代理类的增强逻辑
       //这一行代码很关键,这里是获取代理对象Class对象,Proxy类内部维护了代理
       //对象的缓存,如果缓存里有则直接返回,如果没有,则生成它
       Class cl = getProxyClass0(loader, intfs);
       //由Class对象获取构造器
           final Constructor cons = cl.getConstructor(constructorParams);
           final InvocationHandler ih = h;
           if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
                // create proxy instance withdoPrivilege as the proxy class may
                // implement non-publicinterfaces that requires a special permission
               returnAccessController.doPrivileged(new PrivilegedAction<Object>() {
                    public Object run() {
                        returnnewInstance(cons, ih);
                    }
                });
           } else {
                //最后由构造器返回新的代理对象实例
                return newInstance(cons, ih);
           }
}


接下来我们看代理对象的Class对象是如何生成的,缓存逻辑略去不表,直接看没有缓存的情况,代理类Class对象由ProxyClassFactory工厂生成:


private static final classProxyClassFactory
       implements BiFunction<ClassLoader, Class[],Class>
{
       // prefix for all proxy class names
       private static final String proxyClassNamePrefix = "$Proxy";
 
       // next number to use for generation of unique proxy class names
       private static final AtomicLong nextUniqueNumber = new AtomicLong();
      //验证代码略去
           /*
            * Generate the specified proxy class.
            */
            //这里生成了代理对象二进制字节码流
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces);
           try {
                //通过二进制字节码流加载代理类
                return defineClass0(loader,proxyName,
                                    proxyClassFile,0, proxyClassFile.length);
           } catch (ClassFormatError e) {
                throw newIllegalArgumentException(e.toString());
           }
       }
   }
}


最终生成二进制字节码流用到了Sun的ProxyGenerator类,


public staticbyte[] generateProxyClass(String s, Class aclass[])
    {
        ProxyGenerator proxygenerator = newProxyGenerator(s, aclass);
        //该方法生成代理对象的二进制字节码流
        byte abyte0[] =proxygenerator.generateClassFile();
        //如果要保存该字节码文件,可以将其写到硬盘上,我们稍后分析
        if(saveGeneratedFiles)
            //保存文件部分略去
        return abyte0;
    }

在源码中我们可以通过saveGeneratedFiles变量保存生成的class文件,我们反编译上面的示例生成的class文件:

public final class $Proxy0 extends Proxy implements Hello{
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;


    public $Proxy0(InvocationHandlervar1) throws  {
        super(var1);
    }


    public final boolean equals(Objectvar1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException  Error var3) {
            throw var3;
        } catch (Throwablevar4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
//这里是Hello接口的sayHello方法,在代理对象$Proxy0上的调用被invocationHandler拦截,
//经由其invoke方法调用(即我们之前定义的HelloInvocationHandler的invoke方法),
//留意invoke方法的参数,我们在HelloInvocationHandler里定义invoke方法时并没有使用proxy参数,
//这里proxy参数的位置传入了this变量,即代理对象本身。
// 实际上就是调用HelloInvocationHandler的public Object invoke(Object proxy, Method method, Object[] args)方法    
     public final void sayHello() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }


    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }


    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }


    static {
        try {


            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.jd.y.jdkproxy.Hello").getMethod("sayHello");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");


        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}


实现动态代理的关键部分,通过Proxy动态生成我们具体的代理类。



这个类主要的功能就是,根据被代理对象的信息,动态组装一个代理类,生成$Proxy0.java文件,然后将其编译成$Proxy0.class。这样就可以在运行的时候,根据我们具体的被代理对象生成我们想要的代理类了。这样一来,我们就不需要提前知道我们需要代理谁。也就是说,你想代理谁,想要什么样的代理,我们就给你生成一个什么样的代理类。然后在客户端我们就可以随意进行代理了。

也就是说main函数里面的proxy实际就是$Proxy0的一个实例对象。可知JDK动态代理是使用接口生成新的实现类,实现类里面则委托给InvocationHandler,InvocationHandler里面则调用被代理的类方法。

JDK动态代理类图


至此,总结一下,Proxy类通过ProxyClassFactory生成了继承Proxy类并实现了真实对象接口的 $ProxyX代理对象的二进制字节码流,并加载该字节码返回代理对象Class对象,由该Class对象经反射得到构造器构造了代理对象的实例。

二、CGLIB动态代理

接下来看CGLib如何实现动态代理。示例:

//定义回调函数
public class HelloMethodInterceptor implements MethodInterceptor {


//o 指代理对象 method指目标类中被拦截的方法 objects 指调用拦截方法所需参数 methodProxy 指用来调用目标类被拦截方法的方法
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("Before invoke "+ method.getName());
        methodProxy.invokeSuper(o,objects);
        System.out.println("After invoke "+ method.getName());
        return null;
    }
}


//Cglib方式的客户端
public class ClientByCglib {


    public static void main(String[] args) {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/gaofei12/Work/workspace-idea/demo-myself/demo/");
        //实例化一个增强器,也就是cglib中的一个class generator
        Enhancer eh = new Enhancer();
        //设置目标类
        eh.setSuperclass(HelloImpl.class);
        // 设置拦截对象
        eh.setCallback(new HelloMethodInterceptor());
        // 生成代理类并返回一个实例
        HelloImpl t = (HelloImpl) eh.create();
        t.sayHello();
    }
}


运行结果:
Before invoke sayHello
Hello Lucky!
After invoke sayHello

同时生成三个class文件:一个是代理类,一个是代理类的FastClass,一个是目标类的FastClass。


从上面的示例代码中,我们知道通过cglib生成代理类只需要一个目标类和一个回调函数(增强逻辑),下面我们从在代理对象上调用sayHello()方法出发,分析cglib动态代理的实现原理。

public class HelloImpl$EnhancerByCGLIB$a1ad184a extends HelloImpl implements Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$sayHello$0$Method;
    private static final MethodProxy CGLIB$sayHello$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$equals$1$Method;
    private static final MethodProxy CGLIB$equals$1$Proxy;
    private static final Method CGLIB$toString$2$Method;
    private static final MethodProxy CGLIB$toString$2$Proxy;
    private static final Method CGLIB$hashCode$3$Method;
    private static final MethodProxy CGLIB$hashCode$3$Proxy;
    private static final Method CGLIB$clone$4$Method;
    private static final MethodProxy CGLIB$clone$4$Proxy;


    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("com.jd.y.jdkproxy.HelloImpl$EnhancerByCGLIB$a1ad184a");
        Class var1;
        CGLIB$sayHello$0$Method = ReflectUtils.findMethods(new String[]{"sayHello", "()V"}, (var1 = Class.forName("com.jd.y.jdkproxy.HelloImpl")).getDeclaredMethods())[0];
        CGLIB$sayHello$0$Proxy = MethodProxy.create(var1, var0, "()V", "sayHello", "CGLIB$sayHello$0");
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$equals$1$Method = var10000[0];
        CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
        CGLIB$toString$2$Method = var10000[1];
        CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
        CGLIB$hashCode$3$Method = var10000[2];
        CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
        CGLIB$clone$4$Method = var10000[3];
        CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
    }


    final void CGLIB$sayHello$0() {
        super.sayHello();
    }


    public final void sayHello() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (this.CGLIB$CALLBACK_0 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }
        if (var10000 != null) {
            var10000.intercept(this, CGLIB$sayHello$0$Method, CGLIB$emptyArgs, CGLIB$sayHello$0$Proxy);
        } else {
            super.sayHello();
        }
    }
}

进入回调函数后,跟JDK动态代理InvocationHandler一样,先执行前置增强逻辑,然后将执行目标类的真实逻辑。

注意此处目标类的真实逻辑执行cglib的实现方式与JDK实现方式不同:JDK使用的是反射技术,而cglib则使用了FastClass构建方法索引+继承的方式访问目标类的方法。FastClass动态类为委托类方法调用语句建立索引,使用者根据方法签名(方法名+参数类型)得到索引值,再通过索引值进入相应的方法调用语句,得到调用结果。Fastclass 机制。由于篇幅原因上边只贴了部分代码,在这里抛砖引玉,感兴趣的同学自行读下源码,深入了解一下FastClass。

Cglib是通过直接继承被代理类,并委托为回调函数来做具体的事情。

CGLib动态代理类图


小结

这里我写的代理的功能,当然都是非常简单的写法,只是为了说明这个原理。当然,我们可以像Spring那样将这些AOP写到配置文件,因为网上好多写了怎么通过配置文件注入了,这里就不重复贴了。到这里,你可能会有一个疑问:你上面说,只要放到容器里的对象,都会有容器的公共服务,我怎么没看出来呢?好,那我们回顾一下我们的JDK代理方式的InvocationHandler类HelloInvocationHandler 和 CGLib回调函数HelloMethodInterceptor的代码。


从代码中不难看出,我们代理的功能里没有涉及到任何被代理对象的具体信息,这样有什么好处呢?这样的好处就是将代理要做的事情跟被代理的对象完全分开,这样一来我们就可以在代理和被代理之间随意的进行组合了。也就是说同一个功能我们只需要一个。同样的功能只有一个,那么这个功能不就是公共的功能吗?不管容器中有多少个对象,都可以享受容器提供的服务了。这就是AOP的好处。


JDK动态代理和CGLib方式的比较

类型

机制

回调方式

适用场景

效率

JDK动态代理委托机制

代理类和目标类都实现了同样的接口

InvocationHandler持有目标类

,代理类委托InvocationHandler去调用目标类的原始方法 反射

目标类是接口类

效率瓶颈在反射调用稍慢

CGLIB动态代理

继承机制

代理类继承了目标类并重写了目标方法,通过回调函数MethodInterceptor调用父类方法执行原始逻辑通过FastClass方法索引调用

非接口类,非final类,非final方法

第一次调用因为要生成多个Class对象较JDK方式慢,多次调用因为有方法索引较反射方式快,如果方法过多switchcase过多其效率还需测试


小贴士:

1、CGLib是不能代理final类,或代理被final,private修饰的方法,cglib面对具体类代理,不能是接口。--- CGLib的实现机制 继承,JAVA中final private 修饰的是无法extend的。

2、jdk的代理面向的是接口代理。--- 因为JDK的代理类本身已经继承了Proxy,而Java不允许多重继承,但是允许实现多个接口。


不管是JDK动态代理,还是CGLib动态代理,他们是AOP思想的两种实现方式。我们既要理解其实现原理,更要明白AOP的思想,横向重复、纵向抽取,AOP也是OOP的补充和完善。

相关推荐

那些加上姓就很美的名字,想要起名的可以参照一些啊

刷到是缘分,感谢大家的阅读,希望您能动动小手帮忙点赞,关注,评论。你的支持和鼓励是我前进的动力。在此祝福大家天天快乐,日日开心!0102我们公司新来的实习生叫林晚,第一次听到这名字就觉得特别有韵味。上...

手把手教你写出不被研发怼的需求文档

产品经理这岗位都多少年了,我还以为早就体系成熟、流程闭环了,所以这个系列我也安心停更了。结果最近被研发同事轮番吐槽:需求文档东漏一句西漏一段,“这也叫专业?”虽然不是在点名骂我,但谁让是我带的队呢,脸...

学会这五个练字规律,解决所有笔画长短问题

大家好,用5个规律讲清楚什么时候笔画长,什么时候笔画短。·第一、写的时候是短的笔画多,长的笔画少,记住这一个。·第二、同方向上面只能有一个长,不同方向上面可以同时加长,比如横和竖或者撇捺。这里就有一个...

不要再用“质疑”的眼光看草书,5个字说明草书促进汉字简化发展

【问题思考】我们知道简体字改革之前,我们用的是繁体字,比如“会”字一直写作“會”,那么是什么依据如是作出简化呢?而在简化字改革前的唐代,我们会发现,孙过庭的《书谱》就是写作“会”的样子的,他是穿越了?...

IDEA如何将工程转为maven工程

有时候在使用IDEA编辑器时,从Git上Pull一个maven工程下来是没有自动将工程转换成maven工程,相应的依赖jar包不会自动下载。此时要将它转换成maven工程并自动下载jar包。方法/步骤...

Maven多模块项目构建实战:打造高效开发的模块化体系

Maven多模块项目构建实战:打造高效开发的模块化体系Maven作为Java世界中最为流行的构建工具之一,以其强大的模块化支持能力著称。当你需要管理一个复杂的大规模项目时,合理使用Maven的多模块功...

Maven常用命令有哪些?

Maven是一个强大的项目管理工具,广泛用于构建、管理和部署Java项目。以下是Maven的一些常用命令,这些命令可以帮助开发者完成项目的清理、编译、测试、打包和部署等任务。所有命令都以m...

SpringBoot项目jar、war包启动解析

一、jar包和war包的区别1.1war包war包是JavaWeb应用程序的一种打包方式符合Servlet标准,它是WebArchive的缩写,主要用于存储Web应用程序相关的文件,包括Java...

Maven多模块项目构建:打造高效协作的软件工程基石

Maven多模块项目构建:打造高效协作的软件工程基石在软件开发的世界里,随着项目的复杂度不断攀升,单一模块的架构逐渐显得力不从心。这时,Maven多模块项目应运而生,它像一座桥梁,将不同的功能模块有机...

如何将 Spring Boot 工程打包成独立的可执行 JAR 包

导语:通过将SpringBoot项目打包成独立的可执行JAR包,可以方便地在任何支持Java环境的机器上运行项目。本文将详细介绍如何通过Maven构建插件将SpringBoot...

java maven 工具初步使用

安装与配置下载Maven访问官网https://maven.apache.org下载最新版,解压到本地目录(如C:\maven)。配置环境变量添加以下环境变量:M2_HOME:Maven安...

Maven工程如何使用非Maven仓库jar包

使用Maven之前,一直都是自己手工在网上搜索需要的jar包,然后添加到工程中。以这样的方式开发,工作了好多年,曾经以为以后也会一直这样下去。直到碰上Maven,用了第一次,就抛弃老方法了。Maven...

从原理和源码梳理Springboot FatJar 的机制

一、概述SpringBootFatJar的设计,打破了标准jar的结构,在jar包内携带了其所依赖的jar包,通过jar中的main方法创建自己的类加载器,来识别加载运行其不规...

Maven初步——Maven的下载、配环境、换源、编译及运行

一.maven的基本概念相关定义:Maven是一个项目管理和构建工具,用于构建、发布和管理Java项目,用它的好处很多比如自动帮我们管理包依赖等。1.Maven的四大特性1.Maven引入了一个...

「曹工杂谈」Maven和Tomcat能有啥联系呢,都穿打补丁的衣服吗

前奏我们上篇文章,跟大家说了下,怎么调试maven插件的代码,注意,是插件的代码。插件,是要让主框架来执行的,主框架是谁呢,就是mavencore,可以称之为maven核心吧。maven核心,类似于...

取消回复欢迎 发表评论: