• home > java > spring >

    Spring框架IOC和AOP的实现原理

    Author:zhoulujun Date:

    使用Spring框架的过程中,其实就是为了使用IOC(依赖注入),和AOP(面向切面编程),这两个是Spring的灵魂。IOC就是典型的工厂模式,通过sessionfactory去注入实例。AOP就是典型的代理模式的体现。

    我们是在使用Spring框架的过程中,其实就是为了使用IOC(依赖注入),和AOP(面向切面编程),这两个是Spring的灵魂

    spring的IoC容器是spring的核心,spring AOP是spring框架的重要组成部分。

    主要用到的设计模式有工厂模式和代理模式。

    • IOC就是典型的工厂模式,通过sessionfactory去注入实例。

    • AOP就是典型的代理模式的体现。

    IoC(Inversion of Control) 

    IoC(Inversion of Control)是指容器控制程序对象之间的关系,而不是传统实现中,由程序代码直接操控。控制权由应用代码中转到了外部容器,控制权的转移是所谓反转。 对于Spring而言,就是由Spring来控制对象的生命周期和对象之间的关系;IoC还有另外一个名字——“依赖注入(Dependency Injection)”。从名字上理解,所谓依赖注入,即组件之间的依赖关系由容器在运行期决定,即由容器动态地将某种依赖关系注入到组件之中。  


    在Spring的工作方式中,所有的类都会在spring容器中登记,告诉spring这是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。

    在系统运行中,动态的向某个对象提供它所需要的其他对象。  

    依赖注入的思想是通过反射机制实现的,在实例化一个类时,它通过反射调用类中set方法将事先保存在HashMap中的类属性注入到类中。 总而言之,在传统的对象创建方式中,通常由调用者来创建被调用者的实例,而在Spring中创建被调用者的工作由Spring来完成,然后注入调用者,即所谓的依赖注入or控制反转。 注入方式有两种:依赖注入和设置注入; IoC的优点:降低了组件之间的耦合,降低了业务对象之间替换的复杂性,使之能够灵活的管理对象。

    什么是DI机制?

    OC(控制翻转)是一种编程范式,可以在一定程度上解决复杂系统对象耦合度太高的问题,并不是Spring的专利。IOC最常见的方式是DI(依赖注入),可以通过一个容器,将Bean维护起来,方便在其他地方直接使用,而不是重新new。


    具体的讲:当某个角色 需要另外一个角色协助的时候,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在spring中 创建被调用者的工作不再由调用者来完成,因此称为控制反转创建被调用者的工作由spring来完成,然后注入调用者 因此也称为依赖注入。 

    spring以动态灵活的方式来管理对象 , 注入的两种方式:设置注入和构造注入。 

    • 设置注入的优点:直观,自然 

    • 构造注入的优点:可以在构造器中决定依赖关系的顺序。 

    为什么DI可以起到解耦的作用?

    一个软件系统包含了大量的对象,每个对象之间都有依赖关系,在普通的软件编写过程中,对象的定义分布在各个文件之中,对象之间的依赖,只能通过类的构造器传参,方法传参的形式来完成。当工程变大之后,复杂的逻辑会让对象之间的依赖梳理变得异常困难

    在Spring IOC中,一般情况,我们可以在XML文件之中,统一的编写bean的定义,bean与bean之间的依赖关系,从而增加逻辑的清晰度。而且,bean的创建是由Spring来完成的,不需要编程人员关心,编程人员只需要将精力放到业务的逻辑上面,减轻了思维的负担。


    AOP(Aspect Oriented Programming)

    AOP面向方面编程基于IoC,是对OOP的有益补充;

    这里建议温习一下《java反射机制原理剖析》、《Java注解(批注)的基本原理 》,算安利吧。

    AOP利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了 多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的 逻辑或责任封装起来,比如日志记录,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性

    AOP代表的是一个横向的关 系,将“对象”比作一个空心的圆柱体,其中封装的是对象的属性和行为;则面向方面编程的方法,就是将这个圆柱体以切面形式剖开,选择性的提供业务逻辑。而 剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹,但完成了效果。它可以让编程人员在不修改对象代码的情况下,为这个对象添加额外的功能或者限制

    Java中存在两种代理模式:

    • 静态代理:就是通过接口继承复用的方式来完成的, 代理类与被代理对象实现相同的接口,然后代理类里面会拥有一个被代理对象,代理类与被代理对象相同的方法,活调用被代理对象的方法,不过中间会加以限制,您翻开任何一本设计模式相关的书,翻到代理模式这一节,讲的就是它了。

    • 动态代理:动态代理就是允许我们在程序运行过程中,为动态生成的对象,动态的生成代理。显然,这比静态代理灵活太多了。

    Java默认提供了动态代理的实现方式,但是有限制,它要求被代理对象必须实现某一个接口。为了突破这一限制,为普通类也可以提供代理,CGLib这个库横空出世。

    实现AOP的技术两大类:

    • 采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行

    • 采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。

    Spring实现AOP:JDK动态代理和CGLIB代理 

    AOP涉及概念

    • JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理;其核心的两个类是InvocationHandler和Proxy。 

    • CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的,性能比JDK强;需要引入包asm.jar和cglib.jar。 

    • aopalliance,aop联盟包,统一类aop编程的一些概念,这个包里没有具体的类实现,而是定义了几个重要的概念接口,具体的aop实现,要遵从这些接口编程,才能达到一定的通用性。

    • aspectj包,实现了,通过一种固定的编程语言,通过这种简单的编程语言,我们可以定位到被代理的类,自动完成代理。

    AOP使用场景:

    • Authentication 权限检查        

    • Caching 缓存        

    • Context passing 内容传递        

    • Error handling 错误处理        

    • Lazy loading 延迟加载        

    • Debugging  调试      

    • logging, tracing, profiling and monitoring 日志记录,跟踪,优化,校准        

    • Performance optimization 性能优化,效率检查        

    • Persistence  持久化        

    • Resource pooling 资源池        

    • Synchronization 同步        

    • Transactions 事务管理


    spring 核心组件与概念

    IoC容器负责容纳bean,并对bean进行管理。在Spring中,BeanFactory是IoC容器的核心接口。它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。Spring为我们提供了许多易用的BeanFactory实现,XmlBeanFactory就是最常用的一个。该实现将以XML方式描述组成应用的对象以及对象间的依赖关系。XmlBeanFactory类将持有此XML配置元数据,并用它来构建一个完全可配置的系统或应用。

    IOC实现beans和contex

    beans包实现的核心关注点是BeanFactory,BeanFactory也叫作Bean容器,顾名思义,是用来盛放、管理bean的。

    context包实现的核心关注是ApplicationContext,ApplicationContext也是用来获取Bean的,但是它更高层,它的面向用户是Spring的使用者,而BeanFactory面向的用户更多是Spring开发者。BeanFactory定义了Bean初始化的流程,ApplicationContext定义了从XML读取,到Bean初始化,再到使用的过程。

    Bean在哪定义?

    刚才有说到,Spring通常通过xml文件,来统一的描述bean,bean与bean的依赖关系。所以说,bean的定义表述,发生在xml配置文件之中。这个XML文件就是我们需要读取的资源文件。

    因此,首要任务就是研究与读取XML资源文件相关的类。

    bean.io中存放的是读取资源文件的抽象概念。其中包含了三个类或者接口:

    1. Resource接口,这个接口只有一个方法,InputStream getInputStream() throws IOException;。实现这个接口的类就是一个抽象的资源,可以获取这个资源的输入流,从而获取其中的内容。

    2. UrlResource类,这个类实现了Resource接口,通过构造器传入一个url地址,代表的是这个url所对应的文件。

    3. ResourceLoader类,只有一个方法,public Resource getResource(String location)。输入url的文件地址(并不是真正的URL格式地址),来获取Resource。

    通过分析上面三个类、接口,我们知道,这个包完成了一个任务:通过ResourceLoader这个类,获取某一个地址的Resource,从而获取这个文件的输入流。因为使用了Resource概念,可以使用网络文件或者本地文件。

    Bean如何定义?

    1. BeanDefinition是Bean定义的核心概念,BeanDefinition包含了:bean的对象、bean的类类型、bean的名字,bean的所有属性。这个类对bean的基本信息做好一个包装。

    2. BeanDefinitionReader接口,只有一个方法:void loadBeanDefinitions(String location) throws Exception;,实现这个接口的类,具有将某一个文件中的所有bean定义载入的功能。所以BeanDefinitionReader定义了,在哪载入bean定义,至于载入到哪里、如何载入,稍后看具体实现。

    3. AbstractBeanDefinitionReader抽象类,上面刚说了实现了BeanDefinitionReader接口的类,具有将某一个文件中描述的bean定义载入的功能,AbstractBeanDefinitionReader就实现了这样一个抽象功能。它的作用就是定义,载入到哪和如何载入的问题。在这个类里面,有两个属性:Map<String,BeanDefinition> registry;ResourceLoader resourceLoader;registry是一个注册表,他保存的就是所有的Bean定义,Map结构,key是bean的名字,value就是BeanDefinition。resourceLoader描述了如何载入。

    4. XmlBeanDefinitionReader这是beans.xml包里面的唯一一个方法,也是最重要的方法之一。它继承了AbstractBeanDefinitionReader,实现了所有方法,解决了bean定义中:在哪载入、如何载入、载入到哪的三个大问题。这个类面向用户的方法有两个,一个是loadBeanDefinitions,毫无疑问,这个是必须的。另一个是getRegistry用来获取bean注册表,得到所有bean的信息,registry是bean们在内存中实际的家。但是这个getRegistry方法并不是面向用户的,而是面向ApplicationContext的。

    5. PropertyValuePropertyValue代表一种抽象概念,在xml中,bean的属性包括属性名和属性对象,PropertyValue就是这么一个实体。

    6. BeanReference代表的是Bean的属性不是真实对象,而是另一个bean的引用。

    Bean的组装全过程

    上面两部分是铺垫,而BeanFactory才是重点对象。beans.factory包中有三个类用来定义BeanFactory相关的概念。

    1. BeanFactory接口,只有一个方法:Object getBean(String name) throws Exception;,实现这个接口的类,就具有了得到一个bean的能力。

    2. AbstractBeanFactory类,较为复杂。详情后讲。

    3. AutowireCapableBeanFactory继承了AbstractBeanFactory,实现了applyPropertyValues方法,通过反射,将bean的所有属性,通过set方法注入进去。

    AbstractBeanFactory有三大属性:

    • beanDefinitionMap,类似于registry,但是他是BeanFactory里面私有的,代表的是这个BeanFactory里面暂时有哪些bean定义。

    • beanDefinitionNames代表里面,这个BeanFactory里面有哪些bean(名字)。

    • beanPostProcessors,代理处理器,AOP会用到,详情后讲。

    AbstractBeanFactory实现了几大功能:

    • getBean,这是主要功能,可以获取一个Bean对象。

    • registerBeanDefinition,面向ApplicationContext,用来将XML配置文件里面的bean定义注册到BeanFactory里面。

    • preInstantiateSingletons,面向ApplicationContext,用来在开始的时候,将所有的bean都初始化完毕,避免懒加载。

    • addBeanPostProcessor添加代理处理器。

    • getBeansForType,在BeanFactory里面,获取某一个类型的所有bean。

    经过上面的分析,我们可以知道BeanFactory完成了Bean初始化的整个流程。BeanFactory的工作流程如下:

    1. getBean, 在beanDefinitionMap里面得到bean,如果没有的话,先初始化。(为什么会没有,因为ApplicationContext读取xml文件时候,只是给BeanDefinition服了类类型,并没有赋值对象,这个对象还是需要BeanFactory通过反射生成的)。

    2. createBeanInstance,通过反射,根据BeanDefinition的类对象新建实体对象->将得到的bean对象赋值给beandefinition,然后将BeanDefinition里面的属性都注入到Bean里面,这就完成了doCreateBean。

    3. initializeBean就是调用BeanPostProcessor的postProcessBeforeInitilizztion方法和postProcessAfterIntilizatin方法,获取新的bean,这里会在aop中用到。

    好了,到这BeanFactory就讲完了,下面是更重要的ApplicationContext。

    ApplicationContext-用户与BeanFactory之间的桥梁

    beans.context包有三个类、接口,完成了ApplicationContext的基本功能。

    1. ApplicationContext接口,没有任何方法,只是继承了BeanFactory接口,暗示ApplicationContext与BeanFactory都是获取Bean的地方。

    2. AbstractApplicationContext抽象类,首先,它的构造函数接收入参BeanFactory,所以说ApplicationContext内部具有一个BeanFactory。类似于一种装饰器模式,但不是装饰器模式,类似于代理模式,但也不是代理模式。fresh方法分为三个步骤:1.loadBeanDefinitions,这个是一个模板方法,需要子类实现,它的作用就是从某一个地方读取BeanDefinition,然后写入到ApplicationContext自己的BeanFactory里面,这就是ApplicationContext与BeanFactory之间的联系,也就是ApplicationContext还负责了读取定义。2. registerBeanPostProcessors,这个就是在BeanFactory里面找到BeanPostProcessor,然后将他们放到BeanFactory的beanPostProcessors容器里面,方便BeanFactory初始化使用。3. onRefresh初始化一遍所有的bean。

    3. ClassPathXmlApplicationContext实现了loadBeanDefinitions的方法,将xml文件和BeanFactory结合在一起。

    ApplicationContext初始化流程

    总结-ApplicationContext获取bean流程

    ApplicationContext getBean


    aopallicance核心概念:

    1. Advice增强,说明这是一个,实现这个接口,说明这个类负责某一种形式的增强。

    2. Joinpoint连接点,表示切点与目标方法连接处的信息。

    3. MethodInterceptor继承了Interceptor接口,而Interceptor继承了Advice接口,是一种Advice,但是有一个方法invoke。这个方法需要一个参数MethodInvocation。

    4. MethodInvocation表示的是连接点的信息以及连接点函数的调用。
      结合上面的信息,我们发现,其实MethodInterceptor的invoke方法,调用的就是MethodInvocation的proceed方法,而这个proceed方法呢,应该调用的肯定是Method.invoke方法。所以,这是一种变相调用method.invoke的方式。为什么这样做呢,猜一猜的话,肯定是为了代码的复用,哈哈哈,这是废话。
      在Spring中,还定义了几个核心概念:

    5. Pointcut,切点,可以定位类以及方法

    6. Advisor,可以获取一个增强。

    7. PointcutAdvisor,定义了哪些方法,具有什么类型的增强。

    8. MethodMatcher表示某一个方法是否符合条件

    9. ClassFilter 定义了某个类是否符合条件。

    10. TargetSource被代理的对象,包括对象本身,类类型、所有接口

    11. AdvisedSupport代理相关的元数据,包括被代理的对象,增强等。

    12. ProxyFactory,代理工厂,可以获得一个代理对象,同时具有AdvisedSupport的所有特性。

    13. Cglib2AopProxy,使用cglib实现的动态代理类,继承了AbstractAopProxy抽象类,这个类的主要方法就是getProxy,通过什么呢,通过AdvisorSupport。

    14. ReflectiveMethodInvocation可以获取连接点的信息,代理的对象等。

    15. JdkDynamicAopProxy,和Cglib2AopProxy类一个作用,通过AdvisorSupport来getProxy,不过是使用Java自带的动态代理实现的。
      其中,ProxyFactory是获取一个代理对象的直接工厂,而这个代理对象,可以通过Cglib2AopProxy产生,也可以通过JdkDynamicAopProxy产生。

    Spring AOP之所以能够为动态生成的Bean提供代理,得益于PostProcessor接口。我们会议IOC初始化流程中,最后一部,就是得到BeanFactory之中所有继承了PostProcessor接口的bean,调用它们的postProcessBeforeInitilization、postProcessAfterInitilization方法,来代理bean,生成新的bean。

    基于这个突破口,我们只需要在xml配置文件中,放入PostProcessor对象,Spring就会自动的用这写对象,来代理真正的对象。

    aop目录结构分析



    参考内容:

    Spring IOC和AOP 原理彻底搞懂 https://blog.csdn.net/luoshenfu001/article/details/5816408

    Spring框架IOC和AOP的实现原理与详解 https://blog.csdn.net/baidu_20876831/article/details/78956220

    Spring原理初探----IOC、AOP https://www.jianshu.com/p/c403609185a5

    Spring IOC和AOP 原理彻底搞懂  https://blog.csdn.net/luoshenfu001/article/details/5816408

    Spring原理初探----IOC、AOP https://www.jianshu.com/p/c403609185a5



    转载本站文章《Spring框架IOC和AOP的实现原理》,
    请注明出处:https://www.zhoulujun.cn/html/java/spring/2020_0628_8486.html

    延伸阅读: