Spring使用与实现总结
# IOC
# IOC/DI
- 依赖注入(Dependecy Injection)和控制反转(Inversion of Control)是同一个概念,具体的讲:当某个角色 需要另外一个角色协助的时候,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在 spring 中 创建被调用者的工作不再由调用者来完成,因此称为控制反转。创建被调用者的工作由 spring 来完成,然后注入调用者,因此也称为依赖注入。 IOC 侧重于原理,DI 侧重于实现。
- 依赖倒置原则/面向接口编程:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
# Spring 介绍
- Spring 是轻量级的 JavaEE 的开源框架,具有 IOC/DI,AOP,MVC,TX,ORM 等功能。
- 优点是:
- DI 有效的降低了耦合度。
- AOP 提供了通用的任务的集中管理。
- ORM 简化了对数据库的访问。
- 低侵入式设计,代码污染极低。
# BeanFactory
- BeanFactory 只是对 IOC 容器最基本行为作了定义,而不关心 Bean 是怎样定义和加载的。
- 定义了一些 getBean、containsBean、isSingleton、isPrototype 等基本容器方法。
# XmlBeanFactory
- xmlBeanFactory 是 BeanFactory 的一个实现类。xmlBeanFactory 的功能是建立在 DefaultListablexmlBeanFactory 这个基本容器的基础上的,并在这个基本容器的基础上添加了其他诸如 xml 读取的附加功能。
# Resource
- 仅仅使用 java 标准 java.net.URL 和针对不同 URL 前缀的标准处理器并不能满足我们对各种底层资源的访问,比如:我们就不能通过 URL 的标准实现来访问相对类路径或者相对 ServletContext 的各种资源。虽然我们可以针对特定的 url 前缀来注册一个新的 URLStreamHandler(和现有的针对各种特定前缀的处理器类似,比如 http:),然而这往往会是一件比较麻烦的事情(要求了解 url 的实现机制等),而且 url 接口也缺少了部分基本的方法,如检查当前资源是否存在的方法。
- 相对标准 url 访问机制,Spring 的 Resource 接口对抽象底层资源的访问提供了一套更好的机制。
- Resource 是 Spring 中对外部资源的抽象,最常见的是文件的抽象,特别是 xml 文件,而且 Resource 里面通常是保存了 Spring 使用者的 Bean 定义。
- 其实现类有:ByteArrayResouece,BeanDefinitionResource,InputStreamResource, ClassPathResource 等
- ResourceLoader 接口用于返回 Resource 对象;其实现可以看作是一个生产 Resource 的工厂类。Spring 提供了一个适用于所有环境的 DefaultResourceLoader 实现,可以返回 ClassPathResource、UrlResource。
- ResourceLoader 在进行加载资源时需要使用前缀来指定需要加载:“classpath:path”表示返回 ClasspathResource,“http://path”和“file:path”表示返回 UrlResource 资源,如果不加前缀则需要根据当前上下文来决定,DefaultResourceLoader 默认实现可以加载 classpath 资源。
- 对于目前所有 ApplicationContext 都实现了 ResourceLoader,因此可以使用其来加载资源。
- ClassPathXmlApplicationContext:不指定前缀将返回默认的 ClassPathResource 资源,否则将根据前缀来加载资源;
- WebApplicationContext:不指定前缀将返回 ServletContextResource,否则将根据前缀来加载资源;
# ApplicationContext
- ApplicationContext 是 spring 中较高级的容器。和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。 另外,它增加了企业所需要的功能。
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
- MessageSource, ApplicationEventPublisher, ResourcePatternResolver
- 1.支持不同的信息源。继承了 MessageSource 接口,这个接口为 ApplicationContext 提供了很多信息源的扩展功能,比如:国际化的实现为多语言版本的应用提供服务。
- 2.访问资源。这一特性主要体现在 ResourcePatternResolver 接口上,对 Resource 和 ResourceLoader 的支持,这样我们可以从不同地方得到 Bean 定义资源。这种抽象使用户程序可以灵活地定义 Bean 定义信息,尤其是从不同的 IO 途径得到 Bean 定义信息。这在接口上看不出来,不过一般来说,具体 ApplicationContext 都是继承了 DefaultResourceLoader 的子类。因为 DefaultResourceLoader 是 AbstractApplicationContext 的基类。
- 3.支持应用事件。继承了接口 ApplicationEventPublisher,为应用环境引入了事件机制,这些事件和 Bean 的生命周期的结合为 Bean 的管理提供了便利。
- 4.附件服务。EnvironmentCapable 里的服务让基本的 Ioc 功能更加丰富。
- 5.ListableBeanFactory 和 HierarchicalBeanFactory 是继承的主要容器。
- 最常被使用的 ApplicationContext 接口实现类:
- 1,FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径。
- 2,ClassPathXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。
- 3,WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在 XML 文件中
# ClassPathXmlApplicationContext
# 容器初始化过程
# Bean 的注册
- 以上的这些过程都发生在 new ClassPathXmlApplicationContext 中调用的 AbstractApplicationContext#refresh 方法中。
- AbstractApplicationContext#refresh
- 逻辑:
- 1)初始化前的准备工作,比如对系统属性或者环境变量进行准备及验证
- 2)初始化 BeanFactory,并进行 XML 文件读取(component-scan->包括 class 文件)
- 3)对 BeanFactory 进行各种功能填充,比如@Qualifier 和@Autowired
- 4)子类覆盖方法做额外的处理
- 5)激活各种 BeanFactory 处理器
- 6)注册拦截 bean 创建的 bean 处理器,这里只是注册,真正的调用是在 getBean 的时候
- 7)为上下文初始化 Message 源,即为不同语言的消息体进行国际化处理
- 8)初始化应用消息广播器,并放入 applicationEventMulticaster bean 中
- 9)留给子类来初始化其他的 bean
- 10)在所有注册的 bean 中查找 listener bean,注册到消息广播器中
- 11)初始化剩下的代理实例(非 lazy-init)(bean 的加载)
- 12)完成刷新过程,通知生命周期处理器 lifecycleProcessor 刷新过程,同时发出 ContextRefreshEvent 通知别人。
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 准备 刷新的上下文环境
prepareRefresh();
// 初始化BeanFactory,并进行XML文件的读取
2
3
4
5
6
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 对 BeanFactory 进行各种功能填充
prepareBeanFactory(beanFactory);
try { // 子类覆盖方法做额外的处理 postProcessBeanFactory(beanFactory);
// 激活各种 BeanFactory 处理器 invokeBeanFactoryPostProcessors(beanFactory);
// 注册拦截 Bean 创建的 Bean 处理器,这里只是注册,真正的调用是在 getBean 的时候 registerBeanPostProcessors(beanFactory);
// 为上下文初始化 Message 源,即不同语言的消息体,国际化处理 initMessageSource();
// 初始化应用消息广播器,并放入 applicationEventMulticaster bean 中 initApplicationEventMulticaster();
// 留给子类来初始化其他 bean onRefresh();
// 在所有注册的 bean 中查找 Listener bean,注册到消息广播器中 registerListeners();
// 初始化剩下的单例实例(除了 lazy-init) finishBeanFactoryInitialization(beanFactory);
// 完成刷新过程,通知生命周期处理器 lifecycleProcessor 刷新过程,同时发出 ContextRefreshEvent 通知别人 finishRefresh(); }
catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); }
// Destroy already created singletons to avoid dangling resources. destroyBeans();
// Reset 'active' flag. cancelRefresh(ex);
// Propagate exception to caller. throw ex; }
finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); }
} }
# obtainFreshBeanFactory(创建 beanFactory,解析 XML)
在后面会调用到 loadBeanDefinitions(beanFactory)方法,该方法会创建 XmlBeanDefinitionReader,后面会调用 reader#loadBeanDefinitions 方法。
该方法会:
- 1)Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
将 applicationContext.xml 转为 Resource。
- 2)然后会调用 Resource#getInputStream,然后将该资源转为 XML 的 Document 对象。
最终是使用 jaxp 的 dom 方式读取的 XML 配置文件。(JAXP 是一种标准,SUN API 对其提供了实现)
- 3)使用 BeanDefinitionDocumentReader#registerBeanDefinitions 将 XML 对应的 Document 对象转为 BeanDefinitions。
最终会调用 parseBeanDefinitions(解析标签),它会分开解析默认标签和自定义标签。
默认标签是 import、alias、bean 和 beans 四种。
自定义标签会先找到其对应的 parser,比如 component-scan 标签对应着 ComponentScanBeanDefinitionParser。该 Parser 会调用
PathMatchingResourcePatternResolver#getResources 从包名得到 Resource 封装了的 class 文件(底层是调用了 File API)。MetadataReader 会读入 class 文件,如果有@Component 注解,那么返回其对应的 BeanDefinition。
- 4)标签解析完毕后会将 beanName 和 beanDefinition 作为 key 和 value 放入 beanfactory 的 beanDefinitionMap 中。
# Bean 的加载
- 在 Bean 的注册 refresh 中除了调用了 obtainFreshBeanFactory 完成了 XML2BeanDefinition,还调用了 finishBeanFactoryInitialization(初始化非 lazy-load 且 singleton 的 bean)(BeanDefinition2BeanInstance),该方法会获取所有 beanName,逐个调用 getBean 方法。
# FactoryBean(工厂 Bean,用户定制)
- Spring 通过反射机制利用 bean 的 class 属性指定实现类来实例化 bean。
- Spring 提供了一个 FactoryBean 的工厂类接口,用户可以通过实现该接口定制实例化 bean 的逻辑。
- FactoryBean
public interface FactoryBean<T> {
- // 返回 bean 实例,如果 isSingleton()返回 true,那么该实例会放到 Spring 容器中单例缓存池中 T getObject() throws Exception; Class<?> getObjectType(); boolean isSingleton(); }
- FactoryBean:
- 这个接口使你可以提供一个复杂的逻辑来生成 Bean。它本质是一个 Bean,但这个 Bean 不是用来注入到其它地方像 Service、Dao 一样使用的,它是用来生成其它 Bean 使用的。实现了这个接口后,Spring 在容器初始化时,把实现这个接口的 Bean 取出来,使用接口的 getObject()方法来生成我们要想的 Bean。当然,那些生成 Bean 的业务逻辑也要写 getObject()方法中。
- 其返回的对象不是指定类的一个实例,其返回的是该工厂 Bean 的 getObject 方法所返回的对象。创建出来的对象是否属于单例由 isSingleton 中的返回决定。
- 使用场景:1、通过外部对类是否是单例进行控制,该类自己无法感知 2、在创建 Object 对象之前进行初始化的操作,在 afterPropertiesSet()中完成。(一次性的初始化,保存在成员变量中,并不是每次 getObject 都会调用 afterPropertiesSet,afterPropertiesSet 只会被调用一次)
# ObjectFactory(Spring 使用)
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
2
3
- ObjectFactory:
- 它的目的也是作为一个工厂,来生成 Object(这个接口只有一个方法 getObject())。这个接口一般被用来,包装一个 factory,通过个这工厂来返回一个新实例(prototype 类型)。这个接口和 FactoryBean 有点像,但 FactoryBean 的实现是被当做一个 SPI(Service Provider Interface)实例来使用在 BeanFactory 里面;ObjectFactory 的实现一般被用来注入到其它 Bean 中,作为 API 来使用。就像 ObjectFactoryCreatingFactoryBean 的例子,它的返回值就是一个 ObjectFactory,这个 ObjectFactory 被注入到了 Bean 中,在 Bean 通过这个接口的实例,来取得我们想要的 Bean。
- 总的来说,FactoryBean 和 ObjectFactory 都是用来取得 Bean,但使用的方法和地方不同,FactoryBean 被配置好后,Spring 调用 getObject()方法来取得 Bean,ObjectFactory 配置好后,在 Bean 里面可以取得 ObjectFactory 实例,需要我们手动来调用 getObject()来取得 Bean。
# InitializingBean
- InitializingBean 接口为 bean 提供了初始化方法的方式,它只包括 afterPropertiesSet 方法,凡是继承该接口的类,在初始化 bean 的时候会执行该方法。
# doGetBean
中有三个方法非常关键:getSingleton,createBean 和 getObjectForBeanInstance。
逻辑梳理:
- 1)getSingleton(beanName)
直接尝试从缓存获取或者从 singletonFactories 中的 ObjectFactory 中获取 sharedInstance。
如果已经创建过了,那么调用 getObjectForBeanInstance 返回对应的实例(从缓存中只得到了 bean 的原始状态,还需要对 bean 进行实例化)。
- 2)没有创建过,那么检查 BeanFactory 中是否包含该 beanName 对应的 beanDefinition,没有找到则从父 BeanFactory 中继续查找,直至找到为止(递归)。
- 3)如果存在依赖的 bean,那么递归实例化所依赖的 bean
- 4)根据 scope 分别处理:
- 4.1) 如果是 singleton,那么调用 getSingleton(beanName,objectFactory)(记录加载状态,通过 this.singletonsCurrentlyInCreation.add(beanName)将当前正在创建的 bean 记录在缓存中,这样便可以对循环依赖进行检测)得到 sharedInstance,其中调用了 createBean、之后调用 getObjectForBeanInstance 返回对应的实例。
- 4.2)如果是 prototype,那么调用 createBean,之后调用 getObjectForBeanInstance。
- 4.3) 如果是其他的 scope,那么调用 scope#get(beanName,objectFactory)得到 scopedInstance,其中调用了 createBean,之后调用 getObjectForBeanInstance。
- 5)createBean 方法(创建单例或多例的 bean):
AbstractBeanDefinition#prepareMethodOverrides(验证及准备覆盖的方法,决定实例化策略->反射 or CGLIB)
如果 beanDefinition#getMethodOverrides()为空,即用户没有使用 replace 或 lookup 的配置方法,那么直接使用反射的方式;如果使用了,需要将这两个配置通过的功能切入进去,所以就必须要使用 CGLIB 动态代理的方式将包含两个特性所对应的逻辑的拦截增强器设置进去,这样才可以保证在调用方法的时候会被相应的拦截器增强,返回值为包含拦截器的代理实例。
给 BeanPostProcessor 一个机会来返回代理来替代真正的实例,与 AOP 有关。如果该方法返回 bean 不为空,则跳过后续实际创建 bean 的过程,直接返回代理后的 bean。
doCreateBean
- 1)如果是单例,则需要首先清除缓存 factoryBeanInstanceCache
- 2)实例化 bean,将 BeanDefinition 转换为 BeanWrapper。
转换过程:
① 如果存在工厂方法(factory-method),则使用工厂方法进行初始化(instantiateUsingFactoryMethod)
② 一个类有多个构造函数,每个构造函数都有不同的参数,所以需要根据参数锁定构造函数并进行初始化(autowireConstructor)
③ 如果既不存在工厂方法也不存在带有参数的构造函数,则使用默认的构造函数进行 bean 的初始化(instantiateBean)。
- 3)MergedBeanDefinitionPostProcessor 的应用
bean 合并后的处理,Autowired 注解正是通过此方法实现诸如类型的预解析。
- 4)更新 singletonFactories
- 5)属性填充 populateBean
属性填充时,会 autowireByName(按名获取待注入的属性)或 autowireByType(按类型获取待注入的属性)
填充完毕后会调用客户定制的初始化方法。除了使用配置 init-method 外,还可以使自定义的 bean 实现 InitializingBean 接口,并在 afterPropertiesSet 中实现自己的初始化业务逻辑。
init-method 和 afterPropertiesSet 都是在 ininitalBean 时执行,执行顺序是 afterPropertiesSet 先执行,init-method 后执行,在它们前后分别执行 BeanPostProcesser 的两个方法。
- 6)代理 bean 循环依赖检查
- 7)注册 DisposableBean
如果配置了 destroy-method,这里需要注册以便于在销毁时候调用。
- 6)getObjectForBeanInstance
无论是从缓存中获取到的 bean 还是通过不同的 scope 策略加载的 bean 都只是最原始的 bean 状态,并不一定是我们最终想要的 bean。
比如,我们需要对 FactoryBean 进行处理,那么这里得到的其实是 FactoryBean 的初始状态,但是我们真正需要的是 FactoryBean 中定义的 factory-method(getObject 方法)方法中返回的 bean,而 getObjectForBeanInstance 就是完成这个工作的。
# 注入方式
- Spring 的 IOC 有三种注入方式 :
- 第一是根据属性注入,也叫 setter 注入;
- 第二种是根据构造方法进行注入;
- 第三种是工厂注入,分为静态工厂注入和动态工厂注入
- <bean id="car1" class="com.lzj.spring.beans.factory.StaticFactory"
- factory-method="getCar">
<constructor-arg value="baoma"></constructor-arg>- 在 XML 文件中配置 bean 时,要指定 class 的属性为工厂的类;factory-method 属性指定工厂类中工厂方法,用于创建 bean;constrctor-arg 用于给工厂方法传递参数。
# 循环依赖
Spring 容器循环依赖包括构造器循环依赖和 setter 循环依赖。
核心就是 singletonObjects、singletonFactories、earlySingletonObjects 和 singletonsCurrentlyInCreation 四个集合。
- 1)singletonObjects 是 beanName 与 beanInstance 的 Map,是真正的缓存,beanInstance 是构造完毕的,凡是正常地构造完毕的单例 bean 都会放入缓存中。
- 2)earlySingletonObjects 也是 beanName 与 beanInstance 的 Map,beanInstance 是已经调用了 createBean 方法,但是没有清除加载状态和加入至缓存的 bean。仅在当前 bean 创建时存在,用于检测代理 bean 循环依赖。
- 3)singleFactories 是 beanName 与 ObjectFactory 的 Map,仅在当前 bean 创建时存在,是尚未调用 createBean 的 bean。用于 setter 循环依赖时实现注入。
- 4)singletonsCurrentlyInCreation 是 beanName 的集合,用于检测构造器循环依赖。
getBean 在循环依赖时所执行的步骤是这样的:
- 1)检测当前 bean 是否在 singletonObjects 中,在则直接返回缓存好的 bean;不在则检测是否在 singletonFactories 中,在,则调用其 getObject 方法,返回,并从 singletonFactories 中移除,加入到 earlySingletonObjects 中。
- 2)正常创建,beforeSingletonCreation:检测当前 bean 是否在 singletonsCurrentlyInCreation,如果存在,抛出异常。表示存在构造器循环依赖。如果不存在,则将当前 bean 加入。
- 3)bean 初始化,分为构造方法初始化、工厂方法初始化和简单初始化。如果是构造方法初始化,那么递归地获取参数 bean。其他情况不会递归获取 bean。
- 4)addSingletonFactory:如果当前 bean 不在 singletonObjects 中,则将当前 bean 加入到 singletonFactories 中,并从 earlySingletonObjects 中移除。
- 5)填充属性,简单初始化的话会递归创建所依赖的 bean。
- 6)调用用户初始化方法,比如 BeanPostProcesser、InitializingBean、init-method,有可能返回代理后的 bean。
- 检测循环依赖,如果当前 bean 在 singletonObjects 中,则判断当前 bean(current bean)与 singletonObjects 中的 bean(cached bean)是否是同一个,如果不是,那么说明当前 bean 是被代理过的,由于依赖当前 bean 的 bean 持有的是对 cached bean 的引用,这是不被允许的,所以会抛出 BeanCurrentlyInCreationException 异常。
- 7)afterSingletonCreation:将当前 bean 从 singletonsCurrentlyInCreation 中删除
- 8)addSingleton:将当前 bean 加入到 singletonObjects,然后从 singletonFactories, earlySingletonObjects 中移除,结束
# 构造器循环依赖
- 表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出 BeanCurrentlyInCreationException 异常表示循环依赖。
- 1、Spring 容器创建单例“A” Bean,首先检测 singletonFactories 是否包含 A,发现没有,于是正常创建,然后检测 A 是否包含在 singletonsCurrentlyInCreation 中,没有,则将 A 放入。构造方法初始化时需要 B 实例(A 尚未放入到 singletonFactories 中),于是调用了 getBean(B)方法、
- 2、Spring 容器创建单例“B” Bean,首先检测 singletonFactories 是否包含 B,发现没有,于是正常创建,然后检测 B 是否包含在 singletonsCurrentlyInCreation 中,没有,则将 B 放入。构造方法初始化时需要 C 实例(B 尚未放入到 singletonFactories 中),于是调用了 getBean(C)方法、
- 3、Spring 容器创建单例“C” Bean,首先检测 singletonFactories 是否包含 C,发现没有,于是正常创建,然后检测 C 是否包含在 singletonsCurrentlyInCreation 中,没有,则将 C 放入。构造方法初始化时需要 A 实例(C 尚未放入到 singletonFactories 中),于是调用了 getBean(A)方法、
- 4、Spring 容器创建单例“A” Bean,首先检测 singletonFactories 是否包含 A,发现没有于是正常创建,然后检测 A 是否包含在 singletonsCurrentlyInCreation 中,有,抛出 BeanCurrentlyInCreationException 异常。
# setter 循环依赖
- 表示通过 setter 注入方式构成的循环依赖。
- 对于 setter 注入造成的依赖是通过 Spring 容器提前暴露刚完成构造器注入但未完成其他步骤(如 setter 注入)的 Bean 来完成的,而且只能解决单例作用域的 Bean 循环依赖。
- 1、Spring 容器创建单例“A” Bean,首先检测 singletonFactories 是否包含 A,发现没有,于是正常创建,然后检测 A 是否包含在 singletonsCurrentlyInCreation 中,没有,则将 A 放入。将 A 放入到 singletonFactories 中。注入属性时需要 B 实例,于是调用了 getBean(B)方法、
- 2、Spring 容器创建单例“B” Bean,首先检测 singletonFactories 是否包含 B,发现没有,于是正常创建,然后检测 B 是否包含在 singletonsCurrentlyInCreation 中,没有,则将 B 放入。将 B 放入到 singletonFactories 中。注入属性时需要 C 实例,于是调用了 getBean(C)方法、
- 3、Spring 容器创建单例“C” Bean,首先检测 singletonFactories 是否包含 C,发现没有,于是正常创建,然后检测 C 是否包含在 singletonsCurrentlyInCreation 中,没有,则将 C 放入。将 C 放入到 singletonFactories 中。注入属性时需要 A 实例,于是调用了 getBean(A)方法、
- 4、Spring 容器创建单例“A” Bean,首先检测 singletonFactories 是否包含 A,发现有,于是返回缓存了的 bean,并将 A 从 singletonFactories 删除,返回 A 实例。
- 5、C 得到 A 实例。set 进来,B、A 也是这样。结束。
- 对于“prototype”作用域 Bean,Spring 容器无法完成依赖注入,因为“prototype”作用域的 Bean,Spring 容器不进行缓存,因此无法提前暴露一个创建中的 Bean。
# 常用注解
- 1)将使用了以下注解的类纳入到Spring的容器管理中。
- @Component
- @Controller
- @Service
- @Repository
- @Scope 注解 作用域
- 2)注入注解
- @Resource 默认按名称装配(autowireByName),如果没有指定 name 属性,当注解写在字段上时,默认取字段名。当找不到与名称匹配的 bean 时才按照类型进行装配。但是需要注意的是,如果 name 属性一旦指定,就只会按照名称进行装配。
- @Autowired 默认按类型装配(autowireByType),也可以搭配@Qualifier 实现按名称装配。
# Bean 的作用域
- IOC 容器中 bean 默认是单例的
- 可以使用 scope 配置作用域
- 作用域有两种:singleton,prototype(每次获取都创建一个新的 bean)
# Bean 生命周期
- 有三种方式在 Bean 初始化后和销毁前添加一些操作。
- Bean 的方法加上@PostConstruct 和@PreDestroy 注解(必须有 component-scan 才有效)
- 在 xml 中定义 init-method 和 destory-method 方法
- Bean 实现 InitializingBean 和 DisposableBean 接口
- 其执行顺序关系为:
- Bean 在实例化的过程中:constructor > @PostConstruct >InitializingBean > init-method
- Bean 在销毁的过程中:@PreDestroy > DisposableBean > destroy-method
- 还可以通过 BeanPostProcessor 在 bean 初始化前后添加一些操作。
- 初始化执行顺序为:constructor > BeanPostProcessor#postProcessBeforeInitialization > @PostConstructor > InitializingBean > init-method > BeanPostProcessor# postProcessAfterInitialization
- 调用用户自定义初始化方法之前和之后分别会调用 BeanPostProcessor 的 postProcessBeforeInitialization 和 postProcessAfterInitialization 方法。
- 参见 getBean 中的 initializeBean 方法。
# 示例
public class Car implements InitializingBean, DisposableBean {
public Car() {
System.out.println("constructor...");
}
public void initCar() {
System.out.println("init-method...");
}
@PostConstruct
public void postConstructMethod() {
System.out.println("post construct ..");
}
@PreDestroy
public void beforeDestroy() {
System.out.println("before destroy...");
}
public void destroyCar() {
System.out.println("destroy-method....");
}
@Override
public void destroy() throws Exception {
System.out.println("disposable bean...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("initializing bean...");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class MyBeanPostProcessor implements BeanPostProcessor{
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization "+ bean +" ,"+ beanName);
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization "+ bean +" ,"+ beanName);
return bean;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans-cycle.xml");
Car car = (Car) ctx.getBean("car");
System.out.println(car);
ctx.close();
}
}
2
3
4
5
6
7
8
- <context:component-scan base-package="cycle" >/context:component-scan
<bean id="car" class="cycle.Car" init-method="initCar" destroy-method="destroyCar"></bean><bean class="scope.MyBeanPostProcessor"></bean> - 输出为:
- constructor...
- postProcessBeforeInitialization cycle.Car@2db7a79b ,car
- post construct ..
- initializing bean...
- init-method...
- postProcessAfterInitialization cycle.Car@2db7a79b ,car
- cycle.Car@2db7a79b
- // 关闭了
- before destroy...
- disposable bean...
- destroy-method....
# 自动装配
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:p="http://www.springframework.org/schema/p"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
- <bean id="car" class="autowire.Car"
- p:brand="audi" p:price="300000">
</bean> - <bean id="address" class="autowire.Address"
- p:city="Nanjing" p:street="Xianlin">
</bean> - <bean id="person" class="autowire.Person"
- p:name="NewSong" p:car-ref="car" p:addr-ref="address">
</bean> - 不使用自动装配
- 可以使用 p 命名空间中的 autowire 属性,它的值是 byName 或者 byType
- 如果是 byName,就会根据当前这个 Bean 的类的属性名(由 setter 分隔出来的)在 xml 文件中找同名(id)的 bean,如果是,那么就匹配。
- 因此,我们推荐将 id 设置为首字母小写的类名(类的属性也是如此)
- 并且这个 autowire 可以将多个符合的装配,不仅仅是匹配一个;如果没有匹配的就不装配。
- 如果是 byType,就会根据 Bean 的类型和当前 Bean 的属性的类型进行匹配,前提是每个类只可以对应一个 Bean。如果有重名,那么会出错。
- 而对 byName 而言,可以修改 setter 的方法名,使得由 setter 分隔出来的属性名与 id 一致。
- autowire 一旦使用,就会对所有的引用属性使用自动装配;不能同时使用 byType 和 byName.
- 实际上很少使用自动装配
- <bean id="car" class="autowire.Car"
- p:brand="audi" p:price="300000">
</bean> - <bean id="address" class="autowire.Address"
- p:city="Nanjing" p:street="Xianlin">
</bean> - <bean id="person" class="autowire.Person"
- p:name="NewSong" autowire="byName">
</bean> - 注意 Person 持有了 Car 和 Address,只在 Person 上加 autowire 即可
# AOP
# 简介
# AOP 术语
- 1、切面(aspect/advisor)
- 类是对物体特征的抽象,切面就是对横切关注点的抽象。组合了 Pointcut 与 Advice,在 Spring 中有时候也称为 Advisor。
- 2、连接点(join point)
- 被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring 中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。
- 3、切入点(pointcut)
- 描述的一组符合某个条件的 join point,通常使用 pointcut 表达式来限定 join point。
- 4、通知(advice)
- 所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、返回、环绕通知五类。
- 5、目标对象
- 代理的目标对象
- 6、织入(weave)
- 将 Advice 织入 join point 的这个过程
- 7、引介(introduction)
- 在不修改代码的前提下,引介可以在运行期为类动态地添加一些方法或字段
# Advisor
- 通知 Advice 是 Spring 提供的一种切面(Aspect)。但其功能过于简单,只能将切面织入到目标类的所有目标方法中,无法完成将切面织入到指定目标方法中。
- 顾问 Advisor 是 Spring 提供的另一种切面。其可以完成更为复杂的切面织入功能。PointcutAdvisor 是顾问的一种,可以指定具体的切入点。顾问对通知进行了包装,会根据不同的通知类型,在不同的时间点,将切面织入到不同的切入点。
- Advisor 组合了 Pointcut 与 Advice。
- 除了引介 Advisor 外,几乎所有的 advisor 都是 PointcutAdvisor。
public interface Advisor {
Advice getAdvice();
/**
* @return whether this advice is associated with a particular target instance
*/
boolean isPerInstance();
}
2
3
4
5
6
7
public interface PointcutAdvisor extends Advisor {
/**
* Get the Pointcut that drives this advisor.
*/
Pointcut getPointcut();
}
2
3
4
5
6
7
8
# Advice
public interface Advice {
}
2
3
- 增强(advice)主要包括如下五种类型
- 前置增强(BeforeAdvice):在目标方法执行前实施增强
- 后置增强(AfterAdvice):在目标方法执行后(无论是否抛出遗产)实施增强
- 环绕增强(MethodInterceptor):在目标方法执行前后实施增强
- 异常抛出增强(ThrowsAdvice):在目标方法抛出异常后实施增强
- 返回增强(AfterReturningAdvice):在目标方法正常返回后实施增强
- 引介增强(IntroductionIntercrptor):在目标类中添加一些新的方法和属性
# JDK 动态代理与 CGLIB 代理
- JDK 动态代理:
- 其代理对象必须是某个接口的实现,它是通过在运行时创建一个接口的实现类来完成对目标对象的代理
- CGLIB 代理:在运行时生成的代理对象是针对目标类扩展的子类。
- CGLIB 是高效的代码生产包,底层是依靠 ASM 操作字节码实现的,性能比 JDK 强。
- 相关标签
- <aop:aspectj-autoproxy proxy-target-class=”true”/>
- true 表示使用 CGLIB 代理。
# 源码
# 解析 AOP 标签
- <aop:aspectj-autoproxy />
- 解析配置文件时,一旦遇到 aspectj-autoproxy 注解时就会使用解析器
- AspectJAutoProxyBeanDefinitionParser 进行解析。
- 解析结果是注册了一个 bean:AnnotationAwareAspectJAutoProxyCreator。
# 创建 AOP 代理
- 针对于实现了 InstantiationAwareBeanPostProcessor 接口的 BeanPostProcessor,在 doCreateBean 之前调用,如果需要进行代理,则直接返回代理后的 bean。
- 前面解析 AOP 标签时注册了 AnnotationAwareAspectJAutoProxyCreator,它继承了 AbstractAdvisorAutoProxyCreator,是一个 InstantiationAwareBeanPostProcessor。
# wrapIfNecessary
逻辑:
- 1)获取可以应用到该 bean 的所有 advisor
- 2)创建代理
AbstractAutoProxyCreator.createProxy
在获取了所有对应的 bean 的增强后,便可以进行代理的创建了。
逻辑:
- 1)获取当前类中的属性
- 2)添加代理接口
- 3)封装 Advisor 并加入到 ProxyFactory 中
- 4)设置 proxyFactory 的 target 要代理的类
- 5)进行获取代理操作 ProxyFactory#getProxy
createAopProxy().getProxy(classLoader);
如果目标对象实现了接口,默认情况下会使用 JDK 的动态代理
如果目标对象实现了接口,可以强制使用 CGLIB(proxy-target-class=false)
如果目标对象没有实现接口,必须采用 CGLIB
# Transaction
# 事务
- 编程式事务:所谓编程式事务指的是通过编码方式实现事务,即类似于 JDBC 编程实现事务管理。管理使用 TransactionTemplate 或者直接使用底层的 PlatformTransactionManager。对于编程式事务管理,Spring 推荐使用 TransactionTemplate。
- 声明式事务:管理建立在 AOP 之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional 注解的方式),便可以将事务规则应用到业务逻辑中。
- 显然声明式事务管理要优于编程式事务管理,这正是 spring 倡导的非侵入式的开发方式。
- 声明式事务管理使业务代码不受污染,一个普通的 POJO 对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
- AOP 可以帮助实现声明式事务,将事务管理从业务代码中分离
- • Spring 从不同的事务管理 API 中抽象了一整套的事务机制. 开发人员不必了解底层的事务 API, 就可以利用这些事务机制. 有了这些事务机制, 事务管理代码就能独立于特定的事务技术了.
- • Spring 的核心事务管理抽象是 PlatformTransactionManager,它为事务管理封装了一组独立于技术的方法. 无论使用 Spring 的哪种事务管理策略(编程式或声明式), 事务管理器都是必须的。
# 事务管理器
- 这些都是 PlatformTransactionManager
# 使用注解声明式管理事务
•- 除了在带有切入点, 通知和增强器的 Bean 配置文件中声明事务外, Spring 还允许简单地用 @Transactional 注解来标注事务方法. •- 为了将方法定义为支持事务处理的, 可以为方法添加 @Transactional 注解. 根据 Spring AOP 基于代理机制, 只能标注公有方法. •- 可以在方法或者类级别上添加 @Transactional 注解. 当把这个注解应用到类上时, 这个类中的所有公共方法都会被定义成支持事务处理的. •- 在 Bean 配置文件中只需要启用 tx:annotation-driven 元素, 并为之指定事务管理器就可以了. •- 如果事务处理器的名称是 transactionManager, 就可以在tx:annotation-driven 元素中省略 transaction-manager 属性. 这个元素会自动检测该名称的事务处理器.
# 事务隔离级别
- 设置回滚事务属性
- 默认情况下只对 RuntimeException 和 Error 回滚
- 继承自 Exception 的不会回滚
- rollbackFor 和 noRollbackFor 可以设置对某些特定异常是否回滚
- 值是一个 Class 对象的数组
# 事务传播行为
- 当事务方法被另一个事务方法调用时, 必须指定事务应该如何传播. 例如: 方法可能继续在现有事务中运行, 也可能开启一个新事务, 并在自己的事务中运行.
- 事务的传播行为可以由传播属性指定. Spring 定义了 7 种类传播行为.
# PROPAGATION_REQUIRED(重要)
- 如果当前存在一个事务,则加入当前事务;如果不存在任何事务,则创建一个新的事务。总之,要至少保证在一个事务中运行。PROPAGATION_REQUIRED 通常作为默认的事务传播行为。
- REQUIRED(内层事务 newTransaction 为 false,内层事务回滚时仅设置回滚标记,外层事务进行外层回滚)
# PROPAGATION_SUPPORTS
- 如果当前存在一个事务,则加入当前事务;如果当前不存在事务,则直接执行。 对于一些查询方法来说,PROPAGATION_SUPPORTS 通常是比较合适的传播行为选择。 如果当前方法直接执行,那么不需要事务的支持;如果当前方法被其他方法调用,而其他方法启动了一个事务的时候,使用 PROPAGATION_SUPPORTS 可以保证当前方法能够加入当前事务并洞察当前事务对数据资源所做的更新。 比如说,A.service()会首先更新数据库,然后调用 B.service()进行查询,那么,B.service()如果是 PROPAGATION_SUPPORTS 的传播行为, 就可以读取 A.service()之前所做的最新更新结果,而如果使用稍后所提到的 PROPAGATION_NOT_SUPPORTED,则 B.service()将无法读取最新的更新结果,因为 A.service()的事务在这个时候还没有提交(除非隔离级别是 read uncommitted)。
# PROPAGATION_MANDATORY
- PROPAGATION_MANDATORY 强制要求当前存在一个事务,如果不存在,则抛出异常。 如果某个方法需要事务支持,但自身又不管理事务提交或者回滚的时候,比较适合使用
- PROPAGATION_MANDATORY。
# PROPAGATION_REQUIRES_NEW(重要,两事务完全独立,独立提交、独立回滚)
- 不管当前是否存在事务,都会创建新的事务。如果当前存在事务的话,会将当前的事务挂起(suspend)。 如果某个业务对象所做的事情不想影响到外层事务的话,PROPAGATION_REQUIRES_NEW 应该是合适的选择,比如,假设当前的业务方法需要向数据库中更新某些日志信息, 但即使这些日志信息更新失败,我们也不想因为该业务方法的事务回滚而影响到外层事务的成功提交,因为这种情况下,当前业务方法的事务成功与否对外层事务来说是无关紧要的。
- REQUIRES_NEW(内外层事务平级,内层事务 newTransaction 为 true,suspend 外层事务,抛出异常后内层事务进行内层回滚,resume 外层事务,外层事务捕获到内层抛出的异常后进行外层回滚)
# PROPAGATION_NOT_SUPPORTED
- 不支持当前事务,而是在没有事务的情况下执行。如果当前存在事务的话,当前事务原则上将被挂起(suspend),但要依赖于对应的 PlatformTransactionManager 实现类是否支持事务的挂起(suspend),更多情况请参照 TransactionDefinition 的 javadoc 文档。 PROPAGATION_NOT_SUPPORTED 与 PROPAGATION_SUPPORTS 之间的区别,可以参照 PROPAGATION_SUPPORTS 部分的实例内容。
# PROPAGATION_NEVER
- 永远不需要当前存在事务,如果存在当前事务,则抛出异常。
# PROPAGATION_NESTED(重要,内部事务是外部事务的子事务。子事务出现异常会回滚到 savepoint,外部事务提交/回滚时子事务也进行提交/回滚)
- 如果存在当前事务,则在当前事务的一个嵌套事务中执行,否则与 PROPAGATION_REQUIRED 的行为类似,即创建新的事务,在新创建的事务中执行。 PROPAGATION_NESTED 粗看起来好像与 PROPAGATION_REQUIRES_NEW 的行为类似,实际上二者是有差别的。 PROPAGATION_REQUIRES_NEW 创建的新事务与外层事务属于同一个“档次”,即二者的地位是相同的,当新创建的事务运行的时候,外层事务将被暂时挂起(suspend); 而 PROPAGATION_NESTED 创建的嵌套事务则不然,它是寄生于当前外层事务的,它的地位比当前外层事务的地位要小一号,当内部嵌套事务运行的时候,外层事务也是处于 active 状态。是已经存在事务的一个真正的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交,外部事务回滚,它也会被回滚。
- NESTED(内外层事务嵌套,内层事务 newTransaction 为 false,并创建还原点,抛出异常后 rollback 至还原点,外层事务捕获到内层抛出的异常后进行外层回滚)
# 超时和只读属性
•- 由于事务可以在行和表上获得锁, 因此长事务会占用资源, 并对整体性能产生影响. •- 如果一个事物只读取数据但不做修改, 数据库引擎可以对这个事务进行优化. •- 超时事务属性: 事务在强制回滚之前可以保持多久. 这样可以防止长期运行的事务占用资源. •- 只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务.
- 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持 SQL 执行期间的读一致性;
- 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询 SQL 必须保证整体的读一致性,否则,在前条 SQL 查询之后,后条 SQL 查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持
- read-only="true"表示该事务为只读事务,比如上面说的多条查询的这种情况可以使用只读事务,由于只读事务不存在数据的修改,因此数据库将会为只读事务提供一些优化手段,例如 Oracle 对于只读事务,不启动回滚段,不记录回滚 log。
- (1)在 JDBC 中,指定只读事务的办法为: connection.setReadOnly(true);
- (2)在 Hibernate 中,指定只读事务的办法为: session.setFlushMode(FlushMode.NEVER);
- 此时,Hibernate 也会为只读事务提供 Session 方面的一些优化手段
- (3)在 Spring 的 Hibernate 封装中,指定只读事务的办法为: bean 配置文件中,prop 属性增加“read-Only”或者用注解方式@Transactional(readOnly=true)
- Spring 中设置只读事务是利用上面两种方式(根据实际情况)
- 在将事务设置成只读后,相当于将数据库设置成只读数据库,此时若要进行写的操作,会出现错误。
# 源码
# 解析事务标签
- <tx:annotation-driven />
- 同 AOP 标签,需要一个对应的 BeanDefinitionParser,AnnotationDrivenBeanDefinitionParser。
- InfrastructureAdvisorAutoProxyCreator 作为一个 AbstractAutoProxyCreator,会在 getBean 时调用其 postProcessAfterInstantiation 方法,该方法会创建事务代理。
- 我们之前注册了 BeanFactoryTransactionAttributeSourceAdvisor 这个类,这个类实现了 Advisor 接口。BeanFactoryTransactionAttributeSourceAdvisor 作为一个 advisor,用于对事务方法进行增强。只要类或方法实现了@Transactional 接口,该 Advisor 一定会被加到拦截器链中,对原方法进行事务增强。
- 返回的 Advisor 类型是 BeanFactoryTransactionAttributeSourceAdvisor,而其 beanName 是 TransactionInterceptor。
- 如果方法中存在事务属性(TransactionAttribute),则使用方法上的属性,否则使用方法所在的类上的属性。如果方法所在类的属性上还是没有搜寻到对应的事务属性,那么再搜寻接口中的方法,再没有的话,最后尝试搜寻接口的类上面的声明。
- 解析@Transactional 中的各个属性,并封装到 TransactionAttribute 中返回。
# 创建事务代理
对于标记了@Transactional 的方法而言,会被代理,增强事务功能。
这些方法的 Advisor 增强中包括了 TransactionInterceptor
(BeanFactoryTransactionAttributeSourceAdvisor 对应的 bean)。
TransactionInterceptor 支撑着整个事务功能的架构,它继承了 MethodInterceptor。
TransactionAspectSupport#invokeWithinTransaction(最主要的方法)
逻辑:
- 1)获取事务属性 TransactionAttribute
- 2)加载配置中的 TransactionManager
- 3)不同的事务处理方式使用不同的逻辑,就声明式事务而言,会获取方法信息并创建事务信息 TransactionInfo(此时已经创建了事务)
事务信息(TransactionInfo)与事务属性(TransactionAttribute)并不相同。
前者包含了后者,且包含了其他事务信息,比如 PlatformTransactionManager 以及 TransactionStatus 相关信息。
- 4)try:执行原始方法
- 5)catch:异常,回滚事务,再次抛出异常,7)及以后的不会执行
- 6)finally:清除事务信息
- 7)提交事务
- 8)返回原始方法的返回值
# 开启事务
逻辑:
- 1)获取事务,创建对应的事务实例
- 2)如果当前线程存在事务,那么根据传播行为进行相应处理,处理完成后返回
值得注意的有两点:
- 1)REQUIRES_NEW 表示当前方法必须在它自己的事务里运行,一个新的事务将被启动。而如果有一个事务正在运行的话,则在这个方法运行期间被挂起(suspend)。
- 2)NESTED 表示如果当前正在有一个事务在运行中,则该方法应该运行在一个嵌套的事务中,被嵌套的事务可以独立于封装事务进行提交或者回滚。如果封装事务不存在,行为就像 REQUIRES_NEW。
Spring 主要有两种处理 NESTED 的方式:
首选设置保存点的方式作为异常处理的回滚
JTA 无法使用保存点,那么处理方式和 REQUIRES_NEW 相同,而一旦出现异常,则由 Spring 的事务异常处理机制去完成后续操作。
- 3)事务超时的验证
- 4)事务传播行为的验证
- 5)构建 DefaultTransactionStatus,创建当前事务的状态
- 6)完善 transaction,包括设置 ConnectionHolder、隔离级别、timeout,如果是新连接,则绑定到当前线程
- 7)将事务信息记录在当前线程中。
# 回滚事务
- 一旦事务执行失败,Spring 会通过 TransactionInfo 实例来进行回滚等后续工作。
- 1)默认情况下判断是否回滚默认的依据是 抛出的异常是否是 RuntimeException 或者是 Error 的类型
- 2)如果不满足回滚条件,即使抛出异常也会提交。
- 3)有保存点则回滚到保存点,是新事务则回滚整个事务;存在事务又不是新事务,则做回滚标记,等到事务链执行完毕后统一回滚。
- 4)将挂起事务恢复。
# 提交事务
- 某个事务是另一个事务的嵌入事务,但是这些事务又不在 Spring 的管理范围之内,或者无法设置保存点,那么 Spring 会通过设置回滚标识的方式来禁止提交。首先当某个嵌入事务发生回滚的时候会设置回滚标识,而等到外部事务提交时,一旦判断出当前事务流被设置了回滚标识,则由外部事务来统一进行整体事务的回滚。
- 在提交过程中也不是直接提交的,而是考虑了诸多方面。
- 符合提交的条件如下:
- 当事务状态中有保存点信息的话便不会提交事务;
- 当事务不是新事务的时候也不会提交事务
- 原因是:
- 对于内嵌事务,在 Spring 中会将其在开始之前设置保存点,一旦内嵌事务出现异常便根据保存点信息进行回滚,但是如果没有出现异常,内嵌事务并不会单独提交,而是根据事务流由最外层事务负责提交,所以如果当前存在保存点信息便不是最外层事务,不做提交操作。
# MVC
# 面试题
# 与 Struts 的区别
- SpringMVC 的入口是一个 Servlet 即前端控制器,而 Struts2 入口是一个 Filter 过虑器。
- SpringMVC 是基于方法开发(一个 url 对应一个方法),请求参数传递到方法的形参,可以设计为单例或多例(建议单例),struts2 是基于类开发,传递参数是通过类的属性,只能设计为多例。
# SpringMVC 的控制器是不是单例模式,如果是,有什么问题,怎么解决
- 是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能的,解决方案是在控制器里面不能写字段
# SpingMVC 中的控制器的注解一般用那个,有没有别的注解可以替代
- 一般用@Conntroller 注解,表示是表现层,不能用用别的注解代替.
# @RequestMapping 注解用在类上面有什么作用
- 用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
# 怎么样把某个请求映射到特定的方法上面
- 直接在方法上面加上注解@RequestMapping,并且在这个注解里面写上要拦截的路径
# 如果在拦截请求中,我想拦截 get 方式提交的方法,怎么配置
- 可以在@RequestMapping 注解里面加上 method=RequestMethod.GET
# 如果在拦截请求中,我想拦截提交参数中包含"type=test"字符串,怎么配置
- 可以在@RequestMapping 注解里面加上 params="type=test"
# 我想在拦截的方法里面得到从前台传入的参数,怎么得到
- 直接在形参里面声明这个参数就可以,但必须名字和传过来的参数一样
# 如果前台有很多个参数传入,并且这些参数都是一个对象的,那么怎么样快速得到这个对象
- 直接在方法中声明这个对象,SpringMVC 就自动会把属性赋值到这个对象里面
# 怎么样在方法里面得到 Request,或者 Session
- 直接在方法的形参中声明 request,SpringMVC 就自动把 request 对象传入
# SpringMVC 中函数的返回值是什么.
- 返回值可以有很多类型,有 String, ModelAndView,当一般用 String 比较好
# SpringMVC 怎么处理返回值的
- SpringMVC 根据配置文件中 InternalResourceViewResolver 的前缀和后缀,用前缀+返回值+后缀组成完整的返回值
# SpringMVC 怎么样设定重定向和转发的
- 在返回值前面加"forward:"就可以让结果转发,譬如"forward:user.do?name=method4" 在返回值前面加"redirect:"就可以让返回值重定向,譬如"redirect:http://www.baidu.com"
- forward/redirect:除了跳转至某个页面,也可以跳转至某个 controller 处理方法。
- 并且使用时需要以/开头, 默认是相对于项目的根目录的,不加就是相对于当前 controller 的@RequestMapping 值的路径(没有这个注解就是相对于项目根目录)。
# SpringMVC 用什么对象从后台向前台传递数据的
- 通过 ModelMap 对象,可以在这个对象里面用 put 方法,把对象加到里面,前台就可以通过 el 表达式拿到
# SpringMVC 中有个类把视图和数据都合并的一起的,叫什么
- 叫 ModelAndView
# 怎么样把 ModelMap 里面的数据放入 Session 里面
- 可以在类上面加上@SessionAttributes 注解,里面包含的字符串就是要放入 session 里面的 key
# SpringMVC 怎么和 AJAX 相互调用的
- 通过 Jackson 框架就可以把 Java 里面的对象直接转化成 Js 可以识别的 Json 对象
- 具体步骤如下
- 1.加入 Jackson.jar
- 2.在配置文件中配置 json 的映射
- 3.在接受 Ajax 方法里面可以直接返回 Object,List 等,但方法前面要加上@ResponseBody 注解
# 当一个方法向 AJAX 返回特殊对象,譬如 Object,List 等,需要做什么处理
- 要加上@ResponseBody 注解
# Mapping
# @RequestMapping(映射)
- RequestMapping 是根据该访问路径来决定调用哪个方法来处理,访问路径是由类上的 RequestMapping+方法上的 RequestMapping 决定的。
- 返回值是转发到哪个路径下,转发路径是由 prefix+返回值+suffix 决定的。
# @PathVariable(注入)
# @RequestParam(注入)
# @RequestHeader(注入)
# @CookieValue(注入)
# 使用 POJO 对象绑定请求参数值(注入)
# 使用 Servlet 原生 API 作为参数(注入)
# 数据绑定(注入)/类型转换/校验
# WebDataBinder
# 数据转换
# 数据格式化
- 格式化和类型转换是同时进行的
# 数据校验
# HttpMessageConverter
- HttpMessageConverter 使用
# Model
# ModelAndView
- 在 handle 方法中获取视图和模型,然后将域对象存放到视图和模型(ModelAndView)中。
- 默认是放到 request 域中。
# Map&Model
- 参数的真实类型为 ExtendedModelMap 对象的子类,既实现了 Model 接口,又继承了 ModelMap 类
- 该参数的类型也可以写为 Model 或者 ModelMap,不过一般使用 Map 类型就足够了
# View
<!-- 配置视图解析器 将controller方法返回值解析为实际的物理视图--><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/WEB-INF/views/"></property><property name="suffix" value=".jsp"></property></bean>- 解析流程
- 无论返回值是一个 ModelAndView、String、View,SpringMVC 都会将其转为一个 ModelAndView 对象。
- 通过 Controller 实现 URL 映射得到 ModelAndView,然后 ViewResolver 实例化 View 对象,得到物理视图,然后调用 View 的 render 方法,将 Model 数据渲染到 View 中,然后转发至 View。
# 视图
- 每个请求都会创建一个新的视图对象
# 视图解析器
# InternalResourceViewResolver
- InternalResourceView
- InternalResourceViewResolver
- 这两个接口实现和 JSP 有关的渲染和转发功能。
# annotation-driven
- 前两个 bean 是与 RequestMapping 有关,第三个 bean 与异常处理有关。
- 还可以处理数据类型转换、数据格式化、数据校验、AJAX。
- 以下是 DispatcherServlet 的持有的 handlerAdapters 的数据情况:
# default-servlet-handler
# 异常处理
# HandlerExceptionResolver
# ExceptionHandlerExceptionResolver
- 可以把处理异常的代码放到一个类中,比如 ExceptionControllerAdvice
- 为这个类加一个注解@ControllerAdvice
- 当在当前类找不到对应的注解了@ExceptionHandler 的方法,那么将会在注解了@ControllerAdvice 的类中注解了@ExceptionHandler 方法去处理。
# 拦截器
# 使用
- 第一步:定义一个拦截器,实现 HandlerInterceptor 接口
public class FirstInterceptor implements HandlerInterceptor {
- @Override
public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
- throws Exception {
- System.out.println("[FirstInterceptor] afterCompletion");
- }
- @Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
- throws Exception {
- System.out.println("[FirstInterceptor] postHandle");
- }
- @Override
public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception {
- System.out.println("[FirstInterceptor] preHandle");
- return true;
- }
- }
- 第二步:在 springmvc 配置文件中进行如下配置:
- mvc:interceptors
<!-- 配置自定义拦截器 --><bean class="me.newsong.interceptors.FirstInterceptor"></bean><bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"></bean>- /mvc:interceptors
- [FirstInterceptor] preHandle
- //controller 处理方法被调用
- [FirstInterceptor] postHandle
- [FirstInterceptor] afterCompletion
# 顺序
- 类似于 Filter,当返回 true 时,继续检测其他拦截器,返回 false,直接调到放行过的拦截器的 afterComplication 方法。
- preHandle 是在调用处理方法之前调用
- postHandle 是在调用处理方法之后,渲染视图之前被调用。
- afterCompletion 是在渲染视图之后被调用
- preHandle:权限;日志;事务等
- handle
- postHandle 修改 request 域的属性;修改视图
- render
- afterCompltion 释放资源
- 对于 preHandle 方法,按照拦截器的配置顺序的顺序执行
- 对于 postHandle 方法,按照拦截器的配置顺序的逆序执行
- afterComplication 同 postHandle 方法
# 源码
- DispatcherServlet: SpringMVC 总的拦截器
- HandlerMapping:请求和处理器之间的映射,用于获取 HandlerExecutionChain
- HandlerExecutionChain:持有一组 Interceptor 和实际请求处理器 HandlerAdapter,负责执行 Interceptor 的各个方法和处理方法。
- HandlerAdapter:实际的请求处理器,处理后返回 ModelAndView
- HandlerExceptionResolver:异常处理器,当拦截器的 postHandle 方法调用后检查异常。
- ViewResolver:视图解析器,解析视图名,得到 View,由逻辑视图变为物理视图。
- View :有 render 方法,渲染视图
- 渲染完毕后调用转发
# 初始化 ApplicationContext
- ContextLoaderListener(入口)
- ContextLoaderListener 的作用就是启动 Web 容器时,自动装配 ApplicationContext 的配置信息。
- 因为它实现了 ServletContextListener 这个接口,在 web.xml 配置这个监听器,启动容器时,就会默认执行它实现的方法,使用 ServletContextListener 接口,开发者能够在为客户端请求提供服务之前向 ServletContext 中添加任意的对象。
- 在 ContextLoaderListener 中的核心逻辑是初始化 WebApplicationContext 实例并存放在 ServletContext 中。
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
2
3
- 在初始化过程中,程序首先会读取 ContextLoader 类的同目录下的配置文件 ContextLoader.properties,并根据其中的配置提取将要实现 WebApplicationContext 接口的实现类,并根据这个实现类通过反射的方式进行实例的创建。
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
- 在 ContextLoader 目录下有一个配置文件 ContextLoader.properties
- 内容为:
- org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
# 初始化 DispatcherServlet
第一次访问网站时,会初始化访问到的 servlet。
初始化阶段会调用 servlet 的 init 方法,在 DispatcherServlet 中是由其父类 HttpServletBean 实现的。
逻辑:
- 1)封装及验证初始化参数 解析 init-param 并封装到 PropertyValues 中
- 2)将 DispatcherServlet 转化为 BeanWrapper 实例
- 3)注册相对于 Resource 的属性编辑器
- 4)PropertyValues 属性注入
- 5)servletBean 的初始化(initServletBean)
FrameworkServlet#initServletBean 调用了 FrameworkServlet#initWebApplicationContext
创建或刷新 WebApplicationContext 实例,并对 servlet 功能所使用的变量进行初始化
逻辑:
- 1)对已经创建的 WebApplicationContext 实例进行配置和刷新
(configureAndRefreshWebApplicationContext)
最后会调用 ApplicationContext 中的 refresh 方法(与 IOC 的衔接)
- 2)刷新 Spring 在 Web 功能实现中必须使用的全局变量(onRefresh)
初始化各个模块:
// 初始化文件上传模块 initMultipartResolver(context);
// 初始化国际化模块 initLocaleResolver(context);
// 初始化主题模块 initThemeResolver(context);
// 初始化 HandlerMappings initHandlerMappings(context);
// 初始化 HandlerAdapters initHandlerAdapters(context);
// 初始化 HandlerExceptionResolvers initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context);
// 初始化 ViewResolvers initViewResolvers(context); initFlashMapManager(context);
重点注意初始化 HandlerMappings 和 HandlerAdapters。
初始化 HandlerMappings 是将 Controller 的各个请求方法封装为 HandlerMethod,注册到 mappingRegistry(key 是 RequestMappingInfo,value 是 MappingRegistration,它封装了之前创建的 handlerMethod)中。
初始化 HandlerAdapters 是获取所有实现了 HandlerAdapter 接口的类保存到成员变量 handlerAdapters 中。
RequestMappingHandlerAdapter(请求映射处理器适配器)作为例子,初始化时会去初始化 argumentResolvers、returnValueHandlers 等。
# 处理请求
FrameworkServlet#service(入口)所有请求都会调用 processRequest 方法。
逻辑:
- 1)为了保证当前线程的 LocaleContext 和 RequestAttributes 可以在当前请求处理完毕后还能恢复,提取当前线程的两个属性
- 2)根据当前 request 创建对应的 LocaleContext 和 RequestAttributes,并绑定到当前线程
- 3)委托给 doService(DispatcherServlet#doService)方法进一步处理
- 4)请求处理结束后恢复线程到原始状态
- 5)请求处理结束后无论成功与否都会发布事件通知
doService 的主体是在 doDispatch 方法中进行。
逻辑:
- 1)处理文件上传请求
- 2)将 handler 实例(即 HandlerMethod)和所有匹配的拦截器封装到 HandlerExecutionChain mappedHandler 中。如果没有找到,那么根据策略抛出异常或直接返回错误码。
- 3)getHandlerAdapter(根据 HandlerExecutionChain 获取可以支持它的 HandlerAdapter)
如果是 HandlerMethod,那么返回的是 RequestMappingHandlerAdapter。
- 4)调用 HandlerExecutionChain mappedHandler 的 applyPreHandle
- 5)执行 HandlerAdapter 的 handle 方法,处理请求。
比如 RequestMappingHandlerAdapter#handleInternal,
注入参数 getMethodArgumentValues
遍历请求参数,然后委托给 HandlerMethodArgumentResolver#resolveArgument 进行参数解析,先解析,后使用 WebDataBinder 进行数据转换(如有必要)
执行方法
处理返回值 如果返回值是普通的对象(@ResponseBody),那么 handler 是
RequestResponseBodyMethodProcessor。
- 6)转换视图名称,加上前缀和后缀
- 7)调用 HandlerExecutionChain mappedHandler 的 applyPostHandle
- 8)processDispatchResult(处理 ModelAndView 请求结果)
如果返回的是纯数据(@ResponseBody),mv 就是 null,该方法基本上是空方法。
处理异常:使用 handlerExceptionResolver 来处理异常
如果 handler 处理结果中返回了 view,那么需要对页面进行渲染,调用 render 方法渲染视图(需要跳转的话,在 render 中进行跳转)。
