什么是 Spring 框架?
Spring 是一种轻量级开发框架,旨在提高开发人员的开发效率以及系统的可维护性。
我们一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是:核心容器、数据访问/集成,、Web、AOP(面向切面编程)、工具、消息和测试模块。比如:Core Container 中的 Core 组件是Spring 所有组件的核心,Beans 组件和 Context 组件是实现IOC和依赖注入的基础,AOP组件用来实现面向切面编程。
Spring 官网列出的 Spring 的 6 个特征:
- 核心技术 :依赖注入(DI),AOP,事件(events),资源,i18n,验证,数据绑定,类型转换SpEL。
- 测试 :模拟对象,TestContext框架,Spring MVC 测试,WebTestClient。
- 数据访问 :事务,DAO支持,JDBC,ORM,编组XML。
- Web支持 : Spring MVC和Spring WebFlux Web框架。
- 集成 :远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
- 语言 :Kotlin,Groovy,动态语言。
列举一些重要的Spring模块?
下图对应的是 Spring4.x 版本。目前最新的5.x版本中 Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件。
Spring主要模块
- Spring Core: 基础,可以说 Spring 其他所有的功能都需要依赖于该类库。主要提供 IoC 依赖注入功能。
- Spring Aspects :该模块为与AspectJ的集成提供支持。
- Spring AOP :提供了面向切面的编程实现。
- Spring JDBC : Java数据库连接。
- Spring JMS :Java消息服务。
- Spring ORM : 用于支持Hibernate等ORM工具。
- Spring Web : 为创建Web应用程序提供支持。
- Spring Test : 提供了对 JUnit 和 TestNG 测试的支持。
Spring优点
- 方便解耦,简化开发,Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护交给Spring管理
- 方便集成各种框架,Spring可以整合很多框架,比如Mybatis
- 方便程序的测试,Spring支持JUnit4,可以通过注解很方便的测试Spring程序
- 支持AOP(面向切面编程),Spring提供了面向切面编程
- 声明式事务,只需要配置就可以完成对事务的管理,无需手动编程
IoC
IoC(Inverse of Control:控制反转)是一种设计思想,就是 将原本在程序中手动创建对象的控制权,交由Spring框架来管理。 IoC 在其他语言中也有应用,并非 Spirng 特有。 IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。
Spring IoC的初始化过程:
AOP
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib ,这时候Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:
使用 AOP 之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样大大简化了代码量。我们需要增加新功能时也方便,这样也提高了系统扩展性。日志功能、事务管理等等场景都用到了 AOP 。
Spring 中的 bean 的作用域有哪些?
- singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
- prototype : 每次请求都会创建一个新的 bean 实例。
- request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
- session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。
- global-session:全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话
Spring 中的单例 bean 的线程安全问题
单例 bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。
常见的有两种解决办法:
- 在Bean对象中尽量避免定义可变的成员变量(不太现实)。
- 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。
Spring 中的 bean 生命周期
- Spring对Bean 进行实例化;
相当于程序中的new Xx0 - Spring 将值和Bean的引用注入进Bean对应的属性中;
- 如果Bean实现了BeanNameAware 接口,Spring将Bean的ID传递给setBeanName0方法;
实现BeanNameAware主要是为了通过Bean的引用来获得Bean的ID,一般业务中是很少有在 Bean的ID的 - 如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory(BeanFactory bf)方法并把BeanFactory容器实例作为参数传入。
实现BeanFactoryAware 主要目的是为了获取Spring容器,如Bean通过Spring容器发布事件等 - 如果Bean实现了ApplicationContextAwaer接口,Spring容器将调用
setApplicationContext(ApplicationContext ctx)方法,将bean所在的应用上下文的引用传入进来
作用与BeanFactory类似都是为了获取Spring容器,不同的是Spring容器在调用setApplicationContext方法时会把它自己作为setApplicationContext的参数传入,而Spring容器在调用setBeanDactory前需要程序员自己指定(注入)setBeanDactory里的参数BeanFactory - 如果Bean实现了BeanPostProcess接口,Spring将调用它们的postProcessBeforelnitialization(预初始化)方法
作用是在Bean实例创建成功后对进行增强处理,如对Bean进行修改,增加某个功能 - 如果Bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet方法,作用与在配置文件中对Bean使用init-method声明初始化的作用一样,都是在Bean的全部属性设置成功后执行的初始化方法。
- 如果Bean实现了BeanPostProcess接口,Spring将调用它们的postProcessAfterlnitialization(后初始化)方法
作用与6的一样,只不过6是在Bean初始化前执行的,而这个是在Bean初始化后执行的,时机不同 - 经过以上的工作后,Bean将一直驻留在应用上下文中给应用使用,直到应用上下文被销毁
- 如果Bean实现了DisposableBean接口,Spring将调用它的destory方法,作用与在配置文件中对Bean使用destory-method属性的作用一样,都是在Bean实例销毁前执行的方法。
SpringMVC 工作原理
- 客户端的所有请求都交给前端控制器DispatcherServlet来处理,它会负责调用系统的其他模块来真正处理用户的请求。
- DispatcherServlet收到请求后,将根据请求的信息以及HandlerMapping的配置找到处理该请求的Handler。
- 在这个地方Spring会通过HandlerAdapter对该处理进行封装。
- HandlerAdapter是一个适配器,它用统一的接口对各种Handler中的方法进行调用,处理相应的业务逻辑。
- Handler完成对用户请求的处理后,会返回一个ModelAndView对象给DispatcherServlet,ModelAndView顾名思义,包含了数据模型以及相应的视图的信息。
- ModelAndView的视图是逻辑视图,DispatcherServlet还要借助ViewResolver完成从逻辑视图到真实视图对象的解析工作。
- 当得到真正的视图对象后,DispatcherServlet会利用视图对象对模型数据进行渲染。
- 把 View 返回给请求者(浏览器),客户端得到响应。
Spring 框架中用到了哪些设计模式
- 工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
- 代理设计模式 : Spring AOP 功能的实现。
- 单例设计模式 : Spring 中的 Bean 默认都是单例的。
- 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
- 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
- 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
- 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。
@Component 和 @Bean 的区别是什么?
- 作用对象不同: @Component 注解作用于类,而@Bean注解作用于方法。
- @Component通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用 @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了Spring这是某个类的示例,当我需要用它的时候还给我。
- @Bean 注解比 Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册bean。比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。
将一个类声明为Spring的 bean 的注解有哪些?
我们一般使用 @Autowired 注解自动装配 bean,要想把类标识成可用于 @Autowired 注解自动装配的 bean 的类,采用以下注解可实现:
- @Component :通用的注解,可标注任意类为 Spring 组件。如果一个Bean不知道属于哪个层,可以使用@Component
注解标注。 - @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。
- @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。
- @Controller : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
Spring 管理事务的方式有几种?
-
编程式事务,在代码中硬编码。(不推荐使用)
-
声明式事务,在配置文件中配置(推荐使用)
声明式事务又分为两种:
-
基于XML的声明式事务
-
基于注解的声明式事务
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。
@Transactional(rollbackFor = Exception.class)注解
当@Transactional注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。
在@Transactional注解中如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚。
Mybatis的工作原理
- 读取Mybatis配置文件,sqlmapconfig.xml为Mybatis的全局配置文件,配置了Mybatis的运行环境等信息,例如数据库连接信息。
- 加载映射文件,映射文件即SQL文件,该文件中配置了操作数据库的SQL语句,需要在Mybatis配置文件中加载,sqlmapconfig.xml文件可以加载多个映射文件,每个文件对应数据库中的一张表。
- 构造会话工厂,通过Mybatis的环境等配置信息构建会话工厂SqlSessionFactory。
- 创建会话对象,由会话工厂创建SqlSession对象,该对象包含了执行SQL语句的所有方法。
- Executor执行器:Mybatis底层定义了一个Executor接口来操作数据库,它根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。
- MappedStatement对象:在Executor接口的执行方法中有一个MapperedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息。
- 输入参数映射,输入参数类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型,输入参数映射过程类似于JDBC对preparedStatement对象设置参数的过程。
- 输出结果映射:输出结果类型可以是Map、List等类型,也可以是基本数据类型和POJO类型,输出结果映射过程类似于JDBC对结果集的解析过程。
Mybatis的核心组件
- SqlSessionFactoryBuilder(构造器):会根据配置或者代码来生成SqlSessionFactory,采用的是分布构建的Builder模式。
- SqlSessionFactory(工厂接口):依靠它生成SqlSession,使用的是工厂模式。
- SqlSession(会话):一个既可以发送SQL执行返回结果,也可以获取Mapper的接口。
- Sql Mapper(映射器):由一个Java接口和XML文件组成,需要给出对应的SQL和映射规则,负责发送SQL去执行并返回结果。
MVC设计模式
MVC是 Model、View、Controller的缩写,分别代表web应用程序的三种指责。
- 模型(Model):用于存储数据以及处理用户请求的业务逻辑
- 视图(view):向控制器提交数据,现实模型中的数据
- 控制器(Controller):根据试图提供的请求判断将请求和数据交给那个模型处理,将处理的有关结果交给那个视图更新显示。
基于Servlet的MVC模式的具体实现
- 模型:一个或多个JavaBean对象,用于存储数据(实体模型,由JavaBean类创建)和处理业务逻辑(业务模型,由一般的Java类创建)。
- 视图:一个或多个JSP页面,向控制器提交数据和为模型提供数据显示,JSP页面主要是使用HTML标记和JavaBean标记来显示数据。
- 控制器:一个或多个Servlet对象,根据试图提交的请求进行控制,即将请求转发给处理业务逻辑的JavaBean,并将处理结果存放到实体模型JavaBean中,输出给视图显示。