Spring灵魂拷问系列之Spring基础拷问
0.面试连环炮
Spring IOC和AOP的理解 -> 动态代理 -> JDK/Cglib动态代理 -> Spring Bean线程安全 -> Spring事务实现原理和事务传播机制
1.说说对Spring IOC的理解
以前的一套系统:
web服务器:绑定地址端口,负责接收请求
servlet:引入serviceImpl类来处理请求(doPost、doGet)
像这样每个servlet都新建一个serviceImpl服务类实例,导致耦合度很高,当需要换一个服务实现类时,每个地方都需要修改成新的类,导致改动、测试成本巨大
Spring IOC:控制反转,依赖注入,让系统的类与类之间解耦
Tomcat启动Spring容器,扫描指定路径的包路径,所有加了Spring注解的类会被Spring容器初始和实例化Bean并且交给Spring容器管理,当某个地方使用到时,Spring容器会自动注入实例
Spring MVC:核心Servlet、Filter,负责用户请求的转发,转发给对应的Controller,之后调用相应的服务类进行处理
底层:通过反射来动态的构建对象实例
当改成Spring IOC解耦时,只需要修改service的实现类即可。这样的话,在具体请求过来时,Spring会根据反射动态生成实现类来处理请求了
2.说说对Spring AOP的理解
核心技术:动态代理
不关心底层代码的实现逻辑,在此实现逻辑前后做一些公共的处理,例如事务处理、统计方法耗时等
把代码公共/重复的代码抽取出来,比如日志、事务、通知等业务,做一个切面
3.cglib动态代理和jdk动态代理的区别
动态代理,动态的创建一个代理类和他的实例对象出来,在里面引用真正需要调用的类,代理类在此做一些增强(事务、日志等)
两者区别在于 生成动态代理类的方式
Spring AOP使用的 JDK动态代理 ,生成实现同个接口代理类,构造实例出来。当需要代理的类没有接口时,Spring AOP会改用 Cglib动态代理 ,生成该类的子类(动态生成字节码),覆盖目标类的方法,在覆盖方法中进行增强
- JDK动态代理:代理类实现 InvocationHandler 接口, Proxy 生成代理对象,当调用接口方法时,真正会调用的是代理类的 invoke 方法
- Cglib动态代理:代理类实现 MethodInterceptor 接口, Enhancer 生成代理对象,当调用类方法时,拦截器会拦截在方法前后进行增强( intercept方法 )
4.Spring中的Bean是线程安全的吗
Spring Bean的作用域:
- singleton:默认,单例,只有一个实例
- prototype:为每个bean请求都提供一个实例
- request:为每个网络请求request创建一个实例,请求完成后被垃圾回收
- session:为每个session创建一个实例,请求完成后被垃圾回收
- global-session:标准的HTTP Session作用域
基本大部分时候我们都使用默认的singleton单例作用域
Spring Bean是线程不安全的
例如,bean中存在一个变量data(未被volatile修饰),多线程并发(多个请求同时发起同个服务)时,多个线程同时调用实例A的同一方法(该方法里存在data++),基于线程的工作内存和主内存不一定同步的机制,就会发生线程安全问题
如果不存在实例变量,逻辑处理只操作数据库的话,一般不会有线程安全问题
5.Spring的事务实现原理是什么?
日常编码中,需要对代码逻辑增加事务,使用Transactional注解,Spring会通过AOP/动态代理的机制,对方法进行增强,织入事务,方法执行前开启事务,执行成功后提交事务,异常回滚事务
6.能聊聊你对Spring事务的传播机制的理解吗?
事务传播机制级别 - 最常用的是前4个
- REQUIRED:如果父方法没有事务,则新创建一个事务,存在事务则加入事务
- SUPPORTS:如果父方法存在事务则加入事务,当前没有事务,则不开启事务执行
- REQUIRES_NEW:无论当前父方法有没有事务,都新建一个事务,父方法存在事务时,两者事务是隔离开的
- NESTED:父方法存在事务,则嵌套事务执行(外层代码出错内层代码一起回滚,内层代码出错只有内层回滚),如果没有事务,按照REQUIRED属性执行
- MONDATORY:如果当前父方法存在事务则加入事务,当前没有事务,则抛异常
- NOT_SUPPORTED:强制要求非事务运行,如果父方法存在事务则挂起
- NEVER:非事务方式执行,如果存在事务会报错
方法A调用方法B,希望方法A出错只回滚方法A自己,不回滚方法B,该怎么办
选择事务传播机制的REQUIRES_NEW
方法A调用方法B,方法B只能回滚自己,方法A可以带着方法B一起回滚
NESTED嵌套事务
7.谈谈Spring Boot的核心架构
在早期java web开发时,我们使用SSM框架那一套,需要打包部署到线上的tomcat上,请求到来,根据Spring MVC框架流程,来一一调用controller、service、dao层等。
早期开发时,我们也需要根据业务引入比较多的中间件技术,例如redis、elasticsearch、rabbitmq等,引入时配置比较繁琐复杂,例如引入jar包,编写xml配置文件,定义bean等
SpringBoot
- 内嵌了tomcat,上线时可以直接部署启动;
- 通过 自动装配 的特性,在整合第三方组件时,只要引入相关的starter依赖,会自动做一些配置、定义生成对应bean等操作,只要配置必要的相关地址等配置,一定程度上降低了原先整合的成本,详细可看SpringBoot之自动配置原理
8.Spring Bean的生命周期
Spring Bean大致的生命周期
实例化 -> 初始化 -> Spring容器管理下长期存活 -> 销毁
1.实例化Bean
如果需要使用bean,需要先实例化
- 对于 BeanFactory 容器,通过 createBean 进行实例化
- 对于 ApplicationContext 容器,通过 BeanDefinition 进行实例化
2.依赖注入
当需要使用的bean依赖其他的bean,也需要把依赖的bean创建并且注入进来,注入的方式有两种
- 构造器注入
- setter注入
3.处理Aware接口
如果bean实现了 ApplicationContextAware 接口,Spring容器就会通过 setApplicationContext(ApplicationContext)
方法,把Spring容器传递给这个bean
4.BeanPostProcessor
bean实例化完成之后,如果想对Bean进行一些自定义的处理,那么可以让Bean实现 BeanPostProcessor 接口,将会调用postProcessBeforeInitialization(Object obj,String s)
方法
初始化之前
5.初始化initializingBean与init-method
对bean进行初始化
6.BeanPostProcessor
如果Bean实现 BeanPostProcessor 接口,将会调用postProcessAfterInitialization(Object obj,String s)
方法
由于发生在初始化完成之后,所以经常用于内存或者缓存的操作
7.DisposableBean
当Bean不再被需要时,如果Bean实现了DisposableBean接口,会调用他的destory()
方法
8.destroy-method
如果bean配置的destroy-method属性,会自动调用配置的销毁方法
9.能说说Spring中使用了哪些设计模式吗
1.工厂模式
Spring IOC自己就是一个大工厂,把所有bean实例都放在Spring容器中,需要使用就直接从Spring容器中拿
2.单例模式
Spring Bean默认使用单例,保证类在运行期间只有一个实例对象,最常见的就是懒汉式
public class SingletonTest {
private static volatile SingletonTest singletonTest;
public static SingletonTest getInstance(){
if (singletonTest == null){
synchronized (SingletonTest.class){
if (singletonTest == null){
singletonTest = new SingletonTest();
}
}
}
return singletonTest;
}
}
使用双重检查 + synchronized关键字保证只生成一个实例,volatile保证不发生指令重排
3.代理模式
主要涉及到的就是Spirng AOP,例如XXXAware那些某个增强类,创建动态代理对象实例,在调用被代理对象的方法时,会先执行代理类的增强代码,再执行方法代码,例如Spring Bean生命周期中,实现 ApplicationContextAware 接口后,Spring容器会把 ApplicationContext 注入到bean中