《Spring揭秘》学习部分总结

注:本文只是自己的部分学习记录,如果错漏指出,欢迎批评指正。文中部分截图已不知来处,侵权私删。

一、Spring IoC

1. IoC基本概念

Spring只是实现了IoC的其中一种框架

作用:

  1. 统一对象的构建方式
  2. 自动维护对象之间的依赖关系
  3. 降低系统的实现成本

IoC与DI的关系:可以看作目标与实现方式的关系

IoC容器是一个IoC service provider,有三种实现方式

  • 直接编码(注册,绑定)
  • 文本文件,如xml文件
  • 元数据(Google Guice)

IoC的作用从两个角度来看

  • 解耦

    不需要更改代码,而是更改配置文件。其实从这个角度而言使用注解开发反而又增加了耦合性

  • 简化获取依赖bean的过程

    将获取依赖bean的控制权给IoC容器,而不用自己创建,也不用关心被依赖对象与其它对象之间的依赖关系。

1.1 bean创建的方式

<beans>是xml文件的最顶层标签,有一些属性,比如default-lazy-init、default-autowire等可以对<bean>标签做一些全局设置。需要注意的是设置了lazy-init属性不一定就会延迟初始化,比如A对象非延迟创建且依赖延迟创建的B对象,那么A在初始化的时候就会创建B。

  • 基于className构建(反射)

    <bean class="className"></bean>
    需要类中有空参构造,这里指的是bean的创建方式,所以没涉及到注入
    
  • 基于构造函数

    <bean> 
    <construct></construct>
    </bean>
    
  • 基于工厂(常用于创建第三方类库bean,比如某一个类依赖另一个接口,那么应该通过工厂方法获得,而不要自己new)

    基于静态工厂
    <bean class="className"  factory-method="方法名">
    </bean>
    
    实例工厂用的标签是factory-bean 和factory-method组合
    <bean id="name" class="实例工厂全类名"></bean>
    <bean id="beanName" factory-bean="name" fatory-method="工厂里的方法名"></bean>
    
  • 基于FactoryBean(SqlSessionFactoryBean用了这种方式)

    实现接口FactoryBean,复写getObeject等方法,调用getbean(id名)获取的是getObeject方法的返回值对象,如果想获取FactoryBean实现类本身bean,可以在getbean(&id名)
    <bean id=""  class="实现接口FactoryBean的全类名"></bean>
    

在这里插入图片描述

在这里插入图片描述

1.2 注入的方式

  • 构造方法

    对象构造完后即可使用依赖bean,缺点是注入的bean过多时参数列表过长,而且有多个同类型对象时,通过反射获取构造对象比较麻烦

  • setter方法

    需要有set和空参构造方法

  • 自动注入

    通过Autowire属性

    值有byName和byType等,默认是byName

    如果是@Autowire注解的话是先byType再byName

  • 接口注入(基本不用,需另写接口类,基于cglib实现)

    写一个抽象类,定义一个抽象方法,返回值为注入的bean类型

    <bean class="想创建的bean的类名" look-up-method name="返回值为想注入bean类型的方法的方法名"/>

    可以用来解决单例bean中依赖多例bean的问题和创建有状态bean,实际应用可以利用@lookup注解

  • 实现aware接口

    实现BeanFacoryAware或者ApplicationContextAware接口,获取容器,再调用getBean方法

2. Spring的IoC

2.1 Spring IoC职责

Spring IoC容器不仅仅是一个IoC service provider

在这里插入图片描述

2.2 容器分类

Spring提供了两种容器BeanFactory和ApplicationContext

  • BeanFactory

    基础类型IoC容器,提供完整的IoC服务支持。如果没有特殊指定,默认采用==延迟初始化==策略(lazy-load)。只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入操作。所以,相对来说,容器启动初期速度较快,所需要的资源有限。对于资源有限,并且功能要求不是很严格的场景,BeanFactory是比较合适的IoC容器选择。

  • ApplicationContext

    ApplicationContext在BeanFactory的基础上构建,是相对比较高级的容器实现,除了拥有BeanFactory的所有支持,ApplicationContext还提供了其他高级特性,比如事件发布、国际化信息支持等,这些会在后面详述。ApplicationContext所管理的对象,在该类型容器启动之后,默认全部初始化并绑定完成(AbstractApplicationContext的refresh方法里getbean)。所以,相对于BeanFactory来说,ApplicationContext要求更多的系统资源,同时,因为在启动时就完成所有初始化,容器启动时间较之BeanFactory也会长一些。在那些系统资源充足,并且要求更多功能的场景中,ApplicationContext类型的容器是比较合适的选择。

两者之间的依赖关系如下图
在这里插入图片描述在这里插入图片描述

2.3 容器实现阶段

在这里插入图片描述

容器启动阶段:BeanDefinitionReader读取xml文件,将元数据解析分析后创建对应BeanDefinition,然后再将其注册到BeanDefinitionRegistry,这样容器就启动了。

Bean的实例化阶段:通过容器获取bean时,容器首先会检查bean是否已初始化,如果没有会根据BeanDefinition的信息初始化bean并为其注入依赖。

XML-bean与BeanDefinition的关系

没有 id和name因为存储时是按键值队形式存储在map中的,其中id为key值,而name可看做是bean别名,存储在AliasRegistry,getBean的时候如果是没有设置name属性的bean,默认是“全类名+#+数字”。
在这里插入图片描述
在这里插入图片描述

2.3.1 Servlet启动时容器启动
参考资料:
https://zhuanlan.zhihu.com/p/63212113
https://blog.csdn.net/qq_31854907/article/details/86300901
https://zhuanlan.zhihu.com/p/63588203//两个容器扫描位置配置
https://zhuanlan.zhihu.com/p/63826932//扫描标签解析

WEB容器启动过程:
在这里插入图片描述
具体流程是,容器启动时,会创建xml配置文件中配置的实现了接口ServletContextListener的监听器实例,执行contextInitialized() 方法,创建配置的filter实例,执行init()方法,创建servlet实例,执行init方法。

在Spring项目中,Tomcat启动时会创建一个全局的上下文环境servletContext ,先执行监听器中的方法。容器调用web.xml中配置的contextLoaderListener监听器 ,它会调用父类ContextLoader的initWebApplication-Context()初始化环境WebApplicationContext(即IoC容器,默认XmlWebApplicationContext),加载context-param指定的配置文件(springxml文件)信息到IoC容器中。WebApplicationContext在ServletContext中以键值对的形式保存。此时开始IoC容器的初始化。

IoC容器的初始化由refresh()方法启动(这个方法在ConfigurableApplicationContext接口做了定义,在AbstractApplicationContext抽象类中做了实现具体实现,在AnnotationConfigApplicationContext类的构造函数中直接做了引用),容器启动包括BeanDefinition的Resouce定位(获取资源位置的Resource对象)、载入(解析xml配置文件,获取各个Element信息)和注册(将BeanDefinition放入HashMap中)三个基本过程。

首先是spring容器
在这里插入图片描述
中间过滤器方法执行完后,执行servlet的init方法,启动MVC容器
在这里插入图片描述

可以看到其实有两个父子容器

关于父子容器的问题:

疑问一: 单例的bean在父子容器中存在一个实例还是两个实例?
答:父子容器可以注册id相同的bean,单例bean指的是在一个容器中单例。如果Spring和SpringMVC配置都能扫描到的话,那会初始化两次,Spring 容器先初始化bean,MVC容器再初始化bean,所以应该是两个bean。不过为了防止浪费资源,spring和springMVC配置的扫描包名尽量不要有交集。项目运行期间,获取bean的时候,如果子容器没有会自动取父容器中找。

疑问二:为啥不把所有bean 都在子容器中扫描?
答: 网上很多文章说子容器不支持AOP,其实这是不对的。因为正常会有AOP的相关配置都在Spring容器中配置,如果都迁移到MVC配置文件,则所有bean都在子容器中,相当于只有一个容器了,所以也就实现了AOP,缺点是不利于扩展。

疑问三:把bean都注册到父容器中访问报404
答:SpringMVC在注册HanderMapping关联url和Controller的时候,是回调了initHandlerMethods方法,它默认是去子容器中获取是否有@Controller注解的bean(源码在AbstractHandlerMethodMapping类中),如果获取不到自然就不能访问相应的Handler。解决的办法可以是在springMVC的配置文件中添加如下配置

<bean class="org.springframework.web.servlet.mvc.method.annotation.
             RequestMappingHandlerMapping">
  <property name="detectHandlerMethodsInAncestorContexts">
       <value>true</value>
   </property>
</bean>

不过最好还是将bean注册在相应的容器中,避免不必要的麻烦,建议配置:

spring-mvc.xml
<!-- 只扫描@Controller注解 -->
<context:component-scan base-package="com.xxx.controller" 
                        use-default-filters="false">
    <context:include-filter type="annotation"
                            expression="org.springframework.stereotype.Controller" />
</context:component-scan>
use-default-filters个属性默认为true,会扫描所有注解类型的bean ,不仅仅扫描并注册带
有@Controller注解的Bean,而且还扫描并注册了带有@Component的子注解@Service、@Reposity的Bean,
所以如果只想扫描特定注解,则use-default-filters=“false”禁用掉。


spring.xml
<!-- 配置扫描注解,不扫描@Controller注解 -->
<context:component-scan base-package="com.xxx">
    <context:exclude-filter type="annotation"
                            expression="org.springframework.stereotype.Controller" />
</context:component-scan>
参考链接:
https://blog.csdn.net/wojiao228925661/article/details/82079957
https://www.cnblogs.com/hafiz/p/5875740.html
https://zhuanlan.zhihu.com/p/63212113
2.3.2 Resource定位

构造IoC容器时,需要指定BeanDefinition的信息来源(如从xml文件中来),而这个信息来源需要封装成spring中的Resource类。Resource是Spring用来封装I/O操作的类。Resource是一个接口,具体实现类如ClassPathResource。通过配置文件指定的文件位置完成BeanDefinition的定位,但具体的数据还没开始读入。

Resource只是BeanDefinition位置的封装,具体操作由ResourceLoader完成。这个过程即找到BeanDefinition的位置,为其载入创造I/O操作的条件。

大概的流程如下:

1、 构造函数调用refresh(),该方法在synchronized修饰的同步代码块中,保证不能有多个线程初始化

2、 refresh()先调用createBeanFactory()创建容器

3、 refresh()再调用loadBeanDefinitions(BeanFactory)方法

4、 loadBeanDefinitions()先调用ResourceLoader得到Resource[]数组(定位)

5、 loadBeanDefinitions()再调用Reader读取器(如XMLBeanDefinitionReader)读取Resource[]中资源(完成I/O操作,即开始载入),利用回调把结果传入之前建立的BeanFactory中。

2.3.3 BeanDefinition的载入

把用户定义好的Bean表示成IoC容器内部的数据结构(即BeanDefinition)。BeanDefinition的载入分两部分,首先调用XML解析器得到document对象,但这些document对象没有按照spring的bean规则进行解析。接下来按照spring规则进行解析。大体流程如下:

1、 在Reader读取器(如XMLBeanDefinitionReader的loadBeanDefinitions方法)中调用documentLoader(XML解析器)中的方法,获得document对象;

2、 在Reader读取器(如XMLBeanDefinitionReader的loadBeanDefinitions方法)再调用processBeanDefinition(传入document的element);

3、 processBeanDefinition (element,BeanDefinition)方法调用parseBeanDefinitionElement()方法,解析具体的元素标签(如、、等),将解析得到的结果设置到BeanDefinitionHolder中(BeanDefinitionHolder是BeanDefinition对象的封装类,封装了BeanDefinition、bean的名字等,用它来完成向IoC容器注册)。

**注意:**经过对document逐层解析,我们在XML中定义的BeanDefinition就被载入到了IoC容器中。但是,重要的依赖注入在这个时候还没有发生,现在IoC容器中BeanDefinition存在的还只是一些静态的配置信息。

2.3.4 BeanDefinition的注册

将BeanDefinition放入HashMap中,这些BeanDefinition数据在IoC容器中通过一个HashMap(ConcurrentHashMap<String,BeanDefinition>)来保持和维护,beanName为key,beanDefinition为value。

在上面的processBeanDefinition()方法中调用registerBeanDefinition(BeanDefinitionHolder)开始注册,map.put(beanName,BeanDefinition),registerBeanDefinition中需要synchronized同步代码块,保证数据一致性,以上过程都是在Reader读取器类中完成的(注册的方法由类BeanDefinitionRegistry定义)。

下面是整个流程的代码调用线路:
在这里插入图片描述

在这里插入图片描述

2.4 Ioc注入阶段

依赖注入的过程是用户第一次向IoC容器索要Bean时触发的(BeanFactory.getBean(name)),当然也有例外,也就是我们可以在BeanDefinition信息中通过控制lazy-init属性来让容器完成对Bean的预实例化(在初始化过程中完成,即在refresh()(初始化的入口)时调用getBean()),通过依赖注入生成实例。大体流程如下:

1、 getBean()方法中,首先判断是否已经创建,且为单例,这种情况不要创建。然后根据Bean的名字获取BeanDefinition,当前取不到就到双亲BeanFactory中取,如果还取不到就顺着双亲链一直向上。

2、 取到BeanDefinition后,获取当前Bean所依赖的所有Bean,并递归调用getBean。

3、 通过createBean创建Bean的实例,最后返回Bean。

4、 createBean()中,调用createBeanInstance()创建Bean(默认使用CGLIB一个常用的字节码生成器类库、或使用JVM的反射);

5、 createBean()中,再调用populateBean()方法,将当前Bean的依赖注入,通过递归调用容器的getBean方法,得到当前Bean的依赖Bean,同时也触发对依赖Bean的创建和注入。
在这里插入图片描述
在这里插入图片描述

注:多例对象创建后不归Spring管理,因此不需要缓存

源码链路大致如下:
在这里插入图片描述

2.5 插手容器的启动

Spring提供了一种叫做BeanFactoryPostProcessor的容器扩展机制。该机制允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做相应的修改。这就相当于在容器实现的第一阶段最后加入一道工序,让我们对最终的BeanDefinition做一些额外的操作,比如修改其中bean定义的某些属性,为bean定义增加其他信息等。

PropertyPlaceholderConfigurer、PropertyOverrideConfigurer是两个常用的BeanFactoryPostProcessor而CustomEditorConfigurer可以注册自定义的PropertyEditor用来处理配置文件中数据类型与真正业务对象中所定义的数据类型转换。

针对BeanFactory和ApplicationContext应用BeanFactoryPostProcessor的区别

对于BeanFactory来说,我们需要用手动方式应用所有的BeanFactoryPostProcessor。

对于ApplicationContext来说,ApplicationContext会自动识别配置文件中BeanFactoryPostProcessor并应用它,所以,相对于BeanFactory,在ApplicationContext中加载并应用BeanFactoryPostProcessor,仅需要在XML配置文件中将这些BeanFactoryPostProcessor简单配置一下即可。

  • PropertyPlaceholderConfigurer的具体功能

通常系统管理相关信息如数据源信息不会直接配置到xml文件中,而是配置在property文件中,再通过占位符来读取数据。BeanFactory在第一阶段加载完所有配置信息后,容器中的对象的属性还是以占位符的形式存在,而PropertyPlaceholderConfigurer的作用就是将占位符替换为property文件中相应的数据。

PropertyPlaceholderConfigurer不单会从其配置的properties文件中加载配置项,同时还会检查System类中的Properties,可以通过setSystemPropertiesMode()或者setSystemPropertiesModeName()来控制是否加载或者覆盖System相应Properties的行为。

PropertyPlaceholderConfigurer提供了SYSTEM_PROPERTIES_MODE_FALLBACK、SYSTEM_NEVER和SYSTEM_OVERRIDE三种模式。默认采用的是SYSTEM_PROPERTIES_ MODE_FALLBACK,即如果properties文件中找不到相应配置项,则到System的Properties中查找,我们还可以选择不检查System的Properties或者覆盖它。这也就是为什么我们在配置数据源的properties文件时写的属性名称不要简单的写username,要在前面加上jdbc或者其他前缀,防止PropertyPlaceholderConfigurer读取的是当前计算机用户的名称。

  • PropertyOverrideConfigurer的具体功能:

PropertyOverrideConfigurer能对容器中配置的任何你想**处理的bean定义的property信息进行覆盖替换**。它需要一个专门的配置文件,来说明需要覆盖哪些property,覆盖值为多少,配置规则为:

beanName.propertyName=value

properties文件的信息通常以明文表示,PropertyOverrideConfigurer的父类PropertyResourceConfigurer提供了一个protected类型的方法convertPropertyValue,允许子类覆盖这个方法对相应的配置项进行转换,如对加密后的字符串解密之后再覆盖到相应的bean定义中。PropertyPlaceholderConfigurer同样由这样功能。

在这里插入图片描述

  • CustomEditorConfigurer的具体功能

它只是辅助性地将后期会用到的信息注册到容器,对BeanDefinition没有做任何变动。XML文件的配置信息都是String类型,即容器从XML格式的文件中读取的都是字符串形式,最终应用程序却是由各种类型的对象所构成。要想完成这种由字符串到具体对象的转换(不管这个转换工作最终由谁来做),都需要这种转换规则相关的信息,而CustomEditorConfigurer就是帮助我们传达类似信息的。

Spring内部通过JavaBean的PropertyEditor来帮助进行String类型到其他类型的转换工作,常见的有StringArrayPropertyEditor、ClassEditor、FileEditor、LocaleEditor、PatternEditor,每一个数据类型对应一个PropertyEditor,以key-value的形式保存,key为转换后类型,value为对应的PropertyEditor,这些都是默认加载的。此外,我们还可以自定义PropertyEditor(继承PropertyEditorSupport),通过setCustomEditors将其加入CustomEditorConfigurer。(2.0之后,比较提倡使用propertyEditorRegistrars,需要实现该接口复写相应方法)。

这个功能可以用于在创建bean之前实现一些功能。比如我之前在改造公司老项目的时候,jar包里有个配置类之前是用disconf读取配置的,改造时决定不用disconf了。这个类的特别之处是所有成员包括set和get方法都被设计静态的,也就不能利用@bean设置配置值。此时就可以利用BeanFactoryPostProcessor在容器初始化后bean初始化前将相关配置值设置到配置类中。

2.6 插手Bean的生命周期

对于容器BeanFactory来说,需要根据bean设置的scope来决定是在容器启动时实例化Bean还是在BeanFactory的getBean()方法被调用(指第一次调用,第二次调用的话会先查询容器缓存,不存在再调用createBean方法,多例除外)时实例化Bean。用户主动调用getBean()实例化A对象称为显示调用,而A所依赖的对象则由容器隐式调用getBean()。

对于容器ApplicationContext来说,它在启动后就会实例化所有的Bean。过程可查看AbstractApplicationContext的refresh()方法,不再赘述。
在这里插入图片描述

Spring给予了每个Bean完整的生命周期管理,而不是像原来那种“new完后使用,脱离作用域后被回收”的命运。

具体的过程:

1.Bean的实例化与BeanWrapper

最终得到的是一个包裹bean的wrapper对象

2.各色的Aware接口

  • 对于BeanFactory

    有一下几个常见的接口:

    • BeanNameAware(将id注入到当前实例)
    • BeanClassLoaderAware(将类加载器注入)
    • BeanFactoryAware(将BeanFactory注入)

因此,如果我们想在某个对象内获得容器BeanFactory可以直接实现BeanFactoryAware接口。

  • 对于ApplicationContext

    有以下几个常见的接口(在检测这些接口并设置相关依赖的实现机理上有所不同,使用的是BeanPostProcessor方式):

    • ResourceLoaderAware(注入ApplicationContext)
    • ApplicationEventPublisherAware(注入ApplicationContext)
    • MessageSourceAware(国际化,注入ApplicationContext)
    • ApplicationContextAware(注入ApplicationContext)

前三个是因为ApplicationContext都实现了相应的ResourceLoader等接口,所以容器也就可以作为相应的ResourceLoader对象来使用。

aware接口介绍,简单讲就是获取到自身在Spring容器中的id属性。
https://www.jianshu.com/p/c5c61c31080b
  1. BeanPostProcessor

    ​ BeanPostProcessor与BeanFactoryPostProcessor容易混淆。但只要记住BeanPostProcessor是存在于对象实例化阶段,而BeanFactoryPostProcessor则是存在于容器启动阶段。

    ​ BeanFactoryPostProcessor通常会处理容器内所有符合条件的BeanDefinition

    ​ BeanPostProcessor会处理容器内所有符合条件的实例化后的对象实例,它定义了两个方法:

    ​ postProcessBeforeInitialization、postProcessAfterInitialization,这两个方法都传入了原先的实例对象,给扩展对象实例化过程行为提供了很大的便利。

    ​ 通常比较常见的使用BeanPostProcessor的场景,是处理标记接口实现类,或者为当前对象提供代理实现(Spring的AOP使用BeanPostProcessor来为对象生成相应的代理对象)。ApplicationContext对应的那些Aware接口实际上就是通过BeanPostProcessor的方式进行处理的。当ApplicationContext中每个对象的实例化过程走到BeanPostProcessor前置处理这一步时,ApplicationContext容器会检测到之前注册到容器的ApplicationContextAwareProcessor这个BeanPostProcessor的实现类,然后就会调用其postProcessBeforeInitialization()方法,检查并设置Aware相关依赖。

    对于自定义的BeanPostProcessor,BeanFactory型容器需要手动new一个处理器对象再set到容器,而对于ApplicationContext容器只需要在xml配置即可。

    ApplicationContextAwareProcessor类中的方法
        
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws 
    BeansException {
        
          if (bean instanceof ResourceLoaderAware) { 
              //实现了ResourceLoaderAware接口
             ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
             }
        
          if (bean instanceof ApplicationEventPublisherAware) {
              //实现了ApplicationEventPublisherAware接口
             ((ApplicationEventPublisherAware)bean)
              .setApplicationEventPublisher(this.applicationContext);
             }
        
          if (bean instanceof MessageSourceAware) { 
              //实现了MessageSourceAware接口
             ((MessageSourceAware) bean)
              .setMessageSource(this.applicationContext);
            }
          if (bean instanceof ApplicationContextAware) { 
               //实现了ApplicationContextAware接口
            ((ApplicationContextAware) bean)
              .setApplicationContext(this.applicationContext);
            }
    	return bean; 
    }
    

    ​ 有一种特殊类型的BeanPostProcessor,执行的时机与一般的处理器有所不同。

    ​ InstantiationAwareBeanPostProcessor接口在对象实例化的过程中能导致某种类似电路里“短路”的功能。实际上,并非所有注册到Spring容器内的bean定义都是上图的流程实例化的。在所有的步骤之前,也就是实例化bean对象步骤之前,容器会首先检查容器中是否注册有InstantiationAwareBeanPostProcessor类型的BeanPostProcessor。如果有,首先使用相应的InstantiationAwareBeanPostProcessor来构造对象实例。构造成功后直接返回构造完成的对象实例,而不会按照“正规的流程”继续执行。这就是它可能造成“短路”的原因。

    ​ 不过,通常情况下都是Spring容器内部使用这种特殊类型的BeanPostProcessor做一些动态对象代理等工作,我们使用普通的BeanPostProcessor实现就可以。

  2. InitializingBean和init-method

    postProcessBeforeInitialization

    ​ InitializingBean接口作用在于,在对象实例化过程调用过“BeanPostProcessor的前置处理”之后,会接着检测当前对象是否实现了InitializingBean接口,如果是,则会调用其afterPropertiesSet()方法进一步调整对象实例的状态。比如,在有些情况下,某个业务对象实例化完成后,还不能处于可以使用状态。这个时候就可以让该业务对象实现该接口,并在方法afterPropertiesSet()中完成对该业务对象的后续处理(里面的业务逻辑会是对象实例中第一个执行的方法)。

    ​ 实际使用过程中如果使用InitializingBean接口有较强的代码侵入性,因此spring还提供了的init-method属性,可以利用这个属性定义实例化对象最先执行的方法

  3. DisposableBean与destroy-method

    ​ 当所有的一切,该设置的设置,该注入的注入,该调用的调用完成之后,容器将检查singleton类型的bean实例,看其是否实现了org.springframework.beans.factory.DisposableBean接口。或者其对应的bean定义是否通过的destroy-method属性指定了自定义的对象销毁方法。如果是,就会为该实例注册一个用于对象销毁的回调(Callback),以便在这些singleton类型的对象实例销毁之前,执行销毁逻辑。

二、SpringMVC

1.执行流程

dispatchServlet.property里配置了很多默认的mapping和handler等

SpringMVC的执行流程大致如下:
在这里插入图片描述

请求从前端过来由DispatcherServelt接受到后,会根据配置url的方式,遍历所有HandlerMapping找到对应的Handler(实际上是一个包含处理器、拦截器和异常处理器的执行对象),根据返回的Handler找到对应的适配器HandlerAdapter,由适配器调用Handler的handler()方法,返回处理结果ModelAndView,根据其中的viewName属性取视图仓库ViewResolver找到对应的视图解析器View进行解析,最后返回结果到前端。

UML关系
在这里插入图片描述

2.HandlerMapping介绍

在这里插入图片描述

基于SimpleUrlHandlerMapping配置映射
在这里插入图片描述

无论采用那种mapping与返回的handler类型无关,它只是为了处理不同的url配置方式。

至于返回的是哪种handler与我们实现哪种handler接口有关

3.Handler和HandlerAdapter介绍

在这里插入图片描述
执行handler()方法的时候如果设置有拦截器,那么会执行相应的拦截器内方法。如果出现了异常,那么就会执行异常处理器中的方法。

HandlerInterceptor介绍

拦截器执行原理示意

if(!interceptor.preHandle()){//preHanlde返回false后面的方法都不执行
	return;
}
try{
	handler();
	interceptor.postHandler();
}catch(Exception e){
	//异常处理
}

interceptor.afterCompletion();//preHandler返回的是true,那么该方法一定会执行 	

MVC拦截器执行流程:
在这里插入图片描述

HandlerExceptionResolver

​ 异常处理器,在doHandler方法执行出现异常时,就会开始执行异常处理器的相关代码。在项目中我们一般会自定义异常处理器,有两种常见做法:一是实现HandlerExceptionResolver接口,二是利用@ControllerAdvice注解(在容器启动时会注册org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver对象,在它的afterPropertiesSet里将@ExceptionHandler处理为ExceptionHandlerMethodResolver,在遍历HandlerExceptionResolver处理异常时会调用,可参考:@ExceptionHandler的工作原理)。

配置<mvc:annotation-driven>的作用:
在这里插入图片描述

三、SpringAOP

AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

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

将重复的代码抽取出来(不同的模块),在需要时统一调用,可以不同的模块按不同的顺序调用,解耦。

AOP适合于那些具有横切逻辑的应用:如性能监测,访问控制,事务管理、缓存、对象池管理以及日志记录。

实现:

ProxyFactoryBean是AOP中最重要的一个类,在xml文件中配置,并放入IoC中(),对于ProxyFactoryBean把需要对target目标对象增加的增强处理都通过getObject()方法封装了(工厂模式)。以getObject方法作为入口,spring AOP调用AopProxyFactory作为AopProxy代理对象的生产工厂,由它来负责产生相应的AopProxy代理对象(默认使用JDK的Proxy,或CGLIB)。

在AopProxy代理的接口方法被调用执行时(调用原对象的方法即调用代理对象的方法),首先会触发对这些方法调用进行拦截,这些拦截对目标调用的功能增强提供了工作空间,拦截过程在JDK的proxy代理对象中是通过invoke方法来完成的。

在Spring AOP通过JDK的Proxy方法生成代理对象时,相关的拦截器(一个通知器链)已经配置到代理对象中了。在InvocationHandler的invoke回调中,首先会根据配置来对拦截器是否与当前调用方法相匹配进行判断,如果匹配则相应的拦截器开始发挥作用。这个过程是一个遍历的过程,它会遍历AopProxy代理对象中设置的拦截器链中的所有拦截器。拦截器逐一被调用,在拦截器都调用完后,才对目标对象方法调用。

通知器advisor在xml中配置,放在IoC容器中,拦截器、通知器等一些信息是存放在AdvisorSupport类中,通过getBean获取。在ProxyFactoryBean中能获取IoC容器是因为它实现了BeanFactoryAware接口。

为了完成AOP应用需要的对目标对象增强,对于每种advice通知,spring设计了对应的AdviceAdapter通知适配器,这些通知适配器实现了advice通知对目标对象的不同增强方式(before、after等)。对于这些适配器,在AopProxy回调方法中有一个注册机制。完成注册后,拦截器链中运行的拦截器已经是经过适配的拦截器(实际上是通知器)了。

如果是适配器支持的adapter.supportsAdvice(advice)(advice根据配置文件有before、after等类型),就将生成的包装后的拦截器,放入拦截器链,如MethodBeforeAdviceAdapter,会产生MethodBeforeAdviceInterceptor,这个包装后的拦截器会先调用advice.before方法,在继续遍历拦截器链(methodInvocation.proceed())。如果是after类型,就先继续遍历拦截器链(methodInvocation.proceed()),再调用after方法。Throws是通过catch中捕获异常实现的。相当于拦截器链中有很多adapter包装后的拦截器,依次遍历每个拦截器,调用其方法,从而实现了增强。

好比电源适配器,它是用于电流变换(整流)的设备。适配器的存在,就是为了将已存在的东西(接口)转换成适合我们的需要、能被我们所利用。
在这里插入图片描述

四、几个常见问题

1.Controller的线程安全问题

如果action是多实例的,不存在线程安全的问题。而Controller默认是单例的(效率高)有可能存在线程安全问题,但Controller是基于方法的,所以只要不使用类成员变量,方法中都是局部变量,所以不会出现线程安全问题,如果使用成员变量,可以使用Threadlocal

解决方案:

1、不要在controller中定义成员变量。

2、万一必须要定义一个非静态成员变量时候,则通过注解@Scope(“prototype”),将其设置为多例模式。

3、在Controller中使用ThreadLocal变量

在controller里调用service: controller、service和dao层本身并不是线程安全的,只是如果只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工作内存,是安全的。除非service里有一般的成员变量,那么需要自行解决线程安全问题

参考资料:https://blog.csdn.net/qq_28163175/article/details/90247794

2.循环依赖

循环依赖指的是A依赖B,B依赖A。

对于构造器注入,循环依赖问题无法解决,它在创建完A的时候后就创建B,没有先存入三级缓存。

对于setter注入,如果是多例,因为多例bean创建的时候维护了一个Map,用来判断是否有同名bean在创建,有就抛异常,而且创建后不归spring管,spring不进行缓存,所以无法解决。

如果是单例模式,那么是通过三级缓存递归执行getBean机制来解决的。

一级缓存:
/** 保存所有的singletonBean的实例 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);

二级缓存:
/** 保存所有早期创建的Bean对象,这个Bean还没有完成依赖注入 */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
三级缓存:
/** singletonBean的生产工厂*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
 
/** 保存所有已经完成初始化的Bean的名字(name) */
private final Set<String> registeredSingletons = new LinkedHashSet<String>(64);
 
/** 标识指定name的Bean对象是否处于创建状态  这个状态非常重要 */
private final Set<String> singletonsCurrentlyInCreation =
    Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));

重新补充编辑 04.11
循环依赖的问题比较复杂,还要区分是否为代理对象,大体的逻辑如下图,有时间重新写篇文章整理下。
在这里插入图片描述

参考资料:
http://jinnianshilongnian.iteye.com/blog/1415278
https://zhuanlan.zhihu.com/p/84267654
https://www.jianshu.com/p/8bb67ca11831
https://www.cnblogs.com/youzhibing/p/14337244.html
https://www.jianshu.com/p/3bc6c6713b08

除文中注明的连接外,文中大部分内容总结自《Spring揭秘》

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值