Java面试知识点总结-Spring
Spring核心特性
Spring是什么?
Spring是一个轻量级的开源应用框架,旨在降低应用程序开发的复杂度。Spring 具有以下特性:
- 轻量级表现在完整的 Spring 框架可以在一个大小只有 1MB 多的 JAR 文件里发布
- 非侵入性,即允许应用系统自由选择和组装 Spring 框架中的各个功能模块,而不要求应用必需对 Spring 中的某个类继承或实现,极大地提高一致性
- 使用 IOC 容器管理对象的生命周期,以及对象间的依赖关系,降低系统耦合性
- 基于 AOP 的面向切面编程,将具有横切性质的业务放到切面中,从而与核心业务逻辑分离并提高组件复用性
- Spring 可以很好地与其他框架集成,被称为框架的框架
Spring中应用到的设计模式
- 简单工厂:Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
- 工厂方法:Spring中的FactoryBean就是典型的工厂方法模式。
- 单例:Bean的Singleton
- 适配器:Spring中在对于AOP的处理中有Adapter模式的例子
- 包装器:动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。基本上都是动态地给一个对象添加一些额外的职责。
- 代理:Spring的Proxy模式在aop中有体现,比如JdkDynamicAopProxy和Cglib2AopProxy。
- 观察者:Spring中Observer模式常用的地方是listener的实现。如ApplicationListener。
- 策略:Spring中在实例化对象的时候用到Strategy模式。
- 模板:JdbcTemplate
简述Spring的组件
- Data Access:JDBC;JMS;Transaction
- Web:Web;Servlet;Porlet
- AOP
- Core:Beans;Core;Context;Expression Language
IOC和DI的概念
IOC 指控制反转,是 Spring 中的一种设计思想以及重要特性。IOC 意味着将设计好的类交给容器控制,而不是在对象内部控制。控制,指的是容器控制对象,在传统的开发中,我们通过在对象内部通过 new 进行对象创建,而在 IOC 中专门有一个容器用来创建对象。反转指的是获取对象的方式发生了反转,以往对外部资源或对象的获取依赖于程序主动通过 new 引导,现在则是通过容器实现。容器帮我们查找并注入依赖对象。IOC sh一条面向对象的重要法则,可以指导我们设计出松耦合的程序,是的程序的系统结构变得非常灵活。
DI 指依赖注入,使得组件间的依赖关系由容器在运行期决定,容器可以动态地将某个依赖关系注入到组件之中。依赖指的是应用程序依赖于 IOC 容器注入对象所需的外部资源。注入指的是 IOC 容器向应用程序中注入某个对象或其他外部资源。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。Spring 依赖注入的方式有四种:
- 基于注解注入方式
- set 注入方式
- 构造器注入方式
- 静态工厂注入方式
各种注入方式区别:
1.基于constructor的注入,会固定依赖注入的顺序;该方式不允许我们创建bean对象之间的循环依赖关系,这种限制其实是一种利用构造器来注入的益处 - 当你甚至没有注意到使用setter注入的时候,Spring能解决循环依赖的问题;
2.基于setter的注入,只有当对象是需要被注入的时候它才会帮助我们注入依赖,而不是在初始化的时候就注入;另一方面如果你使用基于constructor注入,CGLIB不能创建一个代理,迫使你使用基于接口的代理或虚拟的无参数构造函数。
3.相信很多同学都选择使用直接在成员变量上写上注解来注入,正如我们所见,这种方式看起来非常好,精短,可读性高,不需要多余的代码,也方便维护;
缺点:
1.当我们利用constructor来注入的时候,比较明显的一个缺点就是:假如我们需要注入的对象特别多的时候,我们的构造器就会显得非常的冗余、不好看,非常影响美观和可读性,维护起来也较为困难;
2.当我们选择setter方法来注入的时候,我们不能将对象设为final的;
3.当我们在field变量上来实现注入的时候
a.这样不符合JavaBean的规范,而且很有可能引起空指针;
b.同时也不能将对象标为final的;
c.类与DI容器高度耦合,我们不能在外部使用它;
d.类不通过反射不能被实例化(例如单元测试中),你需要用DI容器去实例化它,这更像集成测试;
简述Spring的IOC体系
IOC循环依赖及其解决方案
循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。Spring中循环依赖场景有:
- 构造器的循环依赖
- field 属性的循环依赖
对于构造器的循环依赖,Spring 是无法解决的,只能抛出 BeanCurrentlyInCreationException 异常表示循环依赖。Spring 只解决 scope 为 singleton 的循环依赖,对于scope 为 prototype 的 bean Spring 无法解决,直接抛出 BeanCurrentlyInCreationException 异常。
循环依赖的检测较为容易,通过 Bean 创建时给 Bean 打标,如果存在递归调用时发现状态为正在创建,意味着发生了循环依赖。下面对循环依赖的解决方案进行介绍。
在加载 Bean 时会调用 doGetBean()
,首先会根据 beanName 从单例 bean 的缓存中获取,如果不为空则直接返回。缓存有三个,分别如下:
getSingleton() 整个过程如下:首先从一级缓存 singletonObjects 获取。如果没有且当前 beanName 的对象正在创建状态,就尝试从二级缓存中 earlySingletonObjects 获取。如果还是没有获取到且运行 singletonFactories 通过 getObject() 获取,则从三级缓存 singletonFactories 获取。如果获取到则,通过其 getObject() 获取对象,并将其加入到二级缓存 earlySingletonObjects 中 从三级缓存 singletonFactories 删除。一个 bean 要具备如下条件才会添加至缓存中:
- 单例
- 运行提前暴***ean
- 当前 bean 正在创建中
实际上 singletonFactories 这个三级缓存才是解决 Spring Bean 循环依赖的诀窍所在。 addSingletonFactory() 调用后 bean 其实已经被创建出来了,但是它还不是很完美(没有进行属性填充和初始化)。但是对于其他依赖它的对象而言已经足够了(可以根据对象引用定位到堆中对象),所以 Spring 在这个时候选择将该对象提前曝光。
当 SingleTon Bean 属性值填充完毕后添加至一级缓存,同时从二级、三级缓存中删除。
IOC容器的启动过程
(以ClassPathXmlApplicationContext为例)
- 进入构造函数,并先调用父类的的构造函数
- 根据提供的路径,处理配置文件数组,对应方法为:setConfigLocations(configLocations)
- 执行 Refresh 方法,该方法可以反复调用,销毁当前 ApplicationContext 并重新执行一次初始化操作,对应方法为:refresh()
- 进入 Refresh 方法后,首先进入同步块
- 然后执行准备工作,记录容器启动时间,标记当前状态为已启动并处理文件占位符,对应方法为:prepareRefresh();
- 执行 Bean 的加载、解析、注册,使得静态 XML 变为 BeanDefinition ,并注册到 BeanFactory 中。这里只是提取 Bean 的配置信息而非初始化;创建并初始化 BeanFactory 。对应方法为—— obtainFreshBeanFactory()
- 设置 BeanFactory 的类加载器,添加 BeanPostProcessor 接口并手动注册几个特殊的 Bean。对应方法为——prepareBeanFactory(beanFactory)
截止至此,Bean 的加载、解析、注册已经完成;BeanFactory 的创建以及初始化已经完成,如果不是第一次启动还会涉及 BeanFactory 的销毁
- 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法在初始化前做一些用户自定义的行为。对应方法为——postProcessBeanFactory(beanFactory);invokeBeanFactoryPostProcessors(beanFactory)
- 注册 BeanPostProcessor ,对应方法为:registerBeanPostProcessors(beanFactory)
- 做一些额外的初始化:initMessageSource();initApplicationEventMulticaster();onRefresh();registerListeners();
- 对所有可初始化的 SingletonBean 进行初始化,对应方法——finishBeanFactoryInitialization(beanFactory)
- 广播完成状态,对应方法——finishRefresh()
依赖注入发生在什么时刻
- 第一次通过 getBean 方法向 IoC 容器索要 Bean 时,IoC 容器触发依赖注入
- 在 Bean 定义资源中为 <bean> 元素配置了 lazy-init 属性,即让容器在解析注册 Bean 定义时进行预实例化,触发依赖注入</bean>
IOC中的预实例化
IoC 容器的初始化过程就是对 Bean 定义资源的定位、载入和注册,此时容器对 Bean 的依赖注入并没有发生,依赖注入主要是在应用程序第一次向容器索取 Bean 时,通过 getBean 方法的调用完成。当 Bean 定义资源的 <bean> 元素中配置了 lazy-init 属性时,容器将会在初始化的时候对所配置的 Bean 进行预实例化,Bean 的依赖注入在容器初始化的时候就已经完成。这样,当应用程序第一次向容器索取被管理的 Bean 时,就不用再初始化和对 Bean 进行依赖注入了,直接从容器中获取已经完成依赖注入的现成 Bean ,可以提高应用第一次向容器获取 Bean 的性能。</bean>
简述BeanPostProcessor后置处理器的实现
BeanPostProcessor 后置处理器是 Spring-IoC 容器经常使用到的一个特性,这个 Bean 后置处理器可以监听容器触发的 Bean 声明周期事件。后置处理器向容器注册以后,容器中管理的 Bean 就具备了接收 IoC 容器事件回调的能力。
package org.springframework.beans.factory.config; import org.springframework.beans.BeansException; public interface BeanPostProcessor { //为在Bean的初始化前提供回调入口 Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; //为在Bean的初始化之后提供回调入口 Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; }
Autowired的实现原理
Spring-IoC 容器具有依赖自动装配功能,不需要对 Bean 属性的依赖关系做显式的声明,只需要在配置好autowiring 属性,IoC 容器会自动使用 反射 查找属性的类型和名称,然后基于属性的类型或者名称来自动匹配容器中管理的 Bean ,从而自动地完成依赖注入。
AbstractAutoWireCapableBeanFactory 对 Bean 实例进行属性依赖注入。应用第一次通过 getBean 方法(配置了 lazy-init 预实例化属性的除外)向 IoC 容器索取 Bean 时,容器创建 Bean 实例对象,并且对 Bean 实例对象进行属性依赖注入,AbstractAutoWireCapableBeanFactory的populateBean 方法就是实现 Bean 属性依赖注入的功能。
Autowired中的类型
在使用 Autowired 时,可以配置在 <beans> 根标签下,表示对全局 <bean> 起作用,属性名为 default-autowire ;也可以配置在 <bean> 标签下,表示对当前 <bean> 起作用,属性名为 autowire 。取值可以分为如下几种: </bean></bean></bean></beans>
- no:默认,即不进行自动装配,每一个对象的注入比如依赖一个 <property> 标签</property>
- byName:按照 beanName 进行自动装配,使用 setter 注入,如果不匹配则报错
- byType:按照 bean 类型进行自动装配,使用 setter 注入,当有多个相同类型时会报错,解决方法是将不需要的 bean 设置 autowire-candidate=false 或对优先需要进行装配的 bean 设置为 primary=true
- constructor:与 byType 差不多,不过最终属性通过构造函数进行注入
AOP
AOP 指面向切面编程,通过预编译方式和运行期动态代理的一种技术。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而降低业务逻辑各组件的耦合度,提高程序的可重用性,同时提高了开发的效率。 AOP 具有以下核心概念:
- 切面:一些 Pointcut 以及相应的 Advice 的集合
- 连接点:表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它连接点
- 切点:通过制定某种规则来选定一组连接点,这些连接点或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的增强将要发生的地方
- 增强:定义了在切点里面定义的程序点具体要做的操作
- 目标对象:织入增强的目标对象
- 织入:将切面和其他对象连接起来, 并创建增强的过程
AOP应用场景
- 日志
- 权限及安全控制
- 性能统计
- 缓存
- 错误处理
- 懒加载
- 记录跟踪 优化 校准
- 持久化
- 资源池
- 同步
- 事务
AOP增强
- before:在切点前执行,除非before中发生异常,否则切点一定执行
- after:在一个切点正常执行后执行
- around:before+after
- final:无论是正常结束还是异常,都会执行
- after throwing:切点抛出异常后执行
AOP实现原理
AOP 基于动态代理模式实现,代理模式允许调用者在不改变被调用者方法的前提下,通过代理对象对目标方法进行扩展。在 AOP 的设计中,每个 Bean 都会被 JDK或 cglib 代理并有多个方法拦截器。拦截器分为两层,外层由 Spring 内核控制,内层拦截器由用户设置。当代理方法被调用时,先经过外层拦截器,外层拦截器根据方法提供的信息判断哪些内层拦截器应该被执行,然后会创建并执行拦截器链,最后调用目标对象的方法。内层拦截器的设计采用了职责链的设计。
Spring 中 AOP 的实现依赖于动态代理的实现。动态代理主要有两种实现,分别是 JDK 动态代理和 cglib 代理。采用 JDK 动态代理,目标类必须实现某个接口,否则不可用;而 CGLIB 底层是通过使用一个小而块的字节码处理框架 ASM 来转换字节码并生成新的类,覆盖或添加类中的方法。从上述描述中不难看出,cglib 类中方法类型不能设置为final 。在执行效率上,早期的 JDK 动态代理效率远低于 cglib ,而随着 JDK 版本的更新,现在 JDK 动态代理的效率已经和 cglib 不相伯仲。
Bean
Bean是什么
在 Spring 中,Bean 是组成应用程序的主体及由 Spring IoC 容器所管理的对象,被称之为 Bean。简单地讲,Bean 就是由 IoC 容器初始化、装配及管理的对象。而 Bean 的定义以及 Bean 相互间的依赖关系将通过配置元数据来描述。
Bean的作用域
- singleton
- prototype
- request
- session
- globalsession
五种作用域中,request 、 session 和 globalsession 三种作用域仅在基于 Web 的应用中使用。一般在 <bean> 标签中通过 scope 指定作用域类型,也可以在 <beans> 下指定默认全局的 scope 类型。其中 Singleton 与 Prototype 类型的区别在于:Prototype 在交给用户后,IOC 容器不在具有管理权限,即放弃对该 bean 的生命周期管理。而 IOC 容器则会对 Singleton 进行完整的生命周期管理;Singleton 默认采用非延迟初始化,也可通过设置 lazy-init 属性改变初始化方式,但是 prototype 只能采用延迟初始化方式。</beans></bean>
述Bean的生命周期(获得Bean的步骤)
- Spring 对 bean 进行实例化,默认 bean 是单例
- Spring 对 bean 进行依赖注入
- 如果 bean 实现了 BeanNameAware 接口,spring 将 bean 的 id 传给 setBeanName() 方法;
- 如果 bean 实现了 BeanFactoryAware 接口,spring 将调用 setBeanFactory 方法,将 BeanFactory 实例传进来
- 如果 bean 实现了 ApplicationContextAware 接口,它的 setApplicationContext() 方法将被调用,将应用上下文的引用传入到 bean 中
- 如果 bean 实现了 BeanPostProcessor 接口,它的 postProcessBeforeInitialization 方法将被调用,该方法在 bean 初始化之前执行
- 如果 bean 实现了 InitializingBean 接口,Spring 将调用它的 afterPropertiesSet 接口方法,用于初始化 bean 的时候执行,可以针对某个具体的 bean 进行配置
- 如果 bean 使用了 init-method 属性声明了初始化方法,该方法也会被调用,初始化 bean 的时候执行,可以针对某个具体的 bean 进行配置
- 如果 bean 实现了 BeanPostProcessor 接口,它的 postProcessAfterInitialization 接口方法将被调用,该方法在 bean 初始化之后执行
- 此时 bean 已经准备就绪,可以被应用程序使用了,他们将一直驻留在应用上下文中,直到该应用上下文被销毁
- 如果 bean 实现了 DisposableBean 接口,spring 将调用它的 distroy() 接口方法
- 如果 bean 使用了 destroy-method 属性声明了销毁方法,则该方法被调用
BeanFactory
BeanFactory 是一个接口,用于定义工厂的基本职能并对 IOC 容器的基本行为作了定义。它是负责生产和管理 bean 的一个工厂。在 Spring 中,BeanFactory 是 IOC 容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。BeanFactory 只是个接口,并不是 IOC 容器的具体实现。Spring 提供了许多 IOC 容器的实现,比如 XmlBeanFactory, ClasspathXmlApplicationContext , ApplicationContext 等。BeanFactory 中大致定义了如下行为:
- getBean(String name)——根据bean的名字,获取在IOC容器中得到bean实例
- containsBean(String name)——提供对bean的检索,看看是否在IOC容器有这个名字的bean
- isSingleton(String name)——根据bean名字得到bean实例,并同时判断这个bean是不是单例
- getType(String name)——得到bean实例的Class类型
FactoryBean
一般情况下,Spring 通过反射机制利用 <bean> 的 class 属性指定实现类实例化 Bean,在某些情况下,实例化 Bean 过程比较复杂,如果按照传统的方式,则需要在 <bean> 中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。FactoryBean<t>也是一个接口,首先实现了这个接口的类也是一个 Bean ,但是这个 Bean 是一个可以生产其他 Bean 的特类。通过对接口方法的实现,这个 Bean 被附加了工厂行为和装饰器行为,而具有了生产能力。FactoryBean<t>接口中的主要方法如下: </t></t></bean></bean>
- getObject()——获取对象
- getObjectType()——获取对象类型
- isSingleton——是否是单例
值得注意的是如果要获取FactoryBean本身这个Bean,在根据名字传参时要添加一个前缀&
BeanDefinition
BeanDefinition 中保存了 Bean 信息,比如这个 Bean 指向的是哪个类、是否是单例的、是否懒加载、这个 Bean 依赖了哪些 Bean 等等。由代码实现的 Bean 在运行时会转换为 BeanDefinition 存在于 BeanFactory 中。
上下文Context
- FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径
- ClassPathXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。
- WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。
BeanFactory与ApplicationContext的区别
- BeanFactroy 采用的是延迟加载形式来注入 Bean ,即只有在使用到某个 Bean 时(调用getBean()),才对该 Bean 进行加载实例化,这样,我们就不能发现一些存在的 Spring 的配置问题。而 ApplicationContext 则相反,它是在容器启动时,一次性创建了所有的 Bean。这样,在容器启动时,我们就可以发现 Spring 中存在的配置错误
- BeanFactory 和 ApplicationContext 都支持 BeanPostProcessor、 BeanFactoryPostProcessor 的使用,但两者之间的区别是:BeanFactory 需要手动注册,而 ApplicationContext 则是自动注册
- ApplicationContext 包还提供了以下的功能:资源访问,如 URL 和文件;事件传播;载入多个(有继承关系)上下文;MessageSource , 提供国际化的消息访问
- 前者不支持依赖注解,后者支持
MVC
MVC的执行流程
- 用户请求发送至 DispatcherServlet 类进行处理
- DispatcherServlet 类遍历所有配置的 HandlerMapping 类请求查找 Handler
- HandlerMapping 类根据 request 请求的 URL 等信息查找能够进行处理的 Handler,以及相关拦截器 interceptor 并构造 HandlerExecutionChain
- HandlerMapping 类将构造的 HandlerExecutionChain 类的对象返回给前端控制器 DispatcherServlet 类
- 前端控制器拿着上一步的 Handler 遍历所有配置的 HandlerAdapter 类请求执行 Handler
- HandlerAdapter 类执行相关 Handler 并获取 ModelAndView 类的对象。
- HandlerAdapter 类将上一步 Handler 执行结果的 ModelAndView 类的对象返回给前端控制器
- DispatcherServlet 类遍历所有配置的 ViewResolver 类请求进行视图解析
- ViewResolver 类进行视图解析并获取 View 对象。
- ViewResolver 类向前端控制器返回上一步骤的 View 对象
- DispatcherServlet 类进行视图 View 渲染,填充Model
- DispatcherServlet 类向用户返回响应。
SpringMVC中的分层
Dao层主要做数据持久层的工作,负责与数据库进行联络的一些任务皆封装于此。首先设计Dao层的接口,然后在Spring的配置文件中定义此接口的实现类,随后在模块中调用此接口来进行数据业务的处理,而不用关心此接口的具体实现类是哪个类,这样结构变得非常清晰。
Service层主要负责业务模块的应用逻辑应用设计。首先设计接口,再设计其实现类,接着在Spring的配置文件中配置关联关系。定义Service层的业务时,具体要调用已经定义的dao层接口,封装Service层业务逻辑有利于通用的业务逻辑的独立性和重复利用性。服务具有如下特征:抽象、独立、稳定。
Model承载的作用就是数据的抽象,描述了一个数据的定义,Model的实例就是一组组数据。整个系统都可以看成是数据的流动,既然要流动,就一定是有流动的载体。可以理解为将数据库的表结构以Java类的形式表现。
Controller层负责具体的业务模块流程的控制,此层要调用Service层的接口来控制业务流程流转,同样在Spring的配置文件里进行配置。对于具体的业务流程,有不同的控制器。设计过程可以将流程进行抽象归纳,设计出可以重复利用的子单元流程模块。这样不仅使程序结构变得清晰,也大大减少了代码量。
View层是前台页面的展示。
Spring中分层领域模型规约
- DO(Data Object):与数据库表结构一一对应,通过DAO层向上传输数据源对象。从现实世界中抽象出来的有形或无形的业务实体。
- PO(Persistent Object):持久化对象,它跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应PO的一个(或若干个)属性。PO仅仅用于表示数据,没有任何数据操作。通常遵守Java Bean的规范,拥有 getter/setter 方法。
- DTO(Data Transfer Object):数据传输对象,Service或Manager向外传输的对象。泛指用于展示层与服务层之间的数据传输对象。目的是为了EJB的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载。
- BO(Business Object):业务对象。 由Service层输出的封装业务逻辑的对象。BO 包括了业务逻辑,常常封装了对 DAO、RPC 等的调用,可以进行 PO 与 VO/DTO 之间的转换。BO 通常位于业务层,要区别于直接对外提供服务的服务层:BO 提供了基本业务单元的基本业务操作,在设计上属于被服务层业务流程调用的对象,一个业务流程可能需要调用多个 BO 来完成。
- AO(Application Object):应用对象。在Web层与Service层之间抽象的复用对象模型,极为贴近展示层,复用度不高。
- VO(View Object):表示层对象,通常是Web向模板渲染引擎层传输的对象。它的作用是把某个指定页面(或组件)的所有数据封装起来。
- POJO(Plain Ordinary Java Object):在本手册中,POJO专指只有setter/getter/toString的简单类,包括DO/DTO/BO/VO等。
- Query:数据查询对象,各层接收上层的查询请求。 注意超过2个参数的查询封装,禁止使用Map类来传输。
HandlerMapping
HandlerMapping 的作用是根据当前请求的找到对应的 Handler,并将 Handler(执行程序)与一堆 HandlerInterceptor(拦截器)封装到 HandlerExecutionChain 对象中。HandlerMapping 是由 DispatcherServlet 调用,DispatcherServlet 会从容器中取出所有 HandlerMapping 实例并遍历,让 HandlerMapping 实例根据自己实现类的方式去尝试查找 Handler 。
过滤器
过滤器在 JavaWeb 应用中对传入的 request、response 过滤掉一下信息,或设置一些参数,然后再传入具体的业务逻辑中。比如过滤掉非法 url ,或者在传入 servlet、 action 以及 controller 前统一设置字符集,或者去除一些非法字符。所有 servlet 过滤器都必须实现 javax.servlet.Filter
接口。也就是说,过滤器跟 servlet 相关,而跟 Spring 无直接关联。根据 JDK Doc 定义,Filter 过滤器被定义为:对资源的请求或来自资源的响应进行过滤任务的对象。
public class LogCostFilter implements Filter { // Servlet 过滤器的初始化方法,由Web容器创建、初始化过滤器实例后,将唯一一次调用 init 方法 // init方法可以读取 web.xml 中的 Servlet 过滤器的配置初始化参数,所以一般是在 init 方法内执行一些初始化内容 // init 方法必须执行成功,否则不会起作用,出现以下两种情况时,web 容器中 Filter 无效:抛出 ServletException ; init方法执行时间超过 web 容器定义的时间阈值 @Override public void init(FilterConfig filterConfig) throws ServletException { } // Web 容器每接收到一次请求都会调用 doFilter 方法,过滤器就是用这个方法进行过滤任务 // 该方法的参数包含 Servlet 请求和响应以及用来调用下一个 Filter 的 FilterChain // FilterChain 被用来将 request 和 response 传递到过滤器链中的下一个过滤器 // doFilter 方法内经典实现步骤如下: // 检查request合法性 // 使用自定义实现包装request对象,以过滤输入过滤的content或header // 使用自定义实现包装response对象,以过滤输出过滤的content或header // 通过chain.doFilter交给执行链中下一个对象继续处理,或是不这么干以阻塞请求处理 // 在调用过滤器链中的下一个过滤器后,直接在response上设置headers @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {` long start = System.currentTimeMillis(); filterChain.doFilter(servletRequest, servletResponse); System.out.println("Execute cost=" + (System.currentTimeMillis() - start)); } // Web 容器在销毁过滤器实例前(该过滤器的doFilter方法内的所有线程已经退出或是时间阈值已经超过)调用destroy方法(仅一次) // destroy 方法中可以释放 Servlet 过滤器占用的资源 // 当 Web 容器调用destroy方法后,不再会调用该过滤器实例的 doFilter 方法了 // 可以利用destroy方法来清理资源,如内存、文件句柄、线程等 @Override public void destroy() { } }
<filter> <filter-name>filter</filter-name> <filter-class>com.chengc.demos.web.demo1.filters.MyCharsetFilter</filter-class> <init-param> <param-name>charset</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>contentType</param-name> <param-value>text/html;charset=UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
过滤器运行流程:
- 一个 request 请求进来了,先把自己交给 filterChain
- filterChain启动过滤器链,从头开始,把request交给第一个filter,并把自己传给filter
- filter在doFilter里做完自己的过滤逻辑,再调用filterChain的doFilter,以启动下一个过滤器
- filterChain游标移动,启动下一个过滤器,如此循环下去
- 过滤器游标走到链的末尾,filterChain执行收尾工作
过滤器应用场景:
- 身份认证,资源访问权限管理(通过控制对chain.doFilter的方法的调用,来决定是否需要访问目标资源)
- 登录和审计
- 图像转换
- 数据压缩
- 加密
- 触发器
- 解决乱码
拦截器
HandlerInterceptor 是 Spring Web MVC 的拦截器,类似于 Servlet 开发中的过滤器 Filter ,用于对请求进行拦截和处理。拦截器被注册到 Spring,拦截指定规则的请求,基于回调机制执行。一般来说,拦截器只会拦截 action 请求,这一点与过滤器不同。请求先经过过滤器(机会多所有请求进行过滤),然后才会到拦截器。可以应用如下场景:
- 权限检查,如检测请求是否具有登录权限,如果没有直接返回到登陆页面
- 性能监控,用请求处理前和请求处理后的时间差计算整个请求响应完成所消耗的时间
- 日志记录,可以记录请求信息的日志,以便进行信息监控、信息统计等
实现方式为:实现 HandlerInterceptor 接口或实现 WebRequestInterceptor 接口。
public interface HandlerInterceptor { /** * 预处理回调方法,实现处理器的预处理(如检查登陆),第三个参数为响应的处理器,自定义Controller * 返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应; */ boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; /** * 后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。 */ void postHandle( HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception; /** * 整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中 */ void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception; }
在 preHandle 方法中,每个拦截器都能决定是(return false)否(return true)要终止执行链,在决定要终止时通常是发送 HTTP 错误或是返回一个自定义响应。preHandle方法是进行处理器拦截用的,顾名思义,该方法将在HandlerMapping选定一个适合的handler(Controller)对象之后,HandlerAdapter调用handler(Controller)之前调用。Spring中DispatcherServlet链式处理多个拦截器,一个调用链中可以同时存在多个拦截器,且将该handler本身作为执行链的末尾元素。Spring会根据过滤器定义的前后顺序正序地执行拦截器的preHandle方法。Spring的这种Interceptor链式结构也是可以进行中断的,这种中断方式是令preHandle的返回值为false,也就是说当preHandle方法的返回值为false的时候整个请求就结束了。
在 HandlerAdapter 实际调用 handler(controller) 之后,DispatcherServlet 渲染视图之前调用 postHandle 方法。可以通过给定的 ModelAndView 暴露额外的 model 对象给视图。与preHandle方法不同,DispatcherServlet以执行链的相反顺序调用postHandle方法,也就是说先声明的拦截器拥有的postHandle方法反而会被后调用。
在handler任意输出的情况下都会调用afterCompletion方法,因此可被用来做适当的资源清理。afterCompletion 方法只会在当前这个拦截器的 preHandle 方法成功完成且返回值为 true 的时候才会执行。与 postHandle 方法相同,DispatcherServlet 以执行链的相反顺序调用 afterCompletion 方法,也就是说先声明的拦截器拥有的 afterCompletion 方法反而会被后调用。
HandlerAdapter 在触发 handler(即SpringMVC中的Controller)执行前会先调用 HandlerInterceptor。这个机制可以被用于大范围的预处理前,例如认证授权检查或是常见的 handler 行为如区域设置和主题更改。总的来说,拦截器的主要作用就是提出共用的处理代码,减少代码冗余。一般来说,每个HandlerMapping bean定义一个拦截器链。为了将某个拦截器链应用于一组handlers,需要通过一个HandlerMapping bean来映射到期望的handlers。拦截器本身是在application context内被定义的bean,mapping bean通过 interceptors属性引用。
过滤器 vs 拦截器
拦截器Interceptor | 过滤器Filter | |
---|---|---|
原理 | 基于java的反射机制调用handler方法,在其前后调用拦截器的方法 | 基于函数回调 |
创建 | 在context.xml中配置,由Spring容器初始化。 | 在web.xml中配置filter基本属性,由web容器创建 |
servlet 容器 | 拦截器不直接依赖于servlet容器 | 过滤器依赖于servlet容器 |
作用对象 | 拦截器只能对action请求起作用 | 过滤器则可以对几乎所有的请求起作用 |
访问范围 | 拦截器可以访问action上下文、值栈里的对象,可以获取IOC容器中的各个bean,这点很重要,在拦截器里注入一个service,可以调用业务逻辑 | 过滤器也可以使用ContextLoader.getCurrentWebApplicationContext()获取到根Context即XmlWebApplicationContext,注意该context只能访问除了Controller以外的bean,因Controller在另一个xml中配置 |
使用场景 | 即可用于Web,也可以用于其他Application | 基于Servlet规范,只能用于Web |
使用选择 | 可以深入到方法执行前后,使用场景更广 | 只能在Servlet前后起作用 |
过滤器和拦截器的执行顺序:
Spring事务
Spring事务
Spring 的事务管理不需与任何特定的事务 API 耦合,并且其提供了两种事务管理方式:编程式事务管理 和 声明式事务管理。对不同的持久层访问技术,编程式事务提供一致的事务编程风格,通过模板化的操作一致性地管理事务;而声明式事务基于 Spring-AOP 实现,却并不需要程序开发者成为 AOP 专家,亦可轻易使用 Spring 的声明式事务管理。
Spring 编程式事务策略是通过 PlatformTransactionManager 接口体现的,该接口是 Spring 事务策略的核心。该接口有如下核心方法:
- getTransaction(TransactionDefinition definition)
- void commit(TransactionStatus status)
- void rollback(TransactionStatus status)
PlatformTransactionManager 是一个与任何事务策略分离的接口。 PlatformTransactionManager 接口有许多不同的实现类,应用程序面向与平台无关的接口编程,而对不同平台的底层支持由 PlatformTransactionManager 接口的实现类完成,故而应用程序无须与具体的事务 API 耦合。因此使用 PlatformTransactionManage r接口,可将代码从具体的事务 API 中解耦出来。
而 TransactionDefinition 接口用于定义一个事务的规则,它包含了事务的一些静态属性,比如:事务传播行为、超时时间等。同时,Spring 还为我们提供了一个默认的实现类: DefaultTransactionDefinition ,该类适用于大多数情况。如果该类不能满足需求,可以通过实现 TransactionDefinition 接口来实现自己的事务定义,TransactionDefinition 接口中的核心信息为:
- int getIsolationLevel(); // 隔离级别
- int getPropagationBehavior(); // 传播行为
- int getTimeout(); // 超时时间
- boolean isReadOnly(); // 是否只读
TransactionStatus 接口提供了一个简单的控制事务执行和查询事务状态的方法。该接口的功能如下:
- boolean isNewTransaction();
- void setRollbackOnly();
- boolean isRollbackOnly();
Spring的 声明式事务是建立在 Spring-AOP 机制。其本质是对目标方法前后进行拦截,并在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中作相关的事务规则声明(或通过等价的基于标注的方式),便可以将事务规则应用到业务逻辑中。总的来说,声明式事务得益于 Spring-IoC 容器和 Spring-AOP 机制的支持: IoC 容器为声明式事务管理提供了基础设施,使得 Bean 对于 Spring 框架而言是可管理的;而由于事务管理本身就是一个典型的横切逻辑(正是AOP的用武之地),因此 Spring-AOP 机制是声明式事务管理的直接实现者。
除了基于命名空间的事务配置方式,Spring 还引入了基于 Annotation 的方式,具体主要涉及@Transactional 标注。@Transactional 可以作用于接口、接口方法、类以及类方法上:当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性;当作用于方法上时,该标注来覆盖类级别的定义。
事务传播
- PROPAGATION_REQUIRED:如果当前已有事务,则继续使用当前事务;如果当前没有事务,则新建一个事务。当两个方法的传播机制都是REQUIRED时,如果一旦发生回滚,两个方法都会回滚
- PROPAGATION_SUPPORTS:如果其他 bean 调用这个方法时,其他 bean 声明了事务,则就用相应的事务;如果没有声明事务,那就不用事务
- PROPAGATION_MANDATORY:必须在一个已有的事务中执行,否则报错
- PROPAGATION_REQUIRES_NEW:不管是否存在事务,都创建一个新的事务,原来的方法挂起,新的方法执行完毕后,继续执行老的事务
- PROPAGATION_NOT_SUPPORTED:Spring不为当前方法开启事务功能,每条执行语句单独执行,单独提交
- PROPAGATION_NEVER:必须在一个没有的事务中执行,否则报错
- PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与REQUIRED类似的操作
SpringBoot
SpringBoot是什么
SpringBoot 是一个轻量级、快速开发框架。整合了常用的第三方依赖整合(原理:通过Maven子父工程的方式);简化 XML 配置,全部采用注解形式;内置 Http 服务器(Jetty和Tomcat),最终以java 应用程序(Main函数)进行执行。
SpringBoot核心特征
- Springboot 项目为独立运行的 spring 项目,
java -jar xx.jar
即可运行 - 内嵌 Servlet 容器(可以选择内嵌: tomcat,jetty等服务器)
- 提供了 starter 的 pom 配置简化 maven 的配置
- 自动配置 Spring 容器中的 bean。当不满足实际开发场景,可自定义 bean 的自动化配置
- 准生产的应用监控(基于:ssh, http, telnet对服务器运行的项目进行监控)
- Springboot 无需做出 xml 配置,也不是通过代码生成来实现(通过条件注解)
SpringBoot相比于Spring框架有什么优势
SpringMVC 是基于 Servlet 的一个 MVC 框架,主要解决 WEB 开发的问题,因为 Spring 的配置非常复杂,各种 xml,properties 处理起来比较繁琐。于是为了简化开发者的使用,Spring 社区创造性地推出了 SpringBoot,它遵循约定优于配置,极大降低了 Spring 使用门槛,但又不失 Spring 原本灵活强大的功能。SpringBoot 具有如下优点:简化编码;简化配置;简化部署;简化监控
常见SpringBoot注解
@Autowired
Spring2.5引入了@Autowired注释,它可以对类成员变量、方法(setter)及构造函数进行标注,完成自动装配的工作。Spring通过一个BeanPostProcessor对@Autowired进行解析,所以要让@Autowired起作用必须事先在Spring容器中声明AutowiredAnnotationBeanPostProcessor这个Bean。例如:
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
当Spring容器启动时,AutowiredAnnotationBeanPostProcessor将扫描Spring容器中所有Bean,当发现Bean中拥有@Autowired注释时就找到和其匹配(默认按类型匹配)的Bean,并注入到对应的地方。当@Autowired作用于setter方法或构造方法上时,@Autowired将与查找方法参数中引用类型匹配的Bean自动注入。值得注意的的是,@Autowired自动注入时,如果找不到匹配的Bean或有多个匹配的Bean时将会抛出异常,解决方法是使用@Autowired(required = false)与使用@Qualifier注释指定注入Bean的名称从而消除歧义,例如:
@Autowired public void setOffice(@Qualifier("office")Office office) { this.office = office; }
@Qualifier("office")中的office是Bean的名称,所以@Autowired和@Qualifier结合使用时,自动注入的策略就从byType转变成byName。
@Resource
@Resource与@Autowired作用类似,区别在于@Autowired按照byType自动注入,而@Resource默认按照byName自动注入。Spring将@Resource注释的name属性解析为Bean的名字,而type属性则解析为Bean的类型。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。要使@Resource生效还需在Spring容器中注册一个负责处理这些注释的BeanPostProcessor:
package com.baobaotao; import javax.annotation.Resource; public class Boss { @Resource private Car car; // 自动注入类型为 Car 的 Bean @Resource(name = "office") private Office office; // 自动注入 bean 名称为 office 的 Bean }
@Inject
@Inject等价于默认的@Autowired,只是没有required属性。
@Component
使用@Component可以完全消除配置文件中对Bean的定义,@Component标注于类上并配合@Autowired即可。需要注意的是,要在配置文件中额外配置context:component-scan/用于扫描组件
@Scope
用于标注类从而指定单例还是原型
@SpringBootApplication
@SpringBootApplication注解是SpringBoot的核心注解,实际上由诸多注解组成的组合注解.其中主要的三个注解分别为:@SpringBootConfiguration;@EnableAutoConfiguration;@ComponentScan。
@SpringBootConfiguration
@SpringBootConfiguration继承于@Configuration,标注当前类是配置类,并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到Spring容器中,并且实例名就是方法名。
@EnableAutoConfiguration
@EnableAutoConfiguration注解就是SpringBoot能自动进行配置的魔法所在。主要是通过此注解,能把所有符合自动配置条件的bean的定义加载到Spring容器中。
@ComponentScan
@ComponentScan注解默认情况下会扫描@SpringBootApplication所在包及其子包下被@Component,@Controller,@Service,@Repository等注解标记的类并纳入到spring容器中进行管理。具体功能如下:
- 自定扫描路径下边带有@Controller,@Service,@Repository,@Component注解加入Spring容器
- 通过includeFilters加入扫描路径下没有以上注解的类加入spring容器
- 通过excludeFilters过滤出不用加入spring容器的类
@Controller
在SpringMVC中,控制器Controller负责处理由DispatcherServlet分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model,然后再把该Model返回给对应的View进行展示。使用@Controller标记一个类是Controller,然后使用@RequestMapping和@RequestParam等一些注解用以定义URL请求和Controller方法之间的映射,这样的Controller就能被外界访问。此外Controller不会直接依赖于HttpServletRequest和HttpServletResponse等HttpServlet对象,它们可以通过Controller的方法参数灵活的获取。
@RestController
@RestController的编写方式依赖注解组合,@RestController被@Controller和@ResponseBody标注,表示@RestController具有两者的注解语义,因此在注解处理时@RestController比@Controller多具有一个@ResponseBody语义,这就是@RestController和@Controller的区别。
@RequestMapping
@RequestMapping用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。一个实例如下:
@RequestMapping(value = "/helloworld", method = {RequestMethod.GET, RequestMethod.POST}, consumes = "application/json", produces = "application/json", params = "myparam=myvalue", headers = "Refer=http://www.fkit.org/") public String helloworld(Model model) { model.addAttribute("message", "Hello World"); return "welcome"; }
- value指定请求路径
- method指定该方法仅处理某些HTTP请求,可同时支持多个HTTP请求
- consumes指定方法处理请求的提交内容类型
- produces指定返回的内容类型,该类型bi必须是request请求头(Accept)中所包含的类型
- params指定request中必须包含某些参数值时,才让该方法处理
- headers指定request中必须包含指定的header值,才让该方法处理
@RequestBody
@RequestBody注解允许request的参数在reqeust体中,常常结合前端POST请求,进行前后端交互。
@ResponseBody
@ResponseBody是作用在方法上的,@ResponseBody表示该方法的返回结果直接写入HTTP response body中,一般在异步获取数据时使用【也就是AJAX】。在使用@RequestMapping后,返回值通常解析为跳转路径,但是加上 @ResponseBody后返回结果不会被解析为跳转路径,而是直接写入HTTP response body中。比如异步获取json数据,加上 @ResponseBody后,会直接返回json数据。
@RequestParam
@RequestParam 用来接收URL中的参数,如/param?username=001,可接收001作为参数,例如:
@RequestMapping("/users") @ResponseBody public String param(@RequestParam String username){ return "user" + username; }
@RequestHeader
@RequestHeader用于将请求的头信息区数据映射到功能处理方法的参数上。
@PostConstruct和@PreDestroy
Spring允许在Bean在初始化完成后以及Bean销毁前执行特定的操作,既可以通过实现InitializingBean/DisposableBean接口来定制初始化之后/销毁之前的操作方法,也可以通过<bean>元素的init-method/destroy-method属性指定初始化之后/销毁之前调用的操作方法。@PostConstruct和@PreDestroy则对应于上述过程。然而不同于上述方式,使用@PostConstruct和@PreDestroy注释却可以指定多个初始化/销毁方法,那些被标注@PostConstruct或@PreDestroy注释的方法都会在初始化/销毁时被执行。</bean>
@Configuration
@Configuration中所有带@Bean注解的方法都会被动态代理,因此调用该方法返回的都是同一个实例。从定义来看,@Configuration注解本质上还是@Component,因此context:component-scan/或者@ComponentScan都能处理@Configuration注解的类。
@Service
@Service表示被标注的类是业务层Bean。@Service("userService")注解是告诉Spring,当Spring要创建UserServiceImpl的的实例时,bean的名字必须叫做"userService"。
@Repository
@Repository对应数据访问层Bean,@Repository(value="userDao")注解是告诉Spring,让Spring创建一个名字叫“userDao”的UserDaoImpl实例。
@CookieValue
@CookieValue用于将请求的Cookie数据映射到功能处理方法的参数上。