那你聊聊Spring容器的加载过程呗(ioc的启动流程)
上面的过程是整个web容器的启动过程,它里面包含了我们spring容器的启动流程,我现在就给大家详细的讲解我们ioc启动的加载过程
AbstractApplicationContext.java 里的 refresh() 方法,这个方法就是构建整个 IoC 容器过程的完整代码,只要把这个方法里的每一行代码都了解了,基本上了解了大部分 ioc 的原理和功能。
- prepareRefresh() 方法:为刷新准备新的上下文环境,设置其启动日期和活动标志以及执行一些属性的初始化。主要是一些准备工作,不是很重要的方法,可以先不关注
- obtainFreshBeanFactory() 方法:该方法会解析所有 Spring 配置文件(通常我们会放在 resources 目录下),将所有 Spring 配置文件中的 bean 定义封装成 BeanDefinition,加载到 BeanFactory 中。常见的,如果解析到<context:component-scan base-package="com.joonwhee.open" /> 注解时,会扫描 base-package 指定的目录,将该目录下使用指定注解(@Controller、@Service、@Component、@Repository)的 bean 定义也同样封装成 BeanDefinition,加载到 BeanFactory 中。 上面提到的“加载到 BeanFactory 中”的内容主要指的是以下3个缓存:
- beanDefinitionNames缓存:所有被加载到 BeanFactory 中的 bean 的 beanName 集合。
- beanDefinitionMap缓存:所有被加载到 BeanFactory 中的 bean 的 beanName 和 BeanDefinition 映射。
- aliasMap缓存:所有被加载到 BeanFactory 中的 bean 的 beanName 和别名映射。
- prepareBeanFactory(beanFactory) 方法:配置 beanFactory 的标准上下文特征,例如上下文的 ClassLoader、后置处理器等。这个方法会注册3个默认环境 bean:environment、systemProperties 和 systemEnvironment,注册 2 个 bean 后置处理器:ApplicationContextAwareProcessor 和 ApplicationListenerDetector。
- postProcessBeanFactory(beanFactory) 方法:允许子类对 BeanFactory 进行后续处理,默认实现为空,留给子类实现。
- invokeBeanFactoryPostProcessors(beanFactory) 方法:实例化和调用所有 BeanFactoryPostProcessor,包括其子类 BeanDefinitionRegistryPostProcessor。
- registerBeanPostProcessors(beanFactory) 方法:注册所有的 BeanPostProcessor,将所有实现了 BeanPostProcessor 接口的类加载到 BeanFactory 中。
- initMessageSource() 方法:初始化消息资源 MessageSource
- initApplicationEventMulticaster() 方法:初始化应用的事件广播器 ApplicationEventMulticaster。
- onRefresh() 方法:该方法为模板方法,提供给子类扩展实现,可以重写以添加特定于上下文的刷新工作,默认实现为空。(在springboot中这个方法可是加载tomcat容器的)
- registerListeners() 方法:注册监听器。
- finishBeanFactoryInitialization(beanFactory) 方法:该方法会实例化所有剩余的非懒加载单例 bean。除了一些内部的 bean、实现了 BeanFactoryPostProcessor 接口的 bean、实现了 BeanPostProcessor 接口的 bean,其他的非懒加载单例 bean 都会在这个方法中被实例化,并且 BeanPostProcessor 的触发也是在这个方法中。(这个方法其实是核心方法了,包含我们的bea从beandifinition变成我们的容器中的bean最核心的方法了)
- finishRefresh() 方法:完成此上下文的刷新,主要是推送上下文刷新完毕事件(ContextRefreshedEvent )到监听器。
比较重要的是下面几个点
- obtainFreshBeanFactory 创建一个新的 BeanFactory、读取和解析 bean 定义。
- invokeBeanFactoryPostProcessors 提供给开发者对 BeanFactory 进行扩展。
- registerBeanPostProcessors 提供给开发者对 bean 进行扩展。
- finishBeanFactoryInitialization 实例化剩余的所有非懒加载单例 bean。
SpringMVC 工作原理了解吗? 基于页面Controller的类型
- 客户端(浏览器)发送请求,直接请求到 DispatcherServlet。
- DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。
- 解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。
- HandlerAdapter 会根据 Handler 来调用真正的处理器来处理请求,并处理相应的业务逻辑。
- 处理器处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据对象,View 是个逻辑上的 View。
- ViewResolver 会根据逻辑 View 查找实际的 View。
- DispaterServlet 把返回的 Model 传给 View(视图渲染)。
- 把 View 返回给请求者(浏览器)
聊聊Spring 框架中用到了哪些设计模式?
- 工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
- 代理设计模式 : Spring AOP 功能的实现。
- 单例设计模式 : Spring 中的 Bean 默认都是单例的。
- 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
- 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
- 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。
spring事务熟悉不,一般你用哪种实现方式
- 编程式事务,在代码中硬编码。(不推荐使用)
- 声明式事务,在配置文件中配置(推荐使用)
一般在我们企业级开发的过程中,一般都是用的声明式事务,声明式事务也分为2种一种是基于xml的,一种基于注解的,一般用注解的多点
说说 Spring 事务中哪几种事务传播行为?
支持当前事务的情况:
- TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
不支持当前事务的情况:
- TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
说说BeanFactory 和 FactoryBean的区别?
- BeanFactory是个Factory,也就是IOC容器或对象工厂,在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的,提供了实例化对象和拿对象的功能。
- FactoryBean是个Bean,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。
聊聊Spring的循环依赖吧
- spring的循环依赖主要是指两个类相互之间通过@Autowired自动依赖注入对方,即类A包含一个类B的对象引用并需要自动注入,类B包含一个类A的对象引用也需要自动注入。
- 对于循环依赖问题,spring根据注入方式的不同,采取不同的处理策略,对于双方都是使用属性值注入或者setter方法注入,则spring可以自动解决循环依赖注入问题,应用程序可以成功启动;对于双方都是使用构造函数注入对方或者主bean对象(Spring在启动过程中,先加载的bean对象)使用构造函数注入,则spring无法解决循环依赖注入,程序报错无法启动。
首先spring在单例的情况下是默认支持循环引用的;在不做任何配置的情况下,两个bean相互依赖是能初始化成功的;spring源码中在创建bean的时候先创建这个bean的对象,创建对象完成之后通过判断容器对象的allowCircularReferences属性决定是否允许缓存这个临时对象,如果能被缓存成功则通过缓存提前暴露这个临时对象来完成循环依赖;而这个属性默认为true,所以说spring默认支持循环依赖的,但是这个属性spring提供了api让程序员来修改,所以spring也提供了关闭循环引用的功能;
那你说说Spring是如何解决循环引用的
首先,Spring内部维护了三个Map,也就是我们通常说的三级缓存。
笔者翻阅Spring文档倒是没有找到三级缓存的概念,可能也是本土为了方便理解的词汇。
在Spring的DefaultSingletonBeanRegistry类中,你会赫然发现类上方挂着这三个Map:
- singletonObjects 它是我们最熟悉的朋友,俗称“单例池”“容器”,缓存创建完成单例Bean的地方。
- singletonFactories 映射创建Bean的原始工厂
- earlySingletonObjects 映射Bean的早期引用,也就是说在这个Map里的Bean不是完整的,甚至还不能称之为“Bean”,只是一个Instance.
后两个Map其实是“垫脚石”级别的,只是创建Bean的时候,用来借助了一下,创建完成就清掉了。
所以笔者前文对“三级缓存”这个词有些迷惑,可能是因为注释都是以Cache of开头吧。
为什么成为后两个Map为垫脚石,假设最终放在singletonObjects的Bean是你想要的一杯“凉白开”。
那么Spring准备了两个杯子,即singletonFactories和earlySingletonObjects来回“倒腾”几番,把热水晾成“凉白开”放到singletonObjects中。
循环引用的不同的场景,有哪些方法可以解决循环引用
为啥有些注入方式不能解决循环依赖问题呢?源码中看出,他们并没有用到那个earlySingletonObjects这个缓存,所以就不能解决循环依赖
解决Spring无法解决的循环依赖的一些方式
项目中如果出现循环依赖问题,说明是spring默认无法解决的循环依赖,要看项目的打印日志,属于哪种循环依赖。目前包含下面几种情况:
生成代理对象产生的循环依赖 这类循环依赖问题解决方法很多,主要有:
- 使用@Lazy注解,延迟加载
- 使用@DependsOn注解,指定加载先后关系
- 修改文件名称,改变循环依赖类的加载顺序
说说什么是Mybatis?
- Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,加载驱动、创建连接、创建statement等繁杂的过程,开发者开发时只需要关注如何编写SQL语句,可以严格控制sql执行性能,灵活度高。
- 作为一个半ORM框架,MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
说说 #{}和${}的区别是什么?
- #{}是预编译处理,${}是字符串替换。
- Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
- Mybatis在处理时,就是把{}时,就是把时,就是把{}替换成变量的值。
- 使用#{}可以有效的防止SQL注入,提高系统安全性。
说说Mybatis的一级、二级缓存:
一级缓存 事务范围:缓存只能被当前事务访问。缓存的生命周期 依赖于事务的生命周期当事务结束时,缓存也就结束生命周期。 在此范围下,缓存的介质是内存。 二级缓存 进程范围:缓存被进程内的所有事务共享。这些事务有 可能是并发访问缓存,因此必须对缓存采取必要的事务隔离机制。 缓存的生命周期依赖于进程的生命周期,进程结束时, 缓存也就结束了生命周期。进程范围的缓存可能会存放大量的数据, 所以存放的介质可以是内存或硬盘。
聊聊Mybatis的一个整体的架构吧
其实哈,我觉得mybatis框架主要需要做的事情我们是知道的,为啥呢?因为其实如果我们没有mybatis 我们也可以做数据库操作对吧,那就是jdbc 的操作呗,那其实mybatis就是在封装在jdbc之上的一个框架而已,它所需要做的就是那么多,我来总结下
- 首先就是一些基础组件 连接管理,事务管理,配置的加载,缓存的处理
- 然后是核心的功能,我们参数映射,我们的sql解析,sql执行,我们的结果映射
- 之上就是封装我们统一的crud接口就好了,对就这么多咯。
上面就是整个mybatis做的事情了,当然每一块功能处理起来也是不那么简单的。
对Mybatis的源码熟悉吗,找你熟悉的地方聊聊呗
上面的图是整个mybatis的一个核心流程,其实不过是spring也好,mybatis也好,所有的框架我们都可以把他们分为2个部分,一个就是初始化的过程,就是相当于做好准备工作来接客的过程,第二个就是实际的接客过程了,所以不管是讲哪个框架的源码都是这样来的,mybatis当然也是不例外的
- 初始化过程
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("configuration.xml"));
就是我们mybatis的核心在SqlSessionFactory,首先SqlSessionFactory build出来 这个过程就会涉及到解析各种配置文件,第一个要解析的就是configuration然后他的里面有很多的标签,你比如说properties等节点,然后里面有一个mapper节点,就是可以找到我们的mapper.xml 然后又去解析里面的节点,报告各种cach,select 等等,之后把解析好之后xml通过命名空间和我们的mapper接口绑定,并生成代码对象,把他放到konwsmapper 这个map容器里面。最后就可以生成这个SqlSessionFactory
- 真正的执行过程
就是当我们的mybatis准备好之后呢?我们拿到这个sqlsession对象之后,如果是不要spring集成的话,那么接下来当然是要去获取我们的mapper对象了 sqlsession.getMapper,当然这个获取的也是代理对象拉,然后到MapperMethod,然后SqlSession 将查询方法转发给 Executor。Executor 基于 JDBC 访问数据库获取数据。Executor 通过反射将数据转换成 POJO并返回给 SqlSession。将数据返回给调用者。
结束
接下来我们看看SpringBoot 和SpringCloud的组件哈,东西还很多,大家一起加油