JAVA面试高级技术栈-06-Spring
想要了解更多?:
JAVA面试高级技术栈-01-多线程编程
JAVA面试高级技术栈-02Linux基本指令
JAVA面试高级技术栈-03JVM(Java虚拟机)
JAVA面试高级技术栈-04-MySql优化
JAVA面试高级技术栈-05-Redis持久化
JAVA面试高级技术栈-06-Spring
文章目录
sping简介
Spring是个轻量级的框架,通过IOC达到松耦合的目的,通过AOP可以分离应用业务逻辑和系统服务进行内聚性的开发,不过配置各种组件时比较繁琐,所以后面才出选了SpringBoot的框架。
优点
1、Spring是一个开源免费的框架 , 容器
2、Spring是一个轻量级的框架 , 非侵入式的
3、控制反转 IoC , 面向切面 Aop
4、对事物的支持 , 对框架的支持
…
一句话概括:
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。
IOC(控制反转)
传统开发中,需要调用对象的时候,需要调用者手动来创建被调用者的实例,即对象是由调用者new出来的
但是在Spring框架中,创建对象的工作不再由调用者来完成,而是交给IOC容器来创建,再推送给调用者,整个流程完成反转,所以控制反转降低对象之间的依赖关系。
创建一个bean(文章后面会介绍)的方式有xml方式、@Bean注解方式、@Component方式
我们在对一个bean进行实例化后,要对他的属性进行填充,大多数我们都是使用 @Autowire直接的填充依赖注入的,他是有限按照类型进行匹配。
IOC的具体化形式就是依赖注入(DI), 所谓的注入就是给属性赋初值
AOP(面向切面(方法)编程)
AOP是面向切面编程,可以将那些与业务不相关但是很多业务都要调用的代码抽取出来,思想就是不侵入原有代码的情况下对
功能进行增强。利用AOP可以对项目的业务逻辑的各个模块进行分离, 降低业务之间的耦合度
动态代理: 动态代理是程序运行期间, 创建目标对象的代理对象, 对目标对象的方法进行功能性增强(一个复制体, 但是可以进行功能增强, 将一些代码嵌入到对象代理中)
SpringAOP是基于动态代理实现的,动态代理是有两种,一种是jdk动态代理,一种是cglib动态代理;
-
jdk动态代理: 原理是利用反射来实现的,需要调用反射包下的Proxy类的newProxyInstance方法来返回代理对象,这个方法中有三个参数,分别是用于加载代理类的类加载器,被代理类实现的接口的class数组和一个用于增强方法的InvocaHandler实现类。
(默认模式, 如果对象实现若干接口, 使用该形式去做代理)
-
cglib动态代理: 原理是利用asm开源包来实现的,是把被代理类的class文件加载进来,通过修改它的字节码生成子类来处理.
(如果对象没有实现接口, 使用cglib库生成目标对象的代理, 注意:被final修饰过的方法无法获取动态代理)
jdk动态代理要求被代理类必须有实现的接口,生成的动态代理类会和代理类实现同样的接口,cglib则,生成的动态代理类会继承被代理类。Spring默认使用jdk动态代理,当被代理的类没有接口时就使用cglib动态代理
具体实现方案:
- 已经对需要操作的目标对象进行代理
- 指定其处理方式(通知类型,增强)
PS:
通知类型,增强: 前置, 后置, 环绕, 异常, 后置返回, 返回
切点: 指定增强的位置(整个类中方法, 指定的一个方法)
切面: 实现对通知和连接点(可以是切点)
常用的切面:
- 一般切面(只有通知, 没有切点,会对目标对象的所有方法执行增强 )
- 切点切面(指定方法进行增强)
- 引介切面(特殊切面, 定义Filter设置)
原始的代理方式通过xml配置ProxyFactoryBean创建对象, 该方法不适合同时代理多个bean对象, spring中提供了自动地理的方案来解决多对象的同时代理
全局异常处理类
想要定义一个全局异常处理类的话,我们需要在这个类上添加@ContaollerAdvice注解,然后定义一些用于捕捉不同异常类型的方法,在这些方法上添加@ExceptionHandler(value = 异常类型.class)和@ResponseBody注解,方法参数是HttpServletRequest和异常类型,然后将异常消息进行处理。
如果我们需要自定义异常的话,就写一个自定义异常类,该类需要继承一个异常接口,类属性包括final类型的连续id、错误码、错误信息,再根据需求写构造方法;
使用 @ControllerAdvicearch + @ExceptionHandler 注解
这种方式的好处是简单统一,但是需要在每一层都要抛出异常,保证异常能够被controller层接收
// 指定要拦截的位置
@ControllerAdvice(annotations = {RestController.class, Controller.class})
// 设置以json的形式返回数据
@ResponseBody
// 日志记录
@Slf4j
public class GlobalExceptionHandler {
/**
* 帐号存在异常
* @param ex
* @return
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
//SQLIntegrityConstraintViolationException 违反了数据库的唯一约束条件,插入数据时
public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
// 获取异常信息
String exMessage = ex.getMessage();
log.error("捕获到异常:{}",exMessage);
// 处理异常信息
if(exMessage.contains("Duplicate entry")){
String[] msg = exMessage.split(" ");
return R.error("帐号 "+msg[2]+" 已存在!");
}
return R.error("未知错误!");
}
}
循环依赖
循环依赖就是在创建 A 实例的时候里面包含着 B 属性实例,所以这个时候就需要去创建 B 实例,而创 建 B 实例过程中也包含着 A 实例。 这样 A 实例还在创建的过程当中,所以就导致 A 和 B 实例都创建不出来。
spring通过三级缓存来解决循环依赖:
-
一级缓存:单例池,缓存经过了已经初始化完毕的Bean
-
二级缓存 :半成品池,缓存还未初始化完毕的Bean
-
三级缓存:缓存的是获取Bean的代理对象的表达式
我们在创建 A 的过程中,先将 A 放入三级缓存 ,这时要创建B,B要创建A就直接去三级缓存中查找,并且判断需不需要进行 AOP 处理,如果需要就在三级缓存中获取A的代理对象,不需要就取A原始对象。然后将取出的对象放入二级缓存中,这个时候其他需要依赖 A 对象的直接从二级缓存中去获取即可。当B初始化完成进入一级缓存后,A 继续执行生命周期,当A完成了属性的注入后,就可以放入一级缓存了
spring2.6之前默认会解决循环依赖。在spring2.6之后需要通过配置开启解决循环依赖
依赖注入
依赖注入: Dependency Injection
,它是 spring 框架核心 IOC 的具体实现,具体点就是依赖关系的维护称之为依赖注入
我们的程序在编写时, 通过控制反转, 把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。
spring有4种依赖注入方式:
- Set方法注入
- 构造器注入
- 静态工厂的方法注入
- 实例工厂的方法注入
Set方法注入
首先我们有一个实体类User, 里面必须有set方法, 建议里面也有get方法, 这里为了方便展示只使用set方法, 在实际开发过程中应该同时有set和get方法
package org.example.bean;
import java.util.List;
import java.util.Map;
/**
* @author TFY
*/
public class User {
private int id;
private String name;
private List<String> hobbies;
private Map<String, String> map;
public User() {
}
public User(int id, String name, List<String> hobbies, Map<String, String> map) {
this.id = id;
this.name = name;
this.hobbies = hobbies;
this.map = map;
}
/**
* 设置
* @param id
*/
public void setId(int id) {
this.id = id;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 设置
* @param hobbies
*/
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
/**
* 设置
* @param map
*/
public void setMap(Map<String, String> map) {
this.map = map;
}
}
然后我们在配置文件(applicationContext)中进行配置
<!-- spring容器中的每一个子元素都是一个bean对象-->
<!-- 注入User类 id唯一标识 class对应类的全限定类名-->
<bean id = "user" class="org.example.bean.User">
<!-- (主要) 属性注入: setter注入-->
<property name="id" value="1"/>
<property name="name" value="张三丰"/>
<!-- 数组和list集合是相同的, set的话替换为set标签-->
<property name="hobbies">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</property>
<!-- map类型-->
<property name="map">
<map>
<!-- 键值对-->
<entry key="1" value="巧克力"/>
<entry key="2" value="草莓饼干"/>
</map>
</property>
</bean>
</beans>
最后是测试方法
import org.example.bean.User;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test {
//由java管理
@Test
public void Test(){
User user = new User();
user.setName("金毛狮王谢逊");
System.out.println(user);
}
//由spring框架管理
@Test
public void Test2(){
// 先读取spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 然后获取容器中的bean对象
User user = context.getBean("user", User.class);
System.out.println(user);
}
}
构造器注入
我们可以在user实体类中加入构造函数如下
public User(String name, int id) {
this.name = name;
this.id = id;
}
配置文件修改如下
<!-- spring容器中的每一个子元素都是一个bean对象-->
<!-- 注入User类 id唯一标识 class对应类的全限定类名-->
<bean id = "user" class="org.example.bean.User">
<constructor-arg name="name" value="小龙女"/>
<constructor-arg name="id" value="2"/>
</bean>
set和构造器注入有些麻烦, 于是我们进行简化, 在beans标签中添加如下即可
<!-- (重要) 简化配置, 只需要将实体类注入容器, 不需要多余的配置信息 , 获取的话需要new对象或者@Autowired-->
<context:component-scan base-package ="org.example.bean"/>
之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!
注解注入
@Component
@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
public String name = "金毛狮王谢逊";
}
属性注入
- 可以不用提供set方法,直接在直接名上添加@value(“值”)
@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
@Value("金毛狮王谢逊")
// 相当于配置文件中 <property name="name" value="金毛狮王谢逊"/>
public String name;
}
- 如果提供了set方法,在set方法上添加@value(“值”)
@Component("user")
public class User {
public String name;
@Value("金毛狮王谢逊")
public void setName(String name) {
this.name = name;
}
}
@Component三个衍生注解
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。
- @Controller:controller层
- @Service:service层
- @Repository:dao层
写上这些注解,就相当于将这个类交给Spring管理装配了!
Bean
在Spring框架中,Bean是指在Spring容器中管理的一个对象
。Spring容器通过对Bean的管理,实现了依赖注入、面向切面编程、声明式事务管理等核心特性。Spring的Bean可以是普通Java对象、JavaBean、POJO(Plain Old Java Object)等类型的对象,还可以是数据源、连接池、JMS(Java消息服务)等非常复杂的对象。
Bean 的作用域
@scope(Singleton)
(1)Singleton:一个IOC容器只有一个
(2)Prototype:每次调用getBean()都会生成一个新的对象
(3)request:每个http请求都会创建一个自己的bean
(4)session:同一个session共享一个实例
(5)application:整个serverContext只有一个bean
(6)webSocket:一个websocket只有一个bean
Bean 生命周期
实例化 Instantiation->属性赋值 Populate->初始化 Initialization->销毁 Destruction
在这四步的基础上面,Spring 提供了一些拓展点:
-
Bean 自身的方法: 包括了 Bean 本身调用的方法和通过配置文件中的 init-method 和 destroy-method 指定的方法
-
Bean 级生命周期接口方法:包括了 BeanNameAware、BeanFactoryAware、InitializingBean 和 DiposableBean 这些接口的方法
-
容器级生命周期接口方法:包括了 InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为“后处理器”。
-
工厂后处理器接口方法: 包括了 AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer 等等非常有用的工厂后处理器接口的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。
Spring 事务
spring事务有编程式和声明式,我们一般使用声明式,在某个方法上增加@Transactional注解
,这个方法中的sql会统一成功或失败。
原理是:
当一个方法加上@Transactional注解,spring会基于这个类生成一个代理对象并将这个代理对象作为bean,当使用这个bean中的方法时,如果存在@Transactional注解,就会将事务自动提交设为false,然后执行方法,执行过程没有异常则提交,有异常则回滚
事务的声明:
- 编程式
spring事务失效场景
(1)事务方法所在的类没有加载到容器中
(2)事务方法不是public类型
(3)同一类中,一个没有添加事务的方法调用另外以一个添加事务的方法,事务不生效
(4)spring事务默认只回滚运行时异常,可以用rollbackfor属性设置
(5)业务自己捕获了异常,事务会认为程序正常秩序
spring事务的隔离级别
default:默认级别,使用数据库自定义的隔离级别
其它四种隔离级别与mysql一样 , 参考JAVA面试高级技术栈-04-MySql优化-CSDN博客
spring事务的传播行为
(1)支持当前事务,如果不存在,则新启一个事务
(2)支持当前事务,如果不存在,则抛出异常
(3)支持当前事务,如果不存在,则以非事务方式执行
(4)不支持当前事务,创建一个新事务
(5)不支持当前事务,如果已存在事务就抛异常
(6)不支持当前事务,始终以非事务方式执行
spring设计模式
BeanFactory用了工厂模式,AOP用了动态代理模式,RestTemplate用来模板方法模式,SpringMVC中handlerAdaper用来适配器模式,Spring里的监听器用了观察者模
具体可以参考这篇文章: 七种常用的设计模式-CSDN博客
SpringMVC工作原理
MVC模式: Model-View-Controller
(数据-视图-控制器)软件架构模式, 目的是将用户的操作界面和业务逻辑进行分离
SpringMVC工作过程围绕着前端控制器DispatchServerlet,几个重要组件有HandleMapping(处理器映射器)、HandleAdapter(处理器适配器)、ViewReslover(试图解析器)
工作流程:
(1)DispatchServerlet接收用户请求将请求发送给HandleMapping
(2)HandleMapping根据请求url找到具体的handle和拦截器,返回给DispatchServerlet
(3)DispatchServerlet调用HandleAdapter,HandleAdapter执行具体的controller,并将controller返回的ModelAndView返回给DispatchServler
(4)DispatchServerlet将ModelAndView传给ViewReslover,ViewReslover解析后返回具体view
(5)DispatchServerlet根据view进行视图渲染,返回给用
springcloud主要解决什么问题?
解决服务之间的通信、容灾、负载平衡、冗余问题,能方便服务集中管理,常用组件有注册中心、配置中心、远程调用。服务熔断、网关
CAP理论
C:一致性,这里指的强一致性,也就是数据更新完,访问任何节点看到的数据完全一致
A:可用性,就是任何没有发生故障的服务必须在规定时间内返回合正确结果
P:容灾性,当网络不稳定时节点之间无法通信,造成分区,这时要保证系统可以继续正常服务。提高容灾性的办法就是把数据分配到每一个节点当中,所以P是分布式系统必须实现的,然后需要在C和A中取舍
为什么不能同时保证一致性和可用性呢?
当网络发生故障时,如果要保障数据一致性,那么节点相互间就只能阻塞等待数据真正同步时再返回,就违背可用性了。如果要保证可用性,节点要在有限时间内将结果返回,无法等待其它节点的更新消息,此时返回的数据可能就不是最新数据,就违背了一致性了
熔断限流的理解?
SprngCloud中用Hystrix组件来进行
-
限流:限制并发的请求访问量,超过阈值则拒绝;
-
降级:服务分优先级,牺牲非核心服务(不可用),保证核心服务稳定;从整体负荷考虑;
-
熔断:依赖的下游服务故障触发熔断,避免引发本系统崩溃;系统自动执行和恢复降级
熔断
概念:应对微服务雪崩效应的一种链路保护机制。
在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制
是对于消费者来讲,当对提供者请求时间过久时为了不影响性能就对链接进行熔断,
限流
是对于提供者来讲,为了防止某个消费者流量太大,导致其它更重要的消费者请求无法及时处理。限流可用通过拒绝服务、服务降级、消息队列延时处理、限流算法来实现
降级
服务降级是指在面对高并发请求的时候,根据实际业务使用情况以及流量,对一些服务和页面有策略的不处理或者用一种简单的方式进行处理,从而释放服务器资源的资源以保证核心业务的正常高效运行
服务降级和服务熔断区别
- 触发原因不一样,服务熔断由链路上某个服务引起的,服务降级是从整体的负载考虑
- 管理目标层次不一样,服务熔断是一个框架层次的处理,服务降级是业务层次的处理
- 实现方式不一样,服务熔断一般是自我熔断恢复,服务降级相当于人工控制
- 触发原因不同 服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;
一句话:
服务熔断是应对系统服务雪崩的一种保险措施,给出的一种特殊降级措施。而服务降级则是更加宽泛的概念,主要是对系统整体资源的合理分配以应对压力。
常用限流算法
计数器算法:使用redis的setnx和过期机制实现
漏桶算法:一般使用消息队列来实现,系统以恒定速度处理队列中的请求,当队列满的时候开始拒绝请求。
令牌桶算法:计数器算法和漏桶算法都无法解决突然的大并发,令牌桶算法是预先往桶中放入一定数量token,然后用恒定速度放入token直到桶满为止,所有请求都必须拿到token才能访问系统
Hystrix
在分布式环境中,许多服务依赖的一些服务中不可避免地会存在一些失败的情况。 Hystrix是这样一个框架,通过添加容忍延迟和容错逻辑,可帮助我们控制这些分布式服务之间的交互。 Hystrix通过隔离各个服务之间的访问点,减少服务之间的级联故障以及为我们提供回滚策略来实现这一点,所有这些都可以提高分布式系统的整体弹性。
Hystrix现在已经停止维护, 可以使用SpringCloudSentinel来代替
具体参考:sentinel (史上最全)-CSDN博客
目标
- 通过第三方客户端库(通常通过网络)访问依赖关系,从而对延迟和故障进行保护和控制。
- 停止复杂分布式系统中的级联故障。
- 失败迅速并迅速恢复。
- 可能的情况下回退并优雅地降级。
- 启用近实时监控,警报和操作控制。
Spring Security OAuth2
OAuth 2.0是用于授权的行业标准协议。OAuth 2.0为简化客户端开发提供了特定的授权流,包括Web应用、桌面应用、移动端应用等。
OAuth2.0 相关名词解释
- Resource owner(资源拥有者):拥有该资源的最终用户,他有访问资源的账号密码;
- Resource server(资源服务器):拥有受保护资源的服务器,如果请求包含正确的访问令牌,可以访问资源;
- Client(客户端):访问资源的客户端,会使用访问令牌去获取资源服务器的资源,可以是浏览器、移动设备或者服务器;
Authorization server(认证服务器)
:用于认证用户的服务器,如果客户端认证通过,发放访问资源服务器的令牌。
Spring Cloud Sleuth
Spring Cloud Sleuth是一个分布式追踪系统,用于在分布式系统中跟踪和解决请求的性能问题。它提供了一套API和工具,帮助开发人员在微服务架构中跟踪请求的流程,并可以追踪请求在不同微服务之间的传递。
Spring Cloud Sleuth通过在请求的不同环节中添加唯一的标识符,称为Trace ID和Span ID,来追踪请求的路径和性能。Trace ID是整个请求的唯一标识符,Span ID是每个环节中的唯一标识符。通过这些标识符,开发人员可以分析请求的流程和性能瓶颈,并进行优化。
注意事项
使用Spring Cloud Sleuth时,需要注意以下几点:
- 确保所有微服务都添加了Spring Cloud Sleuth的依赖,并配置了相同的Trace ID和Span ID。
- 需要注意微服务之间的调用关系,确保在调用方将Trace ID和Span ID传递给被调用方,并在被调用方正确接收和处理这些信息。
- 需要适当地配置日志输出,以便在日志中查看请求的Trace ID和Span ID,以及相关的性能信息。
Spring Cloud Sleuth
Spring Cloud Sleuth是一个分布式追踪系统,用于在分布式系统中跟踪和解决请求的性能问题。它提供了一套API和工具,帮助开发人员在微服务架构中跟踪请求的流程,并可以追踪请求在不同微服务之间的传递。
Spring Cloud Sleuth通过在请求的不同环节中添加唯一的标识符,称为Trace ID和Span ID,来追踪请求的路径和性能。Trace ID是整个请求的唯一标识符,Span ID是每个环节中的唯一标识符。通过这些标识符,开发人员可以分析请求的流程和性能瓶颈,并进行优化。
注意事项
使用Spring Cloud Sleuth时,需要注意以下几点:
- 确保所有微服务都添加了Spring Cloud Sleuth的依赖,并配置了相同的Trace ID和Span ID。
- 需要注意微服务之间的调用关系,确保在调用方将Trace ID和Span ID传递给被调用方,并在被调用方正确接收和处理这些信息。
- 需要适当地配置日志输出,以便在日志中查看请求的Trace ID和Span ID,以及相关的性能信息。
- 如果需要自定义Trace ID和Span ID的生成方式,可以实现自定义的Trace和Span生成器,并在配置文件中配置使用自定义生成器。