一. Spring生态
Spring是一个软件应用框架,用来解决软件开发过程中碰到的一些共性问题,他的主要作用就是为代码解耦。
Spring的核心是控制翻转IOC和增强AOP,IOC使得开发者不比关心应用的创建流程,AOP可以在不侵入代码的情况下进行功能增强,是一个轻量级开源框架。
随着版本的不断升级,从最初的IOC,AOP等到现在最流行的SpringBoot和SpringCloud,可以说从仅仅一个或几个维度问题的解决,扩展成了多个维度问题的解决,从以前的面向对象,面向切面,到现在的面向服务,我个人认为这个框架已经发展成为一个技术生态或者说一个技术体系。
1. Spring 的优势
整个 Spring 优势,传达出⼀个信号,Spring 是⼀个综合性,且有很强的思想性框架,每学习⼀ 天,就能体会到它的⼀些优势。
- 方便解耦,简化开发
通过Spring提供的IOC容器,可以将对象间的依赖关系交由Spring进⾏控制,避免硬编码所造成的
过度程序耦合。⽤户也不必再为单例模式类、属性⽂件解析等这些很底层的需求编写代码,可以更
专注于上层的应⽤。
- AOP编程的⽀持
通过Spring的AOP功能,⽅便进⾏⾯向切⾯的编程,许多不容易⽤传统OOP实现的功能可以通过
AOP轻松应付。
- 声明式事务的⽀持
@Transactional
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式⽅式灵活的进⾏事务的管理,提⾼
开发效率和质量。
- ⽅便程序的测试
可以⽤⾮容器依赖的编程⽅式进⾏⼏乎所有的测试⼯作,测试不再是昂贵的操作,⽽是随⼿可做的
事情。
- ⽅便集成各种优秀框架
Spring可以降低各种框架的使⽤难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、
Quartz等)的直接⽀持。
- 降低JavaEE API的使⽤难度
Spring对JavaEE API(如JDBC、JavaMail、远程调⽤等)进⾏了薄薄的封装层,使这些API的使⽤
难度⼤为降低。
- 源码是经典的 Java 学习范例
Spring的源代码设计精妙、结构清晰、匠⼼独⽤,处处体现着⼤师对Java设计模式灵活运⽤以及对
Java技术的⾼深造诣。它的源代码⽆意是Java技术的最佳实践的范例。
2. SpringIOC
SpringIOC其实就是控制反转IOC(inversion of control),是软件领域的一种设计思想,目的是通过移交对象控制权的方式,实现对象的科学管理。
实现原理
- spring上下文application继承了BeanFactory,读取xml配置
- application根据bean对象的class路径利用反射调用newInstance()创建对象
- 利用反射调用set注入或者构造注入或自动注入初始化对象,默认单例
- 将对象存入ConcurrentHashMap中,等待使用
依赖注入 Dependency Injection(DI)
依赖注入就是将另一个bean注入创建的bean对象。这就需要一种机制用来激活相应的组件以提供特定的功能,所以依赖注入是控制反转的基础。否则如果在组件不受框架控制的情况下,框架又怎么知道要创建哪个组件?
Spring框架提供了构造函数注入、set方法注入、自动装配注入和注解注入四种注入方式,具体的注入还有工厂模式注入。
自动装配:
- @Resource (默认按名字装配,如果没有则按类型装配,由javax提供)
- @Autowire (默认按类型装配,由Spring提供)
- @Qualifier (配合Autowire注解,按名字进行装配,可以指定具体bean)
延迟加载:
当对象太大,或者启动时间较长,而且不经常使用时,可以设置bean延迟加载lazy-init="true"
,缺点是出现问题难以发现。
获取spring上下文:
配置bean。实现ApplicationContextAware
接口并重写setApplicationContext()
方法保存。
面试题:
- 为什么叫做控制反转?
简单来说,就是将对象的生命周期和对象间的依赖交由spring管理,在需要的时候由BeanFactory创建,实现了反转
- IOC解决了什么问题?
IoC解决对象之间的耦合问题,需要配合依赖注入
- IOC和DI的区别
IOC和DI描述的是同⼀件事情,只不过⻆度不⼀样罢了
IOC站在对象的角度,将创建对象的任务已交给了外部(Spring)
DI站在容器的角度,将对象注入容器中的另一个对象
IOC依赖于DI,才能实现控制反转
3. SpringAOP
AOP原则(Aspect Oriented Programming)既面向切面编程,
实际项目中我们通常将面向对象理解为一个静态过程:
例如一个系统有多少模块,一个模块有哪些对象,对象有哪些属性都已经固定了
面向切面中包含一个一个动态过程:
在对象运行时动态织入一些功能
AOP可以在对象运行时插入动态的功能,作为面向对象功能的补充。Spring的拦截器Interceptor就是基于AOP来实现,可以用来进行权限管理,跳转等一系列功能。
简单来说,就是在面向对象的静态代码中划分出切面,动态地织入一些功能
Spring的AOP的实现是基于JDK动态代理和Cglib框架,底层使用反射。要使用Spring的AOP功能,需要依赖AspectJ框架。
面试题:
- AOP和OOP的关系
OOP是⼀种垂直继承体系,封装、继承和多态,子类继承父类,而类实现接口,是对事物和行为的一种封装
AOP是针对的是封装后共同行为的提取,将他们划分成一个切面,来进行功能的增强,可以看作是OOP的延续
AOP在解决什么问题?
实际项目中通常会将系统两大部分:核心关注点和非核心关注点
编程过程中首先要完成的是什么?核心关注点(核心业务)
非核心关注点如何切入到系统中?硬编码(违背OCP),AOP(推荐)
横切逻辑
OOP编程思想可以解决⼤多数的代码重复问题,但是有⼀些情况是处理不了的,例如横向的重复代码
例如下面eat和run方法内都有一段相同功能的代码,
4. SpringMVC
SpringMVC是实现MVC思想的WEB框架,实现了MVC分层设计。它提取了web项目开发的共性问题,利用分层结构来进行程序的解耦,提高了系统的可维护性,便于系统的升级和扩展。
核心应用组件:
- 前端调度器(DispatcherServlet)(xml配置)
- 映射处理器(HandlerMapping)(spring自带)
- 拦截器(Interceptor)(xml配置)
- 后端处理器(Controller)(包扫描)
- 视图解析器(ViewResolver)(xml配置)
- 业务实现层(BusinessService)(包扫描)
工作流程:
- 客户端发起请求到Web服务器,例如tomcat或Apache
- 请求被过滤器(Filter)处理
- 请求到达前端控制器(DispatcherServlet)
- 前端调度器调用HandlerMapping获取请求处理链(HandlerExecutionChain)
- 执行请求处理链中的拦截器(Interceptors)
- 执行请求处理链中的后端控制器(自己写的Controller)
- Controller通过Service实现类对数据进行操作,返回ModelAndView给前端控制器
- 前端调度器调将Controller返回结果发送给视图解析器(ViewResolver)
- 视图解析器渲染页面后依次通过拦截器和过滤器,返回给客户端Client
- 客户端拿到渲染后的页面.
URL请求参数映射:
- url映射
@RequestMapping("/doSayHello"})
@RequestMapping(value={"/doSayHello", "/user/doSayWelcome"})
- RESTful风格映射(@PathVariable )
public String Module(@PathVariable("module") String module){}
- 指定GET/POST映射
@RequestMapping(value=”doSaveObj”,method=RequestMethod.POST)
@GetMapping //注解配置,这种配置无法正确生成swwag2文档
@PostMapping
请求参数接收:
- 直接用变量接收(@RequestParam )
public String getUserBlog(@RequestParam(required = true, defaultValue = "") Long blogId) {}
- javabean对象接收,需要设置set方法
public String withParamGroup(Message bean) {}
- 获取请求头 @RequestHeader
- 获取cookies的值 @CookieValue(“JT_TOKEN”)
配置:
- 配置前端调度器DispatcherServlet,指定拦截路径
- 配置包扫描
- 配置静态资源放行
- 配置错误页
- 伪静态需要配置jackson解析器
- 注册bean组件
3.1 切面
使用AOP必须先添加切面
创建切面类并添加@Aspect注解和@coment注解
@Aspect
@Service
public class LogAspect{}
切入点
定义AOP的的具体切入点,可以是类或方法,通过注解加表达式实现
- bean表达式:
粗粒度的控制,细化到bean类
@Pointcut("bean(userServiceImpl)") 匹配具体的bean
public void logPointCut1(){}
@Pointcut("bean(*ServiceImpl)") 用*匹配一系列bean
public void logPointCut2(){}
- Within表达式
粗粒度的控制,细化到类
within("aop.service.UserServiceImpl") 指定具体的类
within("aop.service.*") 指定目录下的类
within("aop.service..*") 指定目录下的所有类
- Execution表达式
细粒度的控制,细化到方法,可以通过参数和返回值指定方法
execution(void aop.service.UserServiceImpl.addUser()) 匹配方法
execution(void aop.service.PersonServiceImpl.addUser(String)) 方法参数必须为字符串
execution(* aop.service..*.*(..)) 万能配置
3.2 通知
通知是围绕着Spring切面的执行方法所执行的动作,必须指定具体通知的方法。spring有5大通知,分别是前置通知,后置通知,返回通知,异常通知,环绕通知。
- Before前置通知,方法执行前调用,可以获得传入参数JoinPoint 切入点对象
@Before("doLog()")
public void doBefore(JoinPoint joinPoint){
System.out.println("log before");
}
- After 后置通知,方法执行后调用,可以获得切入点对象
- AfterReturning 返回通知,正常返回后调用,获得切入点对象和返回值Object result
- AfterThrowing 异常通知,运行异常后调用,获得切入点对象和异常对象Exception ex
- Around 环绕通知,包裹着整个方法,并在before前调用,获取环绕切入点对象ProceedingJoinPoint p
PS:通知获取的传入参数必须在参数的第一位
执行顺序:
- 正常执行顺序
- 异常执行顺序
- 多切面执行顺序
责任链模式,
3.3Filter过滤器
Filter(过滤器)作用于在intreceptor(拦截器)之前,不像intreceptor一样依赖于springmvc框架,只需要依赖于serverlet。
Filter主要用来对用户请求进行预处理,业务逻辑处理或者过滤字符编码(CharacterEncodingFilter),可以用来实现权限管理,请求日志打印等功能。
Filter启动是随WEB应用的启动而启动,只需要初始化一次,以后都可以进行拦截。
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进行操作。
4. Spring异常处理
SpringMVC采用了分层架构,在分层架构中,如果不对异常进行处理,异常可能会从DAO层逐步抛出到控制层,可以在控制层对异常进行相关处理。
Spring对异常的处理是从下往上的,并可以指定处理的异常具体如下
- 精确的异常类型处理优先于模糊的异常类型方法
- 子类的异常处理方法优先于父类的异常处理方法
- 父类的异常处理方法优先于全局异常处理方法
局部异常处理
Spring在处理异常时首先会查找自己类下填加了@ExceptionHandler 注解的方法来进行异常处理,并可以指定异常类型。(必须填加@ResponseBody)
@ExceptionHandler(value=Exception.class)
@ResponseBody
public String handleException(Exception e){
System.out.println("局部异常处理");
return e.getMessage();
}
全局异常处理
全局异常处理类必须添加@ControllerAdvice 控制器增强注解
@ExceptionHandler({
Error.class,RuntimeException.class
}) //多异常处理
@ResponseBody //必须添加注解表示的是响应主体
public JsonResult doHandlerError(RuntimeException e){
log.info(e.printStackTrace()); //记录日志
return new JsonResult("系统维护中,请稍候~");
//将异常信息封装到jsonResult,交由jacson转码成json串
}
5. Spring Boot
Spring Boot 是基于“习惯优于配置”的理念开发的,设计目的是用来简化新Spring应用的初始搭建以及开发过程。Spring Boot整合了大部分框架的配置,让程序员能从繁琐的Spring配置中解脱出来,专心于业务的实现,spring boot 全家桶 和 docker容器化技术让微服务的实现拥有了可能。
新特性:
- @RestController
新注解,包含@Controller和@ResponseBody - 启动类
Springboot通过启动类启动,新建启动类并添加@SpringBootApplication( “com.funtl.itoken”)注解,同时指定包包扫描的路径,注意启动类必须放在其他类的外层,否则会扫描错误
主要特点:
- 用来实现微服务
- 自动配置
- 自定义配置
- 模块化
- 独立打包 直接运行
- 内嵌服务器
- Spring cloud的基础
6. Spring Cloud
6.1 微服务架构
在过去几年中,“微服务架构”这一术语如雨后春笋般涌现出来,它描述了一种将软件应用程序设计为一组可独立部署的服务的特定方式。虽然这种架构风格没有明确的定义,但在组织、业务能力上有一些共同的特征:自动化部署,端点智能化,语言和数据的去中心化控制,做到高可用,高并发,高性能。
简单来说,微服务架构风格是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制(通常用HTTP资源API)。这些服务围绕业务能力构建并且可通过全自动部署机制独立部署。这些服务共用一个最小型的集中式的管理,服务可用不同的语言开发,使用不同的数据存储技术。
在分布式系统领域有个著名的CAP定理(C-数据一致性;A-服务可用性;P-服务对网络分区故障的容错性,这三个特性在任何分布式系统中不能同时满足,最多同时满足两个)
- Consistency 一致性
在分布式系统中的所有数据备份,在同一时刻具有同样的值,所有节点在同一时刻读取的数据都是最新的数据副本。 - Pvailability可用性
在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。 - Partition tolerance 分区容错
大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。分区容错的意思是,区间通信可能失败。比如,一台服务器放在中国,另一台服务器放在美国,这就是两个区,它们之间可能无法通信。
一般来说,分区容错无法避免,因此可以认为 CAP 的 P 总是成立,而剩下的 C 和 A 无法同时做到。由于网络原因,为了保证数据的一致性,必须进行分区同步导致可用性下降,如果要保证可用性,又有可能出现数据错误。
技术选型:
- 对数据一致性(C)要求不高的场景,例如电商系统,我们只需要保证分区容错性(P)和可用性(A),对于数据一致性(C)退而求其次仅保证数据的最终一致性即可。这虽然会某些地方影响客户体验,但并不会达到造成用户流失的严重程度。
Zookeeper:CP设计,保证了一致性,集群搭建的时候,某个节点失效,则会进行选举新的Leader,如果半数以上节点不可用,则无法提供服务,因此可用性(A)没法满足。 - 对数据一致性(C)要求较高的场景,例如银行系统,数据一致性(C)必须保证。网络发生故障宁可停止服务(或者只读不写),这是保证分区容错性(P)和数据一致性(C),舍弃可用性(A)。
Eureka:AP设计,无主从节点,一个节点挂了,自动切换其他节点继续使用,去中心化,但数据一致性(C)不满足。
使用了微服务架构后,我们将面临下四个问题:
- 客户端如何访问这么多的服务?
使用API网关聚合服务(zuul) - 服务与服务之间如何通信?
- 同步通信 HTTP(Apache Http Client),RPC(Dubbo)
- 异步通信 消息队列(kafka,RabitMQ)
- 这么多服务如何管理?
为了保证高可用,每一个服务都可能创建多个副本,所以必须有一个注册中心来管理保存这些服务的情况,既服务的注册与发现。- 基于Client的服务注册与发现(Zookeeper,半数选举制)
- 基于Server的服务注册与发现(Eurekar,去中心化互为主从)
- 服务挂了怎么办,如何保证可用性?
重试机制,服务熔断,服务降级,服务限流
目前市场上主流的 解决方法主要有以下三种:
6.2 分布式事务和分布式锁
分布式锁
分布式锁主要用来解决多个微服务共享数据的问题,基于微服务架构,分布式锁必须实现以下特性:
- 高并发高性能
- 高可用非阻塞
- 可重入
- 预防死锁
实现方式:
- Redis 的原子性set命令
- Zookeeper的顺序临时节点
分布式事务
随着数据量的增大出现的分库分表,以及微服务分布式数据库,使得事务的维护变得越来越困难,当下主要有以下几种解决方案:
- 2PC 两次提交
- 解释:定义了一个全局事务管理器,将事务的提交分为2次,一次预提交,一次本地提交。
- 缺点:这种设计忽略了对网络情况的考虑,而且对高并发不友好
- TCC 补偿事务
- 解释:为事务的各种情况设计了相应的处理代码,类似try-catch
- 缺点:复杂场景不好定义,而且设计成本高昂
- 建立本地消息表
- 解释:在本地额外维护了一个本地消息表,每次执行事务都会发送给相关的服务更新表,无论执行成功或则失败都会发送消息,异步同步,保证了最终的一致性
- 缺点: 消息表会耦合到业务系统中,需要设计好一套方案来解决
- 消息队列事务
- 解释:在RocketMQ中实现了分布式事务,实际上其实是对本地消息表的一个封装,将本地消息表移动到了MQ内部。
- 优点:这是目前最好的解决方案,阿里巴巴提供了相关支持
6.3 Spring Cloud
第一套微服务架构解决方案:Spring Boot + Spring Cloud Netflix
Spring Cloud Netflix是一个相对比较新的微服务框架,为开发者提供了在分布式系统(配置管理,服务发现,熔断,路由,微代理,控制总线,一次性 Token,全居琐,Leader 选举,分布式 Session,集群状态)中快速构建的工具,提供全套的分布式系统解决方案,使用 Spring Cloud 的开发者可以快速的启动服务或构建应用、同时能够快速和云平台资源进行对接。
核心组件:
- zuul 网关聚合
- eureka 分布式注册中心
- feign HTTP API客户端通信
- hystrix 熔断器
虽然项目高可用,但是项目已经于去年进入了维护阶段,不建议继续使用。
6.4 Apache Dubbo Zookeeper
第二套微服务架构解决方案:Spring Boot + Dubbo + Zookeeper
利用Dubbo 实现服务间的通信,而Zookeeper作为注册中心和配置中心管理服务,但是这个方案不包括服务聚合,而且zookeeper的选举制度也无法保证服务的高可用,它本身是为了实现分布式锁而设计的。
与Spring Cloud的区别:
- 相对于Spring Cloud的HTTP传输,Dubbo作为一个RPC框架少了http报文和json串损耗,消耗带宽更少。
- Dubbo+Zookeeper并不是转为微服务开发的框架,只是Dubbo的RPC和Zookeeper的分布式锁可以用来进行微服务开发,所以其他方面还需要自行组装搭配
- Dubbo作为阿里巴巴贡献给Apache的项目,还处在孵化中,技术还未完全成熟,而Netfilix 在国外已经有了成熟的例子。
6.5 Spring Cloud Alibaba
第三套微服务架构解决方案:Spring Boot + Spring Cloud Alibaba
Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。
依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。
实现的功能:
- 服务限流降级:默认支持 Servlet、Feign、RestTemplate、Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
- 服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
- 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
- 消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
- 分布式事务:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。。
- 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
- 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。
- 阿里云短信服务:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。