临近下班点,办公室逐渐响起熙攘的讨论声,我只觉得很吵,自觉带上了耳机,忽然同事在背后拍了拍我肩膀,问我还不下班吗,不回去准备下吗?我人忽然傻了,我应该准备什么?同事嘴角上扬,露出了往常没有的兴奋感说,明天七夕啊?我简单回答了他:哦!二十分钟后,整个办公楼只剩下不到几个人,毫无意外我是其中一个,依旧按照平时的节奏,敲着键盘,不为所动。
也可能是七夕,有些许感触,但又不足为外人道,让我想起了这个闲置已久的公众号。好吧,无病呻吟到此结束,进入正题。
相信很多同行的小伙伴在求职时都会遇到这样一个老生长谈的问题,你怎么理解Spring?此时你可能会内心暗自鄙视面试官,表面还要故作镇定,甚至假装不经意间露出谦虚的样子,毕竟在求职时还是要给予面试官充分的尊严,毕竟能在茫茫人海中相遇也是种缘分,再者冒犯面试官也是求职面试时最大的禁忌。说实话类似这样的问题非常有意思,总觉得熟悉却又不完全熟悉,尴尬但又不完全尴尬。怎么回答呢?Spring家族如此庞大,概念颇具主观性,怎样的回答能得到面试官的青睐呢?
1.首先是心态,面试尤其注重心态。
一部分人会有这样一个通病,台下激情四射,口齿伶俐。台上急不择言,逻辑混乱。想要取得一个好的求职结果,起码要有一个好的心态,而心态来源于自信,正所谓“左手代码,右手芳华”,大不了就是“此处不留爷,自有留爷处”是吧,冠希哥说过:“别害怕失败,大不了从头来过”..............文艺青年式废话哈哈。不扯犊子了,接下来切换到技术青年。
2.技术能力
心态来源于自信,自信来源于个性,个性更核心的本质是你本身所具有的技术能力。
什么是Spring
首先总结一下Spring重要模块
Spring Web :提供Web服务
Spring Security :提供身份验证和访问控制
Spring AOP:面向切面编程实现
Spring JDBC: 数据库连接
Spring Core: 提供基础库和API,大名鼎鼎的IOC也是基于此实现
Spring JMS:Java消息服务
SpringMVC基本工作流程
概述:
1)客户端请求Dispatcherservlet
2)根据HandleMapping获取对应Handler
3)由HandlerAdapter处理Handler业务逻辑 返回ModelAndView
4)Dispatcherservlet 获取视图解析器View Resover
5) Dispatcherservlet 组装数据 response客户端
Spring IOC 容器
控制反转思想:将对象的依赖管辖交给IOC容器管理,简化繁琐的开发流程。
概述:读取配置或扫描注解,定义bean,注册到容器beanFactory。
对于IOC容器bean有个比较重要的点,也是一个让很多面试者容易懵逼的重点:IOC维护的bean是否是线程安全的?
答案是否定的,IOC容器本身并没有提供bean的线程安全策略,那Spring在面对多线程并发时是如何做到线程安全的呢?
Spring提供了一个bean实例作用域注解@scope
参数是:singleton:单例 prototype:每次创建一个对象
那么很多时候我们并没有做这种配置却能保证线程安全,这是因为bean本身不是线程安全,但是bean方法的调用是线程安全的
根据《深入理解JVM虚拟机》,2.2.2节:
Java虚拟机栈是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
如果想实现全局变量的线程安全,可以了解下ThreadLocal机制:set存储变量时并不是将变量存储于ThreadLocal本身,通过参数threadLocals将变量存储到当前线程的线程空间中,get获取也是从当前线程空间获取,所以变量只对当前线程有效。
Spring AOP
即面向切面编程思想,在常规的业务纵向处理架构上,将公共处理逻辑抽出,利用Spring bean动态代理对象实现运行时增强。
动态代理:
JDK动态代理(Spring 默认):代理接口实现类,基于反射机制。
CGlib动态代理:针对普通类,创建子类,继承并覆盖属性以及方法。
Spring 事务
事务类型
编程式事务
声明式高级事务
1)基于注解 2)基于xml配置
事务传播特性:由于Spring机制中,不同的业务层service之间会互相调用,每个service都有自己的事务,具体应该执行哪个事务,Spring增加了事务传播特性。
1)PROPAGATION_REQUIRED
如果当前有事务则加入该事务,没有事务,则创建事务
2)PROPAGATION_SUPPORTS
如果当前没有事务,则以非事务方式执行
3)PROPAGATION_MANDATORY
如果当前没有事务,则抛出异常
4)PROPAGATION_NOT_SUPPORTED
以非事务方式执行,如果当前有事务,则挂起当前事务
5)PROPAGATION_NEVER
以非事务方式执行,如果当前有事务则抛出异常
6)PROPAGATION_NESTED
如果当前存在事务,则在嵌套事务内执行,没有事务则创建事务
7)PROPAGATION_REQUIRES_NEW
不论当前是否有事务都创建新的事务
结论:这里要明确的点是:事务传播特性是基于动态代理的,也就是说不同的动态代理对象之间事务才有事务传播行为,非动态代理对象之间以及非动态代理对象和动态代理对象之间是没有事务传播特性的。
举例:
不同Service之间:
ServiceA事务参数为PROPAGATION_REQUIRED
ServiceB事务参数为PROPAGATION_REQUIRES_NEW
ServiceA 调用 ServiceB 场景,当A抛出异常,此时B能入库成功,A不能。
原因分析:
对于B:无论是否有事务,都会创建属于B自己的事务,A事务对B无影响。
假如将B事务参数改为PROPAGATION_REQUIRED,此时B找到当前A事务并加入A事务,A抛出异常,导致同一个事务的B操作回滚。A保存失败同时B保存失败。
同一个Service之间:
A方法加入事务注解
B方法未加事务注解
此时A调用B, A或B抛出异常,B操作不会回滚。
原因分析:
因为事务传播是基于动态代理对象,A方法被外部调用时,属于动态代理对象被执行,此时有事务,但是在A中调用B时,本质上是this调用,即原型bean(普通对象)调用,非动态代理对象调用,事务失效。
事务隔离级别:由于事务之间交叉访问存在边界性,Spring引入事务隔离级别保证和数据安全性。
TransactionDefinition.ISOLATION_DEFAULT: 默认使用数据库隔离级别
TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 读未提交
最低隔离级别,可能会导致脏读,幻读,不可重复读
TransactionDefinition.ISOLATION_READ_COMMITTED: 读已提交 防止脏读
TransactionDefinition.ISOLATION_REPEATABLE_READ: 可重复读 防止脏读,不可重复读
TransactionDefinition.ISOLATION_SERIALIZABLE: 串行化 最高隔离级别,可防止脏读,幻读,不可重复读
备注:
脏读:A事务可以读取到B事务未提交的数据
不可重复读:A事务第一次读到某个数据 此时B事务修改了该数据 A事务再次读取时读不到该数据
幻读:A事务查询全表,此时B事务插入数据,A事务在B提交之前是无法读取到B事务提交的数据的,A再次查询时发现多了一条数据 ,两次查询结果不一致。
具体业务场景:
A查询id=1的数据 :select * from table_name where id = 1;
查询不到则执行:insert into `table_name`(`id`) values (1);
在A事务查询和插入之间B事务执行了insert into `table_name`(`id`) values (1); 此时A事务懵逼,之前并未读取到该数据,现在莫名其妙存在该数据,在A事务视角似乎发生了幻想,即幻读。
“觉得为时已晚的时候,恰恰是最早的时候”。
好啦,以上就是我本期要给大家分享的内容,后续会在此基础上垂直和水平(又想到了AOP)剖析底层实现原理,也会拓展更多其他内容,如果有错误的地方,欢迎大家留言指正。哦对了,下方是我的公众号,关注以后回复“资料”可以获取我平时收录的一些编程学习资料,包括阿里等各大厂面试题,希望与大家共同学习成长和进步。