Spring原理篇

一、IOC

1. 基本概念

控制反转,把对象的创建和对象的调用过程交给Spring进行管理。

2. 底层原理

  1. xml解析:读取xml文件,解析出我们需要的内容。
  2. 工厂模式:最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式,假设我们有两个类,分别是类A和类B,如果我们需要在类A中用到类B的方法,应该怎么做?① 使用工厂模式前:在类A中,利用B的构造器,new一个B的对象出来,通过B的对象来调用方法。② 使用工厂模式后:新建一个Factory类,类中声明一个静态方法getB(),该方法的作用就是返回一个B的对象,此时我们在类A中不需要再去new一个B的对象,只需要调用Factroy.getB()方法即可获得该对象,new一个B对象的工作交给Factory类来完成,Factroy类就是一个工厂类。
  3. 反射:通过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的生命周期

在这里插入图片描述

  1. Bean容器找到配置文件中Spring Bean的定义。
  2. Bean容器利用Java Reflection API创建一个Bean的实例。
  3. 如果涉及到一些属性值利用set()方法设置一些属性值。
  4. 如果Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名字。
  5. 如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
  6. 如果Bean实现了BeanFactoryAware接口,调 setBeanFactory()方法,传入BeanFactory对象的实例。
  7. 与上面的类似,如果实现了其他 *.Aware接口,就调用相应的方法。
  8. 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessBeforeInitialization()方法。
  9. 如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法。
  10. 如果Bean在配置文件中的定义包含init-method 属性,执行指定的方法。
  11. 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessAfterInitialization()方法。
  12. 当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy()方法。
  13. 当要销毁Bean的时候,如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。

四、Spring中的事务管理

1. Spring管理事务的方式

  1. 编程式事务:在代码中硬编码(不推荐使用),通过TransactionTemplate或者TransactionManager手动管理事务,实际应用中很少使用。
  2. 声明式事务:在XML配置文件中配置或者直接基于注解(推荐使用),实际是通过AOP实现(基于@Transactional 的全注解方式使用最多)。

2. Spring事务传播行为

事务传播行为是为了解决业务层方法之间互相调用的事务问题。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。正确的事务传播行为可能的值如下:

  1. TransactionDefinition.PROPAGATION_REQUIRED:使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  2. TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
  3. TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
  4. TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)这个使用的很少。
  5. TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  6. TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  7. TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

3. Spring事务隔离级别

  1. TransactionDefinition.ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,MySQL 默认采用的 REPEATABLE_READ 隔离级别 Oracle 默认采用的 READ_COMMITTED 隔离级别。
  2. TransactionDefinition.ISOLATION_READ_UNCOMMITTED:最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
  3. TransactionDefinition.ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
  4. TransactionDefinition.ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  5. TransactionDefinition.ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

4. @Transactional(rollbackFor = Exception.class)注解

Exception分为运行时异常RuntimeException和非运行时异常。事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。当 @Transactional 注解作用于类上时,该类的所有public方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。在@Transactional注解中如果不配置rollbackFor属性,那么事务只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事务在遇到非运行时异常时也回滚。

五、常见问题

1. @Component和@Bean的区别

  1. @Component 注解作用于类,而@Bean注解作用于方法。
  2. @Component通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用@ComponentScan注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了Spring这是某个类的实例,当我需要用它的时候还给我。
  3. @Bean注解比@Component注解的自定义性更强,而且很多地方我们只能通过@Bean注解来注册bean。比如当我们引用第三方库中的类需要装配到Spring容器时,则只能通过@Bean来实现。

2. @Autowired和@Resource的区别

@Autowired:根据属性类型进行自动装配。
@Resource:如果在Resource注解中的value属性指定了bean的名称,则Spring会只按注解中的value属性查找合适对象,然后进行装配。如果注解中没有指定名称,先按类属性的变量名查找,如果还是未找到,则按类型进行查找(此注解不是spring提供的,而是java提供的)。

3. Spirng的循环依赖问题

3.1 问题描述

说白了就是一个或多个对象实例之间存在直接或间接的依赖关系,这种依赖关系构成了构成一个环形调用。包括:① 自己依赖自己的直接依赖。② 两个对象之间的直接依赖。③ 多个对象之间的间接依赖。

3.2 解决方法:三级缓存

  1. singletonObjects:一级缓存,用于保存实例化、注入、初始化完成的bean实例。
  2. earlySingletonObjects:二级缓存,用于保存实例化完成的bean实例。
  3. singletonFactories:三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。

3.3 解决步骤

  1. A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B,此时的A已经new出来了但是所有的属性均为 null 等待被 init。
  2. B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A。
  3. 然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A。
  4. B顺利初始化完毕,将自己放到一级缓存里面。
  5. 然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A放到一级缓存中。

3.4 缺点

Spring还是有一些无法解决的循环依赖,需要我们写代码的时候注意,例如:使用构造器注入其他Bean的实例,这个就没办法了,要手动改代码。

4. 单例Bean的线程安全问题

大部分时候我们并没有在项目中使用多线程,所以很少有人会关注这个问题。单例Bean存在线程问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的。常见的解决办法有两种:① 在Bean中尽量避免定义可变的成员变量。② 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中(推荐的一种方式)。不过,大部分Bean实际都是无状态(没有实例变量)的(比如 Dao、Service),这种情况下, Bean是线程安全的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值