蹊源的Java笔记—Spring
前言
在现如今的Java
开发中有一个不得不提的工具,那就是Spring
,Spring
是Java EE
编程领域的一个轻量级开源框架, 是为了解决企业级编程开发中的复杂性,实现敏捷开发的应用型框架 。本期博客带领大家去了解Spring
相关的知识点。
线程并发与线程安全可参考我的博客:蹊源的Java笔记—线程并发与线程安全
Mysql数据库可参考我的博客:蹊源的Java笔记—Mysql数据库
正文
Spirng
Spring是Java EE
(Java
企业应用)编程领域的一个轻量级开源框架, 是为了解决企业级编程开发中的复杂性,实现敏捷开发的应用型框架 。
所谓敏捷开发是指以用户的需求进化为核心,采用迭代、循序渐进的方法进行软件开发方式。
Spring常用的七大模块
Spring Core
Spring Core封装包是框架的最基础部分,提供IOC
控制反转和DI
依赖注入特性:
- IOC控制反转:一种编程思想,借助容器来管理对象之间的依赖关系。
- DI依赖注入:
DI
是实现IOC
的一种手段,其内部是通过Java
的反射机制来实现的。
Spring Context
Spring Context构建于Core
封装包基础的上下文封装包,提供了一种框架式的对象访问方法,Spring
上下文包括企业服务,例如JNDI
、电子邮件、国际化、校验等。
JNDI
提供统一的客户端API
,通过不同的访问提供者接口JNDI
服务供应接口(SPI
)的实现.
Spring Dao
Spring Dao对数据访问进行封装:
-
Dao
中来管理异常处理和不同数据库提供商抛出的异常。 - 简化了错误处理,并极大的降低了需要编写的异常代码数量,比如打开和关闭连接。
Spring ORM
Spring ORM提供了对象关系映射,包括Mybatis
、Hibernate
等。这些对象关系映射框架都遵从Spring
的通用事务和Dao
的层次结构。
Spring AOP
Spring AOP声明式事务,是一种OOP
的延伸,用于给不存在继承关系的对象之间引用一个公共行为。
Spring Web
Spring Web,Spring
中的Web
提供了基础的针对WEB
开发的集成特性。
Spring Web MVC
Spring Web MVC是一个Model-View-Controller Web
框架,它是基于前端控制器Servel
并发处理http
请求并进行展示。
Spring IOC容器
Spring注册Bean到IOC容器的流程:
- 读取
Bean
的配置信息 - 根据
Bean
注册表实例化Bean
- 将
Bean
实例放在Spring
容器中 - 应用程序通过
Bean
缓存池(内部是通过HashMap
来实现的)来使用Bean
Spring Bean的作用域:
- 单例模式(默认):
Spring
创建Bean
的原则是不等bean
创建完成就将beanFactory
提早放到缓存中,如果其他bean
依赖这个bean
可以直接使用,这种三级缓存的机制很好地解决了循环依赖的问题。 - 多例模式(原型模式):每次使用时都会创建新的
Bean
实例,Bean
调用setAllowCircularReferences(false)
来禁止循环依赖,否则出现循环依赖会直接抛异常。 - Request模式:一次
request
一个实例, 当前 Http
请求结束,该 bean
实例也将会被销毁. - Session模式:在一次
Http Session
中,容器会返回该 Bean
的同一实例。而对不同的 Session
请求则会创建新的实例,该 bean
实例仅在当前 Session
内有效。 - global Session模式(不常使用):在一个全局的
Http Session
中,容器会返回该 Bean
的同一个实例,仅在 使用portlet context
时有效。
在xml
配置文件中添加配置属性scope= prototype
使用,或者使用注解@Scope("prototype")
Bean的生命周期
- Bean的实例化:由
BeanFactory
读取Bean
定义文件,并生成各个实例。 - IOC依赖注入:执行
Bean
的属性依赖注入。 - BeanName:执行
BeanNameAware
的setBeanName()
, 此处传递的就是 Spring
配置文件中 Bean
的 id
值 - BeanFactory:执行
BeanFactoryAware
的setBeanFactory()
- 传入Spring上下文:执行
ApplicationContextAware
接口的setApplicationContext()
- 初始化预处理:
BeanPostProcessors
的postProcessBeforeInitialization()
- 构造:如果
Bean
在Spring
配置文件中配置了init-method
属性会自动调用其配置的初始化方法。 - 加工构造后:
BeanPostProcessors
的processAfterInitialization()
,如果是单例模式会将Bean
的实例放在Bean
的缓存池中。 - 自动清理阶段:执行
DisposableBean
的destroy()
- 自配置清理:如果这个
Bean
的Spring
配置中配置了destroy-method
属性,会自动调用其配置的销毁方法。
Spring常用注解
1.自动注入(也叫Spring自动装配)
- @Resources: 默认是
byname
- @Autowired: 默认是
bytype
,如果存在多个相同类型的,通过变量名可以与Bean
的id
或者name
进行匹配 - @Autowired+@Qualifier(“name”): 可以实现等同
@Resources
的效果
2.声明Bean定义
3.@Configuration:用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法
-
@EnableAutoConfiguration
,如果使用@Configuration
的话需要在启动类添加这个注解,用于扫描配置类。 - 等同于在
spring
配置文件中 <context :annotation-config>
4.@Constroller与@RestConstroller的区别
-
@Constroller
使用了视图解析器 -
@RestConstroller
没有使用视图解析器,不能打开jsp
5.@ComponentScan 对Bean进行扫描
- 等同于在
Spring
配置文件中 <context:component-scan base-package="包名">
6.@Bean和@Component的区别
相同点:两者的结果都是为Spring
容器注册Bean
.
不同点:@Component
通常是通过类路径扫描来自动侦测以及自动装配到Spring
容器中。
@Bean
注解通常是我们在标有该注解的方法中定义产生这个bean
的逻辑。
AOP面向切面编程
应用场景:需要多个不具有继承关系的对象引入一个公共行为
使用方法:
- 在
XML
配置文件中 加入<aop:aspect-autoproxy>
开启AOP
支持,并在XML
中定义切面 - 使用
@Aspect
注解定义切面,启动类加上@EnableAspectJAutoProxy(proxyTargetClass =true)
来开启AOP
支持
Spring AOP的实现方式
JDK动态代理
- 类对象必须实现接口
-
JDK
动态代理,背后是借助Java
多态的特性,因为JDK
动态代理生成的class
文件已经继承了Proxy
,而Java
是单继承的,不能继承目标对象,只能实现目标对象,所以是基于JDK
动态代理是基于接口的。
JDK动态代理主要涉及两个类:
- InvocationHandler:是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。
- Proxy: 利用
InvocationHandler
动态创建 一个符合某一接口的实例,生成目标类的代理对象。
Cglib动态代理
-
Cglib
是一个强大的高性能,高质量的代码生成类库, 可以在运行期扩展 Java
类与实现 Java
接口,CgLib
封装了asm
,可以再运行期动态生成新 的 class
。
特别要注意的是:
- 目标类实现接口的情况下使用
JDK动态代理
,没有实现接口的情况下使用Cglib动态代理
。 - 可以使用
ProxyTargetClass = true
,强制所有都使用Cglib
动态代理。 -
Cglib
所创建的动态代理对象在实际运行时候的性能要比JDK
动态代理高不少,有研究表明,大概要高10倍;但是Cglib
在创建对象的时候所花费的时间却比JDK
动态代理要多很多,有研究表明,大概有8倍的差距; - 对于
singleton
的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用Cglib
动态代理,反正,则比较适用JDK
动态代理。
使用AOP主要的应用场景:
-
Authentication
权限检查 -
Caching
缓存 -
Context passing
内容传递 -
Error handling
错误处理 -
Lazy loading
延迟加载 -
Debugging
调试 -
logging
, tracing
, profiling and monitoring
日志记录,跟踪,优化,校准 -
Performance optimization
性能优化,效率检查 -
Persistence
持久化 -
Resource pooling
资源池 -
Synchronization
同步 -
Transactions
事务管理
循环依赖
循环依赖指的是Spring Bean
之间相互依赖的情况。
解决循环依赖的前置条件:
- 出现循环依赖的
Bean
必须要是单例 - 依赖注入的方式不能全是构造器注入的方式
Spring在创建Bean的时候默认是按照自然排序来进行创建的,IOC容器先创建A,再创建B:
- A、B均采用
setter
方式相互注入 - A采用
setter
方式获取B,B采用构造器方式获取A
以上两种情况的循环依赖时可以解决循环依赖,其他情况都会异常抛出。
解决循环依赖的流程:
- 当A完成了实例化并添加进了
Bean
的缓存池(一级缓存)中。 - 为A进行属性注入了,在注入时发现A依赖了B,会去实例化B。
- 在创建B的时候,因为A已经放在
Bean
的缓存池(一级缓存)当中了,所以无论B采用的setter
方式还是构造器方式都可以获取A。
这里需要注意的是:
- 如果A采用的是构造器方式,创建A时发现依赖于B,于是会先去创建B,但是B又依赖于A,并且缓存没有A,所以会直接因为循环依赖,导致启动异常。
-
@Autowired
、@Resources
实际上都是setter
方式注入依赖。
AOP在解决循环依赖的问题上,会使用三级缓存的方式去完成:
- singletonObject:一级缓存,这里的
bean
是已经创建完成的,一旦进行getBean
操作时,我们第一时间就会寻找一级缓存 - earlySingletonObjects:二级缓存,该缓存所获取到的
bean
是提前曝光出来的,是还没创建完成的。 - singletonFactories:三级缓存为早期曝光对象工厂,这个工厂的目的在于延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,才去提前生成代理对象。
知识点:
- 添加单例缓存,当
bean
被创建完以后进行的操作。这时候说明bean
已经创建完,删除二三级缓存,直接留下一级缓存,并且注册该bean
。 - 二级缓存可以提前曝光被其他
Bean
所引用,它可以解決循环依赖。但是二级缓存无法解决AOP
循环依赖的问题,因为不可能每次执行singleFactory.getObject()
方法都给我产生一个新的代理对象,所以还要借助另外一个缓存来保存产生的代理对象。 - 三级缓存是用来来保存产生的代理对象,但它并没有所谓性能上的“提升”。
Spring MVC
- 模型(
Model
)代表数据控制器。数据的读取,插入,更新都是由模型来负责。 - 视图(
View
)是展示给用户的最终页面。视图负责将数据以用户友好的形式展现出来。 - 控制器(
Controller
)是模型,视图以及其他任何处理 HTTP
请求所必须的资源之前的中介
SpringMVC处理请求的的控制器
- DispatcherServlet前端控制器:接收用户请求
- HandlerAdapter处理器适配器:选择合适的处理器,并且调用相应功能处理方法
- ViewResolver视图解析器:用于视图解析
用户发送请求和返回响应的流程:
- 发送请求 至
DispatcherServlet
- 映射处理器 获取处理器映射至
DispatcherServet
-
HandlerAdapter
进行处理器适配 - 调用处理器相应功能处理方法 (获得
View
和model
至DispatcherServlet
) 这里涉及Controller
-
ViewResolver
接收View
进行视图解析 -
Model
加入到View
中进行视图渲染 -
DispatcherServlet
返回响应
Spring 事务管理
事务
事务是访问并可能更新数据库中各种数据项的一个程序执行单元。
在关系性数据库中,事务可以是一条sql
,一组sql
语句。
声明式事务与编程式事务
- 声明式事务管理使业务代码不受污染,一个普通的
POJO
对象,只要加上注解就可以获得完全的事务支持。 - 声明式事务管理它的最细粒度只能作用到方法级别,而编程式事务那样可以作用到代码块级别。
Spring对事物的支持
Spring
并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate
或者JTA
等持久化机制所提供的相关平台框架的事务来实现。
常见的有:
- Jdbc事务:
mybayis
通常使用这个方式进行事务管理 - Hibernate事务:
Hibernate
的事务管理方式 - Java持久化API事务:
JPA
就是基于这种方式 - Java原生API事务 :使用以上多种事务
Transational注解
@Transational是一种声明式事务的一种方式,利用的是java
的反射机制,实现动态代理,再借助泛型实现,能够忽略class
的类型,基于AOP
做事务上的操作。
-
Service
类前加上@Transactional
,声明这个service
所有public
方法需要事务管理。 -
@Transactional
放在方法前面需要方法是public
级别的。 - 实际开发中我们使不使用
@Transactional
取决于我们是否需要保留中间态错误数据。
@Transactional参数说明:
- propagation :用于指定传播行为
- isolation:用于指定隔离级别
- rollbackFor:当发生
BizException
进行事务回滚
事务的特点
- 原子性:多条指令作为一个集体,要么都执行,要么都不执行
- 一致性:比如 a=100 b=100, a、b之间交易 总和一定是200
- 隔离性:一个事务不受其他事务的影响
- 持久性:事务一旦提交,它对数据库的改变时永久性的
传播行为
场景:当一个事务去调用另一个事务时,用于定义事务的边界:
- Propagation.REQUIRED:表示当前方法必须运行在事务中,如果当前事务存在,方法将会在该事务中运行,否则,会启动一个新的事务。(默认)
- Propagation.SUPPORTS:表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在事务中运行。
- Propagation.MANDATORY:表示当前方法必须运行在事务中,如果事务不存在,则会抛出异常。
- PROPAGATION_REQUIRES_NEW:表示当前方法必须运行在自己的事务。一个新的事务将会被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用
JtaTransactionManager
作为事务管理器,则需要访问TransactionManager
对象。 - PROPAGATION_NOT_SUPPORTED:表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将会被挂起。如果使用
JtaTransactionManager
作为事务管理器,则需要访问TransactionManager
对象。 - PROPAGATION_NEVER:表示该方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常。
- PROPAGATION_NESTED:表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么行为和
Propagation.REQUIRED
一致。但要注意当前事务管理器是否支持嵌套事务。
隔离级别
场景: 隔离级别定义一个事务可能受其他并发事务活动活动影响的程度。
mysql
事务隔离级别 默认是 :可重复读
此外还有:DEFAULT
使用数据库默认的事务隔离级别.
脏读:读取出来的数据是无效数据。( 脏读发生在一个事务读取了被另一个事务改写但尚未提交的数据时。如果这些改变在稍后被回滚了,那么第一个事务读取的数据就会是无效的)
幻读:读取数据时读取出来多条历史数据。( 当一个事务(T1)读取几行记录后,另一个并发事务(T2)插入了一些记录时,幻读就发生了。在后来的查询中,第一个事务(T1)就会发现一些原来没有的额外记录。)
不可重复读:数据由于锁机制,只能被读取一次。 (发生在一个事务执行相同的查询两次或两次以上,但每次查询结果都不相同时。这通常是由于另一个并发事务在两次查询之间更新了数据)
Spring boot
SpringBoot是基于Spring4
的条件注册的一套快速开发整合包, 实现了自动配置,降低了项目搭建的复杂度。
SpringBoot的优势
- 可快速构建独立的
Spring
应用程序 - 嵌入的
Tomcat
,无需部署 WAR
文件 - 简化了
Maven
配置 - 自动配置
Spring
- 提供了
actuator
模块用于健康检查 - 无需任何
XML
配置
SpringBoot的启动过程主要做的三件事:
- 进行
SpringApplication
的初始化模块,配置一些基本的环境变量、资源、构造器、监听器; - 调
run
方法,启动流程的监听模块、加载配置环境Environment
模块、及核心的创建上下文环境applicationContext
模块; - 自动化配置模块,创建
spring
容器, refreshContext()
,实现starter
自动化配置,spring.factories
文件加载, bean
实例化
spring.factories
就像是工厂一样配置了大量的接口对应的实现类,我们通过这些配置 + 反射处理就可以拿到相应的实现类。
SpringBoot启动的16个阶段:
- 新建
module
,在主程序类加入断点,启动springboot
- 首先进入
SpringAplication
类run
方法 -
run
方法新建SpringApplication
对象 -
run
方法首先创建并启动计时监控类 - 接着通过
configureHeadlessProperty
设置java.awt.headless
的值 - 调用
getRunListeners
创建所有spring
监听器 -
DefaultApplicationArguments
初始化应用应用参数 -
prepareEnvironment
根据运行监听器和参数准备spring
环境 - 调用
createApplicationContext
方法创建应用上下文 - 通过
prepareContext
准备应用上下文 -
refreshContext
方法刷新上下文 - 调用
stop
方法停止计时监控器类 - 调用
started
发布应用上下文启动完成事件 -
callRunners
方法执行所有runner
运行器 - 调用
running
发布应用上下文就绪事件 - 最后返回应用上下文