重新开始学习编程系列Day09——最熟悉的陌生人Spring框架

  • Post author:
  • Post category:其他




一、前言

大家好,我是苍何。最近思考了一个问题,为什么会出现公司面试造火箭,工作扭螺丝的现象,包括各种八股文的连环大绝杀问到你不会为主,其实这是考察你的知识面以及掌握的深度,而为什么需要这样呢?归其原因,无非是通过筛选找到那些会思考的人,他们需要的并不是CRUD的工具人,而是会思考能创新的工程师。

当你深刻理解到这点,我想不用刻意去学习,在工作中,肯定会吾日三省吾身。

于是乎,这个重新开始学习编程系列文章出来了。

愿与君共勉!

Spring框架为我们开发简化了很多,不用区考虑对象的创建和管理,将Bean交给spring,两大核心IOC和AOP,三级缓存、循环依赖,后置处理器、spring的事务处理等,往往每天都在用,但底层还是不大熟悉,所以称之为最熟悉的陌生人。



二、IOC

控制反转,将对象的生命周期和对象之间的关系交给spring容器进行管理

依赖注入的思想是通过反射机制实现的,在实例化一个类时,它通过反射调用类中set方法将事先保存在HashMap中的类属性注入到类中。 总而言之,在传统的对象创建方式中,通常由调用者来创建被调用者的实例,而在Spring中创建被调用者的工作由Spring来完成,然后注入调用者,即所谓的依赖注入or控制反转。 注入方式有两种:依赖注入和设置注入; IoC的优点:降低了组件之间的耦合,降低了业务对象之间替换的复杂性,使之能够灵活的管理对象。



三、AOP



1、概述

面向切面编程,底层是基于代理来实现的。



2、静态代理和动态代理

静态代理:在运行前就已经将.class文件编译好

动态代理:在程序运行时,运用反射机制动态创建而成。



3、动态代理的2种实现方式


JDK动态代理:


① 通过Java.lang.reflect.Proxy类来动态生成代理类

② 代理类要实现InvocationHandler接口;

③ JDK代理只能基于接口进行动态代理的;


CJLIB动态代理


CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法去技术拦截所有的父类方法的调用,并顺势织入横切逻辑。


spring中用的哪种代理?


如果一个类实现了接口,则用JDK动态代理,否则用CJLIB代理



四、spring的三级缓存和循环依赖



1、循坏依赖

类A实例化需要加载类B,类B实例化又需要加载类A,这就是循环依赖



2、三级缓存

Spring在创建Bean的过程中分为三步:

1、实例化对象,就是new对象
2、属性注入,为对象填充属性和注入依赖
3、初始化,完成AOP代理


第一级缓存:


存放成品bean,完全初始化


第二级缓存:


存放半成品的Bean,已经创建但是未注入属性和初始化


第三级缓存:


存的是Bean工厂对象,用来生成半成品的Bean,并放入二级缓存

获取bean是先从缓存获取
1、只针对单例的bean,多例的后面讨论
2、默认的singletonObjects缓存不存在要get的beanName时,判断beanName是否正在创建中
3、从early缓存earlySingletonObjects中再查询,early缓存是用来缓存已实例化但未组装完成的bean
4、如果early缓存也不存在,从singletonFactories中查找是否有beanName对应的ObjectFactory对象工厂
5、如果对象工厂存在,则调用getObject方法拿到bean对象
6、将bean对象加入early缓存,并移除singletonFactories的对象工厂



3、三级缓存解决循环依赖

在这里插入图片描述

Spring通过三级缓存解决了循环依赖。一级缓存为单例池,二级缓存为早期曝光对象,三级缓存为早期曝光对象工厂。当A、B两类发生循环引用,在A实例化之后,将自己提早曝光(即加入三级缓存),如果A初始AOP代理,该工厂对象返回的是被代理的对象,若未被代理,返回对象本身。当A进行属性注入时,经过之前实例化步骤,此时轮到B属性注入,调用getBean(a)获取A对象,由于A处理正在创建集合中,此时也发了循环依赖,所以可以从三级缓存获取对象工厂(如果A被AOP代理,此时返回就是代理对象),并把对象放到二级缓存中,这样保证A只经过一次AOP代理。接下来,B走完Spring生命周期流程,并放入单例池中。当B创建完后,会将B注入A,A走完Spring生命周期流程。到此,循环依赖结束。



五、Spring后置处理器BeanPostProcessor

作用:允许我们在工厂里所有的bean被加载进来后但是还没初始化前,对所有bean的属性进行修改也可以add属性值。

触发时间:先执行工厂后置处理器,再执行构造方法,最后init-method。

package xz.quartz.analysis;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class beanpostpro implements BeanPostProcessor{

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("before"+beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("after"+beanName);
        return bean;
    }

}



六、Spring事务的传播行为



1、PROPAGATION_REQUIRED

required,如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。( 也就是说如果A方法和B方法都添加了注解,在默认传播模式下,A方法内部调用B方法,会把两个方法的事务合并为一个事务 )



2、PROPAGATION_SUPPORTS

supports,如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。



3、PROPAGATION_MANDATORY

mandatory,如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。



4、PROPAGATION_REQUIRES_NEW

requires_new,重新创建一个新的事务,如果当前存在事务,暂停当前的事务。( 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )



5、ROPAGATION_NOT_SUPPORTED

not_supported,以非事务的方式运行,如果当前存在事务,暂停当前的事务



6、PROPAGATION_NEVER

never,以非事务的方式运行,如果当前存在事务,则抛出异常。



7、PROPAGATION_NESTED

nested,如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作



七、 Spring的@Transactional如何实现



1、作用

用来管理事务,底层是基于spring AOP来实现的



2、注解失效的几种场景

A、@Transactional注解只能应用到public修饰符上,用在非public方法会导致失效

B、默认情况下此注解会对unchecked异常进行回滚,对checked异常不回滚。

那什么是unchecked,什么是checked呢?通俗的说,编译器能检测到的是checked,检测不到的就是unchecked。

派生于Error或者RuntimeException(比如空指针,1/0)的异常称为unchecked异常。

继承自Exception的异常统称为checked异常,如 IOException、TimeoutException等。

C、@Transactional 注解属性 rollbackFor 设置错误

rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。

在这里插入图片描述

D、同一个类中方法调用,导致@Transactional失效

开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。

那为啥会出现这种情况?其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。

也就是同一个类下,调用一个已经有注解的方法,该方法给别的类调用,事务不生效,但是如果是不同类下调用有事务的方法,默认情况下事务是生效的。

E、捕获了异常,但是没有手动回滚事务

手动回滚事务的方法:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); //手动开启事务回滚



八、BeanFactory和ApplicationContext的联系和区别

1、ApplicationContext是BeanFactory的子类

2、如果使用ApplicationContext,如果配置的bean是singleton,那么不管你有没有或想不想用它,它都会被实例化。好处是可以预先加载,坏处是浪费内存。

3、BeanFactory,当使用BeanFactory实例化对象时,配置的bean不会马上被实例化,而是等到你使用该bean的时候(getBean)才会被实例化。好处是节约内存,坏处是速度比较慢。多用于移动设备的开发。

4、没有特殊要求的情况下,应该使用ApplicationContext完成。因为BeanFactory能完成的事情,ApplicationContext都能完成,并且提供了更多接近现在开发的功能。



版权声明:本文为qq_43270074原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。