10. Spring 自动装配
spring 配置文件中 节点的 autowire
参数可以控制 bean 自动装配的方式
- 在 XML 中有5种装配方式:
- no(default):默认方式是不装配,通过人工指定 ref 属性进行装配 bean
- byName : 根据名称进行装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。
- byType - 根据参数的数据类型进行自动装配。
- constructor - 根据构造函数进行装配
- autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。
- 基于注解的自动装配方法:
使用@Autowired
、@Resource
注解来自动装配指定的bean。
在使用@Autowired
注解之前需要在Spring配置文件进行配置,<context:annotation-config />
。
在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:
如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
如果查询的结果不止一个,那么@Autowired会根据名称来查找;
如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。
@Autowired可用于:构造函数、成员变量、Setter方法
注:@Autowired和@Resource之间的区别:
(1) @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。
(2) @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。
11. Spring 如何解决循环依赖问题
循环依赖的几种情况如下:
- 通过构造方法进行依赖注入的时候的循环依赖问题
- 通过set 注入产生的循环依赖问题(多例模式)
- 通过set 注入产生的循环以来问题(单例模式)
目前解决在单例模式下的 set 注入的循环依赖问题,主要是通过二级缓存和三级缓存进行处理,在对象实例化之后,依赖注入之前,Spring 先将 Bean实例的引用存储在第三级缓存中。
对于构造注入的循环依赖可以使用延迟加载的方案进行解决:
对于类A和类B都是通过构造器注入的情况,可以在A或者B的构造函数的形参上加个@Lazy注解实现延迟加载。当实例化对象时,如果发现参数或者属性有@Lazy注解修饰,那么就不直接创建所依赖的对象了,而是使用动态代理创建一个代理类。在注入依赖时,类A并没有完全的初始化完,实际上注入的是一个类B的代理对象,只有当类A首次被使用的时候才会被完全的初始化。
12. Spring 事务
Spring 事务的本质就是对数据库事务的支持。
事务具备ACID四种特性,ACID,Atomic(原子性)、Consistency(一致性)、Isolation(隔离性)和Durability(持久性)
事务的传播特性:
事务传播行为就是多个事务方法互相调用时,如果定义不同事务之间的一个传播规则,Spring中定义了7种:
- propagation_requierd:如果当前没有事务,那么就新建一个事务,如果当前已经存在事务,那么就加入到这个事务中。默认!
- propagation_supports:支持当前事务,如果没有当前事务,那么就以非事务的方式进行;
- propagation_mandatory:强制使用当前事务,如果当前没有事务,那么就抛出异常;
- propagation_required-new:新建事务,如果当前存在事务,那么就把当前事务挂起;
- propagation_not_supports:不支持事务,以非事务的方式进行,如果当前存在事务,那么就把当前事务挂起;
- propagation_never:以非事务方式执行,如果当前存在事务则抛出异常;
- propagation_nested:嵌套事务,如果当前存在事务则在当前事务的内部进行嵌套然后进行,如果当前没有事务则按照默认的 required 方式进行;
事务隔离级别:
一般说到事务隔离级别就会想到数据在没有事务隔离关系的时候会发生什么问题,而我们的事务隔离级别就是针对这些问题提出的解决方案:
- 脏读:指当一个事务正在访问数据,并且对数据做了修改,此时还没有提交到数据库中,被另外的一个事务访问了该条数据然后使用了还没有提交回去的数据。
- 不可重复读:在一个事务内,多次对同一数据进行读取,产生结果不一致的情况;当前事务还没有结束,另一个事务对当前事务操作过的数据进行了修改,然后当前事务再次对数据进行读取的时候出现了前后读取数据不一致的问题。称之为不可重复读。
- 幻影读:一个事务先后读取某个范围内的数据记录,但是两次读取的记录数不一样,称之为幻读(两次执行同一条 select 语句会出现不同的结果,第二次读会增加一数据行,并没有说这两次执行是在同一个事务中)。
那么针对上面出现的几种常见的问题,提出事务隔离级别的的概念:
- read uncommitted:读未提交,允许另一个事务看到当前事务还没有提交的数据,这样很容易出问题,一般不使用;针对上面的现象啥问题都解决不了。
- read committed:读已提交,保证当前事务被提交后才能被别的事务读取,另一个事务不能够读取还没有提交的数据;能够解决脏读问题。
- read repeatable:可重复读,保证了脏读和不可重复读的现象不会出现数据问题,但是可能会出现幻读。
- serializable:序列化,可以保证上面的所有情况不会发生,但是相对实现比较复杂,这里先不过多阐述,解决方案MVCC,可以参考这篇文章:MySQL的MVCC及实现原理
Spring 中的隔离级别:
- ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。
- ISOLATION_READ_UNCOMMITTED:读未提交,允许事务在执行过程中,读取其他事务未提交的数据。
- ISOLATION_READ_COMMITTED:读已提交,允许事务在执行过程中,读取其他事务已经提交的数据。
- ISOLATION_REPEATABLE_READ:可重复读,在同一个事务内,任意时刻的查询结果都是一致的。
- ISOLATION_SERIALIZABLE:所有事务逐个依次执行。
Spring 事务的种类:
声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式后者 AOP切面的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。
- 编程式事务管理
TransactionTemplate
编程式事务现在基本不怎么用,但是也有存在的意义,这里有一个编程式事务的 样例
https://www.jianshu.com/p/bc6396926ed7 - 基于
TransactionProxyFactoryBean
的声明式事务 - 基于注解
@Transactional
的声明式事务 - 基于AspectJ 的 AOP声明式事务
13. Spring 中常用的设计模式
- 工厂模式:使用 BeanFactory 创建 bean实例
- 单例模式:bean模式是单例的
- 策略模式:例如Resource 的实现类,针对不同的资源文件,实现了不同的资源获取方法
- 代理模式:动态代理,静态代理,在AOP中使用的是基于接口的实现的JDK动态代理和基于继承实现的CGLIB动态代理,(扯远了,还有静态代理的 AspectJ,性能更优,一般面试会问到一些细节)
- 模板方法:可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。比如 RestTemplate, JmsTemplate, JpaTemplate
- 适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式,Spring MVC中也是用到了适配器模式适配Controller
- 观察者模式:Spring事件驱动模型就是观察者模式的一个经典应用。
- 桥接模式:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,客户在每次访问中根据需要会去访问不同的数据库