【八股文】Spring系列

1.Spring的优点

2.Spring用了哪些设计模式

3.什么是ioc

4.ioc容器初始化过程

5.Bean的生命周期

6.Bean的作用域

7.BeanFactory和FactoryBean的区别

8.什么是aop

9.aop有哪些实现方式

10.aop相关术语

11.aop原理(源码分析)

12.Spring通知有哪些类型

13.Spring事务

14.什么是SpringMVC

15.SpringMVC的主要组件

16.请描述一下SpringMVC的工作流程,描述一下DispatcherServlet的工作流程

17.拦截器Interceptor和过滤器Filter的区别

18.SpringBoot自动装配

19.SpringBoot启动流程

1.Spring的优点

  • 轻量,基本版本大约2MB。
  • 通过控制反转和依赖注入实现松耦合。
  • 支持面向切面的编程,并且把应用业务逻辑和系统服务分开。
  • 通过切面和模板减少样板式代码。
  • 方便集成各种优秀框架。内部提供了对各种优秀框架的直接支持(如:Hibernate、MyBatis等)。
  • 方便程序的测试。Spring支持Junit4,添加注解便可以测试Spring程序。

Spring的两大核心

ioc(控制反转):他就是不会直接创建对象,而是把对象声明出来,把创建对象的权力交给容器,这么做的目的主要是为了解耦

ioc原理:工厂模式 + 反射

ioc初始化的时候,先去加载配置文件,然后解析配置文件,Spring就知道扫哪些路径(类)了,Spring就把这些类加载成BeanDefinition(定义Bean的配置元信息接口),生成Spring Bean。然后通过反射进行装配(就知道哪些字段需要注入哪些Bean了)

aop(面向切面):将公共逻辑封装成切面,跟业务代码进行分离,可以减少系统的重复代码和降低模块之间的耦合。比如日志处理

aop原理:动态代理

2.Spring用了哪些设计模式

  • 简单工厂模式:BeanFactory,跟据传入的唯一一个标识来获取Bean对象
  • 工厂方法模式:FactoryBean

3.什么是ioc

控制反转,把创建对象的权力交给容器,松耦

4.ioc容器初始化过程

Spring IOC容器初始化的关键环节就在AbstractApplicationContext#refresh()方法,主要方法都是在refresh()方法中完成

@Override
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
 		// 第⼀步:刷新前的预处理
 		prepareRefresh();
 		/*
 		第⼆步:
 		获取BeanFactory;默认实现是DefaultListableBeanFactory
 		加载BeanDefition 并注册到 BeanDefitionRegistry
 		*/
 		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
 		// 第三步:BeanFactory的预准备⼯作(BeanFactory进⾏⼀些设置,⽐如context的类加载器等)
 		prepareBeanFactory(beanFactory);
 		try {
 			// 第四步:BeanFactory准备⼯作完成后进⾏的后置处理⼯作
 			postProcessBeanFactory(beanFactory);
 			// 第五步:实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean
 			invokeBeanFactoryPostProcessors(beanFactory);
 			// 第六步:注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执⾏
 			registerBeanPostProcessors(beanFactory);
 			// 第七步:初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
 			initMessageSource();
 			// 第⼋步:初始化事件派发器
 			initApplicationEventMulticaster();
 			// 第九步:⼦类重写这个⽅法,在容器刷新的时候可以⾃定义逻辑
 			onRefresh();
 			// 第⼗步:注册应⽤的监听器。就是注册实现了ApplicationListener接⼝的监听器bean
 			registerListeners();
 			/*
			 第⼗⼀步:
 			初始化所有剩下的⾮懒加载的单例bean
 			初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性)
 			填充属性
 			初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、init-method⽅法)
 			调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处
 			*/
 			finishBeanFactoryInitialization(beanFactory);
 			/*
 			第⼗⼆步:
 			完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事件 (ContextRefreshedEvent)
 			*/
 			finishRefresh();
 }
}

5.Bean的生命周期

1)Spring对bean进行实例化

2)Spring将值和bean的引用注入到bean对应的属性中

3)如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBean-Name()方法;

4)如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;

5)如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来

6)如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessBeforeInitialization()方法

7)如果bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet()方法,进行属性设置。类似地,如果bean使用initmethod声明了初始化方法,该方法也会被调用

8)如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessAfterInitialization()方法

9)此时bean已经准备就绪,可以被应用程序使用了,他们将一直驻留在应用上下文中,直到该应用上下文被销毁

10)如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用

6.Bean的作用域

1)singleton(单例):Spring中的bean默认都是单例的。

2)prototype(原型):每次请求都会创建一个新的bean实例。

3)request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。

4)session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。

5)global-session:全局session作用域。

Bean是否为线程安全的?

原型:线程安全的

单例:分为无状态bean和有状态bean

有状态的bean在多线程的环境下不安全(业务逻辑对单例的成员属性有修改),一般用prototype模式或者使用threadlocal解决线程安全问题。

7.循环依赖怎么解决

通过三层缓存

第一层:初始化后的Bean

第二层:创建好了,但没有初始化的Bean

第三层:创建Bean的工厂对象

A和B

A创建过程中,发现需要B,A就先将自己放到了三级缓存,然后去实例化B了

B实例化的时候发现没有A,先查一级缓存,没有,再去查二级缓存,没有,查三级缓存,找到了A然后把三级缓存里面的A放到二级缓存里面,并删除三级缓存里面的A

B顺利初始化后,将自己放到一级缓存,此时B已经创建完成,然后继续创建A,A现在可以从一级缓存里拿到B了,A也就可以创建完成了

7.BeanFactory和FactoryBean的区别

BeanFactory:管理Bean的容器,Spring生成Bean都是由这个接口的实现来管理的

FactoryBean:通常是用来创建比较复杂的bean,一般的bean 直接用xml配置即可,但如果一个bean的创建过程中涉及到很多其他的bean 和复杂的逻辑,直接用xml配置比较麻烦,这时可以考虑用FactoryBean,可以隐藏实例化复杂Bean的细节。

8.什么是aop

面向切面编程,作为面向对象的一种补充,讲公共逻辑(事务管理,日志,缓存)封装成切面,跟业务代码进行分离,可以减少系统的重复代码和降低模块之间的耦合度。

9.aop有哪些实现方式

静态代理和动态代理

  • 静态代理:代理类在编译阶段生成,在编译阶段将通知织入Java字节码中,也称编译时增强。AspectJ使用的是静态代理。
    • 动态代理:代理类在程序运行时创建,AOP框架不会去修改字节码,而是在内存中临时生成一个代理对象,在运行期间对业务方法进行增强,不会生成新类。

10.aop相关术语

  • 切面(Aspect):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容
  • 连接点(join point):指方法,在Spring AOP中,一个连接点总是代表一个方法的执行。连接点是在应用执行过程中能够插入切面的一个点。
  • 通知(Advice):切面的工作被称为通知
  • 织入(weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。

在目标对象的生命周期里有以下时间点可以进行织入:

1)编译器:切面在目标类编译时被织入。AspectJ的织入编译器是以这种方式织入切面的。

2)类加载期:切面在目标类加载到JVM时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。

3)运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面。

11.aop原理(源码分析)

如果我们为spring的某个bean配置了切面,那么spring在创建这个bean的时候,实际上创建的是这个bean的代理对象,我们后续对bean中方法的调用,实际上调用的是代理类重写的代理方法。

12.Spring通知有哪些类型

通知实际上是程序运行时要通过Spring AOP框架来触发的代码段

Spring切面可以应用5种类型的通知:

  • 前置通知(Before):在目标方法被调用之前调用通知功能;
  • 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
  • 返回通知(After-returning ):在目标方法成功执行之后调用通知;
  • 异常通知(After-throwing):在目标方法抛出异常后调用通知;
  • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的逻辑。

13.Spring事务

spring支持的事务管理类型有两种:

编程式事务管理:通过编程的方式管理事务,灵活性好,但是难维护

声明式事务管理(常用):将业务代码和事务管理隔离,只需用注解和XML配置来管理事务(主要是通过注解@Transactional)

其实事务操作就是AOP的一个核心体验,当一个方法添加@Transactional注解之后,spring会基于这个类生成一个代理对象,会将这个代理对象作为bean,当使用这个代理对象的方法的时候,如果有事务处理,那么会先把事务的自动提交给业务,然后就执行具体逻辑,如果没有异常就直接提交,出现异常就进行回滚。

spring事务传播行为

解决 多个事务方法相互调用时,事务如何在在这方法中传播

14.什么是SpringMVC

SpringMVC是一个基于Java,实现了MVC设计模式的请求驱动类型的轻量级Web框架。通过把模型(Model)-视图(View)-控制器(Controller)分离,将web层进行解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合

它解决了WEB开发中常见的问题(参数接收,文件上传,表单校验,国际化),支持Restful风格的url请求

15.SpringMVC的主要组件

1).HandlerMapping(处理器映射器)

是用来查找Handler的,标注了@RequestMapping的每个方法都可以看成一个Handler,Handler负责具体实际的请求处理,在请求达到后,HandlerMapping的作用便是找到请求相应的处理器Handler和Interceptor

2).HandlerAdapter(处理器适配器)

要让固定的Servlet处理方法调用Handler来进行处理

当HandlerMapping获取了定位请求处理器Handler,DispatcherServlet会将得到的Handler告知HandlerAdapter,HandlerAdapter再根据请求去定位请求的具体处理方法是哪一个

3).HandlerExceptionResolver

用于处理Handler产生的异常清空。它的作用是跟据异常设置ModelAndView,之后交给渲染方法进行渲染,渲染方法会将ModelAndView渲染成页面

4).ViewResolver(视图解析器)

找到渲染的模板(第一件大事)和所用的技术(第二件大事,其实就是找到视图的类型,如jsp)

5).RequestToViewNameTranslator

RequestToViewNameTranslator 组件的作⽤是从请求中获取 ViewName.因为 ViewResolver 根据ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置 ViewName,便要通过这个组件从请求中查找 ViewName。

6).LocaleResolver

ViewResolver 组件的 resolveViewName ⽅法需要两个参数,⼀个是视图名,⼀个是 Locale。LocaleResolver ⽤于从请求中解析出 Locale,⽐如中国 Locale 是 zh-CN,⽤来表示⼀个区域。这个组件也是 i18n 的基础。

7).ThemeResolver

是⽤来解析主题的。主题是样式、图⽚及它们所形成的显示效果的集合。

8).MultipartResolver

MultipartResolver ⽤于上传请求,通过将普通的请求包装成 MultipartHttpServletRequest 来实现。MultipartHttpServletRequest 可以通过 getFile() ⽅法 直接获得⽂件。如果上传多个⽂件,还可以调⽤ getFileMap()⽅法得到Map<FileName,File>这样的结构,MultipartResolver 的作⽤就是封装普通的请求,使其拥有⽂件上传的功能。

9).FlashMapManager

FlashMap ⽤于重定向时的参数传递,⽐如在处理⽤户订单时候,为了避免重复提交,可以处理完post请求之后重定向到⼀个get请求,这个get请求可以⽤来显示订单详情之类的信息。

16.请描述一下SpringMVC的工作流程,描述一下DispatcherServlet的工作流程

(1)用户发送请求至前端控制器DispatcherServlet;

(2) DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handler;

(3)处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet;

(4)DispatcherServlet 调用 HandlerAdapter处理器适配器;

(5)HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制器);-------这里才去了controller的handler

(6)Handler执行完成返回ModelAndView;-------handler执行完毕,要把数据返回给前端了

(7)HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;

(8)DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;

VIewResolver的作用:找到渲染的模板(第一件大事)和所用的技术(第二件大事,其实就是找到视图的类型,如jsp)

(9)ViewResolver解析后返回具体View;

(10)DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)

(11)DispatcherServlet响应用户。

17.拦截器Interceptor和过滤器Filter的区别

过滤器是servlet中的接口,主要可以用于在请求进入servlet之前拦截请求HttpServletRequest并跟据需要进行一些检查等逻辑操作,也可以在HttpServletResponse返回客户端之前进行一些逻辑操作。作用是用于对传入的request和响应的response进行进行一些处理,比如对请求参数进行校验,或者设置,检验头部信息,再或者进行一些非法行为校验

拦截器是Servlet中的接口,所以它可以获取到spring中的一些bean和其他资源,在面向切面编程中应用比较广,拦截其实就是一种AOP思想

18.SpringBoot自动装配

1)@SpringBootApplication

2)@EnableAutoConfiguration //启动自动配置功能,其实就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到ioc容器

3)上面这个是一个组合注解,包含两个注解如下:

@AutoConfigurationPackage   //自动配置包

里面包含了@Import({Registrar.Class}),它就是将Registrar这个组件导入到容器中,可查看Registrar类中的registerBeanDefinitions就是导入组件类的具体实现

@Import({AutoConfigurationImportSelector.class})    //自动配置类扫描导入

AutoConfigurationImportSelector可以帮助springboot应用将所有符合条件的@Configuration配置到加载到当前springboot创建并使用的ioc容器

如果继续研究这个方法,追踪过去,可以看到AutoConfigurationImportSelector类是通过selectImports方法执行,使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的/META-INF/spring.factories进行加载,

19.SpringBoot启动流程

通过run方法,做两件事情

1)SpringApplication实例的初始化创建

2)调用run()启动项目

详细可看源码:

SpringApplication初始化:

我们逐步分析:

第一步:用于判断当前webApplicationType应用的类型。deduceFromClasspath()方法用于查看Classpath类路径下是否存在某个特征类,从而判断当前webApplicationType类型是servlet应用(spring5之前的传统MVC应用)还是reactive应用(spring5开始出现的WebFlux交互式应用)

第二步:在初始化器设置过程中,会使用Spring类加载器SpringFactoriesLoader从META-INF/spring.factories类路径下的spring.factories文件中获取所有的可用初始化器ApplicationContextInitializer

第三步:类似上一步,从spring.factories文件中获取所有可用的监听器类ApplicationListener

第四步:设置项目启动类

run()启动运行:

public ConfigurableApplicationContext run(String... args) { 
    StopWatch stopWatch = new StopWatch(); stopWatch.start(); 
    ConfigurableApplicationContext context = null; 
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList(); 		           			this.configureHeadlessProperty(); 
    // 第一步:获取并启动监听器 
    SpringApplicationRunListeners listeners = this.getRunListeners(args); 
    listeners.starting(); 
    Collection exceptionReporters; 
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); 
        // 第二步:根据SpringApplicationRunListeners以及参数来准备环境 
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments); this.configureIgnoreBeanInfo(environment); 
        // 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体 
        Banner printedBanner = this.printBanner(environment); 
        // 第三步:创建Spring容器 
        context = this.createApplicationContext(); 
        exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, new Object[]{context}); 
        // 第四步:Spring容器前置处理 
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner); 
        // 第五步:刷新容器 
        this.refreshContext(context); 
        // 第六步:Spring容器后置处理 
        this.afterRefresh(context, applicationArguments); 
        stopWatch.stop(); 
        if(this.logStartupInfo) { 
            (new StartupInfoLogger(this.mainApplicationClass)) .logStarted(this.getApplicationLog(), stopWatch); } 
        // 第七步:发出结束执行的事件 
        listeners.started(context); 
        // 返回容器 
        this.callRunners(context, applicationArguments); 
    } catch (Throwable var10) { 
        this.handleRunFailure(context, var10, exceptionReporters, listeners); 
        throw new IllegalStateException(var10); 
    }
    try {
        listeners.running(context); 
        return context;
    } catch (Throwable var9) { 
        this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null); 
        throw new IllegalStateException(var9); 
    } 
}

20.Starer

starer相当于模块,它能将模块所需的依赖整合起来并对模块内的bean跟据环境进行自动配置。使用者只需要依赖相应功能的starer。

原理:

主要还是基于它提供的起步依赖和自动配置

起步依赖:其实就是将具备某种功能的坐标打包到一起,可以简化依赖导入的过程。

自动配置:就是无须手动配置xml,自动配置并管理bean,可以简化开发过程。

八股文合集 文章被收录于专栏

本专栏是我总结的八股大全

全部评论
楼主,想问一下这是你面试中常遇到的问题,还是你觉得重要的的面经呢?
点赞 回复 分享
发布于 2022-11-27 20:47 江西
6
点赞 回复 分享
发布于 2022-11-07 08:59 山东

相关推荐

不愿透露姓名的神秘牛友
07-08 13:15
点赞 评论 收藏
分享
07-07 12:47
门头沟学院 Java
码农索隆:竟然还真有卡体检报告的
点赞 评论 收藏
分享
不愿透露姓名的神秘牛友
昨天 11:24
大家还是用ai改吧,我心疼得要死,就当花钱买教训吧,人家直接拿完钱就跑路了
程序员小白条:简历修改700....神奇,又不是帮你面试,咋的,简历修改从双非变92了还是没实习变成有大厂实习了
点赞 评论 收藏
分享
评论
13
97
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务