Spring
Spring是一个于2003年兴起的轻量级的IOC和AOP容器框架,以 IOC(控制反转) 和 AOP(面向切面编程)为核心,目的是用于简化企业应用程序的开发。它的优势主要体现于方便解耦、支持AOP思想编程、支持声明式事务,可以集成各种优秀的框架,这也是它如此受欢迎的主要原因。
IOC
说到Spring框架,就不得不说它的两大核心之一IOC,IOC不是什么技术,而是一种设计思想。它的目的是指导我们设计出更加松耦合的程序。
IOC就是控制反转,控制反转就是指对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到 Spring 容器中。最直观的表现就是创建对象我们不用再去new了,可以由Spring自动的生产,我们只需要从Spring容器中获取就可以。
IOC的底层使用的是java的反射机制,根据配置文件动态的去创建对象并管理对象。
IOC的主要功能有两个,分别是依赖查找和依赖注入,我们只需要关注依赖注入即可,依赖注入就是程序在运行的时候我们依赖IOC容器去注入对象需要的外部资源,也就是给对象的属性赋值。IOC有三种依赖注入方式:通过构造方法注入、通过set方法注入,以及通过注解注入。
IOC有两大核心接口,也是IOC的两大容器,分别是BeanFactory和ApplicationContext。
BeanFactory是Spring里面最底层的接口,定义了IOC的基本功能,主要是Spring自身内部调用,它在第一次使用某个Bean时,也就是调用getBean时,才会创建这个Bean对象。
ApplicationContext由BeanFactory派生而来,主要是Spring的使用者也就是我们来调用,它是在容器启动时,一次性创建并加载了所有的Bean对象。
Bean对象的作用域我们是可以通过配置文件中bean标签的scope属性或者@Scope注解来指定的,scope属性有五个值,分别是singleton单例模式,只在容器加载时创建一次;prototype多例模式,每次获取bean的时候,创建一个新对象;request应用于web项目中,表示一次请求一个bean对象;session应用于web项目中,表示一次会话一个bean对象;globalSession用于分布式web开发,bean对象绑定全局session对象。
Bean对象的生命周期是这样的,对于单例模式,是容器启动后创建所有Bean对象,对于多例模式,是在第一次使用该Bean对象时创建,对象创建之后,执行配置的init-method初始化方法,当Bean对象不再需要了,单例模式下在容器关闭对象销毁之前会执行配置的destroy-method销毁方法。
如果我们使用注解开发的话,常用的注解如下:
对象创建的注解,@Component、@Controller、@Service、@Repository,这几个注解底层的源码是一模一样的,当spring容器启动时,根据包扫描配置自动扫描到这些注解,通过反射创建对象,并存入到容器中。默认存入容器的ID是当前类名的首字母小写,我们也可以通过配置value属性自定义这些对象在容器中的唯一标识。
生命周期的注解,@Scope用于指定对象的作用域,@PostConstruct用于指定对象的初始化方法,@PreDestory用于指定对象的销毁方法。
依赖注入的注解,@Autowired,配置到依赖关系的属性上,默认按照对象类型(接口类型)从容器中查找对象并注入赋值,如果按照类型查询出多个对象,按照属性名作为id从容器中查找对象并注入赋值,它不需要配置set方法或构造方法,底层使用暴力反射对属性直接赋值。也可以结合@Qualifier共同使用,可以注入容器中指定id的对象。
使用Spring框架我们还需要注意的一点是单例模式下的bean是线程不安全的,最浅显的解决办法就是将一些存在线程安全问题的bean的作用域由“singleton”变更为“prototype”。绝大部分的bean都是可以为单例模式的,因为Spring内部使用了ThreadLocal对线程安全问题进行了处理。
ThreadLocal就是为每一个线程都提供了一个独立的变量副本,避免了多个线程对数据的访问冲突,我们在开发的时候,可以把不安全的变量封装进ThreadLocal。Spring中的事务管理器就是使用的ThreadLocal来操作连接的。SpringMVC中通过@Autowired获取的servlet原生API对象底层也是使用的ThreadLocal来保证不同请求使用的不是同一个对象的。
AOP
接下来说一下Spring的另外一个核心:AOP。
AOP,面向切面编程,是一种思想, 它的目的就是在不修改源代码的基础上,对原有功能进行增强。
AOP底层使用的是动态代理,所谓的动态代理就是说AOP不会去修改源代码,而是每次运行时在内存中临时为目标对象生成一个代理对象,这个代理对象包含了目标对象所有的方法,对其中一些特定的方法做增强处理。
AOP 中的动态代理主要有两种方式,jdk动态代理和cglib动态代理:
jdk动态代理是基于接口的动态代理,生成的代理对象和被代理对象实现了同一个接口;cglib是基于子类的动态代理,通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用 cglib 做动态代理的。jdk的效率要高于cglib,所以当被代理类有接口的时候,使用jdk动态代理,被代理类没有接口的时候,使用cglib动态代理。
使用AOP,我们需要了解以下名词的具体含义:
切面(Aspect):被抽取的公共模块。它包含了通知和切入点。切面是一种描述,描述了一件事: 一个什么样的功能添加到了哪个切入点的什么位置上。
连接点(Join point):被代理对象中的所有方法。
通知(Advice):在切面的某个特定的连接点(Join point)上增强的功能。
通知分为5种类型:
前置通知,后置通知,异常通知,最终通知,环绕通知。
切入点(Pointcut):切入点是指我们要对哪些连接点进行拦截增强,即被增强的方法。
目标对象(Target Object):被一个或者多个切面(aspect)所通知(advise)的对象,即被代理对象。
代理对象(Proxy):生成的动态代理对象。
AOP的应用场景有:记录日志、缓存处理(应用于service层的查询,目标方法是从数据库获取数据,我们对该切入点配置环绕通知,在切入点之前从redis缓存中获取数据,如果获取到了直接返回,没有获取到,调用目标方法从数据库获取,在切入点后将获取到的数据存入到redis缓存中),以及事务管理。
事务管理
Spring的事务管理有编程式和声明式两种方式。Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,Spring是无法提供事务功能的。
编程式事务是将业务代码和事务代码放在一起书写,它的耦合性太高,我们在开发中不使用。
声明式事务是建立在AOP之上的。其本质是通过AOP功能对方法的前后进行拦截,在方法开始之前创建事务,在方法结束后根据执行情况提交或回滚事务。
声明式事务的优点就是可以将业务代码和事务代码完全分离开发,然后通过配置的方式实现运行时组装运行。缺点就是只能作用到方法级别,无法像编程式事务一样作用到代码块级别。
事务有四大特性,分别是原子性:事务是一个不可分割的单位,事务中的操作要么都发生,要么都不发生;一致性:事务前后数据的完整性必须保持一致;隔离性:是指多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所影响;持久性:指一个事务一旦提交了,对数据库中数据的改变就是永久性的。
如果事务的隔离级别设置不当就有可能引发并发读取问题。有可能会出现脏读,也就是一个事务读取到了另一个事务还没有提交的数据;不可重复读,也就是一个事务中两次读取的数据内容不一致,原因是其他事务在这两次读取之间对数据做了update更新;还有可能出现幻读,是指一个事务中读取的数据数量不一致,原因是其他事务在这两次读取之前做了insert插入数据或者delete删除数据操作。
Spring中有五个隔离级别,我们一般是使用默认隔离级别,也就是使用数据库默认的事务隔离级别,还有四个隔离级别分别是读未提交、读已提交、可重复读、序列化。
Spring的事务传播行为就是指当一个业务方法被另一个业务方法调用时,应该如何进行事务控制。通过配置属性propagation的值可以对传播行为进行设置,我们只需要重点关注两个值,一个是PROPAGATION_REQUIRED,指必须有事务,用于增删改方法使用,另一个是PROPAGATION_SUPPORTS,指支持有事务,但不要求必须有事务,用于查询方法使用。另外要注意增删改方法Read Only要为false(非只读事务),查询方法Read Only可为true(只读事务)。