Spring整理
描述下IOC的过程?
XML-(读取)->Resource-(解析)->BeanDefinition-(注册)->BeanFactory
BeanDefinitionRegistry
BeanDefinitionRegistry 接口提供了向容器手工注册 BeanDefinition 对象的方法。
BeanDefinitionReader
作用是读取 Spring 配置文件中的内容,将其转换为 IoC 容器内部的数据结构:BeanDefinition。
BeanDefinition
ioc实现中我们在xml中描述的Bean信息最后都将保存至BeanDefinition对象中,其中xml bean与BeanDefinition程一对一的关系。
什么是IOC?
Spring 通过一个配置文件描述 Bean 及 Bean 之间的依赖关系,利用 Java 语言的反射功能实例化Bean 并建立 Bean 之间的依赖关系。 Spring 的 IoC 容器在完成这些底层工作的基础上,还提供了 Bean 实例缓存、生命周期管理、 Bean 实例代理、事件发布、资源装载等高级服务。
什么是AOP?
AOP(Aspect Orient Programming),作为面向对象编程的一种补充,广泛应用于处理一些具有横切性质的系统级服务,如日志收集、事务管理、安全检查、缓存、对象池管理等。
Spring事务失效的原因有哪些?
- 数据库引擎不支持事务
- bean没有被spring管理
- 方法不是 public 的 大概意思就是 @Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。
- 自身调用问题,也就是类中的方法1调用方法2。而没有经过 Spring 的代理类,默认只有在外部调用事务才会生效
- 数据源没有配置事务管理器
- 默认的隔离级别不支持事务Propagation.NOT_SUPPORTED: 表示不以事务运行,当前若存在事务则挂起
- 没有抛出异常【注意是没抛出异常,如果try,catch是不行的】,事务被吃了,try catch也行,但是要手动回滚
JDK动态代理如何使用?
1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法
2.创建被代理的类以及接口
3.通过Proxy的静态方法
newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理
4.通过代理调用方法
JDK动态代理生成的类为什要求实现接口?
因为最终生成的代理类public final class $Proxy0 extends Proxy implements Hello已经继承了Proxy类,java不支持多继承。
Spring是如何选择代理模式的?
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
Spring是如何管理事物的?
PlatfromTransactionManager是Spring事务管理的核心接口
Spring 并不直接管理事务,而是提供了多种事务管理器,通过这个接口,Spring 为各个平台如
- JDBC(DataSourceTransactionManager)
- Hibernate(HibernateTransactionManager)
- JPA(JpaTransactionManager)
等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
Spring的事物管理器用Theadlocal为不同线程维护了一套独立的connection副本。保证线程之间不会互相影响。
Spring是如何定义事物信息的?
事务定义信息-TransactionDefinition
定义事务的隔离级别、传播行为、是否超时、是否只读等
事务管理器接口 PlatformTransactionManager 通过 getTransaction(TransactionDefinition definition) 方法来得到一个事务,这个方法里面的参数是 TransactionDefinition 类 ,这个类就定义了一些基本的事务属性。
Spring有哪些隔离级别?
TransactionDefinition 接口中定义了五个表示隔离级别的常量:
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的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
Spring的事物传播行为有哪些?
TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
不支持当前事务的情况:
TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
其他情况:
TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
Spring事物的实现方式有哪些?
1.编程式事务:配置文件中有对应配置,很少用这这事物管理了
2.使用了XML配置的方式进行事物管理,但是没有使用AOP,基于TransactionProxyFactoryBean
3.要采用注解的方式,需要在配置文件中开启注解事务。
4.基于XML、aop的配置方式,目前项目中都是使用这种方式,有效的减少了代码量
Spring如何解决循环依赖问题?
①构造器的循环依赖:这种依赖spring是处理不了的,直接抛出BeanCurrentlylnCreationException。
②单例模式下的setter循环依赖:通过“三级缓存”处理循环依赖。
③非单例循环依赖:无法处理。
比如:A依赖于B、B依赖于A,多个bean之间相互依赖,形成了一个闭环。Spring的循环依赖,是Spring容器注入时候出现的问题。
实例化A时,将A这个并不完整的对象缓存起来,这样当B实例化后,注入A的时候,能够从容器中获取到A对象,完成初始化。
最后将B对象注入到A中,A完成初始化。
当获取单例bean时,会依次访问一级缓存、二级缓存、三级缓存,缓存命中则返回。通过三级缓存来解决缓存依赖问题。
第一级缓存(单例池:维护着所有创建完成的Bean)singletonObjects
第二级缓存: earlySingletonObjects(维护早期暴露的Bean(只进行了实例化,并未进行属性注入))
第三级缓存:
Map
Spring为什么默认bean为单例?
Spring提供了5种scope分别是singleton、prototype、request、session、global session。
单例:一个bean被声明为单例时,处理多次请求时spring容器里只实例化一个bean,后续的请求公用这个对象,这个对象存储在一个map中,当有请求时,先在缓存中(map)查找是否存在,存在则使用,不存在才实例化一个对象原型:每当有请求来就实例化一个新的bean,没有缓存以及从缓存中查由于不会每次都新创建新对象所以有一下几个
性能上的优势:
1 减少了新生成实例的消耗新生成实例消耗包括两方面,第一,spring会通过反射或者cglib来生成bean实例这都是耗性能的操作,其次给对象分配内存也会涉及复杂算法。
2 减少jvm垃圾回收由于不会给每个请求都新生成bean实例,所以自然回收的对象少了。
3 可以快速获取到bean因为单例的获取bean操作除了第一次生成之外其余的都是从缓存里获取的所以很快。
单例bean的劣势单例的bean一个很大的劣势就是他不能做到线程安全,由于所有请求都共享一个bean实例,所以这个bean要是有状态的一个bean的话可能在并发场景下出现问题,而原型的bean则不会有这样问题(但也有例外,比如他被单例bean依赖),因为给每个请求都新创建实例。
Spring中用到的设计模式有哪些?
- 简单工厂
Spring中的BeanFactory就是一个简单工厂,根据传入一个唯一标识bean来获取Bean对象,也就是getbean.
- 工厂方法
实现了FactoryBean接口的bean是一类叫做Factory的bean.其特点是,spring会在使用getbean调用获得该bean时,会自动调用该bean的getobject方法,所以返回的不是factory的这个bean而是这个bean.getObject()方法的返回值。
- 工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
- 代理设计模式 : Spring AOP 功能的实现。
- 单例设计模式 : Spring 中的 Bean 默认都是单例的。
- 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
- 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
- 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
- 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。
什么是代理模式?
代理模式 (Proxy Pattren) 也称为委托模式,是属于结构型设计模式,作用是为为其它对象提供一种代理以控制对这个对象的访问。简单来说这就是给目标对象生成一个代理对象,并由代理对象控制对目标对象的引用。
ApplicationContext与BeanFactory有何异同?
ApplicationContext:是BeanFactory的子类,除了提供BeanFactory相同的方法之外,还提供AOP的集成,消息资源的处理,事件发布,应用的特定上下文(比如web),在ApplicationContext 容器启动之后,默认全部初始化并绑定完成,所以,对于BeanFactory来说,ApplicationContext 往往要求更多的系统资源
ApplicationContext的三个实现类:
- ClassPathXmlApplication:它是从类的根路径下加载xml配置文件(推荐用这种)。
- FileSystemXmlApplication: 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。(但使用不灵活,不推荐)
- AnnotationConfigApplication:当我们使用注解配置容器对象时,需要使用此类来创建spring容器。它用来读取注解。(springboot默认使用这个)
Bean的生命周期?
- Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
- Bean实例化后对将Bean的引入和值注入到Bean的属性中(ioc注入)
- 如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法,此处传递的就是Spring配置文件中Bean的id值
- 如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
- 如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来
- 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用那个的方法,也可以被应用于内存或缓存技术;
- 如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用
- 如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
- 如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。
SpringMVC从request到controller的过程
DispatcherServlet→处理器映射→控制器
为什么要用三级缓存来解决循环依赖问题(只用一级缓存行不行,只用二级缓存行不行)?
只用一级缓存也是可以解决的,但是会复杂化整个逻辑,半成品对象是没法直接使用的(存在 NPE 问题),所以 Spring 需要保证在启动的过程中,所有中间产生的半成品对象最终都会变成成品对象,如果将半成品对象和成品对象都混在一级缓存中,那么为了区分他们,势必会增加一些而外的标记和逻辑处理,这就会导致对象的创建过程变得复杂化了。
ApplicationContext和BeanFactory有什么不同?
1 ApplicationContext提供了更丰富的功能
- 支持国际化
- 统一资源的访问方式
- 提供在监听器中注册bean事件。
- 同时加载多个文件
- 载入多个上下文。
2 加载方式不同
BeanFactory采用的是延迟加载的形式来注入bean,也就是只有用到某个bean的时候才对该bean进行加载实例化,这样的坏处是加入某个bean的某个属性没有被注入,只有使用时才会发现。
ApplicationContext是在容器启动时一次创建了所有的bean