# 什么是Spring框架?

Spring是一种轻量级的开发框架,旨在提高开发人员的开发效率以及系统的可维护性。我们一般情况下说的Spring框架是指Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。

Spring Core:基础,可以说Spring其他所有的功能都依赖于该类库。主要提供IOC和DI功能。

Spring Aspects:该模块为与AspectJ的集成提供支持。

Spring AOP:提供面向切面的编程实现。

Spring JDBC:Java数据库连接。

Spring JMS:Java消息服务。

Spring ORM:用于支持Hibernate等ORM工具。

Spring Web:为创建Web应用程序提供支持。

Spring Test:提供了对JUnit和TestNG测试的支持。

# Spring框架的优点

  • 轻量: Spring 是轻量的,基本的版本大约2MB
  • **控制反转:**Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们
  • **面向切面的编程(AOP):**Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开
  • **容器:**Spring 包含并管理应用中对象的生命周期和配置
  • **MVC框架:**Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品
  • **事务管理:**Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)
  • **异常处理:**Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常

# Spring的Bean生命周期

  1. Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
  2. Bean实例化后对将Bean的引入和值注入到Bean的属性中
  3. 如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法
  4. 如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
  5. 如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。
  6. 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。
  7. 如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用
  8. 如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。
  9. 此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
  10. 如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。

# 谈谈你对控制反转的理解

Ioc,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

  • 谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

  • 为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

# ApplicationContext通常的实现

ApplicationContext通常有三种实现方式:FileSystemXmlApplicationContextClassPathXmlApplicationContextWebXmlApplicationContext

  • FileSystemXmlApplicationContext:此容器从一个XML文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。

  • ClassPathXmlApplicationContext:此容器也从一个XML文件中加载beans的定义,这里,你需要正确设置classpath因为这个容器将在classpath里找bean配置。

  • WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean。

# Spring Bean作用域

Spring 3 中为 Bean 定义了 5 种作用域,分别为 singleton(单例)、prototype(原型)、 request、session 和 global session,5 种作用域说明如下:

  • singleton:单例模式,Spring IoC 容器中只会存在一个共享的 Bean 实例,无论有多少个 Bean 引用它,始终指向同一对象。该模式在多线程下是不安全的。Singleton 作用域是 Spring 中的缺省作用域,也可以显示的将 Bean 定义为 singleton 模式,配置为:

    <bean id="userDao" class="com.ioc.UserDaoImpl" scope="singleton"/>
    
  • prototype:原型模式,每次通过 Spring 容器获取 prototype 定义的 bean 时,容器都将创建一个新的 Bean 实例,每个 Bean 实例都有自己的属性和状态,而 singleton 全局只有一个对象。根据经验,对有状态的bean使用prototype作用域,而对无状态的bean使用singleton作用域。

  • request:在一次 Http 请求中,容器会返回该 Bean 的同一实例。而对不同的 Http 请求则会产生新的Bean,而且该bean仅在当前 Http Request 内有效,当前 Http 请求结束,该 bean 实例也将会被销毁。

    <bean id="loginAction" class="com.cnblogs.Login" scope="request"/>
    
  • session:在一次 Http Session 中,容器会返回该 Bean 的同一实例。而对不同的 Session 请 求则会创建新的实例,该 bean 实例仅在当前 Session 内有效。同 Http 请求相同,每一次 session 请求创建新的实例,而不同的实例之间不共享属性,且实例仅在自己的 session 请求 内有效,请求结束,则实例将被销毁。

  • global Session:在一个全局的Http Session中,容器会返回该 Bean 的同一个实例,仅在使用 portlet context 时有效。

# 谈谈你对MVC的理解

MVC是Model—View—Controler的简称。即模型—视图—控制器。MVC是一种设计模式,它强制性的把应用程序的输入、处理和输出分开。

MVC中的模型、视图、控制器它们分别担负着不同的任务。

视图: 视图是用户看到并与之交互的界面。视图向用户显示相关的数据,并接受用户的输入。视图不进行任何业务逻辑处理。

模型: 模型表示业务数据和业务处理。相当于JavaBean。一个模型能为多个视图提供数据。这提高了应用程序的重用性

控制器: 当用户单击Web页面中的提交按钮时,控制器接受请求并调用相应的模型去处理请求。

然后根据处理的结果调用相应的视图来显示处理的结果。

MVC的处理过程:首先,控制器接受用户的请求,调用相应的模型来进行业务处理,并返回数据给控制器。控制器调用相应的视图来显示处理的结果。并通过视图呈现给用户。

# Spring MVC 原理

Spring 的模型-视图-控制器(MVC)框架是围绕一个 DispatcherServlet 来设计的,这个 Servlet 会把请求分发给各个处理器,并支持可配置的处理器映射、视图渲染、本地化、时区与主题渲染 等,甚至还能支持文件上传。

  1. 客户端(浏览器)发送请求,直接请求到 DispatcherServlet
  2. DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler
  3. 解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。
  4. HandlerAdapter 会根据 Handler来调用真正的处理器开处理请求,并处理相应的业务逻辑。
  5. 处理器处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据对象,View 是个逻辑上的 View
  6. ViewResolver 会根据逻辑 View 查找实际的 View
  7. DispaterServlet 把返回的 Model 传给 View(视图渲染)。
  8. 最后,DispacherServlet把 View 返回给请求者(浏览器)

# Spring MVC中注解及作用

  • @Controller:用于标识控制层组件,标识这个类是一个控制器,分发处理器会扫描使用了该注解的类的方法,并检测该方法是否使用了@RequestMapping注解。
  • @RestController:相当于@Controller@ResponseBody的效果
  • @Component:泛指组件,当组件不好归类时,我们可以使用这个组件进行标注
  • @Respository:用于注解dao层,在daoImpl实现类上面注解
  • @Service:用于标注业务层组件
  • @RequestMapping:一个用来处理请求地址映射的注解,可以用于方法或者类上,用于类上,表示类中所有响应请求的方法都以该方法作为父路径。
  • @Autowired:它可以对类成员变量、方法或构造函数进行标注,完成自动装配的工作。通过@Autowired注解可以消除set、get方法。
  • @ResponseBody:将java对象转成json,并且发送给客户端
  • @RequestBody:将客户端请求过来的json转成java对象
  • @RequestParam:当表单参数和方法形参名字不一致时,做一个名字映射
  • @PathVarible:用于获取uri中的参数,比如user/1中1的值
  • @Valid:实现数据校验,可以结合hibernate validator一起使用
  • @CookieValue:用来获取Cookie中的值
  • @RequestHeader:用来把Request请求header部分的值绑定到参数上

# 将一个类声明为Spring的bean的注解有哪些

我们一般使用@Autowired注解去自动装配bean。而想要把一个类标识为可以用@Autowired注解自动装配的bean,可以采用以下的注解实现:

  1. @Component注解。通用的注解,可标注任意类为Spring组件。如果一个Bean不知道属于哪一个层,可以使用@Component注解标注。

  2. @Controller注解。对应Spring MVC的控制层,即Controller层,主要用于接受用户请求并调用Service层的方法返回数据给前端页面。

  3. @Service注解。对应服务层,即Service层,主要涉及一些复杂的逻辑,需要用到Dao层(注入)。

  4. @Repository注解。对应持久层,即Dao层,主要用于数据库相关操作。

# Srping事务

# Spring事务的定义与特性

事务逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败。

原子性 (atomicity):强调事务的不可分割. 一致性 (consistency):事务的执行的前后数据的完整性保持一致. 隔离性 (isolation):一个事务执行的过程中,不应该受到其他事务的干扰 持久性(durability) :事务一旦结束,数据就持久到数据库

# Spring事务中的隔离级别有哪几种

TransactionDefinition接口中定义了五个表示隔离级别的常量:

ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,Mysql默认采用的REPEATABLE_READ隔离级别;Oracle默认采用的READ_COMMITTED隔离级别。

ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。

ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。

ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。

ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

# Spring事务中的脏读、不可重复读、幻读是什么

脏读 :一个事务读到了另一个事务的未提交的数据。 不可重复读 :一个事务读到了另一个事务已经提交的update 的数据导致多次查询结果不一致。 幻读 :一个事务读到了另一个事务已经提交的insert 的数据导致多次查询结果不一致。

# Spring事务中有哪几种事务传播行为

事务传播行为指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。

TransactionDefinition接口中定义了7个表示事务传播行为的常量。

支持当前事务的情况:

PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

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

PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)。

不支持当前事务的情况:

PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。

PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。

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

其他情况:

PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED

# Spring支持的事务管理类型

Spring支持两种类型的事务管理:编程式事务管理和声明式事务管理

  • 编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。
  • 声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。

# Transactional失效场景

Transactional失效场景可以分为三种:

  1. 第一种Transactional注解标注方法修饰符为非public时,@Transactional注解将会不起作用。
  2. 在类内部调用调用类内部@Transactional标注的方法。这种情况下也会导致事务不开启。
  3. 事务方法内部捕捉了异常,没有抛出新的异常,导致事务操作不会进行回滚。

# Spring AOP基本概念

AOP(Aspect Orient Programming),直译过来就是面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。

AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。

核心关注点和横切关注点

从该图可以很形象地看出,所谓切面,相当于应用对象间的横切点,我们可以将其单独抽象为单独的模块。

通过AOP,我们可以在不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。AOP 的本质是由 AOP 框架修改业务组件的多个方法的源代码。

# AOP的应用场景

  • 日志处理
  • 用户登录
  • 权限(Authentication )
  • 性能优化(Performance optimization)
  • 事务(Transactions )
  • 记录跟踪 优化 校准(logging, tracing, profiling and monitoring)
  • 调试(Debugging)
  • 懒加载(Lazy loading)
  • 错误处理(Error handling)
  • 资源池(Resource pooling)
  • 同步(Synchronization)

# AOP的优点

  • 降低模块之间的耦合度。
  • 使系统容易扩展。
  • 更好的代码复用。

# AOP的底层实现

AOP 实现的关键在于**[代理模式]**,AOP 主要分为静态代理和动态代理。静态代理的代表为 AspectJ;动态代理的代表为 Spring AOP。

  • 所谓静态代理,就是 AOP 框架会在编译阶段生成 AOP 代理类,因此也称为编译时增强,它会在编译阶段将 AspectJ(切面)weaving(织入)到 Java 字节码中,运行的时候就是增强之后的 AOP 对象。

  • 所谓动态代理,就是 AOP 框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个 AOP 对象,这个 AOP 对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并调回原对象。

# AOP实现方式

类型 原理 优点 缺点
静态AOP 在编译期,切面直接以字节码的形式编译到目标字节 码文件中。
AspectJ属于静态AOP,是在编译时进行增强,会在编译的时候
将AOP逻辑织入到代码中,需要专有的编译器和织入器。
被织入的类性能不受影响。 不够灵活
动态AOP(JDK动态代理) 在运行期,目标类加载后,为接口动态生成代理类,将切面植入到代理类中。
Java从1.3引入动态代理。实现原理是为被代理的业务接口生成代理类,
将AOP逻辑写入到代理类中,在运行时动态织入AOP,使用反射执行织入的逻辑。
主要实现方式依赖java.lang.reflect包下的InvocationHandler和Proxy类。
Java标准库原生支持,使用简单
,无需引用额外的包。相对于静态AOP更灵活。
被代理的类必须是接口,灵活性受到一些限制;使用反射会影响一些性能。
动态代码字节生成(CGLib) 在运行期,目标类加载后,动态构建字节码文件生成目标类的子类,将切面逻辑加入到子类中。 没有接口也可以织入,灵活性高。 扩展类的实例方法为final时,则无法进行织入
自定义类加载器 在运行期,目标加载前,将切面逻辑加到目标字节码中。 可以对绝大部分类织入。 如果用到了其他类加载器,则这些类将不被织入

# AOP常用术语

AOP 领域中的特性术语:

  • 通知(Advice): AOP 框架中的增强处理。通知描述了切面何时执行以及如何执行增强处理。
  • 连接点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。
  • 切点(PointCut): 可以插入增强处理的连接点。
  • 切面(Aspect): 切面是通知和切点的结合。
  • 引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
  • 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,这个过程就是织入。

# Spring AOP通知类型

注解 通知
@Before 通知方法在目标方法调用前执行
@After 通知方法在目标方法返回或异常后执行
@AfterReturning 通知方法在目标方法返回后执行
@AfterThrowing 通知方法在目标方法异常后执行
@Around 通知方法将目标方法封装起来

# Spring AOP切点指示器

Spring AOP 所支持的 AspectJ 切点指示器

切点指示器

在Spring中尝试使用AspectJ其他指示器时,将会抛出IllegalArgumentException异常。

当我们查看上面展示的这些spring支持的指示器时,注意只有execution指示器是唯一的执行匹配,而其他的指示器都是用于限制匹配的。这说明execution指示器是我们在编写切点定义时最主要使用的指示器,在此基础上,我们使用其他指示器来限制所匹配的切点。

下图的切点表达式表示当Instrument的play方法执行时会触发通知。 切点表达式结构 我们使用execution指示器选择Instrument的play方法,方法表达式以 * 号开始,标识我们不关心方法的返回值类型。然后我们指定了全限定类名和方法名。对于方法参数列表,我们使用 .. 标识切点选择任意的play方法,无论该方法的入参是什么。 多个匹配之间我们可以使用链接符 &&||来表示 “且”、“或”、“非”的关系。但是在使用 XML 文件配置时,这些符号有特殊的含义,所以我们使用 “and”、“or”、“not”来表示。

# Spring AOP使用示例

参考链接 使用案例 (opens new window)

LastUpdated: 8/29/2021, 5:26:36 PM