Spring相关
[toc]
Spring相关
1. Spring框架
- 优点:
- 方便解耦,简化开发: 所有对象创建和依赖关系的维护交给spring管理
- AOP编程的支持: 提供面向切面编程, 方便实现对程序进行权限拦截, 运行监控等功能
- 声明式事务支持: 通过配置就可完成对事务的管理, 无需手动编程
- 方便程序测试: 对Junit4支持, 方便测试Spring程序
- 方便集成框架: 内部提供对优秀框架支持
- 降低JavaEE API使用难度: 对JAVAEE等api封装, 降低难度
- 缺点:
- 轻量级框架, 但是大而全
- 依赖反射, 反射影响性能
- 使用门槛高
1.1 使用的设计模式
- 工厂模式: BeanFactory就是简单工程模式的体现, 创建对象实例
- 单例模式: Bean默认为单例模式
- 代理模式: AOP使用JDK代理和CGLIB字节码生成技术
- 模板方法: 解决代码重复问题, RestTemplate
5, 观察者模式: 定义对象一对多依赖关系, 当对象状态发生变化, 所有依赖对象都会得到通知被动更新, 如 ApplicationListener
1.2 标准事件
- 上下文更新(ContextRefreshedEvent): 在调用ConfigurableApplicationContext 接口中的refresh()方法
- 上下文开始(ContextStartedEvent): 当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发
- 上下文停止(ContextStoppedEvent): 当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发
- 上下文关闭(ContenxtClosedEvent): 当ApplicationContext被关闭时触发该事件
- 请求处理(RequestHandledEvent): http请求结束触发该事件
1.3 IOC
控制反转IOC(Inversion of Control), 把传统由程序代码控制的对象调用权交给容器, 通过容器来实现对象组件的装配和管理.
Spring IOC负责创建对象, 管理对象(通过依赖注入DI), 装配对象, 配置对象, 并且管理对象的声明周期
作用:
- 管理对象的创建和依赖的维护.
- 解耦, 由容器去维护具体的对象
- 托管了类的产生过程, 比如过需要在类的产生过程中做一些处理(如代理, 把代理交给容器, 应用程序无需关系如何完成代理)
优点:
- IOC或者DI把应用程序代码量降到最低
- 是应用容易测试, 单元测试不再需要单例和JNDI查找机制
- 最小代价和最小侵入性使松散耦合实现
- 支持加载服务使的饿汉式初始化和懒加载
1.4 BeanFactory和ApplicationContext
BeanFactory:
- 都可以当作Spring容器,
- 包含各种Bean的定义, 读取bean配置文档, 管理Bean的加载,实例化,控制Bean的声明周期, 维护Bean的依赖关系
- 延迟加载方式注入bean, 只有在使用到某个bean(调用getBean()时), 才会对该Bean加载实例化. 不能发现存在的Spring配置问题(如没有注入, 当BeanFactory加载后, 只有第一次调用才会抛出异常)
- 编程方式被创建
- 支持BeanPostProcessor和BeanFactoryPostProcessor, 但BeanFactory需手动注册
ApplicationContext:
- 都可以当作Spring容器, 是BeanFactory的子接口.
- 除了提供BeanFactory所具有功能外, 还提供: 继承MessageSource支持国际化; 统一的资源文件访问方式; 提供监听器中注册bean的事件; 同时加载多个配置文件, 载入多个(由继承关系)上下文, 使每个上下文专注特点层次(如web层)
- 容器启动时, 一次性创建所有bean. 启动时就能发现Spring配置错误. 预载入所有单实例bean, 当使用使, 就不用等待. 所以占用内存空间, 配置bean较多时, 启动程序较慢
- 除了编程方式, 还能以声明方式创建(如使用ContextLoader)
- 支持BeanPostProcessor和BeanFactoryPostProcessor, 但ApplicationContext自动注册
- 定义了refresh()方法, 刷新整个容器, 重新加载/刷新所有bean
1.5 Bean作用域
- singleton: 在每个spring ioc容器只有一个实例, 默认, 非线程安全
- prototype: 一个bean定义多个实例, 频繁创建销毁bean代来很大性能开销
- request: 每次请求都会创建一个bean
- session: 每个HTTP session中, 一个bena 对应一个session
- global-session: 全局的HTTP session中, 一个bean定义对应一个实例
1.6 Bean生命周期
bean在Spring容器创建到销毁经历若干阶段
- 实例化: 实例化对象
- 填充属性: 将值和bean的引用注入到bean对应的属性中
- 调用BeanNameAware接口: 如果bean实现了BeanNameAware接口, 将bean的id 传递给setBeanName()方法
- 调用BeanFactoryAware接口: 如果bean实现了BeanFactoryAware接口, 将BeanFactory容器实例传递调用setBeanFactory()方法
- 调用ApplicationContextAware接口: 如果bean实现了ApplicationContextAware接口, 将ApplicationContext容器实例传递调用setApplicationContext()方法
- 调用BeanPostProcessor接口: 如果bean实现了BeanPostProcessor接口, postProcessBeforeInitialization()方法
- 调用InitializingBean接口: 如果bean实现了InitializingBean接口, 将调用afterPropertiesSet()方法. 如果bean使用了init-method(注解@PostConstruct)声明初始化方法, 该方法也会被调用
- 调用BeanPostProcessor接口: 如果bean实现了BeanPostProcessor接口, postProcessAfterInitialization()方法
- 调用DisposableBean接口: 如果bean实现了DisposableBean接口, 将调用destroy()方法. 如果bean使用了destroy-method(注解@PreDestroy)声明销毁方法, 该方法也会被调用
1.7 Spring AOP与AspectJ AOP
AspectJ: 静态代理增强, 在编译阶段生成AOP代理类, 编译期增强, 会在编译阶段将切面织入java字节码中, 运行时就是增强后的AOP对象
SpringAOP: 动态代理, 不会去修改字节码, 每次运行时在内存临时未方法生成一个AOP对象, 该AOP对象包含了目标对象的全部方法, 并且在特定的切点做增强处理, 并回调原对象方法
切面(Aspect): 通知与切点的结合; 通知和切点定义了切面全部内容, SpringAOP中, 切面使用通用类(基于模式的风格) 或者在普通类中已@AspectJ注解实现
连接点(Join point): 指方法, 代表一个方法的执行. 连接点是在应用执行过程中能够插入切面的一个点, 这个点在调用方法时, 抛出异常时, 修改字段时. 切面代码利用这些点插入到应用正常流程中, 并添加新的行为
通知(Advice): 切面的工作被称为通知
切入点(Pointcut): 切点定义会在匹配通知所要植入的一个或多个连接点. 通常使用明确的类和方法名称, 或者利用正则表达式定义所匹配的类和方法名称类指定这些切点
引入(Introduction): 引入允许向现有类添加新的方法或属性
对象目标(Target Object): 一个或多个切面所通知的对象. 通常是一个代理对象, 或者叫做被通知对象. 永远是一个被代理对象
织入(Weaving): 切面应用到目标对象并创建新的代理对象的过程, 目标对象生命周期在编译器, 类加载期, 运行期可进行织入
- 编译期织入: 目标类编译时被织入, AspectJ织入编译器已这种方式织入切面
- 类加载期织入: 类加载到JVM时被织入. 需要特殊类加载器, 可以在目标类被引入应用前增强该目标类的字节码. AspectJ5的加载时织入就是以这种方式织入切面
- 运行期织入: 切面在运行的某一刻被织入. 一般情况下, 在织入切面时, AOP容器会为目标对象动态创建代理对象. SpringAOP就是这种方式织入切面
Spring切面通知类型:
- 前置通知(Before): 目标方法被调用前通知
- 后置通知(After): 目标方法完成后调用通知, 不关心方法输出是什么
- 返回通知(After-returning): 目标方法成功执行后调用通知
- 异常通知(After-throwing): 目标方法抛出异常后调用通知
- 环绕通知(Around): 包裹被通知方法, 在被通知的方法调用之前和之后执行自定义行为
通知执行顺序
无异常
around, before advice before advice target method 执行 around after adivce after advice afterReturning
异常
around, before advice before advice target method 执行 around after adivce after advice afterThrowing:异常发生 抛出异常
1.8 自动装配
spring框架xml配置共由5种自动装配:
- no: 默认方式, 不自动装配, 根据ref属性配置
- byName: 根据bean名称自动装配, 如果bean的property与另外一个bean的name相同, 就自动装配
- byType: 根据参数数据类型自动装配
- constructor: 构造函数装配, 构造韩式根据byType装配
- autoDetect: 自动探测, 有构造方法, 根据构造方法装配, 否则使用byType自动装配
@Autowire自动装配过程:
自动装载AutowireAnnotationBeanProcessor后置处理器, 当扫描到@Autowire, @Resource, @Inject时,IOC容器自动查找需要的bean, 并装配对象属性, 在使用@Autowire时, 首先在容器种查找对应bean
- 如果刚好有一个. 将bean配置给@Autowire指定数据
- 不止一个, 则@Autowire根据名称查找
- 结果为空, 则抛出异常. 可以设置required=false
@Autowire与@Resource区别
- @Autowire: 按类型装配, 默认情况下要求依赖对象必须存在(可设置required=false)
- @Resource: 按照名称装配, 名称找不到匹配bean时才按照类型装配
1.9 常用注解
@Component: 通用注解, 任意类为Spring组件
@Repository: 持久层Dao层, 主要用于数据库相关操作
@Service: 服务层, 涉及复杂逻辑, 需要用到Dao层
@Controller: 控制层, 接收用户请求并调用Service层返回数据给前端
@RestController: @Controller与@ResponseBody合集, 将返回值直接填入HTTP响应体中, REST风格控制器
@Scope: Spring Bean作用域. singleto, prototype, request, session等
@Configuration: 声明配置类
@PathVariable: 路径参数
@RequestParam: 查询参数
@RequestBody: 请求参数为JSON格式
@Value: 配置信息
@ConfigurationProperties: 读取配置信息并与Spring Bean绑定
@ControllerAdvice: 定义全局异常处理类
@ExceptionHandler: 异常处理方法
@JsonIgnore: 类属性上,过滤特点字段不返回或者不解析
@JsonFormat: 格式化JSON数据
1.10 循环依赖
Spring内部有3级缓存:
- singletonObjects: 一级缓存, 保存实例化, 已注入, 初始化完成的bean实例
- earlySingletonObjects: 二级缓存, 保存实例化完成的bean对象
- singletonFactories: 三级缓存, 用于保存bean创建工厂, 以便于后面扩展有机会创建代理对象
如果service1, service2循环依赖:
- service1开始, 从一级缓存获取不到实例, 创建实例, 提前暴露加入到三级缓存
- service1开始解决依赖注入, 从一级缓存种获取service2实例
- 获取不到service2, 则创建实例, 提前暴露加入到三级缓存, 加入到三级缓存的是调用的
getEarlyBeanReference()
方法, 这个方法会执行AOP逻辑, 若bean未被aop拦截, 从bean种获取的ObjectFactocy返回的就是原始的service2对象 - service2又要去查找service1的实例
- service1从三级缓存获取到service2实例, 添加到二级缓存.
- service2依赖注入完成, service2 初始化完成, 将service2放入一级缓存
- service1获取到刚创建完成的service1实例, service1初始化完成, 将service1放入一级缓存.
2. Spring事务
2.1 事务特性
原子性(Atomicity): 一个事物所有操作, 要么全部完成,要么全部失败; 不会出现中间环节. 事务在执行过程中发生错误, 会被回滚(Rollback)到事务开始前状态. 事务不可分割,不可简约
一致性(Consisitency): 事务开始前结束后, 数据库完整性没有被破坏. 写入的资料必须完全符合所有的预设约束,触发器, 级联回滚
隔离性(Isolation): 运行多个并发事务同时对其数据进行读写和修改的能力, 隔离性可防止多个事务并发执行时由于交叉执行而导致数据的不一致. 分为不同级别: 未提交读(Read uncommitted), 提交读(read committed), 可重复读(repeatable read), 串行化(Serializable)
持久性(Durability): 事务处理结束后, 对数据修改就是永久的, 系统故障也不会丢失
2.2 管理事务方式
- 编程式事务: 代码中通过
TransactionTemplate
或者TransactionManager
手动管理事务(不推荐使用) - 声明式事务: 基于XML配置或者基于注解, 通过AOP实现
2.3 事务传播
- PROPAGATION_REQUIRED: 如果没有事务, 则创建新事务; 如果存在事务,则加入该事务
- PROPAGATION_SUPPORTS: 如果存在事务, 则加入事务; 如果不存在事务.则以非事务运行
- PROPAGATION_MANDATORY: 如果存在事务, 则加入事务; 如果不存在事务, 则抛出异常
- PROPAGATION_REQUIRES_NEW: 不管是否存在事务, 都创建新的事务
- PROPAGATION_NOT_SUPPORTED: 以非事务执行, 如果存在事务,则事务挂起
- PROPAGATION_NEVER: 以非事务执行, 如果存在事务, 则抛出异常
- PROPAGATION_NESTED: 如果存在事务, 则在嵌套事务内执行; 如果没有事务, 则按照PROPAGATION_REQUIRED执行
2.4 事务隔离
- ISOLATION_DEFAULT: 底层数据库设置隔离级别
- ISOLATION_READ_UNCOMMITTED: 未提交读, 最低隔离级别; 事务未提交前,可被其他事务读取(会出现幻读, 脏读, 不可重复读)
- ISOLATION_READ_COMMITTED: 提交读, 事务提交后才能被其他事务读取到(可防止脏读, 当仍会造成幻读, 不可重复读)
- ISOLATION_REPEATABLE_READ: 可重复读, 保证多次读取同一数据时, 其值和事务开始时一致, 禁止读取别的事务未提交的数据, 可防止脏读,不可重复读; 但幻读仍可能发生
- ISOLATION_SERIALIZABLE: 顺序读, 事务逐次执行;代价最高最可靠的隔离级别
脏读: 一个事务可读取另外一个事务未提交数据.
不可重复读: 一个事务,多次读取同一数据
幻读: 事务内多次查询结果不一致.产生原因时另一事务修改了当前事务结果集数据
3. SpringMVC
3.1 工作流程
- 客户端发送请求, 直接请求
DispatcherServlet
- 根据请求调用
HandlerMapping
, 解析请求对应的Handler
- 对应
Handler
(即controller)后, 开始由HandlerAdapter
适配器处理 HandlerAdapter
会根据Handler
来调用真正处理器开始处理请求, 并处理相应的处理逻辑- 处理器处理完业务数据后, 会返回
ModelAndView
对象,Model
是返回的数据对象,View
是逻辑上的视图 ViewResolver
根据View
找到实际的视图DispatcherServlet
将返回的Model
传给View
(视图渲染)- 将
View
返回给请求者
3.2 过滤器和拦截器区别
过滤器(Filter): 只希望选择符合要求的数据, 定义要求的工具,就是过滤器
拦截器(Interceptor): 流程进行时, 希望干预它的进展, 甚至中止它, 就是拦截器做的事
4. SpringBoot
4.1 JavaConfig
提供了配置Spring IOC的纯java实现, 避免XML配置:
- 面向对象配置. 充分利用java中的面向对象功能, 一个配置类可以继承另一个, 重新它的@Bean方法等
- 减少或者消除XML配置
- 类型安全和重构优化. 现在可以按类型而不是按名称检索bean, 不需要任何强制转换或基于字符串查找
4.2 自动配置原理
主要基于@EnableAutoCongiguration
,@Configuration
, @ConditionOnClass
注解
@EnableAutoConfiguration: 启用Spring自动装配机制
过程:
- 实际通过
AutoConfigurationImportSelector
类, importSelector方法获取所有符合条件的类的全限定类名 - 判断自动装配开关是否打开
spring.boot.enableautoconfiguration=true
- 获取
@EnableAutoCongiguration
注解中的exclude
和excludeName
- 获取所有需要自动装配的类, 读取
META-INF/spring.factories
- 根据
@ConditionOnXXX
, 筛选所有Configuration
- @ConditionOnBean: 在容器中有指定bean的条件下
- @ConditionOnMissingBean: 在容器中没有指定bean的条件下
- @ConditionOnSingleCandidate: 指定Bean在容器中只有一个, 或者多个时设置了@Primary
- @ConditionOnClass: 类路径下有指定类的条件下
- @ConditionOnMissingClass: 类路径下没有指定类的条件下
- @ConditionOnProperty: 指定属性是否有指定值
- @ConditionOnResource: 类路径是否有指定值
- @ConditionOnExpression: 基于SpEL表达式作为判断条件
- @ConditionOnJava: 基于Java版本作为判断条件
- @ConditionOnJndi: JDNI存在的条件下存在指定的位置
- @ConditionOnNotWebApplication: 非web项目条件下
- @ConditionOnWebApplication: Web项目条件下
4.2 boostrap.yml和application.yml
bootstrap.yml: 由父ApplicationContext加载, 比application.yml优先加载, 在应用程序上下文引导阶段生效, bootstrap属性不能被覆盖
application.yml: 由ApplicationContext加载, 用于spring boot项目自动化装配
5. SpringCloud
微服务优点:
- 单一职责: 每个微服务只负载自己业务领域的功能
- 自治: 每个微服务一个单独实例, 独立部署,升级; 服务与服务通过REST等形式的标准接口通信; 一个实例可以被替换为另一种实现, 对其他微服务不产生影响
- 逻辑清晰: 单一职责使微服务看起来逻辑清晰,易于维护
- 简化部署: 修改一处可单独部署一个服务
- 可扩展: 应对业务增长通过横向或纵向扩展, 通常采用横向扩展
- 灵活组合: 灵活组合微服务实现不同功能
- 技术异构: 不同服务可根据字节业务特点选择不同技术架构
缺点:
- 复杂度高: 需考虑被调用方故障, 过载, 消息丢失等异常情况, 代码逻辑更复杂. 微服务间事务性操作, 无法利用数据库本身事务保证机制保证一致性, 需引入二阶段提交等技术
- 运维复杂: 系统由多个独立微服务构成, 需要设计良好监控系统对各个微服务状态进行监控.
- 通信延迟: 微服务直接调用由时间损耗, 造成通信延迟
5.1 Eureka
服务发现: 服务的中介, 整个过程由3个角色: 服务提供者, 服务消费者, 服务中介
服务提供者: 提供一些自己能够执行的服务给外界
服务消费者: 需要使用服务的用户
服务中介: 服务提供者,消费者桥梁, 服务提供者自己注册到服务中介那, 服务消费者在服务中介中寻找注册在服务中介的服务提供者
服务注册: Eureka客户端向Eureka服务端注册时, 提供自生元数据, 如ip地址,端口,运行状态url, 主页等.
服务续约: Eureka客户端每隔30秒发送一次心跳来续约, 告知Eureka服务当前服务仍正常存活. 如果Eureka Server在90秒没收到客户端续约消息, 则将实例从注册表中剔除
获取注册表信息: Eureka客户端从服务端获取注册信息, 并缓存到本地. 客户端会使用该信息远程调用其他服务. 该注册列表定期更新一次
服务下线: 客户端关闭时向服务器发送取消请求. 发送后, 该客户端实例将从服务器注册表中剔除.
服务剔除: 客户端未在3个续约周期向服务端发送服务续约. 则服务端将该服务实例从服务注册列表中删除
Eureka与Zookeeper区别:
Zookeeper: 保证CP, zk因为master节点故障, 剩余节点进行master选举时间过长, 选举期间整个集群不可用.
Eureka: 保证AP, 节点平等; 当向一个Eureka注册或者连接失败, 自动切换到其他节点, 只要有一台还在, 就可以保证服务可用, 不过查到的信息可能不是最新的(不保证一致性), 当15分钟内超过85%节点没有正常心跳, 就任务客户端与注册中心发生网络故障, 进入保护状态:
- Eureka不再从注册列表中移除因长时间没收到心跳而过期的服务
- 仍能接收新服务注册和查询, 但不会被同步到其他节点(保证当前节点依然可用)
- 网络稳定后. 当前实例新注册信息会被同步到其他节点
缺点:
某个服务不可用, 其他客户端不能立即知道, 需要1-3个心跳周期. 但是由于基于Hystrix容错和降级, 当掉调用不可用时, Hystrix也能立即感知到, 通过熔断机制来降级服务调用
5.2 Ribbon
消费端负载均衡项目, 与Nginx不同(nginx为集中式负载均衡器)
负载测率:
- RoundRobinRule: 轮询策略. 经过一轮轮询没找到可用provider, 其最多轮询10轮, 若最终没找到则返回null
- RandomRule: 随机策略, 所有可用provider随机选择一个
- BestAvailableRule: 最大可用策略, 过滤出故障provider后, 选项当前并发请求数最小的
- WeightedResponseTimeRule: 带加权轮询策略, 对各provider响应时间进行加权处理, 然后轮询方式来获取相应的provider
- AvailabilityFilteringRule: 可用过滤策略, 先过滤出故障或者请求大于阈值的部分provider, 然后线性轮询方式从过滤后provider清单中选一个
- ZoneAvoidanceRule: 区域感知策略, 先使用主过滤条件(区域负载器, 选择最优区域)对所有实例过滤并返回过滤后的实例清单, 依次使用次过滤条件列表中的过滤条件对主过滤条件的结果过滤, 判断最小过滤数(默认1)和最小过滤百分比(默认0), 最后对满足条件的服务使用RoundRobinRule选择一个服务器实例
- RetryRule: 重试策略, 先根据RoundRobinRule策略获取provider, 若获取失败, 则在指定时限内重试, 默认500毫秒
5.3 OpenFeign
服务调用, 基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求
5.4 Hystrix
熔断, 降级库, 提高系统的弹性
服务雪崩: 部分服务阻塞或者不可用, 导致服务调用者不可用, 最后引发整个系统崩溃无法响应请求
熔断: 在指定窗口内请求失败率达到设定阈值, 系统将通过断路器直接将此请求链路断开
降级: 更好的用户体验, 当一个方法调用异常时, 通过执行另一种代码逻辑来给用户友好的恢复, 后备处理逻辑
限流: 设置semaphore.maxConcurrentRequests,coreSize,maxQueueSize和queueSizeRejectionThreshold设置信号量模式下的最⼤并发量, 线程池大小,缓冲区大小和缓冲区降级策略
熔断原理以及如何恢复:
- 服务健康状况= 请求失败数/请求总数
- 熔断器开关状态转换是由当前服务健康状况和设定阈值比较决定的
2.1 熔断器关闭时, 请求允许通过熔断器. 如果当前健康状况高于设定阈值, 开关继续保持关闭. 如果健康状况低于阈值, 则开关切换到打开状态
2.2 熔断器打开时, 请求被禁止通过
2.3 熔断器处于打开状态, 经过一段时间后, 熔断器自动进入半开状态, 这时只允许一个请求通过. 当请求调用成功时, 熔断器恢复到关闭状态. 若请求失败, 熔断器继续保持打开状态, 接下来的请求禁止通过
5.4 zuul
系统唯一对外入口, 介于客户端与服务端之间, 对请求进行鉴权, 限流, 路由, 监控等功能
限流:
- 漏桶算法: 请求通过漏桶时, 漏桶以一定速度出水, 出水流入速度超过最大会直接溢出.
- 令牌桶算法: 以一个恒定速度往桶里放入令牌, 如果请求需要被处理, 则需要从桶里获取一个令牌,当桶里没有令牌时, 则拒绝服务
- redis计数器限流: 基于setNx操作(设置过期时间,存在跨时间端统计问题); 基于zset(滑动窗口, 通过range计算请求量.zset会变得很大); 基于令牌桶(定时任务放入数据到令牌桶, 请求时通过leftpop获取令牌)
5.4 Config
应用/系统/模块的配置文件存放到统一的地方然后进行管理git或者SVN
动态修改配置文件: 使用Spring Cloud Bus + Spring Cloud Config来通知服务更新配置
5.4 Bus
管理和广播分布式系统中的消息, 也可作为消息总线
当使用@RefreshScope就可以进行配置的动态修改