一、springboot自动配置原理
自动装配,简单来说就是自动把第三方组件的Bean装载到Spring IOC器里面,不需要开发人员再去写Bean的配置。在Spring Boot应用里面,只需要在启动类加上@SpringBootApplication注解就可以实现自动装配。
原理是:
- 引入Starter启动依赖组件的时候,这个组件里面必须要包含配置类,在这个配置类里面通过@Bean注解、声明需要装配到IOC容器的Bean对象
- 配置类是放在第三方的jar包里面,然后通过SpringBoot中的约定优于配置思想,把这个配置类的全路径放在spring.factories文件中。这样SpringBoot就可以知道第三方jar包里面的配置类的位置
- SpringBoot拿到所第三方jar包里面声明的配置类以后,再通过Spring提供的ImportSelector接口,实现对这些配置类的动态加载
二、springmvc工作流程
- 1、 用户发送http请求至前端控制器(DispatcherServlet)
- 2、前端控制器会去遍历所有的处理器映射器(HandlerMapping),寻找一个可以处理该HTTP请求的处理器,生成处理器及处理器拦截器一并返回给前端控制器
- 3、前端控制前会调用处理器适配器(HandlerAdapter),将请求传递给处理器,处理器调用业务层对数据逻辑进行处理,控制器完成请求处理后,返回ModelAndView对象传给处理器适配器,最终由处理器适配器返回给前端控制器。
- 4、 前端控制器将ModelAndView传给视图解析器进行渲染视图
- 5、将处理过后的ModelAndView视图交给前端控制器,并由前端控制器响应给客户端。
具体参考:SpringMVC工作流程 -- 详解_布诺i的博客-CSDN博客_springmvc工作流程
三、描述下spring bean 生命周期
Spring的生命周期被分为五个阶段:
- 创建前准备阶段
- 创建实例阶段
- 依赖注入阶段
- 容器缓存阶段
- 销毁实例阶段
创建前准备阶段:
这个阶段主要是开始bean加载之前,从spring上下文中去获取相关的配置并且解析,然后找到bean有关的一些配置内容
创建实例阶段:
这个阶段主要是通过反射去创建bean实例对象,并且扫描和解析bean的声明的一些属性.
依赖注入阶段:
这个阶段会监测被实例化的bean是否会存在其他依赖,如果存在其他依赖,就需要将这些依赖注入到bean里面,比如说通过@Autowired等注解去完成依赖注入的配置,
这个阶段又会触发一些扩展的调用,
- 比如说常见的扩展类BeanPostProcessors,它用来实现bean初始化前后的一个回调;再比如InitializingBean,这个类里面有一个afterPropertiesSet方法,它给属性去赋值.其次还会调用一些后缀Aware接口。
- 如果 Bean 实现了BeanNameAware 接口,则 Spring调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
- 如果 Bean 实现了 BeanClassLoaderAware 接口,则 Spring 调用 setBeanClassLoader() 方法传入classLoader的引用。
- 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
- 还有其他一些诸如 EnvironmentAware ,EmbeddedValueResolverAware等接口
容器缓存阶段
这个阶段主要是将bean保存在IOC容器中缓存起来,到了这个阶段,bean就可以去被开发者使用了,那么这个阶段涉及到的操作有,常见的init-method属性配置的方法会在这个阶段被调用
销毁实例阶段
这个阶段是完成spring应用上下文的时候,将去销毁spring上下文中所有bean,如果bean实现了DisposableBean接口的话,或者配置了destory-method属性的方法都将在这个阶段被调用.
四、Spring中的单列bean是线程安全的吗?
在Spring中Bean默认是单列模式,spring框架没有对单列bean进行多线程的封装处理
- 无状态就是操作不能保存数据,是线程安全的。
- 有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象 ,可以保存数据,是非线程安全的。
解决办法:
- 改变bean的作用域,把singleton变成prototype 这样相同请求bean相当于new bean(),可以保证线程安全
- Spring使用ThreadLocal解决线程安全,ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
参考:Spring在单例模式下的线程安全_嗨,您好的博客-CSDN博客_spring单例模式怎么保证线程安全
五、Spring事务什么时候会失效
spring事务的原理是aop ,进行了切面的增强,那么失败的根本原因是这个aop不起作用的,常见如下:
- 发生自调用:类里面使用this调用本类的方法,此时this对象不是代理类,而是userService对象本身
- 方法不是public @Transactional只能用于public方法上,否则事务不会生效
- 数据库不支持事务
- 没被spring管理
- 异常被吃掉,事务不会回滚
六、Spring的事务传播机制
- REQUIRED:默认值,支持当前事务,如果没有事务会创建一个新的事务
- SUPPORTS:支持当前事务,如果没有事务的话以非事务方式执行
- MANDATORY:支持当前事务,如果没有事务抛出异常
- REQUIRES_NEW:创建一个新的事务并挂起当前事务
- NOT_SUPPORTED:以非事务方式执行,如果当前存在事务则将当前事务挂起
- NEVER:以非事务方式进行,如果存在事务则抛出异常
- NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与REQUIRED类似的操作
参考:spring事务传播机制_钧令的博客-CSDN博客_spring事务传播机制
七、mybatis里的#{}和${}的区别
#{}是预编译处理,${}是字符串替换。
Mybatis在处理#{}时,会将sql中的#替换为?号,调用PreparedStatement的set方法来赋值;
Mybatis在处理${}时,就是把${}替换成变量的值。
使用#{}可以有效的防止SQL注入,提高系统安全性。
八、谈谈对ioc的理解
ioc容器实际上是一个map,里面存放是各种对象,在项目启动的时候会读取配置文件里的bean节点,根据限定类名使用反射创建对象放入map里面,这时候map里面有各种对象,接下来我们在代码中使用里面的对象时候,通过依赖注入。
依赖注入是实现ioc的方法,就是在ioc容器运行期间,动态的将某种依赖关系注入到对象中。
控制反转指控制权不是由当前的对象来管理的,由其它类来管理。
九、谈谈对aop的理解
将程序中的交叉业务逻辑(比如日志、事务),封装成一个切面,然后注入到目标对象中,aop可以对某个对象或某个对象的功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外的做一些事情,在某个方法执行之后额外的做一些事情
十、springboot核心注解是什么,它有哪些注解组成
@SpringBootApplication是核心注解 这个注解是整个SpringBoot项目的入口注解,是SpringBoot项目必不可少的注解,它是复合注解,由一下注解组成:
1、@SpringBootConfiguration:SpringBoot的配置类,将此注解标注在某个类上表示这个是一个SpringBoot的配置类。
2、@EnableAutoConfiguration:开启自动配置功能,SpringBoot会帮我们自动配置;
3、@ComponentScan:开启包的扫描功能
十一、springboot中如何解决跨域问题
跨域都是通过前端JSONP来解决,但只能处理get类型请求,像post、put、delete就不支持,所以一般采用后端跨域通过WebMvcConfiguration接口来重写addCorsMappings方法,解决跨域问题。
十二、springboot多环境切换的⽅法
1111是Spring对不同环境提供不同配置功能的⽀持,可以通过激活不同的环境版本,实现快速切换环境;
例如:
application-test.properties 代表测试环境配置
application-dev.properties 代表开发环境配置
但是Springboot并不会直接启动这些配置⽂件,它默认使⽤application.properties主配置⽂件;
我们需要通过⼀个配置来选择需要激活的环境:
spring.profiles.active=dev
spring.profiles.active可以指定配置环境,如指定为dev就会选择 application-dev.properties ⽂件作为配置环境,同理,要指定
不同的环境配置,只需指定application-xxx.properties的xxx即可。
十三、说下mybatis的一级缓存和二级缓存
一级缓存是指sqlSession,默认都是开启的,在同一个sqlsession中执行相同的sql,第一次去查询数据库,存在缓存中,第二次直接从缓存中获取,当有insert update delete 操作时,会清空缓存,重新获取
二级缓存是指mapper级别的,默认不开启,如果多个sqlsession之前需要共享内存,则需要使用二级缓存,使用二级缓存属性类,需要实现序列化接口(用来保存对象的状态)
十四、说清楚spring如何解决循环依赖
循环依赖,注意,这里说的是依赖,而不是调用,这是两个概念,一定不要混淆。
循环依赖从字面意思来看,就是A依赖B,然后B依赖A,当然,这个依赖过程也可以更长,不一定就非要两个互相依赖,十个八个也是循环依赖,只要形成了一个闭环。
Spring中的循环依赖包括两种,构造器循环依赖和setter循环依赖。
构造器循环依赖
当使用构造器注入方式时,Spring是无法解决循环依赖的,在出问题时会报错,抛出BeanCurrentlyInCreationException异常。
setter循环依赖
主要来说一下setter循环依赖,通过Spring在创建bean时的一级、二级、三级缓存的概念解决的。
注意:这里解决的只是单例模式下的setter循环依赖,非单例模式下的依然没有办法解决,在业务环境中应当尽量避免此类情况。
创建Bean的缓存概念:
- 一级缓存:singletonObjects,可以称为成品池,存放完全实例化属性赋值完成的Bean,直接可以使用。
- 二级缓存:earlySingletonObjects,可以称为半成品池,存放早期Bean的引用,尚未属性装配的Bean
- 三级缓存:singletonFactories,可以称为工厂池,存放实例化完成的Bean工厂。
- X1在创建时,首先根据构造函数创建bean,暴露一个Factory给三级缓存(工厂池),并且将其放入二级缓存(半成品池);然后进行属性的装配,发现有依赖关系,查询三级缓存是否存在,如没有,前往创建。
- 创建X2时,同X1,前往创建X3。
- 创建X3时,这时三级缓存中已经存在X1,即可直接注入,然后将X3的bean对象放入一级缓存(成品池)。
- 随后X2、X1依次可以创建完成,并且放入一级缓存中。
- 如此就完成setter循环依赖问题的解决,核心就是这个三级缓存。
注意:这里的bean对象创建完成,放入一级缓存中时,会将对应的二级、三级缓存清掉。
参考:高频面试题-说清楚Spring如何解决循环依赖? - SegmentFault 思否
十五、springboot、springmvc和spring有什么区别
- spring 是一个ioc容器、用来管理bean、使用依赖注入实现控制反转、可以很方便整合各种框架、提供aop弥补oop的代码重复问题,方便将不同类相同方法抽取成切面,自动注入给方法执行,比如日志、异常
- springmvc 是spring对web框架的一个解决方案,提供了前端控制器,用来接收请求,然后定义了一套路由策略(url到handle的映射)及适配执行handle,将handle结果使用视图解析技术生成试图展示给前端
- springboot 是spring提供的一个快熟开发工具包,让程序员更加方便,更加快速的开发spring+springmvc应用,简化了配置(约定了默认配置)。整合了一系解决方案(starter机制),开箱即用
- Spring cloud是⼀个基于Spring Boot实现的服务治理框架,⽤于微服务架构中管理和协调各个服务。它提供了一系列组件方便用户快速搭建微服务,如服务发现注册nacos、 配置中⼼nacos、服务网关gateway,客户端负载配置Ribbon、服务间调用openfeign、短路器Hystrix、分布式事务服务Seata等