Spring 是一款开源的轻量级 Java 开发框架,我们一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,例如,Spring core、Spring JDBC、Spring MVC 等,使用这些模块可以很方便地协助我们进行开发。
Spring4.x 版本:
Spring5.x 版本:
Spring5.x 版本中 Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件。
Spring 各个模块的依赖关系如下:
Core Container:Spring 框架的核心模块,也可以说是基础模块,主要提供 IoC、DI 等功能的支持。Spring 其他所有的功能基本都需要依赖于该模块,我们从上面那张 Spring 各个模块的依赖关系图就可以看出来。以下模块为 Core Container 的子模块
spring-core:Spring 框架基本的核心工具类。
spring-beans:提供对 bean 的创建、配置和管理等功能的支持。
spring-context:提供对国际化、事件传播、资源加载等功能的支持。
spring-expression:提供对表达式语言(Spring Expression Language) SpEL 的支持,只依赖于 core 模块,不依赖于其他模块,可以单独使用。
AOP
spring-aspects:该模块为与 AspectJ 的集成提供支持。
spring-aop:提供了面向切面编程的实现。
spring-instrument:提供了为 JVM 添加代理的功能。具体来讲,它为 Tomcat 提供了一个织入代理,能够为 Tomcat 传递类文件,就像这些文件是被类加载器加载的一样。没有理解也没关系,这个模块的使用场景非常有限。
Data Access/Integration
spring-jdbc:提供了对数据库访问的抽象 JDBC。不同的数据库都有自己独立的 API 用于操作数据库,而 Java 程序只需要和 JDBC API 交互,这样就屏蔽了数据库的影响。
spring-tx:提供对事务的支持。
spring-orm:提供对 Hibernate、JPA、iBatis 等 ORM 框架的支持。
spring-oxm:提供一个抽象层支撑 OXM(Object-to-XML-Mapping),例如:JAXB、Castor、XMLBeans、JiBX 和 XStream 等。
spring-jms : 消息服务。自 Spring Framework 4.1 以后,它还提供了对 spring-messaging 模块的继承。
Spring Web
spring-web:对 Web 功能的实现提供一些最基础的支持。
spring-webmvc:提供对 Spring MVC 的实现。
spring-websocket:提供了对 WebSocket 的支持,WebSocket 可以让客户端和服务端进行双向通信。
spring-webflux:提供对 WebFlux 的支持。WebFlux 是 Spring Framework 5.0 中引入的新的响应式框架。与 Spring MVC 不同,它不需要 Servlet API,是完全异步。
Messaging
spring-messaging 是从 Spring4.0 开始新加入的一个模块,主要职责是为 Spring 框架集成一些基础的报文传送应用。
Spring Test
Spring 团队提倡测试驱动开发(TDD)。有了控制反转 (IoC)的帮助,单元测试和集成测试变得更简单。
Spring 的测试模块对 JUnit(单元测试框架)、TestNG(类似 JUnit)、Mockito(主要用来 Mock 对象)、PowerMock(解决 Mockito 的问题比如无法模拟 final, static, private 方法)等等常用的测试框架支持的都比较好。
Spring 一般指的是 SpringFramework,他是很多模块的集合,其中最重要的模块是 Spring-Core, Spring 中其他模块的功能实现基本都依赖于该模块。
Spring MVC 就是 Spring 众多模块中的一个,Spring MVC 主要用于快速构建 MVC 架构的 Web 程序。
Spring 目的是简化应用程序的开发,但使用 Spring 进行开发,配置起来过于麻烦。因此,出现了Spring Boot,目的是简化 Spring 开发。
IoC(Inversion of Control,控制反转)是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Ioc 容器来管理。并且,对象之间的相互依赖关系也交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以把应用从复杂的依赖关系中解放出来,很大程度上简化应用的开发。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件或定义好注解就可以了,完全不用考虑对象是怎么被创建出来的。
在实际项目中一个 Service 类可能依赖了很多其他的类,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
Note:
IoC是一种思想,在其他语言中也有对 IoC 思想的实现,并非 Spring 特有的。
在 Spring 中, IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map,Map 中存放的是各种对象。
简单来说,Spring Bean 代指的就是那些被 IoC 容器所管理的对象。我们可以通过配置文件或者注解的方式,告诉 IoC 容器,需要帮助我们管理哪些对象。
第一类是声明 bean 的注解,有 @Component、@Controller、@Service、@Repository
第二类是依赖注入相关的,有 @Autowired、@Resourse
第三类是设置 bean 的作用域 @Scope
第四类是 Spring 配置相关的,比如 @Configuration,@ComponentScan 和 @Bean
第五类是跟 aop 相关做增强的注解 @Aspect、@Pointcut、@Before、@After、@Around
@Component 注解定义在类上,而 @Bean 注解定义在配置类中的方法。
@Component 注解可以标识任意类生成一个对象,加入到 IoC 容器,@Bean 注解一般用来将第三方技术的对象或者自定义的对象加入到 IoC 容器。
@Autowired 属于 Spring 提供的注解,优先根据类型注入,当根据类型无法注入的时候,比如一个接口有多个实现类,会根据名称注入。
@Resource 属于 JDK 提供的注解,优先根据名称注入,当根据名称无法注入的时候,会根据类型注入。
@Autowired 支持在属性、构造函数、参数、方法上使用。@Resource 支持在属性和方法上使用,不支持在构造函数或参数上使用。
Spring 中 Bean 的作用域最常用的是下面二种:
Singleton:在 Singleton 作用域下 IoC 容器中的 bean 实例都是唯一的,Spring 中的 bean 默认都是单例的。
Prototype:在 Prototype 作用域下每次获取 bean,都会创建一个新的 bean 实例。
下面这些仅在 Web 应用中可用
request(请求 bean):每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
session(会话 bean):每一次来自新 session 的 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。
application/global-session(应用 Bean):每个 Web 应用在启动时创建一个 Bean,该 bean 仅在当前应用启动时间内有效。
websocket:每一次 WebSocket 会话产生一个新的 bean。
如何配置 bean 的作用域呢?
xml 方式:
注解方式:
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
return new Person();
}
Spring 框架中的 Bean 是否线程安全,取决于其作用域和状态。以最常用的两种作用域 singleton 和 prototype 为例。有状态 Bean 是指包含可变的成员变量。
singleton 作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题。如果这个 bean 是有状态的话,那就存在线程安全问题。
prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题。
但是,在 singleton 作用域下,其实大部分 Bean 都是无状态的。比如 Dao、Service。对于有状态单例 Bean 的线程安全问题,常见的有两种解决办法:
在 Bean 中尽量避免定义可变的成员变量。
在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中。
有状态的 bean 举例:
在下面的例子中,count 是一个有状态的 Bean,因为它有一个成员变量 count,该变量可以在 increment 方法调用时发生改变。如果将该 Bean 声明为 Singleton 作用域,则该 bean 存在线程安全问题。
@Component
public class Counter {
private int count;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
循环引用其实就是循环依赖,也就是两个或两个以上的 bean 互相持有对方,形成了闭环。比如 A依赖于 B,B 依赖于 A。
AOP 的意思是面向切面编程,能够将那些与业务无关,但被业务模块所共同调用的功能封装起来。例如,事务处理、日志管理、权限控制等,可以减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP 是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:
AOP 切面编程涉及到的一些专业术语:
Before(前置通知):目标对象的方法调用之前触发
After (后置通知):目标对象的方法调用之后触发
AfterReturning(返回通知):目标对象的方法调用完成,在返回结果值之后触发
AfterThrowing(异常通知):目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
Around (环绕通知):编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法
使用 @Order 注解直接定义切面顺序
// 值越小优先级越高
@Order(3)
@Component
@Aspect
public class LoggingAspect implements Ordered {
实现 Ordered 接口重写 getOrder 方法。
@Component
@Aspect
public class LoggingAspect implements Ordered {
// ....
@Override
public int getOrder() {
// 返回值越小优先级越高
return 1;
}
}
MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示相分离来组织代码。
Spring MVC 是当前最优秀的 MVC 框架,天生与 Spring 框架集成。Spring MVC 下我们一般把后端项目分为 Service 层(处理业务)、Dao 层(数据库操作)、Entity 层(实体类)、Controller 层(控制层,返回数据给前台页面)。相比于之前的 MVC 框架,比如 Struts2,Spring MVC 使用起来更加简单,运行速度更快。
@RequestMapping:用于映射请求路径。
@ResponseBody:注解实现将 controller 方法返回对象转化为 json 对象响应给客户端。
@RequestHeader:获取指定的请求头数据。
处理常见的 HTTP 请求类型:
@GetMapping:GET 请求。
@PostMapping:POST 请求。
@PutMapping:PUT 请求。
@DeleteMapping:DELETE 请求。
前后端传值:
@RequestParam:@RequestParam 用于获取查询参数。
@Pathvariable:@PathVariable 用于获取路径参数
@RequestBody:实现接收 http 请求体的 JSON 数据,将 JSON 转换为 java 对象。
系统会使用 HttpMessageConverter 或者自定义的 HttpMessageConverter 将请求的 body 中的 json 字符串转换为对象,HTTP请求的请求方法可能是 POST、PUT、DELETE、GET 请求。
DispatcherServlet:核心的中央处理器,负责接收请求、分发,并给予客户端响应。
HandlerMapping:处理器映射器,根据 URL 去匹配查找能处理的 Handler,并会将请求涉及到的拦截器和 Handler 一起封装。
HandlerAdapter:处理器适配器,根据 HandlerMapping 找到的 Handler,适配执行对应的 Handler。
Handler:请求处理器,处理实际请求的处理器。
ViewResolver:视图解析器,根据 Handler 返回的逻辑视图 / 视图,解析并渲染真正的视图,并传递给 DispatcherServlet 响应客户端
Spring MVC 原理如下图所示:
流程说明(重要):
客户端(浏览器)发送请求,DispatcherServlet 拦截请求。
DispatcherServlet 根据请求信息调用 HandlerMapping。HandlerMapping 根据 URL 去匹配查找能处理的 Handler(也就是我们平常说的 Controller 控制器),并会将请求涉及到的拦截器和 Handler 一起封装。
DispatcherServlet 调用 HandlerAdapter 适配器执行 Handler 。
Handler 完成对用户请求的处理后,会返回一个 ModelAndView 对象给 DispatcherServlet ,ModelAndView 顾名思义,包含了数据模型以及相应的视图的信息。Model 是返回的数据对象,View 是个逻辑上的 View。
ViewResolver 会根据逻辑 View 查找实际的 View。
DispatcherServlet 把返回的 Model 传给 View(视图渲染)。
把 View 返回给请求者
一般使用注解的方式统一异常处理,具体会使用到 @ControllerAdvice + @ExceptionHandler 这两个注解。
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
@ExceptionHandler(BaseException.class)
public ResponseEntity> handleAppException(BaseException ex, HttpServletRequest request) {
//......
}
@ExceptionHandler(value = ResourceNotFoundException.class)
public ResponseEntity handleResourceNotFoundException(ResourceNotFoundException ex, HttpServletRequest request) {
//......
}
}
这种异常处理方式下,会给所有或者指定的 Controller 织入异常处理的逻辑(AOP),当 Controller 中的方法抛出异常的时候,由被 @ExceptionHandler 注解修饰的方法进行处理。
ExceptionHandlerResolver 中 getMappedMethod 方法决定了异常具体被哪个被 @ExceptionHandler 注解修饰的方法处理异常。
Spring 实现的事务本质就是 aop 完成,对方法前后进行拦截,在执行方法之前开启事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
编程式事务:在代码中硬编码,通过 TransactionTemplate 或者 TransactionManager 手动管理事务,实际应用中很少使用。
声明式事务:在 XML 配置文件中配置或者直接基于 @Transactional 注解使用,实际是通过 AOP 实现。
当 @Transactional 注解作用于类上时,该类的所有 public 方法都会具有该类型的事务
当 @Transactional 注解作用于方法上时,该方法将具有该类型的事务,也可以在方法上使用该注解来覆盖类上的定义
在 @Transactional 注解中如果不配置 rollbackFor 属性,那么事务只会在遇到运行时异常的时候才会回滚,加上属性 rollbackFor = Exception.class,可以让事务在遇到非运行时异常时也回滚。
事务的传播行为指的是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何传播。记住前三种常用的差不多就够了。
和事务传播行为这块一样,为了方便使用,Spring 也相应地定义了一个枚举类:Isolation
public enum Isolation {
//采用数据库的默认隔离级别
DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
//读未提交
READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
//读已提交
READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
//可重复读
REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
//串行化
SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
private final int value;
Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
Default(默认):采用数据库的默认隔离级别。
READ_UNCOMMITTED(读未提交):读未提交指的是允许一个事务读取另一个事务未提交的数据,可能会导致脏读、幻读、不可重复读。
READ-COMMITTED(读已提交):读已提交指的是一个事物提交之后,它做的变更才会被其他事务看到,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
REPEATABLE-READ(可重复读):可重复读指的是对相同数据的多次读取,结果都是一致的,除非数据是事务本身所修改的,可以阻止脏读和不可重复读,但幻读仍有可能发生。
SERIALIZABLE(可串行化):所有的事务依次逐个执行,这样事务之间就不可能产生干扰,该级别可以防止并发事务产生的所有问题。
如果在方法内部捕获并处理了异常,没有抛出,就会导致事务失效
如果方法抛出编译时异常,不去处理也会导致事务失效,可以在 @Transactional 注解上配置rollbackFor 属性为 Exception,这样不管是什么异常,都会回滚事务
如果方法不是 public 修饰的,也会导致事务失效
读取 MyBatis 核心配置文件(mybatis-config.xml),加载运行环境和映射文件
构造会话工厂 SqlSessionFactory,一个项目只需要一个,单例的,一般由 spring 进行管理
会话工厂创建 SqlSession 对象,这里面就包含了执行 SQL 语句的所有方法
每一个 SqlSession 都会拥有一个 Executor 对象,通过 Executor 对象操作数据库的接口执行 SQL,同时负责查询缓存的维护
Executor 接口的执行方法中有一个 MappedStatement 类型的参数,封装了映射信息
最后关闭 SqlSession,释放资源
MappedStatement 封装的映射信息
参数映射:封装了将 Java 对象映射到 SQL 语句中的参数。参数映射规定了如何将方法参数中的值传递给 SQL 语句中的占位符。
结果映射:封装了将 SQL 查询结果映射到 Java 对象的规则。结果映射指定了 SQL 查询返回结果的列与 Java 对象属性之间的对应关系。
Mybatis 提供的 # 占位符和 $ 占位符,都是实现动态 SQL 的一种方式,通过这两种方式把参数传递到 XML 之后,在执行 SQL 之前,Mybatis 会对这两种占位符进行动态解析。
# 占位符是向 PreparedStatement 中的预处理语句设置参数,而 PreparedStatement 中的 SQL 语句是预编译的,SQL 语句中使用了占位符。并且在设置参数的时候,如果有特殊字符,会自动进行转义。所以 # 号占位符可以防止 SQL 注入。
而使用 $ 的方式传参,相当于直接把参数拼接到了原始的 SQL 里面,会存在 SQL 注入的问题。
所以 $ 和 # 最大的区别在于,前者是动态参数,后者是占位符,动态参数无法防止 SQL 注入的问题,所以在实际应用中,应该尽可能的使用 # 号占位符。
所以 # 和 $ 最大的区别在于,前者是占位符,后者是动态参数,动态参数无法防止 SQL 注入的问题,所以在实际应用中,应该尽可能使用 # 号占位符。
Note:$ 符号的动态传参,可以适合应用在一些动态 SQL 场景中,比如动态传递表名、动态设置排序字段等。
除了常见的 select、insert、update、delete 标签之外
还有很多其他的标签,比如