本章是整理知识内容,为强化知识长期更新。
Spring
- Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。
Spring IOC = Spring Bean + Spring Context特性
- 主要由以下几个模块组成:
- Spring Core:核心类库,提供IOC服务
- Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等)
- Spring AOP:AOP服务切面编程
- Spring Type Conversion:类型转换
- Spring Data Binding :数据绑定
- Spring Express Language:Spring 表达式
- Spring Resources:资源管理
- Spring Events:事件
- Spring i18n:国际化
- Spring Validation:校验
- Spring Data Access:数据存储
- JDBC Java API:对JDBC的抽象,简化了数据访问异常的处理
- Transactions:事务抽象 EJB简化版本
- O/R Mapping:映射
- DAO Support:对JDBC的抽象,简化了数据访问异常的处理
- XML Marshalling 编列
- Spring Integration
- Remoting:远程调用
- JMS:Java消息服务
- JCA:Java连接架构
- JMS:Java管理扩展
- Email:Java 邮件客户端
- Tasks:本地任务
- Scheduling:本地调度
- Caching:缓存抽象
- Test : Spring 测试
- Mock Objects:模拟对象框架
- Test Context Framework:TestContext框架
- Spring Mvc Test:Spring MVC 测试
- WebTestClient :Web 测试客户端
- Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传
- Spring Servlet Support
- Spring MVC:提供面向Web应用的Model-View-Controller实现
- WebSocket
- SockJS
- Spring Reactive
- Spring WebFlux
- WebClient
- WebSocket
- Spring Servlet Support
Spring 编程模型
- 面向对象
- 契约接口:Aware、BeanPostProcess
- 设计模式:观察者、组合模式、模版模式
- 面向切面
- 动态代理:JdkDynamicAopProxy
- 字节码提升:ASM、CGLib、AspectJ
- 面向元编程
- 注解:模式注解 @Component @Service
- 配置:Environment抽象、PropertySources
- 范型:GenericTypeResolver
- 函数驱动
- ApplicationEventPubilsher
- Reactive Spring WebFlux
- 模块驱动
- Maven Artifacts
- OSGI Bundles
- Java 9 Automatic Modules
- Spring @Enable*
Sprng 设计思想
- Object-Oriented Programming (OOP)
- IOC / DI
- Domain-DrivenDevelopment (DDD)
- Test-DrivenDevelopment (TDD)
- Event-DrivenPrograming (EDP)
- Functional Programing (FP)
Spring IOC
IOC对于Spring框架来说,就是由Spring来负责控制对象的生命周期和对象间的关系。
所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中,所以,以来注入DI 和控制反转IOC 是从不同的角度描述的同一间事情,就是值通过引用IOC容器,利用以来关系注入的方式,实现对象之间的解耦。DI是IOC的实现。
IoC(Inversion of Control,翻译为“控制反转”)不是一个具体的技术,而是一种设计思想。与传统控制流相比,IoC 会颠倒控制流,在传统的编程中需要开发者自行创建并销毁对象,而在 IoC 中会把这些操作交给框架来处理,这样开发者就不用关注具体的实现细节了,拿来直接用就可以了,这就是控制反转。
- IoC 很好的体现出了面向对象的设计法则之一——好莱坞法则:“别找我们,我们找你”。即由 IoC 容器帮对象找到相应的依赖对象并注入,而不是由对象主动去找。
- DI(Dependency Injection,翻译为“依赖注入”)表示组件间的依赖关系交由容器在运行期自动生成,也就是说,由容器动态的将某个依赖关系注入到组件之中,这样就能提升组件的重用频率。通过依赖注入机制,我们只需要通过简单的配置,就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心资源来自哪里、由谁实现等问题。
- IoC 和 DI 其实是同一个概念从不同角度的描述的,由于控制反转这个概念比较含糊(可能只理解成了容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以 2004 年被开发者尊称为“教父”的 Martin Fowler(世界顶级专家,敏捷开发方法的创始人之一)又给出了一个新的名字“依赖注入”,相对 IoC 而言,“依赖注入”明确描述了“被注入对象依赖 IoC 容器配置依赖对象”。
平常的Java开发中,程序员在某个类中需要依赖其它类的方法。通常是new一个依赖类再调用类实例的方法,这种开发存在的问题是new的类实例不好统一管理。 Spring提出了依赖注入的思想,即依赖类不由程序员实例化,而是通过Spring容器帮我们new指定实例并且将实例注入到需要该对象的类中。依赖注入的另一种说法是”控制反转”。通俗的理解是:平常我们new一个实例,这个实例的控制权是我们程序员。而控制反转是指new实例工作不由我们程序员来做而是交给Spring容器来做。
实现方式
- JavaSE
- Java Beans
- Java ServiceLoader SPI
依赖查找
单属性查找、集合对象查找
依赖查找是主动依赖查找方式,通常需要依赖容器API标准实现,而依赖注入是通过手动或者自动依赖绑定方式实现。
实时查找
- BeanFactory.getBean(String beanName) 根据名称查找
- BeanFactory.getBean(Class
beanClazz) 根据Bean类型查找
延迟查找
查询条件
- 单个Bean对象
- 集合Bean对象
- 条件查找(在查找过程中 Name 和 Id都是坚定Bean的条件,在一个上下文中只能存在一种)
- Bean Name + Bean Type查找
- Bean Id + Bean Type查找
- Java 注解
演示代码
- xml配置文件
1 |
|
- Java代码
1 | /** |
- Console
1 | 实时查找 DelayLookup{name='spring'} |
依赖注入
自定义Bean注入。
- 通过Bean注入。
- 通过Bean集合注入。
容器内部依赖注入。
容器内部建立Bean注入。
构造方法注入。
- 构造器注入是不允许循环依赖的存在,日常开发推荐该方式,如果出现循环依赖应该考虑自身代码实现是否合理。
工厂的方法注入。
字段注入
- 编码注入
- Java注解注入
- @Autowired
- @Resource
- @Inject
- Java注解注入
- 编码注入
方法注入
回调注入
演示代码
- 配置文件
1 |
|
- java代码
1 | /** |
- Console
1 | 单一类型查找 SuperInjectionBean{address='杭州'} InjectionBean{name='spring'} |
Autowired和Resource关键字
- @Resource和@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。
- Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualififier注解一起使用:
1 | public class TestServiceImpl { |
- Resource默认按照ByName自动注入,由J2EE提供,需要导入包javax.annotation.Resource。@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。
1 | public class TestServiceImpl { |
BeanFactory与ApplicationContext
BeanFactory 是 Bean 容器,ApplicationContext通过组合BeanFactory提供依赖查找的能力。常用的 AOP、事务以及 事件等,这些都被 ApplicationContext 支持。
BeanFactory是Spring底层ioc容器,它维护存储了很多bean定义的信息、Bean配置、生命周期管理。
ApplicationContext是BeanFactory超子集。
BeanFactory与FactoryBean
BeanFactory与FactoryBean都是接口。
- BeanFactory是IOC容器,就是一个Factory或者说IOC容器对象工厂。
- BeanFactory是单一查找的核心
- FactoryBean是创建Bean的一种方式。FactoryBean就是一个Bean。
- 在Spring中,所有的Bean都是由BeanFactory管理。
ObjectFactory 与 BeanFactory
ObjectFactory 与 BeanFactory 均提供依赖查找的能力
ObjectFactory 仅关注一个或一种类型的 Bean 依赖查找,并
且自身不具备依赖查找。
BeanFactory 则提供了单一类型、集合类型以及层次性等多种依赖查
找方式。
生命周期
- 容器
- Java Beans
BeanDefinition
BeanDefinition是Spring Framework中定义Bean的配置元信息接口。
创建bean
- 基于xml创建
- 基于Java注解创建
- @Bean
- @Component
- @Import
1 | /** |
- Console
1 | # 运行setupXmlApplicationContext |
配置元信息
可以通过Bean定义、IOC容器配置、外部属性配置。
- 基于XML文件
- 基于Properties
- 基于Java注解
- 基于Java API
实际作用
- AOP抽象、事务抽象、事件机制、SPI机制、第三方集成。
Spring AOP
AOP(Aspect-Oriented Programming:⾯向切⾯编程)能够将那些与业务⽆关,却为业务模块所共同调⽤的逻辑或责任(例如事务处理、⽇志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
- Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接⼝,那么Spring AOP会使⽤JDK Proxy,去创建代理对象,⽽对于没有实现接⼝的对象,就⽆法使⽤ JDK Proxy 去进⾏代理了,这时候Spring AOP会使⽤Cglib ,这时候Spring AOP会使⽤ Cglib ⽣成⼀个被代理对象的⼦类来作为代理。
- Spring Aop是运行时增强。
- Cglib Aop是编译器增强。
AOP专门用于处理系统中分布于各个模块中交叉关注点的问题,在JavaEE应用中,常常通过AOP来处理一些横切性质的系统级服务,如事物管理、安全检查、缓存对象池管理。
- 1.)切面 -Aspect 切面类 比如日志类
- 2.) 连接点 - Join Point 加入切点的那个点
- 3.)通知 -Advice 是在切面某个特定的连接点上的动作。
- 4.)切入点 - Point Cut 匹配连接点的断点
- 5.)引入 - Introduction
- 6.)目标对象 - Target Object 被一个或多个通知对象,永远被代理的对象。
- 7.) AOP代理 - AOP Proxy AOP创建的对象用来实现切面对象。
- 8.)织入 - Weaving
- 9.)AOP通俗的理解:
一个组建A,不关心其他常用的服务组件B,但是这个组件A使用组件B的时候不是组建A自身去调用,而是通过配置等其他方式,比如Spring中可以通过XML配置文件。这样就使的A压根不需要知道服务组件B是怎么样的,A只关心自己的业务逻辑,具体A使用B的时候,配置文件去做,与具体的A组件无关。
Spring框架中的设计模式
- 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例
- 单例模式:Bean默认为单例模式
- 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
- 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate
- 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener,Spring 事件驱动模型就是观察者模式很经典的⼀个应⽤。
- 适配器模式 :Spring AOP 的增强或通知(Advice)使⽤到了适配器模式、spring MVC 中也是⽤到了适配器模式适配 Controller 。
- 策略模式: Bean的实例化的时候决定采用何种方式初始化bean实例(反射或者CGLIB动态字节码生成),Resource 接口是具体资源访问策略的抽象。
- 装饰器模式:Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。
- 模版模式:Spring Template 都采用了该模式。
Bean注册方式
- XML 配置文件的注册方式
1 | <bean id="person" class="org.springframework.beans.Person"> |
Java 注解的注册方式
1 | // 可以使用 @Component 注解方式来注册 Bean |
Java API 的注册方式
1 | // 使用 BeanDefinitionRegistry.registerBeanDefinition() 方法的方式注册 Bean |
Spring Bean 的作用域
Singleton Bean 无论是依赖查找还是依赖注入,均为同一个对象。
Singleton Bean 只是在当前容器内是一位,存在多个容器的时候,每个容器可以存储一份对象。
Prototype Bean 无论是依赖查找还是依赖注入,均为新生成对象。
- singleton
- 表示在 Spring 容器中只有一个 Bean 实例,以单例的形式存在,是默认的 Bean 作用域。
- prototype
- 原型作用域,每次调用 Bean 时都会创建一个新实例,也就是说每次调用 getBean() 方法时,相当于执行了 new Bean()。
- 扩展
request
- 每次 Http 请求时都会创建一个新的 Bean,该作用域仅适应于 WebApplicationContext 环境。
session
- 同一个 Http Session 共享一个 Bean 对象,不同的 Session 拥有不同的 Bean 对象,仅适用于 WebApplicationContext 环境。
global session
- 全局session共享一个bean对象。
application
- 全局的 Web 作用域,类似于 Servlet 中的 Application。
webSocket
Spring 中的单例 bean 的线程安全问题
- 单例 bean 存在线程问题,主要是因为当多个线程操作同⼀个对象的时候,对这个对象的⾮静态成员变量的写操作会存在线程安全问题。在类中定义⼀个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的⼀种⽅式)。
Bean 生命周期
- 对于 Spring Bean 来说,并不是启动阶段就会触发 Bean 的实例化,只有当客户端通过显式或者隐式的方式调用 BeanFactory 的 getBean() 方法时,它才会触发该类的实例化方法。当然对于 BeanFactory 来说,也不是所有的 getBean() 方法都会实例化 Bean 对象,例如作用域为 singleton 时,只会在第一次,实例化该 Bean 对象,之后会直接返回该对象。但如果使用的是ApplicationContext 容器,则会在该容器启动的时候,立即调用注册到该容器所有 Bean 的实例化方法。getBean() 方法是属于 BeanFactory 接口的,它的真正实现是AbstractAutowireCapableBeanFactory 的 createBean() 方法,而 createBean() 是通过 doCreateBean() 来实现的,具体源码实现。
- 实例化Bean
- 对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefifinition对象中的信息,实例化所有的bean。
- 设置对象属性(依赖注入)
- 实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefifinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成依赖注入。
- 处理Aware接口
- 接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean。
- 如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值。
- 如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。
- 如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文。
- 接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean。
- BeanPostProcessor
- 如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。
- InitializingBean 与 init-method
- 如果实现相关方法或者配置了 init-method则会自动调用其配置的初始化方法
- BeanPostProcessor
- 如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术。
- getBean
- 可以正常使用这个bean了。
- DisposableBean
- 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法。
- destroy-method
- 如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。
- 精简下
- 实例化Bean: Ioc容器通过获取BeanDefinition对象中的信息进行实例化,实例化对象被包装在BeanWrapper对象中。
- 设置对象属性(DI):通过BeanWrapper提供的设置属性的接口完成属性依赖注入。
- 注入Aware接口(BeanFactoryAware, 可以用这个方式来获取其它 Bean,ApplicationContextAware):Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给bean。
- BeanPostProcessor:自定义的处理(分前置处理和后置处理)
- InitializingBean和init-method:执行我们自己定义的初始化方法使用
- destroy:bean的销毁
Spring配置
在 Spring Boot 中,其核心设计理念是对配置信息的管理采用约定优于配置。
Profile
Spring Boot 对配置文件的命名也做了一定的约定,分别使用 label 和 profile 概念来指定配置信息的版本以及运行环境,其中 label 表示配置版本控制信息,而 profile 则用来指定该配置文件所对应的环境。
1 | /{application}.yml |
- 在yaml配置文件中指定profile
1 | spring: |
- 代码中使用
1 |
|
- 加载顺序
1 | –file:./config/ 1 |
- 站位符
1 | ${spring.application.name} // 配置文件的信息 |
Spring自动装配
SpringBoot 工程的启动类上都必须要加一个 @SpringBootApplication 注解,而且还要在 run 方法中导入我们的主类,我们就从这个被导入的主类上的注解开始分析。
- 基于添加jar依赖自动对Spring Boot程序进行配置
- spring-boot-autoconfiguration
- 开启自动装配
- @EnableAutoConfiguration
- @SpringBootApplication
1 |
|
SpringBootApplication
@SpringBootApplication 注解是一个复合注解。除去 Java 的 4 个元注解外,分别是 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan
1 |
|
SpringBootConfiguration
@SpringBootConfiguration 注解本质是一个 @Configuration 注解,而 @Configuration 本质是一个 @Component 注解。
- @SpringBootConfiguration 注解的启动类,本质就是一个配置类(ConfigurationClass),此类中可以声明一个或者多个被 @Bean 注解标记的方法。
1 |
|
ComponentScan
执行包的扫描,如果 useDefaultFilters 属性为 true,那么它会将被 @component 注解标记以及以 @component 为元注解的注解标记的类都扫描到 IOC 容器中去。
1 |
|
EnableAutoConfiguration
通过@Import注解将特定的类导入到容器中
- 引入AutoConfigurationImportSelector
- 读取META-INF/spring.factories
1 |
|
- AutoConfigurationImportSelector类中通过 SpringFactoriesLoader.loadFactoryNames() 去 classpath 下的 META-INF/spring.factories 中获取了所有需要自动装配的类,放入到了 IOC 容器中。
![image-20211102145526599](/Users/zengqingfeng/Library/Application Support/typora-user-images/image-20211102145526599.png)
- 默认的信息
1 | # Logging Systems |
Spring SPI
SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。
JDK中的SPI
- JDK 提供了用于服务查找的一个工具类 java.util.ServiceLoader 来实现 SPI 机制。当服务提供者提供了服务接口的一种实现之后,我们可以在 jar 包的 META-INF/services/ 目录下创建一个以服务接口命名的文件,该文件里配置着一组 Key-Value,用于指定服务接口与实现该服务接口具体实现类的映射关系。而当外部程序装配这个 jar 包时,就能通过该 jar 包 META-INF/services/ 目录中的配置文件找到具体的实现类名,并装载实例化,从而完成模块的注入。SPI 提供了一种约定,基于该约定就能很好地找到服务接口的实现类,而不需要在代码里硬编码指定。
SpringFactoriesLoader 类似这种 SPI 机制,只不过以服务接口命名的文件是放在 META-INF/spring.factories 文件夹下,对应的 Key 为 EnableAutoConfiguration。SpringFactoriesLoader 会查找所有 META-INF/spring.factories 文件夹中的配置文件,并把 Key 为 EnableAutoConfiguration 所对应的配置项通过反射实例化为配置类并加载到容器中。
- Spring 的 SPI 配置文件是一个固定的文件 -
META-INF/spring.factories
,功能上和 JDK 的类似,每个接口可以有多个扩展实现,使用起来非常简单:
- Spring 的 SPI 配置文件是一个固定的文件 -
1 | //获取所有factories文件中配置的LoggingSystemFactory |
自动装配注解
- @Configuration 主要编写JavaConfig类
- @Conditional 条件注解
- 类条件
- @ConditionalOnClass 包含类的时候
- @ConditionalOnMissingClass 不包含类的时候
- 属性条件
- @ConditionalOnProperty 属性条件
- Bean条件
- @ConditionalOnBean 存在某个Bean
- @ConditionalOnMissingBean 不存在某个Bean
- @ConditionalOnSingleCanditate 上下文仅存在一个Bean
- 资源类
- @ConditionalOnResource
- Web应用条件
- @ConditionalOnWebApplication 是web应用的
- @ConditionalOnNotWebApplication 非web应用的
- 其他
- @ConditionalOnExpression 表达式条件
- @ConditionalOnJava 指定不同的版本
- @ConditionalOnJndi 满足Jndi条件
- 执行顺序
- @AutoConfigureBefore 某些配置之前
- @AutoConfigureAfter 某些配置执行之后
- @AutoConfigureOrder 执行顺序
- 类条件
- 实现方式
- 编程式 实现
ImportSelector
接口,并且将实现类声明称bean。 - 配置文件 META-INF/spring.factories
- 编程式 实现
Spring循环依赖
当bean的作用域是Prototype的是不支持循环依赖的。不然会出现套娃,
- 可能出现的依赖场景
- 构造器循环依赖
- 属性注入循环依赖
- Spring维护了三个Map,所谓的三级缓存。
- singletonObjects:第一级缓存,里面放置的是实例化好的单例对象
- earlySingletonObjects:第二级缓存,里面存放的是提前曝光的单例对象。
- singletonFactories:第三级缓存,里面存放的是要被实例化的对象的对象工厂。
- 创建bean的时候Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取,如果还是获取不到就从三级缓存singletonFactories中取(Bean调用构造函数进行实例化后,即使属性还未填充,就可以通过三级缓存向外提前暴露依赖的引用值(提前曝光),根据对象引用能定位到堆中的对象,其原理是基于Java的引用传递),取到后从三级缓存移动到了二级缓存完全初始化之后将自己放入到一级缓存中供其他使用,因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。
1 | public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { |
首先尝试去从一级缓存 singletonObjects 单例池中去获取,没有获取到并且要获取的 Bean 正在创建中,再去查找二级缓存 earlySingletonObjects,如果还是没有找到,就再去三级缓存 singletonFactories 中去找,找到了就从三级缓存 singletonFactories 中移除,放入到二级缓存 earlySingletonObjects 中去,这样做的好处就是,如果还有其他对象尝试获取这个 Bean,那么可以直接从二级缓存中取出来,就不需要再调用 getEarlyBeanReference() 方法了。最终,创建好的 Bean,全部 put 到了单例池 singletonObjects 中了。
如何解决循环依赖
- 构造器循环依赖解决办法:在构造函数中使用@Lazy注解延迟加载。在注入依赖时,先注入代理对象,当首次使用时再创建对象说明:一种互斥的关系而非层次递进的关系,故称为三个Map而非三级缓存的缘由 完成注入;
Spring中Filter和Interceptor的区别
- Filter是基于函数回调(doFilter()方法)的,而Interceptor则是基于Java反射的(AOP思想)。
- Filter依赖于Servlet容器,而Interceptor不依赖于Servlet容器。
- Filter对几乎所有的请求起作用,而Interceptor只能对action请求起作用。
- Interceptor可以访问Action的上下文,值栈里的对象,而Filter不能。
- 在action的生命周期里,Interceptor可以被多次调用,而Filter只能在容器初始化时调用一次。
- Filter在过滤是只能对request和response进行操作,而interceptor可以对request、response、handler、modelAndView、exception进行操作。
- interceptor 的执行顺序大致为:
- 请求到达 DispatcherServlet
- DispatcherServlet 发送至 Interceptor ,执行 preHandle
- 请求达到 Controller
- 请求结束后,postHandle 执行
- Spring 中主要通过 HandlerInterceptor 接口来实现请求的拦截,实现 HandlerInterceptor 接口需要实现下面三个方法:
- preHandle() – 在handler执行之前,返回 boolean 值,true 表示继续执行,false 为停止执行并返回。
- postHandle() – 在handler执行之后, 可以在返回之前对返回的结果进行修改
- afterCompletion() – 在请求完全结束后调用,可以用来统计请求耗时等等
1 | import javax.servlet.http.HttpServletRequest; |
- Servlet 的 Filter 接口需要实现如下方法:
void init(FilterConfig paramFilterConfig)
– 当容器初始化 Filter 时调用,该方法在 Filter 的生命周期只会被调用一次,一般在该方法中初始化一些资源,FilterConfig 是容器提供给 Filter 的初始化参数,在该方法中可以抛出 ServletException 。init 方法必须执行成功,否则 Filter 可能不起作用,出现以下两种情况时,web 容器中 Filter 可能无效: 1)抛出 ServletException 2)超过 web 容器定义的执行时间。doFilter(ServletRequest paramServletRequest, ServletResponse paramServletResponse, FilterChain paramFilterChain)
– Web 容器每一次请求都会调用该方法。该方法将容器的请求和响应作为参数传递进来, FilterChain 用来调用下一个 Filter。void destroy()
– 当容器销毁 Filter 实例时调用该方法,可以在方法中销毁资源,该方法在 Filter 的生命周期只会被调用一次。
Spring 事务
Spring的事物抽象
Spring事物的特性
- 隔离级别
- 处理并发事务带来的问题
- 事物传播
- 处理业务代码互相调用事物问题
- 事物回滚
- 处理业务失败会滚业务事物问题
- 事物超时
- 处理事物允许的执行时间问题
- 是否只读
- 据说可以只读提高事物执行效率
- 隔离级别
事物开启方式
- 编程式事务 代码中硬编码开启事物。
- 声明式事务
- 基于注解开启事物。
- 基于xml配置文件开启事物。
Spring 事务中的隔离级别
TransactionDefinition 接⼝中定义了五个表示隔离级别的常量:
TransactionDefinition.ISOLATION_DEFAULT: 使⽤后端数据库默认的隔离级别,Mysql 默认采⽤的 REPEATABLE_READ隔离级别 Oracle 默认采⽤的 READ_COMMITTED隔离级别。
TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的阻⽌脏读,但是幻读或不可重复读仍有可能发⽣。
TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻⽌脏读,但是幻读或不可重复读仍有可能发⽣。
TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同⼀字段的多次读取结果都是⼀致的,除⾮数据是被本身事务⾃⼰所修改,可以阻⽌脏读和不可重复读,但幻读仍有可能发⽣。
TransactionDefinition.ISOLATION_SERIALIZABLE: 最⾼的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执⾏,这样事务之间就完全不可能产⽣⼲扰,也就是说,该级别可以防⽌脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会⽤到该级别。
Spring 事务中事务传播⾏为
- ⽀持当前事务的情况:
- TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加⼊该事务;如果当前没有事务,则创建⼀个新的事务。
- TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加⼊该事务;如果当前没有事务,则以⾮事务的⽅式继续运⾏。
- TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加⼊该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
- 不⽀持当前事务的情况:
- TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER: 以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。
- 其他情况:
- TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
TransactionStatus接口介绍
- TransactionStatus接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息.
1 | public interface TransactionStatus{ |
Spring Boot 启动流程
Spring Boot 程序的入口是 SpringApplication.run(Application.class, args) 方法
1 | public ConfigurableApplicationContext run(String... args) { |
Spring Boot 的启动流程
1、创建并启动计时监控类
- 此计时器是为了监控并记录 Spring Boot 应用启动的时间的,它会记录当前任务的名称,然后开启计时器。
2.声明应用上下文对象和异常报告集合
- 此过程声明了应用上下文对象和一个异常报告的 ArrayList 集合。
3.设置系统属性 headless 的值
- 设置 Java.awt.headless = true,其中 awt(Abstract Window Toolkit)的含义是抽象窗口工具集。设置为 true 表示运行一个 headless 服务器,可以用它来作一些简单的图像处理。
4.创建所有 Spring 运行监听器并发布应用启动事件
- 此过程用于获取配置的监听器名称并实例化所有的类。
5.初始化默认应用的参数类
- 也就是说声明并创建一个应用参数对象。
6.准备环境
- 创建配置并且绑定环境(通过 property sources 和 profiles 等配置文件)。
7.创建 Banner 的打印类
- 就是出个那个logo也可以自己定义。
8.创建应用上下文
- 根据不同的应用类型来创建不同的 ApplicationContext 上下文对象。
9.实例化异常报告器
- 它调用的是 getSpringFactoriesInstances() 方法来获取配置异常类的名称,并实例化所有的异常处理类。
10.准备应用上下文
- 此方法的主要作用是把上面已经创建好的对象,传递给 prepareContext 来准备上下文,例如将环境变量 environment 对象绑定到上下文中、配置 bean 生成器以及资源加载器、记录启动日志等操作。
11.刷新应用上下文
- 此方法用于解析配置文件,加载 bean 对象,并且启动内置的 web 容器等操作。
12.应用上下文刷新之后的事件处理
- 这个方法的源码是空的,可以做一些自定义的后置处理操作。
13.停止计时监控类
- 停止此过程第一步中的程序计时器,并统计任务的执行信息。
14.输出日志信息
- 把相关的记录信息,如类名、时间等信息进行控制台输出。
15.发布应用上下文启动完成事件
- 触发所有 SpringApplicationRunListener 监听器的 started 事件方法。
16.执行所有 Runner 运行器
- 执行所有的 ApplicationRunner 和 CommandLineRunner 运行器。
17.发布应用上下文就绪事件
- 触发所有的 SpringApplicationRunListener 监听器的 running 事件。
18.返回应用上下文对象
- 到此为止 Spring Boot 的启动程序就结束了。
Spring代理
- 动态代理
- 在程序运行时利用反射动态创建代理对象<复用性,易用性,更加集中都调用invoke。
- 静态代理
- 程序运行前代理类的.class文件就存在了。
Spring中获取代理对象
spring中获取代理对象的三种方式:
- 直接autowire基于接口注入。
@Autowire applicationcontext
,通过applicationcontext.getbean获取代理对象。- 通过
aopcontext.currentproxy
方法获取(注意:需要启动类上增加注解@enableaspectjautoproxy(exposeproxy=true));
- 如果spring启动时循环依赖导致报错,可以通过依赖注入属性增加@lazy注解解决。
JDK Proxy 和 CGLib 有什么区别
- JDK Proxy
- JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现。
- JDK Proxy 是通过拦截器加反射的方式实现的;
- JDK Proxy 只能代理继承接口的类;
- CGLib
- CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;
- CGLib 无需通过接口来实现,它是通过实现子类的方式来完成调用的。