SSM《SSM学习》重点

为什么要有Spring?

  • EJB太重,且性能不好。使用时,需要实现一大堆接口。

Spring的核心功能:

  • IOC:对象的创建权交给了Spring,无需手动new。
  • DI:依赖注入,无需手动setXXX();
  • AOP:面向切面。
  • 事务:统一的事务管理接口。
  • 容器:提供了应用对象的容器。
  • 生态:IOC和AOP使其具备了糅合其他应用的能力。

IOC

IOC重要接口

Spring提供两种容器类型:

  • BeanFactory。提供基础的IoC容器服务。默认采用lazy-load,所以启动也快。对象用到的时候才会生成及绑定。
  • ApplicationContext。间接继承自BeanFactory,并扩展了其它接口功能。提供了事件发布等高级特性。对象在容器启动后就全部加载了。

以下是两者关系,可以看到ApplicationContext还继承了其他三个接口:

image-20200917174308456

几个重要的接口

  • BeanFactory接口:Bean容器,定义了容器内Bean的访问方式。
  • BeanDefinition接口:“Bean定义”的Bean,封装了各个文件中的Bean定义。
  • BeanDefinitionRegistry接口:定义了Bean的注册逻辑。

上述代码中,DefaultListableBeanFactory同时实现了BeanFactory接口和BeanDefinitionRegistry接口。各个类关系如下图:

image-20201229203330529

关于BeanDefinition:

  1. 每个受管的对象,在容器中都会有一个BeanDefinition的实例(instance)与之对应,该实例负责保存对象的所有必要信息,包括其对应的对象的class类型、是否是抽象类、构造方法参数以及其他属性等
  2. 当客户端向BeanFactory请求对象时,BeanFactory会根据BeanDefinition的信息为客户端返回一个完备可用的对象实例。
  3. 注意到BeanDefinition的Bean也是Bean,所以bean定义的Bean要先注册到容器。
  4. BeanDefinition的两个主要实现类:RootBeanDefinitionChildBeanDefinition

BeanDefinitionReader接口:用于将配置文件内容映射到BeanDefinition

采用外部文件配置时,一般处理顺序如下:

  1. BeanDefinitionReader把文件映射到BeanDefinition:由文件格式对应的BeanDefinitionReader实现类相应的配置文件内容映射到BeanDefinition。
  2. BeanDefinition注册到BeanDefinitionRegistry:BeanDefinitionReader将映射后的BeanDefinition注册到一个BeanDefinitionRegistry。
  3. BeanDefinitionRegistry完成Bean的注册和加载

BeanDefinitionReader相当于桥梁:大部分工作,包括解析文件格式、装配BeanDefinition等,都是由BeanDefinitionReader的相应实现类来做的,BeanDefinitionRegistry只负责保管而已。BeanDefinitionReader相当于一个桥梁,一头连接文件路径读入文本,然后装配为BeanDefinition,最后另外一头注册到BeanDefinitionRegistry

常见bean属性

autowire5种自动绑定模式

  • no
  • byName根据字段的名字去容器中查找id与该字段名一致的bean,注入到该字段
  • byType根据字段的类型去容器中查找id与该字段的类型一致的bean,注入到该字段。如果找到多个,需要手动指定“该选用哪一个”。
  • constructor:是针对构造方法参数的类型进行的自动绑定,它同样是byType类型的绑定模式。
  • autodetect:byType和constructor模式的结合体。若对象拥有默认无参数的构造方法,则容器优先考虑byType的自动绑定模式。否则,使用constructor模式。

scope属性:对象的作用域,或生命周期

  • singleton
  • prototype
  • 2.0后引入了只用于Web应用的另外三种:
    • request:*Spring容器,即xmlwebApplicationContext会为每个HTTP请求创建一个全新的对象来给当前请求使用。请求结束后,该对象实例的生命周期结束。*request可以看作是prototype的一种特例,语意上差不多,只是使用场景不太一样。
    • session:Spring容器会为每个独立的session创建一个全新的对象实例。
    • *global sessionglobal session应用在基于portlet的Web应用程序中才有意义,它映射到portlet的global范围的session。用在普通的基于servlet的Web应用中时,容器会把它看作是普通的session
  • bean相当于是构建对象的模板,而scope则指明需要根据这个模板构造多少对象实例,又该让这些构造完的对象实例存活多久

depends-on属性:指定非显式依赖

  • 前面提到的所有其他元素用于标记显示依赖depends-on用于标记非显示依赖

  • 可以通过前面提到的所有元素,来显示的指定bean之间的依赖关系。这样在Spring在初始化当前bean之前,就可以先初始化它依赖的其他bean。

azy-init属性

  • 延迟初始化,即懒加载。
  • 主要针对ApplicationContext容器的bean。与BeanFactory默认懒加载不同,ApplicationContext在容器启动时,就会马上对所有的“singleton的bean定义”进行实例化。

bean元素与方法相关的子元素

预设概念:主体对象即主对象。依赖对象即主体对象的属性字段,该字段是一个对象。

<lookup-method>子元素:

  • 场景:bean元素scope属性的“漏洞”:依赖对象(即属性)的scope为prototype,主体对象为singleton。Bean注入完成后,通过get方法获取依赖对象,但由于bean注入已经完成,依赖对象已绑定,所以每次返回的都是同一个对象,和依赖对象的prototype语义“不一致”。即,主体对象是单例,它的属性,即依赖对象是多例,此时每次返回都是单例。怎么处理。
  • Spring提供的处理上述问题的手段。通过方法来注入对象

方法替换:<replaced-method >子元素

  • 方法注入是通过方法来注入主体对象的依赖对象
  • 方法替换则类似于AOP,直接把方法逻辑都给换掉了。可以实现简单的方法拦截功能。

IOC实现原理

SpringIOC的实现主要可分为两个阶段容器启动阶段Bean实例化阶段

为了便于扩展,在上述两个阶段中,又加入了相应的各种容器扩展点

两阶段示意图:

image-20210223185857875

容器加载阶段

配置元数据 –> BeanDefinitionReader–> BeanDefinition –>BeanDefinitionRegistry –> BeanFactoryPostProcessor

Bean实例化阶段

此时所有的BeanDefinition 都注册到了BeanDefinitionRegistry

某个请求直接调用容器的getBean()方法,或是根据依赖关系隐式调用到getBean()时,就会激活Bean实例化阶段

主要流程

  1. 先检查所请求的对象之前是否已经初始化。
  2. 若没有,则根据注册的BeanDefinition实例化被请求对象,并为其注入依赖。
  3. 实例化完毕后,若该对象实现了某些回调接口,则继续根据回调接口的要求来装配它
  4. 对象装配完毕后,容器立即将其返回请求方使用。
容器启动阶段的扩展点:BeanFactoryPostProcessor

(1)用途

对已经注册到容器的BeanDefinition进行修改。相当于在容器实现的第一阶段最后加入一道工序,让我们对最终的BeanDefinition做一些额外的操作,比如修改其中bean定义的某些属性,为bean定义增加其他信息等。

(2)如何使用

如何使用BeanFactoryPostProcessor

  1. 实现org.springframework.beans.factory.config.BeanFactoryPostProcessor接口
  2. 同时实现org.springframework.core.Ordered接口:因为可能有多个处理器,需要明确PostProcessor的执行顺序。

(3)常用的实现类

Spring已经提供了一些现成的BeanFactoryPostProcessor实现类,供我们使用:

  • org.springframework.beans.factory.config.PropertyPlaceholderConfigurer:属性变量赋值,解析xml文件中的占位符。如果Bean属性使用的是占位符,那么在第一阶段结束时,BeanFactory中保存的对象属性信息还是以占位符的形式存在的
  • org.springframework.beans.factory.config.PropertyOverrideConfigurer:属性覆盖。根据自己的properties文件,直接把bean的属性值覆盖掉,和占位符无关。
  • org.springframework.beans.factory.config.CustomEditorConfigure:注册自定义的属性编辑器。。说简单点就是,配置中的字符串到某类型怎么转换。例如对yyyy/MM/dd形式的日期格式转换提供支持等。

(4)两种容器的不同使用方式

BeanFactory容器和ApplicationContext容器使用方式不太一样:

  • BeanFactory:需要手动代码应用所有的BeanFactoryPostProcessor

    // 声明将被后处理的BeanFactory实例
    ConfigurableListableBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("...")); 
    // 声明要使用的BeanFactoryPostProcessor 
    PropertyPlaceholderConfigurer propertyPostProcessor = new PropertyPlaceholderConfigurer(); 
    propertyPostProcessor.setLocation(new ClassPathResource("...")); 
    // 关键步骤:执行后处理操作
    propertyPostProcessor.postProcessBeanFactory(beanFactory);
    
  • ApplicationContext会自动识别配置文件中的BeanFactoryPostProcessor并应用它。我们只需要配置注入即可。

    <beans>
        <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 
            <property name="locations"> 
                <list> 
                    <value>conf/jdbc.properties</value> 
                    <value>conf/mail.properties</value> 10 </list> 
            </property> 
        </bean> 
    </beans>
    

(5)详细介绍常用的3个BeanFactoryPostProcessor实现类

略。

Bean实例化阶段的扩展点

以下两种情况会触发Bean实例化:

  • BeanFactory:对象实例化默认是采用延迟初始化。例如A依赖B,对A进行初始化,此时会先隐式初始化依赖项B
  • ApplicationContext:会实例化所有的bean。在启动阶段完成后,会立即*调用所有注册到ApplicationContext容器的Bean的实例化方法getBean()*。也就是说,当我们拿到ApplicationContext的引用时,所有Bean已实例化完毕。参考AbstractApplicationContext的refresh()方法。

Bean的实例化过程,见下图:

image-20210228115438118

1.BeanWrapper

总述:

  • 第一步,获取BeanWrapper实例:根据BeanDefinition、CglibSubclassingInstantiationStrategy、bean定义类型获取。
  • 第二步,设置Bean属性:用BeanWrapper设置。参考PropertyEditor。使用BeanWrapper方式取代了反射。
2.各种Aware接口

其实就是相当于再给Bean一堆可能用得着的属性。如beanFactory对象,可以让bean有从容器中获取其他bean的能力。

Aware接口的使用时机及作用

  1. 第一步,对象实例化完成。
  2. 第二步,相关属性及依赖设置完成。
  3. 第三步,检查当前对象是否实现了一堆Aware结尾的接口。若实现,则将这些Aware接口中定义的依赖注入到当前对象
3.BeanPostProcessor

BeanPostProcessor声明了两个方法,分别在Bean进行前置处理后置处理

  • postProcessBeforeInitialization():前置处理。
  • postProcessAfterInitialization():后置处理。

用途:

  1. 用来处理标记接口实现类。典型的就是,前面提到的,ApplicationContext的一系列Aware,就是用BeanPostProcessor的实现类ApplicationContextAwareProcessor实现的,通过它的的postProcessBeforeInitialization()来实现的。
  2. 对对象实现代理,替换或者字节码增强当前对象实例等:例如AOP使用BeanPostProcessor来为对象生成相应的代理对象。

BeanPostProcessor用法如下

  • 标注需要解密的实现类,例如定义一个接口去标记。
  • 实现一个BeanPostProcessor,判断对象是否符合条件,符合时就进行处理。
  • 把实现的BeanPostProcessor注册到容器即可。

代码示例:

public class PasswordDecodePostProcessor implements BeanPostProcessor { 
    public Object postProcessAfterInitialization(Object object, String beanName) 
        throws BeansException { 
        return object; 
    } 
    public Object postProcessBeforeInitialization(Object object, String beanName) 
        throws BeansException { 
        // 对所有符合条件的对象,进行处理
        if(object instanceof PasswordDecodable){ 
            String encodedPassword =  ((PasswordDecodable)object).getEncodedPassword();
            String decodedPassword = decodePassword(encodedPassword); 
            ((PasswordDecodable)object).setDecodedPassword(decodedPassword); 
        } 
        return object; 
    } 
    private String decodePassword(String encodedPassword) { 
        // 实现解码逻辑
        return encodedPassword; 
    } 
} 
4.InitializingBean和init-method

InitializingBean作用:在对象实例化过程调用过“BeanPostProcessor的前置处理”后,若当前对象实现了InitializingBean接口,则会*调用其afterPropertiesSet()方法进一步调整对象实例状态*。因为有些业务对象在实例化完成后,还要进行一些统一的业务处理,才是可用的。

使用举例:

开源的库ObjectLabKit,用户类在使用它提供的Datecalculator类进行外汇结算时,需要先向Datecalculator对象提供计算时需要排除的休息日信息。

也就是说,用户类的Bean,需要一个初始化方法,来为Datecalculator类型的工厂,先提供休息日信息。

5. DisposableBean与destroy-method

DisposableBean作用:以上,实例化、注入、设置、调用完成后,容器会检查singleton类型(prototype由用户进行销毁)的bean实例,看其是否实现了org.springframework.beans.factory.DisposableBean接口,或是是否通过<bean>的destroy-method属性指定了自定义的对象销毁方法。如果是,就为该实例注册一个用于对象销毁的回调(Callback),对象销毁之前,执行该回调方法

loC容器之ApplicationContext

ApplicationContextBeanFactory功能的基础上主要进行了以下扩展:

  • BeanFactoryPostProcessor、BeanPostProcessor以及其他特殊类型bean的自动识别
  • 容器启动后bean实例的自动初始化
  • 统一的资源加载策略
  • 国际化的信息支持
  • 容器内事件发布
统一资源加载策略

Spring提出了一套基于以下两个接口的资源策略:

  • org.springframework.core.io.Resource资源抽象策略,即资源的表示,如何描述资源,包括形式和场合,以及如何访问/交互该资源。
  • org.springframework.core.io.ResourceLoader资源加载策略,即资源的查找,如何定位/加载资源。

为何Spring要提供新的资源加载策略

  1. JavaSE提供的标准类java.net.URL描述力不足:即资源的表示不够全面。资源可以是任何形式,如二进制、字节流、文件等。资源可以存在于任何场合,如文件系统、Java应用的Classpath、URL可以定位的地方等。但java.net.URL只限于基于HTTP、FTP、File等协议的网络形式发布的资源资源定位。
  2. JavaSE提供的标准类java.net.URL职责划分不清:没有把资源的查找资源的表示划分开。资源查找后,返回的形式多种多样,没有用统一的资源抽象接口进行抽象。

ApplicationContext与ResourceLoader

ApplicationContext接口:*ApplicationContext接口继承了ResourcePatternResolver接口,而ResourcePatternResolver接口实现了ResourceLoader接口*。这就是ApplicationContext支持Spring内统一资源加载策略的原因。

国际化信息支持

Spring在Java SE的基础上,进一步抽象了国际化信息的访问接口。

容器内部事件发布

ApplicationContext提供了容器内事件发布功能,基于Java SE的标准自定义事件类实现

下面先来看下JavaSE的事件发布怎么实现。

1.JavaSE的自定义事件发布

JavaSE的自定义事件发布,主要依赖以下两个

  • java.util.EventObject类:用于扩展所有的自定义事件类型。
  • java.util.EventListener接口:用于扩展时间的监听器。

自定义事件发布实现方式如下:

  1. 定义事件:给出自定义事件类型,扩展EventObject即可。
  2. 定义监听器:实现针对自定义事件类的事件监听器接口。
  3. 定义发布者:组合事件类和监听器,发布事件。其实就是生产者-消费者模式。

2.Spring 的容器内事件发布类结构

Spring容器内事件的各个类如下:

  • 事件抽象类:org.springframework.context.ApplicationEvent
  • 监听器接口:org.springframework.context.ApplicationListener
  • 发布者接口:ApplicationEventPublisher。ApplicationContext继承了它。

3.Spring 容器内事件发布的应用

Spring的事件机制主要是用来在单一容器内使用的,虽然可以配合Remoting实现远程,但是会很奇怪。

下面单一容器内是如何使用的示例 。

(1)首先我们要有发布者,怎么注入呢?

  • 使用ApplicationEventPublisherAware接口:容器启动时会自动识别Bean是否实现了该接口,然后将ApplicationContext容器本身作为ApplicationEventPublisher注入当前对象,因为ApplicationContext容器本身就是一个ApplicationEventPublisher。
  • 使用ApplicationContextAware接口:直接使用ApplicationContext当然可以,很好理解。

(2)订阅者:MethodExecutionEventListener

ApplicationListener只通过void onApplicationEvent(ApplicationEvent event)这一个事件处理方法来处理事件。

public class MethodExecutionEventListener implements ApplicationListener { 
    public void onApplicationEvent(ApplicationEvent evt) { 
        if(evt instanceof MethodExecutionEvent) 
        { 
            // 执行处理逻辑
        } 
    } 
}

(3)MethodExeuctionEventPublisher

发布者,直接使用aware注入的eventPublisher来发布事件即可,不用再自己实现发布逻辑.

(4)注册到ApplicationContext容器

MethodExeuctionEventPublisherMethodExecutionEventListener注册到ApplicationContext容器即可。

循环依赖

略。

image-20220712191711857

AOP

切面的实现

实现:加载配置,将切面类的所有切面方法根据使用的注解生成对应Advice,并将Advice连同切入点匹配器和切面类等信息一并封装到Advisor。

AOP的创建工作是交给AnnotationAwareAspectJAutoProxyCreator来完成的。

xml标签 -》 ConfigBeanDefinitionParser加载转换器转换标签 -》 AspectJAutoProxyBeanDefinitionParser加载转换器转换标签 -》AnnotationAwareAspectJAutoProxyCreator创建代理 -》最终通过BeanPostProcessor处理完毕。

切面方法转成Advisor:上述方法本质上的思路是:用DCL双重锁的单例实现方式,拿到切面类里的切面方法,将其转换成advisor(并放入缓存中)。

回头看,主要是处理使用了@Aspect注解的切面类,然后将切面类的所有切面方法根据使用的注解生成对应Advice,并将Advice连同切入点匹配器和切面类等信息一并封装到Advisor的过程。

postProcessAfterInitialization

  • 有了Adisor, 注入到合适的位置并交给代理(cglib和jdk)实现了。

著作权归https://pdai.tech所有。 链接:https://pdai.tech/md/spring/spring-x-framework-aop-source-2.html

Spring AOP初始化的过程,具体如下:

  1. IOC Bean加载方法栈中找到parseCustomElement方法,找到parse aop:aspectj-autoproxy的handler(org.springframework.aop.config.AopNamespaceHandler)

  2. AopNamespaceHandler注册了<aop:aspectj-autoproxy/>的解析类是AspectJAutoProxyBeanDefinitionParser

  3. AspectJAutoProxyBeanDefinitionParser的parse 方法 通过AspectJAwareAdvisorAutoProxyCreator类去创建

  4. AspectJAwareAdvisorAutoProxyCreator

    实现了两类接口,BeanFactoryAware和BeanPostProcessor;根据Bean生命周期方法找到两个核心方法:postProcessBeforeInstantiation和postProcessAfterInitialization

    1. postProcessBeforeInstantiation:主要是处理使用了@Aspect注解的切面类,然后将切面类的所有切面方法根据使用的注解生成对应Advice,并将Advice连同切入点匹配器和切面类等信息一并封装到Advisor
    2. postProcessAfterInitialization:主要负责将Advisor注入到合适的位置,创建代理(cglib或jdk),为后面给代理进行增强实现做准备。

代理的创建

postProcessAfterInitialization的方法,即Spring AOP的代理(cglib或jdk)的创建过程。

获取所有advisor后,如果有advisor,则说明需要增强,即需要创建代理,创建代理的方法如下:

Spring默认在目标类实现接口时是通过JDK代理实现的,只有非接口的是通过Cglib代理实现的。当设置proxy-target-class为true时在目标类不是接口或者代理类时优先使用cglib代理实现。

CGLib代理源码

JDK代理源码

Mybatis


转载请注明来源