Spring的原理分析
一、IOC
1. 基本概念
控制反转,把对象的创建和对象的调用过程交给Spring进行管理。
2. 底层原理
- xml解析:读取xml文件,解析出我们需要的内容。
- 工厂模式:最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式,假设我们有两个类,分别是类A和类B,如果我们需要在类A中用到类B的方法,应该怎么做?① 使用工厂模式前:在类A中,利用B的构造器,new一个B的对象出来,通过B的对象来调用方法。② 使用工厂模式后:新建一个Factory类,类中声明一个静态方法getB(),该方法的作用就是返回一个B的对象,此时我们在类A中不需要再去new一个B的对象,只需要调用Factroy.getB()方法即可获得该对象,new一个B对象的工作交给Factory类来完成,Factroy类就是一个工厂类。
- 反射:通过Class对象的一个实例(内存中的.class文件)来在程序运行时创建对象,操作其属性和方法。
二、AOP
1. 基本概念
面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。通俗来说,就是不通过修改源代码方式,在主干功能里面添加新功能。
2. 底层原理
动态代理:动态代理简单来说就是我们在调用一个类的某个方法时,不是直接创造这个对象进行调用,而是创建这个对象的代理类,通过调用代理类的inovke()方法来调用该类的方法,并且在调用的同时,我们还可以在代理类的inovke()方法中对该方法进行增强。
3. Spring AOP和AspectJ AOP的区别
- Spring AOP属于运行时增强,而AspectJ是编译时增强。
- Spring AOP基于代理(Proxying),而AspectJ基于字节码操作(BytecodeManipulation)。
- Spring AOP已经集成了AspectJ,AspectJ应该算的上是 Java 生态系统中最完整的AOP框架了。
- AspectJ 相比于Spring AOP功能更加强大,但是Spring AOP相对来说更简单。
- 如果我们的切面比较少,那么两者性能差异不大,但是,当切面太多的话,最好选择AspectJ,它比Spring AOP快很多。
三、Bean的生命周期
- Bean容器找到配置文件中Spring Bean的定义。
- Bean容器利用Java Reflection API创建一个Bean的实例。
- 如果涉及到一些属性值利用set()方法设置一些属性值。
- 如果Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名字。
- 如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
- 如果Bean实现了BeanFactoryAware接口,调 setBeanFactory()方法,传入BeanFactory对象的实例。
- 与上面的类似,如果实现了其他 *.Aware接口,就调用相应的方法。
- 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessBeforeInitialization()方法。
- 如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法。
- 如果Bean在配置文件中的定义包含init-method 属性,执行指定的方法。
- 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessAfterInitialization()方法。
- 当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy()方法。
- 当要销毁Bean的时候,如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。
四、Spring中的事务管理
1. Spring管理事务的方式
- 编程式事务:在代码中硬编码(不推荐使用),通过TransactionTemplate或者TransactionManager手动管理事务,实际应用中很少使用。
- 声明式事务:在XML配置文件中配置或者直接基于注解(推荐使用),实际是通过AOP实现(基于@Transactional 的全注解方式使用最多)。
2. Spring事务传播行为
事务传播行为是为了解决业务层方法之间互相调用的事务问题。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。正确的事务传播行为可能的值如下:
TransactionDefinition.PROPAGATION_REQUIRED
:使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。TransactionDefinition.PROPAGATION_REQUIRES_NEW
:创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。TransactionDefinition.PROPAGATION_NESTED
:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。TransactionDefinition.PROPAGATION_MANDATORY
:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)这个使用的很少。TransactionDefinition.PROPAGATION_SUPPORTS
: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。TransactionDefinition.PROPAGATION_NOT_SUPPORTED
:以非事务方式运行,如果当前存在事务,则把当前事务挂起。TransactionDefinition.PROPAGATION_NEVER
:以非事务方式运行,如果当前存在事务,则抛出异常。
3. Spring事务隔离级别
TransactionDefinition.ISOLATION_DEFAULT
:使用后端数据库默认的隔离级别,MySQL 默认采用的 REPEATABLE_READ 隔离级别 Oracle 默认采用的 READ_COMMITTED 隔离级别。TransactionDefinition.ISOLATION_READ_UNCOMMITTED
:最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。TransactionDefinition.ISOLATION_READ_COMMITTED
:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。TransactionDefinition.ISOLATION_REPEATABLE_READ
:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。TransactionDefinition.ISOLATION_SERIALIZABLE
:最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
4. @Transactional(rollbackFor = Exception.class)注解
Exception分为运行时异常RuntimeException和非运行时异常。事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。当 @Transactional 注解作用于类上时,该类的所有public方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。在@Transactional注解中如果不配置rollbackFor属性,那么事务只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事务在遇到非运行时异常时也回滚。
五、常见问题
1. @Component和@Bean的区别
- @Component 注解作用于类,而@Bean注解作用于方法。
- @Component通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用@ComponentScan注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了Spring这是某个类的实例,当我需要用它的时候还给我。
- @Bean注解比@Component注解的自定义性更强,而且很多地方我们只能通过@Bean注解来注册bean。比如当我们引用第三方库中的类需要装配到Spring容器时,则只能通过@Bean来实现。
2. @Autowired和@Resource的区别
@Autowired:根据属性类型进行自动装配。
@Resource:如果在Resource注解中的value属性指定了bean的名称,则Spring会只按注解中的value属性查找合适对象,然后进行装配。如果注解中没有指定名称,先按类属性的变量名查找,如果还是未找到,则按类型进行查找(此注解不是spring提供的,而是java提供的)。
3. Spirng的循环依赖问题
3.1 问题描述
说白了就是一个或多个对象实例之间存在直接或间接的依赖关系,这种依赖关系构成了构成一个环形调用。包括:① 自己依赖自己的直接依赖。② 两个对象之间的直接依赖。③ 多个对象之间的间接依赖。
3.2 解决方法:三级缓存
- singletonObjects:一级缓存,用于保存实例化、注入、初始化完成的bean实例。
- earlySingletonObjects:二级缓存,用于保存实例化完成的bean实例。
- singletonFactories:三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。
3.3 解决步骤
- A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B,此时的A已经new出来了但是所有的属性均为 null 等待被 init。
- B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A。
- 然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A。
- B顺利初始化完毕,将自己放到一级缓存里面。
- 然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A放到一级缓存中。
3.4 缺点
Spring还是有一些无法解决的循环依赖,需要我们写代码的时候注意,例如:使用构造器注入其他Bean的实例,这个就没办法了,要手动改代码。
4. 单例Bean的线程安全问题
大部分时候我们并没有在项目中使用多线程,所以很少有人会关注这个问题。单例Bean存在线程问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的。常见的解决办法有两种:① 在Bean中尽量避免定义可变的成员变量。② 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中(推荐的一种方式)。不过,大部分Bean实际都是无状态(没有实例变量)的(比如 Dao、Service),这种情况下, Bean是线程安全的。