本文对Spring、Mybatis相关知识做了一个相对完整的总结,涉及到Spring IOC\DI、Bean、AOP、一二三级缓存、Mybatis运行原理、组件等。特别注意,口语化八股文系列,仅作突击复习核心知识点用,推荐有一定八股基础的人食用,更细的点需要大家自行查询相关详细图文资料
IOC意为控制反转,在Spring中就是用户将Bean的管理权限交给Spring,由Spring IOC容器来创建、配置和销毁Bean。DI意为依赖注入,Spring IOC容器将外部的文件、对象、数据等资源注入给程序中的对象。IOC是设计思想,DI就是它的具体实现。
BeanFactory是Spring IOC容器的顶层接口,定义了一些基础功能和规范,内部只包含getBean、contains等获取和判断Bean的接口,装载Bean时是懒加载。
ApplicationContext是它的子类,在BeanFactory基础上有更丰富的功能,采用预加载,在启动时创建所有Bean。
FactoryBean同样也是一个接口,用来生成复杂的bean,单独用意义不大,通常配合Bean生命周期中的InitializingBean、BeanNameAware等接口,实现更复杂的操作。
Spring 框架支持以下五种 bean 的作用域:
总结一下,Bean容器查找并加载Spring管理的Bean,进行实例化,然后进行属性赋值,如果实现了BeanNameAware之类的以Aware为后缀的接口,则调用对应的方法。如果有实现BeanPostProcessor接口,则在Bean调用初始化方法的前后执行BeanPostProcessor的前后置方法。当要销毁Bean,调用对应的销毁方法。个人在使用时,比较常见的是实现BeanDefinitionRegistryPostProcessor接口,重写方法将IOC容器中的某个Bean给移除,通常是将三方框架中的某些配置类给干掉。
Spring有三级缓存,其实就是三个Map,一级缓存存储实例化和初始化都完成的成品Bean。二级缓存存的是半成品,仅完成实例化,没有完成属性注入和初始化,用以解决对象创建时的循环依赖问题。三级缓存存储Bean工厂对象,用来生成半成品Bean放入二级缓存中,这一级缓存用来解决存在AOP时的循环依赖问题。
普通循环依赖,a依赖b,b依赖a。获取a,先看一级缓存有没有数据,没有的话将a的BeanFactory对象放入三级缓存。a开始属性注入,发现b没有创建,实例化b,同样将b的BeanFactory对象放入三级缓存。b也开始属性注入,发现一、二级缓存没有a,调用三级缓存中a的BeanFactory.getObject方法,将a放入二级缓存并清除三级缓存(一二三顺序获取,清除释放空间)。此时b成功注入a,完成初始化,放入一级缓存,并清除二三级缓存。继续a属性注入,此时从一级缓存中获取b,完成初始化,并清除a的二三级缓存。最后获取b,从一级缓存中取出即可。
干掉二级缓存,只用一三级行不行?不行,如果一级缓存中没有值,从BeanFactory.getObject中每次都拿到新的代理对象,如果有二级缓存,那么就会有单例的对象放入二级缓存中,根据一二三顺序获取,就不会每次在三级缓存中重新生成一个新的。
如果干掉三级缓存,只有一二级行不行?如果存在AOP则不行,因为三级缓存生成的对象可能是原始的,而不是我们想要的代理增强对象,从而导致初始化异常。
Spring使用三级缓存解决循环依赖?终于完全弄明白了_spring三级缓存如何解决循环依赖_三七有脾气的博客-CSDN博客
工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
代理设计模式 : Spring AOP 功能的实现。
单例设计模式 : Spring 中的 Bean 默认都是单例的。
模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。
代理分为静态和动态两种,动态代理根据实现方式细分为Cglib和JDK两种。静态代理需要实现接口,将原接口的实现类放入代理类作为代理对象,再重写接口的每个方法做增强。一般不会用静态代理,耦合严重,一般使用动态代理。JDK动态代理是用过反射机制生成一个和被代理类实现相同接口且继承Proxy类的代理类,实现InvocationHandler接口重写invoke方法进行代理增强。Cglib动态代理通过使用ASM开源包,加载被代理对象的class文件,并修改其字节码文件生成一个继承被代理对象的子类来作为代理对象,并重写代理方法使其调用自定义的方法拦截器去执行。
两种动态代理的区别:
Spring AOP中的代理使⽤的默认策略:
Spring支持编程式事务管理和声明式事务管理两种方式:
声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中。唯一不足地方是,声明式事务管理最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。
spring的事务传播行为
spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。
① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘
③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
Spring中的隔离级别
① ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。
② ISOLATION_READ_UNCOMMITTED:读未提交,允许另外一个事务可以看到这个事务未提交的数据。
③ ISOLATION_READ_COMMITTED:读已提交,保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新。
④ ISOLATION_REPEATABLE_READ:可重复读,保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新。
⑤ ISOLATION_SERIALIZABLE:一个事务在执行的过程中完全看不到其他事务对数据库所做的更新。
HandlerMapping(处理器映射器)
HandlerMapping 是⽤来查找 Handler 的,也就是处理器,具体的表现形式可以是类,也可以是 ⽅法。⽐如,标注了@RequestMapping的每个⽅法都可以看成是⼀个Handler。Handler负责具 体实际的请求处理,在请求到达后,HandlerMapping 的作⽤便是找到请求相应的处理器 Handler 和 Interceptor.
HandlerAdapter(处理器适配器)
HandlerAdapter 是⼀个适配器。因为 Spring MVC 中 Handler 可以是任意形式的,只要能处理请求即可。但是把请求交给 Servlet 的时候,由于 Servlet 的⽅法结构都是 doService(HttpServletRequest req,HttpServletResponse resp)形式的,要让固定的 Servlet 处理 ⽅法调⽤ Handler 来进⾏处理,便是 HandlerAdapter 的职责。
HandlerExceptionResolver
HandlerExceptionResolver ⽤于处理 Handler 产⽣的异常情况。它的作⽤是根据异常设置 ModelAndView,之后交给渲染⽅法进⾏渲染,渲染⽅法会将 ModelAndView 渲染成⻚⾯。
ViewResolver
ViewResolver即视图解析器,⽤于将String类型的视图名和Locale解析为View类型的视图,只有⼀ 个resolveViewName()⽅法。从⽅法的定义可以看出,Controller层返回的String类型视图名 viewName 最终会在这⾥被解析成为View。View是⽤来渲染⻚⾯的,也就是说,它会将程序返回 的参数和数据填⼊模板中,⽣成html⽂件。ViewResolver 在这个过程主要完成两件事情: ViewResolver 找到渲染所⽤的模板(第⼀件⼤事)和所⽤的技术(第⼆件⼤事,其实也就是找到 视图的类型,如JSP)并填⼊参数。默认情况下,Spring MVC会⾃动为我们配置⼀个 InternalResourceViewResolver,是针对 JSP 类型视图的。
RequestToViewNameTranslator
RequestToViewNameTranslator 组件的作⽤是从请求中获取 ViewName.因为 ViewResolver 根据 ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置 ViewName, 便要通过这个组件从请求中查找 ViewName。
LocaleResolver
ViewResolver 组件的 resolveViewName ⽅法需要两个参数,⼀个是视图名,⼀个是 Locale。 LocaleResolver ⽤于从请求中解析出 Locale,⽐如中国 Locale 是 zh-CN,⽤来表示⼀个区域。这 个组件也是 i18n 的基础。
ThemeResolver
ThemeResolver 组件是⽤来解析主题的。主题是样式、图⽚及它们所形成的显示效果的集合。 Spring MVC 中⼀套主题对应⼀个 properties⽂件,⾥⾯存放着与当前主题相关的所有资源,如图 ⽚、CSS样式等。创建主题⾮常简单,只需准备好资源,然后新建⼀个“主题名.properties”并将资 源设置进去,放在classpath下,之后便可以在⻚⾯中使⽤了。SpringMVC中与主题相关的类有 ThemeResolver、ThemeSource和Theme。ThemeResolver负责从请求中解析出主题名, ThemeSource根据主题名找到具体的主题,其抽象也就是Theme,可以通过Theme来获取主题和 具体的资源。
MultipartResolver
MultipartResolver ⽤于上传请求,通过将普通的请求包装成 MultipartHttpServletRequest 来实现MultipartHttpServletRequest 可以通过 getFile() ⽅法 直接获得⽂件。如果上传多个⽂件,还 可以调⽤ getFileMap()⽅法得到Map这样的结构,MultipartResolver 的作⽤就 是封装普通的请求,使其拥有⽂件上传的功能。
FlashMapManager
FlashMap ⽤于重定向时的参数传递,⽐如在处理⽤户订单时候,为了避免重复提交,可以处理完 post请求之后重定向到⼀个get请求,这个get请求可以⽤来显示订单详情之类的信息。这样做虽然 可以规避⽤户重新提交订单的问题,但是在这个⻚⾯上要显示订单的信息,这些数据从哪⾥来获得 呢?因为重定向时么有传递参数这⼀功能的,如果不想把参数写进URL(不推荐),那么就可以通 过FlashMap来传递。只需要在重定向之前将要传递的数据写⼊请求(可以通过 ServletRequestAttributes.getRequest()⽅法获得)的属性OUTPUT_FLASH_MAP_ATTRIBUTE 中,这样在重定向之后的Handler中Spring就会⾃动将其设置到Model中,在显示订单信息的⻚⾯ 上就可以直接从Model中获取数据。FlashMapManager 就是⽤来管理 FalshMap 的。
从配置的⻆度也能够总结发现:serlvet、filter、listener是配置在web.xml中的,⽽interceptor是 配置在表现层框架⾃⼰的配置⽂件中的 在Handler业务逻辑执⾏之前拦截⼀次 在Handler逻辑执⾏完毕但未跳转⻚⾯之前拦截⼀次 在跳转⻚⾯之后拦截⼀次
第一步:获取并启动监听器
this.getRunListeners(args)和listeners.starting()方法主要用于获取SpringApplication实例初始化过程中初始化的SpringApplicationRunListener监听器并运行。
第二步:根据SpringApplicationRunListeners以及参数来准备环境
this.prepareEnvironment(listeners, applicationArguments)方法主要用于对项目运行环境进 行预设置,同时通过this.configureIgnoreBeanInfo(environment)方法排除一些不需要的运行环境
第三步:创建Spring容器
根据webApplicationType进行判断, 确定容器类型,如果该类型为SERVLET类型,会通过反射装载对应的字节码,也就是AnnotationConfigServletWebServerApplicationContext,接着使用之前初始化设置的context(应用上下文环境)、environment(项目运行环境)、listeners(运行监听器)、applicationArguments(项目参数)和printedBanner(项目图标信息)进行应用上下文的组装配置,并刷新配置
第四步:Spring容器前置处理
这一步主要是在容器刷新之前的准备动作。设置容器环境,包括各种变量等等,其中包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础
第五步:刷新容器
开启刷新spring容器,通过refresh方法对整个IOC容器的初始化(包括bean资源的定位,解析,注册等等),同时向JVM运行时注册一个关机钩子,在JVM关机时会关闭这个上下文,除非当时它已经关闭
第六步:Spring容器后置处理
扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。
第七步:发出结束执行的事件
获取EventPublishingRunListener监听器,并执行其started方法,并且将创建的Spring容器传进去了,创建一个ApplicationStartedEvent事件,并执行ConfigurableApplicationContext 的publishEvent方法,也就是说这里是在Spring容器中发布事件,并不是在SpringApplication中发布事件,和前面的starting是不同的,前面的starting是直接向SpringApplication中的监听器发布启动事件。
第八步:执行Runners
用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序。其中,Spring Boot提供的执行器接口有ApplicationRunner 和CommandLineRunner两种,在使用时只需要自定义一个执行器类实现其中一个接口并重写对应的run()方法接口,然后Spring Boot项目启动后会立即执行这些特定程序
#{}是预编译处理,${}是字符串替换。使用#{}可以有效的防止 SQL 注入,提高系统安全性。
在处理#{}时,MyBatis会自动生成PreparedStatement,使用参数绑定(?)的方式来设置值;
在处理时,就是把{}时,就是把时,就是把{}替换成变量的值。
count(1) 包括了忽略所有列,用1代表代码行,在统计结果的时候, 不会忽略列值为NULL
count(*) 包括了所有的列,相当于行数,在统计结果的时候, 不会忽略列值为NULL。MySQL推荐统计行数语法,和count(1)效果一致。
count(列名) 只包括列名那一列,在统计结果的时候,会忽略列值为空(这里的空不是指空字符串或者0,而是表示null)的计数, 即某个字段值为NULL时,不统计。
Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。
Mybatis 分页的三种方式:
分页插件的原理:
实现 Mybatis 提供的接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql。举例:select * from student,拦截 sql 后重写为:select t.* from (select * from student)t limit 0,10
Mybatis插件
Mybatis 仅可以编写针对 ParameterHandler、ResultSetHandler、StatementHandler、Executor 这 4 种接口的插件,Mybatis 通过动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler 的 invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
Mybatis动态sql可以在Xml映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值 完成逻辑判断并动态拼接sql的功能。
Mybatis提供了9种动态sql标签: trim|where|set|foreach|if|choose|when|otherwise|bind 。其执行原理为,使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。
一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Sessionflush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;
对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D操作后,默认该作用域下所有 select 中的缓存将被 clear。
Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而 Mybatis 在查询关联对象或关联集合对象时,需要手动编写 sql 来完成,所以,称之为半自动 ORM 映射工具。
Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association指的就是一对一,collection 指的就是一对多查询。在 Mybatis 配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。
它的原理是,使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB()是 null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName()方法的调用。这就是延迟加载的基本原理
作者:云雨雪
链接:https://juejin.cn/post/7217601930917773349
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。