Spring灵魂拷问系列之Spring基础拷问


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动态代理 ,生成该类的子类(动态生成字节码),覆盖目标类的方法,在覆盖方法中进行增强

  1. JDK动态代理:代理类实现 InvocationHandler 接口, Proxy 生成代理对象,当调用接口方法时,真正会调用的是代理类的 invoke 方法
  2. 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

  1. 内嵌了tomcat,上线时可以直接部署启动;
  2. 通过 自动装配 的特性,在整合第三方组件时,只要引入相关的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中


评论
  目录