1.什么是Spring
Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求
2.Spring的优点
- Spring属于低侵入式设计,代码的污染极低
- Spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性
- Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用
- Spring对于主流的应用框架提供了集成支持
3.Spring的IoC理解
-
什么是IoC
IOC,Inversion of Control,控制反转,指将对象的控制权转移给Spring框架,由 Spring 来负责控制对象的生命周期(比如创建、销毁)和对象间的依赖关系,IoC容器的本质是一个map,里面存放了Bean对象
最直观的表达就是,以前创建对象的时机和主动权都是由自己把控的,如果在一个对象中使用另外的对象,就必须主动通过new指令去创建依赖对象,使用完后还需要销毁(比如Connection等),对象始终会和其他接口或类耦合起来。而 IOC 则是由专门的容器来帮忙创建对象,将所有的类都在 Spring 容器中登记,当需要某个对象时,不再需要自己主动去 new 了,只需告诉 Spring 容器,然后 Spring 就会在系统运行到适当的时机,把你想要的对象主动给你。也就是说,对于某个具体的对象而言,以前是由自己控制它所引用对象的生命周期,而在IOC中,所有的对象都被 Spring 控制,控制对象生命周期的不再是引用它的对象,而是Spring容器,由 Spring 容器帮我们创建、查找及注入依赖对象,而引用对象只是被动的接受依赖对象,所以这叫控制反转
-
什么是DI
IoC 的一个重点就是在程序运行时,动态的向某个对象提供它所需要的其他对象,这一点是通过DI(Dependency Injection,依赖注入)来实现的,即应用程序在运行时依赖 IoC 容器来动态注入对象所需要的外部依赖。而 Spring 的 DI 具体就是通过反射实现注入的,反射允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性
4.Spring的AOP理解
OOP面向对象,允许开发者定义纵向的关系,但并不适用于定义横向的关系,会导致大量代码的重复,而不利于各个模块的重用。
AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。可用于权限认证、日志、事务处理。
IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理,Spring中如果代理的对象有接口,就会使用jdk动态代理,如果没有会使用cglib继承该类来代理。cglib在创建的时候比较慢,但是在运行的时候会比jdk要快
-
JDK动态代理只提供接口的代理,不支持类的代理,要求被代理类实现接口。JDK动态代理的核心是InvocationHandler接口和Proxy类,在获取代理对象时,使用Proxy类来动态创建目标类的代理类(即最终真正的代理类,这个类继承自Proxy并实现了我们定义的接口),当代理对象调用真实对象的方法时, InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起
InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理对象; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用
-
如果被代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的
5.Spring AOP和AspectJ AOP的区别
Spring Aop是运行时增强,基于动态代理实现,AspectJ Aop是编译期增强,是基于字节码修改增强。静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理
- AspectJ是静态代理,也称为编译时增强,AOP框架会在编译阶段生成AOP代理类,并将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象
- Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法
6.Spring AOP里面的几个名词的概念
-
连接点(Join point):指程序运行过程中所执行的方法。在Spring AOP中,一个连接点总代表一个方法的执行。
-
切面(Aspect):被抽取出来的公共模块,可以用来会横切多个对象。Aspect切面可以看成 Pointcut切点 和 Advice通知 的结合,一个切面可以由多个切点和通知组成。
在Spring AOP中,切面可以在类上使用 @AspectJ 注解来实现
-
切点(Pointcut):切点用于定义 要对哪些Join point进行拦截。
切点分为execution方式和annotation方式。execution方式可以用路径表达式指定对哪些方法拦截,比如指定拦截add*、search*。annotation方式可以指定被哪些注解修饰的代码进行拦截
-
通知(Advice):指要在连接点(Join Point)上执行的动作,即增强的逻辑,可以理解为所要增强的方法,比如权限校验和、日志记录等。通知有各种类型,包括Around、Before、After、After returning、After throwing。
-
目标对象(Target):包含连接点的对象,也称作被通知(Advice)的对象。 由于Spring AOP是通过动态代理实现的,所以这个对象永远是一个代理对象。
-
织入(Weaving):通过动态代理,在目标对象(Target)的方法(即连接点Join point)中执行增强逻辑(Advice)的过程。
-
引入(Introduction):添加额外的方法或者字段到被通知的类。Spring允许引入新的接口(以及对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制
7.Spring通知(Advice)有哪些类型
- Before Advice:前置通知,在一个方法执行前被调用
- After Advice:最终通知, 在方法执行之后调用的通知,无论方法执行是否成功(不论是正常返回还是异常退出)
- AfterReturning Advice:后置通知,仅当方法成功完成后执行的通知(如果连接点抛出异常,则不执行)
- AfterThrowing advice:异常通知, 在方法抛出异常退出时执行的通知
- Around Advice:环绕通知,包围一个连接点的通知,这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也可以选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行
8.BeanFactory和ApplicationContext的区别
- ApplicationContext是BeanFactory的子接口,所以ApplicationContext提供了更完整的功能,比如国际化之类的功能
- BeanFactory采用的是延迟加载形式来注入Bean的,只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。但是这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFactory加载后,直至第一次调用getBean()方法才会抛出异常
- ApplicationContext是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查以来的属性是否注入。ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例确保当你需要的时候,你就不用等待,因为它们已经被创建好了,不过也能设置懒加载
- 相对于BeanFactory,ApplicationContext唯一不足的地方就是占用内存空间,当应用程序配置Bean较多时,程序启动较慢
- BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册
- BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader
9.Spring容器的启动流程
10.Bean的生命周期
- 通过构造器创建 bean 实例(无参数构造)
- 为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
- BeanPostProcessor前置处理:如果想对Bean进行一些自定义的前置处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法
- init-method:如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法
- bean 可以使用了(对象获取到了)
- BeanPostProcessor后置处理:如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术
- destroy-method:最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法
11.Bean的作用域
-
singleton:默认作用域,单例bean,每个容器中只有一个bean的实例。
-
prototype:为每一个bean请求创建一个实例。
-
request:为每一个request请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
-
session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同的实例。
-
global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在global-session中。
12.Spring框架中的单例Bean是线程安全的吗
Spring中的Bean默认是单例模式的,框架并没有对Bean进行多线程的封装处理
- 对于prototype作用域的Bean,每次都创建一个新对象,也就是线程之间不存在Bean共享,因此不会有线程安全问题
- 对于singleton作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。但是如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Controller类、Service类和Dao等,这些Bean大多是无状态的,只关注于方法本身
- 有状态Bean(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的
- 无状态Bean(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的
- 解决方法:
- 对于有状态的bean(比如Model和View),就需要自行保证线程安全,最浅显的解决办法就是将有状态的bean的作用域由“singleton”改为“prototype”。
- 也可以采用ThreadLocal解决线程安全问题,为每个线程提供一个独立的变量副本,不同线程只操作自己线程的副本变量。
13.Spring框架中都用到哪些设计模式
- 工厂模式:Spring使用工厂模式,通过BeanFactory和ApplicationContext来创建对象
- 单例模式:Bean默认为单例模式
- 策略模式:例如Resource的实现类,针对不同的资源文件,实现了不同方式的资源获取策略
- 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
- 模板方法:可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。有很多Template,比如RestTemplate, JmsTemplate, JpaTemplate
- 桥接模式:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,客户在每次访问中根据需要会去访问不同的数据库
- 适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式,Spring MVC中也是用到了适配器模式适配Controller
- 观察者模式:Spring事件驱动模型就是观察者模式的一个经典应用
14.Spring事务的5种隔离级别
- DEFAULT:是Spring事务默认的隔离级别,使用数据库默认的事务隔离机制
- 未提交读(read uncommited):是最低的事务隔离级别,它允许另外一个事务可以读取当前事务未提交的数据。脏读,不可重复读,幻读都有可能发生
- 已提交读(read commited): 保证一个事务提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。避免了脏读,但是不可重复读和幻读都有可能发生
- 可重复读(repeatable read):保证一个事务不会修改已经由另一个事务读取但未提交或者未回滚的数据,避免了脏读和不可重复读,但是幻读有可能发生
- 可串行化(serializable):最严格的事务隔离级别,支持事务串行执行,资源消耗最大,避免了脏读,不可重复读,幻读
15.什么是脏读、不可重复读、幻读?
- 脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如A事务执行过程中,B事务读取了A事务的修改。但是由于某些原因A事务没有完成提交,发生了回滚操作,则B事务所读取的数据是不正确的,这就是脏读。
- 不可重复读 :表示一个事务读到另一个事务已经提交的updated数据,导致多次查询结果不一致。比如B事务读取了两次数据,在这两次的读取过程中A事务修改了数据,导致B事务的这两次读取出来的数据不一样,这就是不可重复读
- 幻读 :表示一个事务读到另一个事务已经提交的insert数据,导致多次查询结果不一致。比如B事务读取了两次数据,在这两次的读取过程中A事务添加了数据,导致B事务的这两次读取出来的数据不一样,这就是幻读
16.Spring事务的7种传播级别
- PROPAGATION_REQUIRED级别: 默认的spring事务传播级别,如果已经存在一个事务,则支持当前事务。如果当前不存在事务,则开启一个新的事务。
- PROPAGATION_SUPPORTS级别: 如果已经存在一个事务,则支持当前事务。如果当前不存在事务,则使用非事务的方式执行
- PROPAGATION_MANDATORY级别: 如果已经存在一个事务,则支持当前事务。如果当前不存在事务,则抛出异常。
- PROPAGATION_REQUIRES_NEW级别: 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。
- PROPAGATION_NOT_SUPPORTED级别: 总是使用非事务的方式执行,并挂起任何存在的事务
- PROPAGATION_NEVER级别: 使用非事务的方式执行,如果当前存在事务,则抛出异常。
- PROPAGATION_NESTED级别: 如果当前存在事务,则嵌套在事务内执行。如果当前不存在事务,则按PROPAGATION_REQUIRED属性执行。