面试笔记(Spring & SpringMVC)
一、Spring的介绍
Spring最重要的就是三点:
1. IOC控制反转
IOC的控制反转是把类的控制权交给Spring来进行管理,在使用的时候,只需要在Spring的配置文
件中配置bean标签,将类的全路径名,以及如果有参数的将参数也配置上去。这样的话Spring就
会通过反射机制来给这个类进行实例化,并且放到Spring容器中。
2. DI依赖注入
IOC需要结合DI依赖注入进行使用,把我们需要使用的类注入到我们需要的地方就可以了,
依赖注入的方式有:1、构造器注入2、setget注入3、注解注入。一般都使用@Autowired
或者@Resource注解进行注入。
3. AOP切面编程
AOP的切面编程是可以在不改变源代码的同时进行逻辑增强。在配置文件中,配置好切点,实现
切面的逻辑就可以实现代码增强,代码增强可以是在执行前,执行中,执行后都可以。一般我们
都使用在**权限认证,日志,事务控制**,
二、AOP的实现原理
AOP底层使用的是动态代理的方式实现的,所谓的动态代理就是在不改变源代码的前提下,每次运行时都会
在内存中临时生成一个AOP对象,这个对象包括了目标对象的所有方法,并且在特定的切点做了增强处理,
并回调目标对象的方法。
SpringAOP中动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
-
JDK动态代理只提供接口的代理,不支持类的代理。核心invoactionHandler 接口和Proxy 类,invoactionHandler通过invoke() 方法反射来调用目标类中的代码,动态的将横切逻辑编织在一起。然后Proxy类通过InvoactionHandler 接口动态的去创建一个符合接口的实例,生成目标类的代理类对象。
-
如果代理类没有实现InvoactionHandler 接口,那么SpringAOP就会使用CGLIB来动态代理目标类,CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行的时候动态的生成指定类的子类对象,并覆盖其中特定的方法进行代码增强,从而实现AOP。CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用 CGLIB 做动态代理的。
三、IOC容器的详细介绍
Spring 提供了两种IOC容器,分别为BeanFactory 和ApplicationContext
**BeanFactory **:是基础类型的IOC容器,提供了比较完整的IOC服务支持。简单来说,BeanFactory就是管理Bean的工厂,主要负责Bean的初始化,并且调用他们的生命周期方法。
ApplicationContext :是BeanFactory的子接口,也被成为应用上下文对象,不仅提供了BeanFactory的所有功能,还添加了I18n(国际化)、资源访问、事件传播等方面的良好支持。
他们两个的主要区别在于,如果Bean的某个属性没有注入,则使用BeanFactory加载后,第一次调用getBean()方法会抛出异常,但是ApplicationContext会初始化时自检,这样有利于检查所依赖的属性是否注入。
所以通常在开发的时候都会选用ApplicationContext
四、SpringBean的生命周期
整体来说有4个步骤:实例化bean,属性赋值,初始化bean,销毁bean
- 首先就是实例化bean,容器通过获取BeanDefinition对象中的信息进行实例化
- 属性赋值,利用依赖注入完成 Bean 中所有属性值的配置注入
- 初始化bean,如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
- 销毁bean,和init-method一样,通过给destroy-method指定函数,就可以在bean销毁前执行指定的逻辑
五、SpringBean的作用域
Spring 容器中的 bean 可以分为 5 个范围:
(1)singleton:单例模式,使用 singleton 定义的 Bean 在 Spring 容器中只有一个实例,这也是 Bean 默认的作用域。 controller、service、dao层基本都是singleton的
(2)prototype:原型模式,每次通过 Spring 容器获取 prototype 定义的 Bean 时,容器都将创建一个新的 Bean 实例。
(3)request:在一次 HTTP 请求中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Request 内有效。
(4)session:在一次 HTTP Session 中,容器会返回该 Bean 的同一个实例。而对不同的 HTTP 请求,会返回不同的实例,该作用域仅在当前 HTTP Session 内有效。
(5)global-session:全局作用域,在一个全局的 HTTP Session 中,容器会返回该 Bean 的同一个实例。
六、事务的传播特性
七、事务的隔离级别
Spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过 binlog实现的。隔离级别有四种:
- Read uncommitted (读未提交):读未提交,允许另外一个事务可以看到这个事务未提交的数据,最低级别,任何情况都无法保证
- Read committed (读已提交):保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新,可避免脏读的发生。
- Repeatable read (可重复读):保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新,可避免脏读、不可重复读的发生。 mysql innerDB 默认隔离级别
- Serializable (串行化):一个事务在执行的过程中完全看不到其他事务对数据库所做的更新,可避免脏读、不可重复读、幻读的发生。
八、Spring中使用的设计模式
(1)工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
(2)单例模式:Bean默认为单例模式。
(3)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
(4)模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
(5)观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。
九、springMVC的执行流程
- 客户端(浏览器)发送请求,直接请求到DispatcherServlet。
- DispatcherServlet根据请求调用HandlerMapping,解析请求对应的Handler
- 解析到对应的Handler 也就是我们的Controller
- HandlerAdapter会根据Handler来调用真正的Handler来处理请求并且执行响应的逻辑代码。
- 处理器处理完之后,会返回一个ModelAndView对象,Model是返回的数据,View是逻辑意义上的View。
- ViewResolver会根据逻辑意义上的View去查询真正的View。
- DispatcherServlet把返回的Model传给View进行渲染。
- 然后吧View返回给浏览器
十、SpringMVC中的常用注解
- RequestMapping :可以指定类或者方法的请求路径,可以使用method字段指定请求方式
- GetMapping、PostMapping:规定了请求方式的方法的请求路径
- RequestParam:接收单一参数的
- PathVariable:从路径中提取参数
- CookieValue:从Cookie中提取参数
- RequestBody:用于接收JSON对象,将JSON对象转换为Java对象。
- ResponseBody:返回JSON格式的数据
- RestController:就相当于@controller+@ResponseBody两个组合。一般在前后端分离的项目中只写接口时使用,标明整个类都是返回JSON格式数据的。
十一、@Autowred 和 @Resource的区别?
@Autowred 默认按照类型注入,如果需要指定名称,搭配@Qualifier; 是Spring提供的注解
@Resource 默认按照名称注入,如果指定的名称不存在,按照类型查找; 是JDK自带的注解
十二、Spring 如何解决 Bean 循环依赖问题
利用缓存机制解决循环依赖问题,spring设计了三级缓存解决循环依赖的问题,
分别是一级缓存:singletonObjects;二级缓存:earlySingletonObjects;三级缓存:singletonFactories;
一级缓存:singletonObjects,存放初始化后的单例对象;
二级缓存:earlySingletonObjects,存放实例化,未完成初始化的单例对象(未完成属性注入的对象);
三级缓存:singletonFactories,存放ObjectFactory对象;
三级缓存之间逐级取,流程如下:
1、getBean()获取实例,Spring首先从一级缓存singletonObjects中获取;
2、如果获取不到,就从二级缓存earlySingletonObjects中获取,如果还是获取不到意味着bean没有实例化;
3、这时singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取;(代理也是从三级缓存生产的)
4、如果从三级缓存中获取到就从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存;
5、这个bean存在会等待下一次轮寻的时候去赋值(解析@Autowared,@Resource)注解等,属性赋值完成后,将bean存入一级缓存;
十三、分布式锁解决方案
1:基于数据库实现分布式锁;
基于数据库的实现方式的核心思想是:在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。
2:基于Redis 中setnx的特性来实现方式;
SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0;
(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
3:基于ZooKeeper的实现方式;
ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:
(1)创建一个目录mylock;
(2)线程A想获取锁就在mylock目录下创建临时顺序节点;
(3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
(4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
(5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。
这里推荐一个Apache的开源库Curator,它是一个ZooKeeper客户端,Curator提供的InterProcessMutex是分布式锁的实现,acquire方法用于获取锁,release方法用于释放锁。
优点:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。
缺点:因为需要频繁的创建和删除节点,性能上不如Redis方式。
上面的三种实现方式,没有在所有场合都是完美的,所以,应根据不同的应用场景选择最适合的实现方式。
在分布式环境中,对资源进行上锁有时候是很重要的,比如抢购某一资源,这时候使用分布式锁就可以很好地控制资源。
当然,在具体使用中,还需要考虑很多因素,比如超时时间的选取,获取锁时间的选取对并发量都有很大的影响,上述实现的分布式锁也只是一种简单的实现,主要是一种思想。