深入理解SpringIOC&AOP

IOC

控制反转(依赖注入)

某一接口具体实现类的选择控制权从调用类中移除,转交给第三方决定,即Spring容器借由Bean配置来进行控制。
又可以理解为让调用类对某一接口实现类的依赖关系由第三方注入,以移除调用类对某一接口实现类的依赖。

Spring通过配置文件或注解描述类与类之间的依赖关系,自动完成类的初始化和依赖注入工作。

IOC的核心:Class文件
Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息,如构造函数、属性和方法等。

ClassLoader重要方法

  • Class loadClass(String name)
    name参数指定类装载器需要装载类的名字
    该方法有个重载方法 loadClass(String name,boolean resolve)
    resolve参数告诉类装载器是否需要解析该类。
    在类初始化之前,应考虑进行类解析的工作,但并不是所有的类都需要解析。
    如果JVM只需要知道该类是否存在或找出该类的超类,那么就不需要进行解析.

  • getClassLoadingLock(String className)
    为类的加载操作返回一个锁对象

  • Class defineClass(String name,byte[] b,int off,int len)
    将类文件的字节数组转换成JVM内部的java.lang.Class对象

  • Class findSystemClass(String name)
    从本地文件系统载入Class文件
    该方法是JVM默认使用的装载机制

  • Class findLoadedClass(String name)
    查看ClassLoader是否已装入某个类

  • ClassLoader getParent()
    获取类装载器的父装载器

注:在访问private或protected成员变量和方法时,必须通过setAccessible(boolean access)方法取消Java语言检查,否则将抛出IllegalAccessException

Spring通过一个配置文件描述Bean及Bean之间的依赖关系,利用Java语言的反射功能实例化Bean并建立Bean之间的依赖关系。

BeanFactory&ApplicationContext

Bean工厂(BeanFactory)是Spring框架最核心的接口,它提供了高级IoC的配置机制。BeanFactory使管理不同类型的Java对象成为可能
应用上下文ApplicationContext建立在BeanFactory基础之上,提供了更多面向应用的功能,它提供了国际化支持和框架事件体系,更易于创建实际应用
BeanFactory是Spring框架的基础设施,面向Spring本身
ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合都可以直接使用ApplicationContext而非底层的BeanFactory

BeanFactory

BeanFactory是类的通用工厂,它可以创建并管理各种类的对象
Bean: Spring称这些被创建和管理的Java对象为Bean,所有可以被Spring容器实例化并管理的Java类都可以成为Bean

ApplicationContext

BeanFactory在初始化容器时,并未实例化Bean,直到第一次访问某个Bean时才实例化目标Bean
ApplicationContext则在初始化应用上下文时就实例化所有单实例的Bean
因此ApplicationContext的初始化时间会比BeanFactory稍长一些

WebApplicationContext

WebApplicationContext的初始化方式和BeanFactory、ApplicationContext有所区别,因为WebApplicationContext需要ServletContext实例,也就是说
它必须在拥有Web容器的前提下才能完成启动工作

Bean的生命周期

BeanFactory中Bean的生命周期

  1. 通过getBean()调用某一个Bean

  2. 调用InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation()方法

  3. 实例化

  4. 调用InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation()方法

  5. 调用InstantiationAwareBeanPostProcessor的postProcessPropertyValues()方法

  6. 设置属性值

  7. 调用BeanNameAware的setBeanName()方法,将配置文件中该Bean对应的名称设置到Bean中

  8. 调用BeanNameAware的setBeanFactory()方法,将BeanFactory容器实例设置到Bean中

  9. 调用BeanPostProcessor的postProcessBeforeInitialization()方法对Bean进行加工操作
    入参bean是当前正在处理的Bean,而beanName是当前Bean的配置名,返回的对象为加工处理后的Bean
    用户可以使用该方法对某些Bean进行特殊的处理,甚至改变Bean的行为.
    BeanPostProcessor在Spring框架中占有重要的地位,为容器提供对Bean进行后续加工处理的切入点,
    Spring容器所提供的各种"神奇功能"(如AOP、动态代理等)都通过BeanPostProcessor实施

  10. 调用InitializingBean的afterPropertiesSet()方法

  11. 通过init-method属性配置的初始化方法

  12. 调用BeanPostProcessor的postProcessAfterInitialization()方法,容器再次获得对Bean进行
    加工处理的机会

  13. 如果在Bean中指定的作用范围为"prototype",则将准备就绪的Bean交给调用者,调用者负责Bean后续生命的管理,Spring不再管理这个Bean的生命周期

  14. 如果在Bean中指定的作用范围为"singleton",则将Bean放入SpringIoC容器的缓存池中,并将Bean引用返回给调用者,Spring继续对这些Bean进行后续的生命管理

  15. 调用DisposableBean的destroy()方法,可以在此编写释放资源、记录日志等操作.

  16. 通过destroy-method属性配置的销毁方法,完成Bean资源的释放

ApplicationContext中Bean的生命周期

BeanFactoryPostProcessor工厂后处理器是容器级的,仅在应用上下文初始化时调用一次,其目的是完成一些配置文件的加工处理工作.

ApplicationContext和BeanFactory另一个最大的不同之处在于:前者会利用Java反射机制自动识别出配置文件定义的BeanPostProcessor、InstantiationAwareBeanPostProcessor和BeanFactoryPostProcessor,并自动将它们注册到应用上下文中,而后者需要在代码中通过手动调用addBeanPostProcessor()方法进行注册.

Bean作用域

  1. Singleton(默认)
    在SpringIoC容器中仅存在一个Bean实例,Bean以单实例的方式存在

  2. prototype
    每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行new XxxBean()操作

  3. request
    每次HTTP请求都会创建一个新的Bean.
    该作用域仅适用于WebApplicationContext()环境

  4. session
    同一个HTTP Session共享一个Bean,不同的HTTP Session使用不同的Bean.
    该作用域仅适用于WebApplicationContext()环境

  5. globalSession
    同一个全局Session共享一个Bean,一般用于Portlet应用环境.
    该作用域仅适用于WebApplicationContext()环境

注解

  • @Component - 对类进行标注,它可以被Spring容器识别,Spring容器自动将POJO转换为容器管理的Bean
  • @Repository - 用于对DAO实现类进行标注
  • @Service - 用于对Service实现类进行标注
  • @Controller - 用于对Controller实现类进行标注
  • @Autowired - Spring通过该注解实现Bean的依赖注入
  • @Lazy - 在Spring容器启动的时候,对于在Bean上标注@Lazy及@Autowired注解的属性,不会立即注入属性值,而是延迟到调用此属性的时候才会注入属性值
  • @PostConstruct- =init-method
  • @PreDestroy - =destroy-method
  • @Configuration - 为Spring容器提供Bean定义的信息

Spring内部工作机制

  1. 初始化BeanFactory
    根据配置文件实例化BeanFactory
    在obtainFreshBeanFactory()方法中,首先调用refreshBeanFactory()方法刷新BeanFactory,然后调用getBeanFactory()方法获取BeanFactory
    在这一步里,Spring将配置文件的信息装入容器的Bean定义注册表(BeanDefinitionRegistry)中,但此时Bean还未初始化

  2. 调用工厂后处理器
    根据反射机制从BeanDefinitionRegistry中找出所有实现了BeanFactoryPostProcessor接口的Bean,并调用其postProcessBeanFactory()方法

  3. 注册Bean后处理器
    根据反射机制从BeanDefinitionRegistry中找出所有实现了BeanPostProcessor接口的Bean,并将它们注册到容器Bean后处理器的注册表中

  4. 初始化消息源
    初始化容器的国际化消息资源

  5. 初始化应用上下文事件广播器
    用户可以在配置文件中为容器定义一个自定义的事件广播器,只要实现ApplicationEventMulticaster即可,Spring会通过反射机制将其注册成容器的事件广播器.
    如果没有找到配置的外部事件广播器,则Spring自动使用SimpleApplicationEventMulticaster作为事件广播器.

  6. 初始化其他特殊的Bean:由具体子类实现
    这是一个钩子方法,子类可以借助这个方法执行一些特殊的操作,如AbstractRefreshableWebApplicationContext就使用该方法执行初始化ThemeSource的操作

  7. 注册事件监听器
    Spring根据反射机制,从BeanDefinitionRegistry中找出所有实现org.springframework.context.applicationListener的Bean
    将它们注册为容器的事件监听器,实际操作就是将其添加到事件广播器锁提供的事件监听器注册表中

  8. 初始化所有单实例的Bean,使用懒加载模式的Bean除外
    初始化Bean后,将它们放入Spring容器的缓存池中

  9. 完成刷新并发布容器刷新事件
    创建上下文刷新事件,事件广播器负责将这些事件广播到每个注册的事件监听器中。
    Spring委托ApplicationEventMulticaster将事件通知给事件监听器

BeanDefinition

BeanDefinition是配置文件元素标签在容器中的内部表示
Spring通过BeanDefinition将配置文件中的配置信息转换为容器的内部表示,并将这些BeanDefinition注册到BeanDefinitionRegistry中.
一般情况下,BeanDefinition只在容器启动时加载并解析,除非容器刷新或重启,这些信息不会发生变化.

创建最终的BeanDefinition主要包括两个步骤

  1. 利用BeanDefinitionReader读取承载配置信息的Resource,通过XML解析器解析配置信息的DOM对象,简单地为每个生成对应的BeanDefinition对象
  2. 利用容器中注册的BeanFactoryPostProcessor对半成品的BeanDefinition进行加工处理,将以占位符表示的配置解析为最终的实际值,这样半成品的BeanDefinition就成为成品的BeanDefinition

InstantiationStrategy

org.springframework.beans.factory.support.InstantiationStrategy负责根据BeanDefinition对象创建一个Bean实例
Spring之所以将实例化Bean的工作通过一个策略接口进行描述,是为了可以方便地采用不同的实例化策略,以满足不同的应用需求

InstantiationStrategy类继承结构

InstantiationStrategy
SimpleInstantiationStrategy
是最常用的实例化策略,该策略利用Bean实现类的默认构造函数,带参构造函数或工厂方法创建Bean的实例
CglibSubclassingInstantiationStrategy
扩展了SimpleInstantiationStrategy,为需要进行方法注入的Bean提供了支持,它利用CGLib类库为Bean动态生成子类,在子类中生成方法注入的逻辑,然后使用这个动态生成的子类创建Bean的实例

InstantiationStrategy仅负责实例化Bean的操作,相当于执行Java语言中new的功能,它并不会参与Bean属性的设置工作
所以由InstantiationStrategy返回的Bean实例实际上是一个半成品的Bean实例,属性填充的工作留待BeanWrapper来完成

BeanWrapper

org.springframework.beans.BeanWrapper是Spring框架中重要的组件类
BeanWrapper相当于一个代理器,Spring委托BeanWrapper完成Bean属性的填充工作

BeanWrapper还有两个顶级类接口,分别是PropertyAccessor和PropertyEditorRegistry
PropertyAccessor接口定义了各种访问Bean属性的方法
PropertyEditorRegistry是属性编辑器的注册表

故BeanWrapper实现类BeanWrapperImpl具有三重身份

  1. Bean包裹器
  2. 属性访问器
  3. 属性编辑表注册表

IOC容器的初始化流程分析

  1. Resource定位
  2. BeanDefinition的载入
  3. 向IoC容器注册这些BeanDefinition

循环依赖

循环依赖就是循环引用,也就是两个或者两个以上的bean互相持有对方,最终形成闭环。
如:在进行getBean的时候,A对象中去依赖B对象,而B对象又依赖C对象,但是对象C又去依赖A对象,结果就造成A、B、C三个对象都不能完成实例化,出现了循环依赖。就会出现死循环,最终导致内存溢出的错误。
解决方法:
缓存+对象锁(待补充具体实现)

PropertyPlaceholderConfigurer

Spring提供了一个PropertyPlaceholderConfigurer,它能够使Bean在配置时引用外部属性文件,PropertyPlaceholderConfigurer实现了BeanFactoryPostProcessorBean接口,因而也是一个Bean工厂后处理器

PropertyPlaceholderConfigurer继承自PropertyResourceConfigurer类,后者有几个有用的protected方法,用于在属性使用之前对属性列表中的属性进行转换

容器事件

Java通过java.util.EventObject类和java.util.EventListener接口描述事件和监听器,某个组件或框架如需事件发布和监听机制,都需要通过扩展它们进行定义

事件源:事件的产生者,任何一个EventObject都必须拥有一个事件源。
事件监听器注册表:组件或框架的事件监听器不可能漂浮在空中,而必须有所依存,也就是说组件或框架必须提供一个地方保存事件监听器,这便是事件监听器注册表。
一个事件监听器注册到组件或框架中,其实就是保存在事件监听器注册表中。当组件和框架中的事件源产生事件时,就会通知这些位于事件的监听器注册表中的监听器。
事件广播器:它是事件和事件监听器沟通的桥梁,负责把事件通知给事件监听器。

事件体系其实是观察者模式的一种具体实现方式,它并没有任何神秘之处

Spring事件类结构

ApplicationEvent唯一构造函数是ApplicationEvent(Object source),通过source指定事件源,它有两个子类.
ApplicationContextEvent:容器事件,它拥有4个子类,分别表示容器启动,刷新,停止及关闭的事件.
RequestHandlerEvent:这是一个与Web应用相关的事件,当一个HTTP请求被处理后,产生该事件.只有在web.xml中定义了DispatcherServlet时才会产生该事件。它拥有两个子类,分别代表Servlet及Portlet的请求事件.

SpringMVC

DispatcherServlet内部逻辑

DispatcherServlet中的initStrategies方法
initMultipartResolver();//初始化上传文件解析器
initLocaleResolver();//初始化本地化解析器
initThemeResolver();//初始化主题解析器
initHandlerMappings();//初始化处理器映射器
initHandlerAdapters();//初始化处理器适配器
initHandlerExceptionResolvers();//初始化处理器异常解析器
initViewResolvers();//初始化视图解析器

DispatcherServlet默认组件

本地化解析器
主题解析器
处理器映射
处理器适配器
异常处理器
视图名称翻译器
视图解析器

aop基本概念

AOP:Aspect Oriented Programing的简称,最初被译为"面向方面编程",又称"面向切面编程"。

适用场景

只适合那些具有横切逻辑的应用场合,如性能监测,访问控制,事务管理及日志记录。

aop术语

  • 连接点(JoinPoint)
    程序执行的某个特定位置,一个类或一段程序代码拥有一些边界性质的特定点,这些代码中的特定点就被称为"连接点"。
    Spring仅支持方法的连接点,仅能在方法调用前、方法调用后、方法抛出异常时,及方法调用前后这些程序执行点织入增强。

  • 切点(PointCut)
    AOP通过"切点"定位特定的连接点。
    连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。
    在Spring中,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件,SpringAOP的规则解析引擎负责解析切点所设定的查询条件,找到对应的连接点。

  • 增强(Advice)
    增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。
    结合执行点的方位信息和切点信息,就可以找到特定的连接。

  • 目标对象(Target)
    增强逻辑的织入目标类

  • 引介(Introduction)
    一种特殊的增强,它为类添加一些属性和方法.这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,也可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。

  • 织入(Weaving)
    将增强添加到目标类的具体连接点上的过程。
    根据不同的实现技术,AOP有三种织入方式:

  1. 编译期织入,这要求使用特殊的Java编译器
  2. 类装载期织入,这要求使用特殊的类装载器
  3. 动态代理织入,在运行期为目标类添加增强生成子类的方式
    Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
  • 代理(Proxy)
    一个类被AOP织入增强后,就产生了一个结果类,它是融合了原类和增强逻辑的代理类。

  • 切面(Aspect)
    切面由切点和增强(引介)组成,它既包括横切逻辑的定义,也包括连接点的定义。
    SpringAOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入切面所指定的连接点中。

SpringAOP的代理机制

SpringAOP使用了两种代理机制
一种是基于JDK的动态代理,另一种是基于CGLib的动态代理。
之所以需要两种代理机制,是因为JDK本身只提供接口的代理,而不支持类的代理。

JDK动态代理

动态代理:开发者在运行期创建接口的代理实例。
JDK动态代理主要涉及java.lang,reflect包中的两个类:Proxy和InvocationHandler
InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起
Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象

使用JDK创建代理有一个限制,即它只能为接口创建代理实例

CGLib动态代理

CGLib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有的父类方法的调用并顺势织入横切逻辑

由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中的final或private方法进行代理

代理知识小结

SpringAOP的底层就是通过使用JDK或CGLib动态代理技术为目标Bean织入横切逻辑的。

虽然通过PerformanceHandler或CglibProxy实现了性能监视横切逻辑的动态织入,但这种实现方式存在3个明显需要改进的地方

  1. 目标类的所有方法都添加了性能监视横切逻辑,而有时这并不是我们所期望的,我们可能只希望对业务类中的某些特定方法添加横切逻辑

  2. 通过硬编码的方式指定了织入横切逻辑的织入点,即在目标业务方法的开始和结束前织入代码

  3. 手工编写代理实例的创建过程,在为不同类创建代理时,需要分别编写相应的创建代码,无法做到通用

CGLib所创建的动态代理对象的性能依旧比JDK所创建的动态代理对象的性能高不少,但CGLib在创建代理对象时所花费的时间却比JDK代理多
对于singleton的代理对象或者具有实例池的代理,因为无须频繁创建代理对象,所以比较适合采用CGLib动态代理技术,反之则适合采用JDK动态代理技术

增强

增强类型

Spring支持五种类型的增强
前置增强:org.springframework.aop.BeforeAdvice
表示在目标方法执行前实施增强
后置增强:org.springframework.aop.AfterReturningAdvice
表示在目标方法执行后实施增强
环绕增强:org.aopalliance.intercept.MethodInterceptor
表示在目标方法执行前后实施增强
异常抛出增强:org.springframework.aop.ThrowsAdvice
表示在目标方法抛出异常后实施增强
引介增强:org.springframework.aop.IntroductionInterceptor
表示在目标类中添加一些新的方法和属性

proxyFactory

Spring定义了org.springframework.aop.framework.AopProxy接口,并提供了两个final类型的实现类(Cglib2AopProxy、JdkDynamicAopProxy)
如果通过 ProxyFactory 的setInterfaces方法指定目标接口进行代理, 则 ProxyFactory 使用JdkDynamicAopProxy.
如果是针对类的代理,则使用Cglib2AopProxy
此外,还可以通过ProxyFactory的setOptimize(true)方法让ProxyFactory启动优化代理方式,这样,针对接口的代理也会使用Cglib2AopProxy.

ProxyFactoryBean是FactoryBean接口的实现类
FactoryBean负责实例化一个Bean
ProxyFactoryBean负责为其他Bean创建代理实例,它在内部使用ProxyFactory来完成这项工作.
可配置属性:
target:代理的目标对象
proxyInterfaces(interfaces):代理所要实现的接口,可以是多个接口

interceptorNames:需要织入目标对象的Bean列表,采用Bean的名称指定
这些Bean必须是实现了org.aopalliance.intercept.MethodInterceptor或org.springframework.aop.Advisor的Bean,配置中的顺序对应调用的顺序
singleton:返回的代理是否是单实例,默认为单实例
interceptorNames是String[]类型的,它接收增强Bean的名称而非增强Bean的实例。这是因为ProxyBeanFactory内部在生成代理类时,需要使用增强Bean的类,
而非增强Bean的实例,以织入增强类中所写的横切逻辑代码,因而可以说增强是类级别的。

optimize:当设置为true时,强制使用CGLib动态代理.对于singleton的代理,我们推荐使用CGLib;对于其他作用域类型的代理,最好使用JDK动态代理。
原因是虽然CGLib创建代理时速度慢,但其创建出的代理对象运行效率较高,而使用JDK创建代理的表现正好相反。
proxyTargetClass:是否对类进行代理(而不是对接口进行代理)。当设置为true时,使用CGLib动态代理。

切面

增强被织入目标类的所有方法中。假设我们希望有选择地织入目标类的某些特定方法中,就需要使用切点进行目标连接点的定位。
增强提供了连接点方位信息,切点进一步描述了织入哪些类的哪些方法上。

Spring支持两种方法匹配器:静态方法匹配器和动态方法匹配器.
静态方法匹配器:仅对方法名签名(包括方法名和入参类型及顺序)进行匹配
动态方法匹配器:会在运行期检查方法入参的值

切点类型

静态方法切点
动态方法切点
注解切点
表达式切点
流程切点
复合切点

切面类型

由于增强既包含横切代码,又包含部分连接点信息(方法前,方法后主方位信息),所以可以仅仅通过增强类生成一个切面。
但切点仅代表目标类连接点的部分信息(类和方法的定位),所以仅有切点无法制作出一个切面,必须结合增强才能制作出切面。

Advisor 代表一般切面,仅包含一个Advice。
PointcutAdvisor 代表具有切点的切面,包含Advice和Pointcut两个类,这样就可以通过类、方法名及方法方位等信息灵活地定义切面的连接点,提供更具适用性的切面。
IntroductionAdvisor 代表引介切面

PointcutAdvisor

主要有6个具体的实现类
DefaultPointcutAdvisor:最常用的切面类型,它可以通过任意Pointcut和Advice定义一个切面,唯一不支持的是引介的切面类型,一般可以通过扩展该类实现自定义的切面。
NameMatchMethodPointcutAdvisor:通过该类可以定义按方法名定义切点的切面
RegexpMethodPointcutAdvisor:对于按正则表达式匹配方法名进行切点定义的切面,可以通过扩展该实现类进行操作
StaticMethodMatcherPointcutAdvisor:静态方法匹配器切点定义的切面,默认情况下匹配所有的目标类
AspectJExpressionPointcutAdvisor:用于AspectJ切点表达式定义切点的切面
AspectJPointcutAdvisor:用于AspectJ语法定义切点的切面

动态代理&动态切面
动态代理的"动态"是相对于那些编译期生成代理和类加载期生成代理而言的。动态代理是运行时动态产生的代理。

在Spring中,不管是静态切面还是动态切面,都是通过动态代理技术实现的。

所谓静态切面,是指在生成代理对象时就确定了增强是否需要织入目标类的连接点上。
而动态切面,是指必须在运行期根据方法入参的值来判断增强是否需要织入目标类的连接点上。

posted @ 2021-03-20 15:00  cos晓风残月  阅读(115)  评论(0)    收藏  举报