一、Spring基础
1. 什么是spring?
Spring( Spring Framework) 是一款开源的轻量级 Java 开发框架,旨在提高开发人员的开发效率以及系统的可维护性,使得开发者只需要关心业务需求。
spring可以集成多个模块协助开发,比如spring支持IOP和AOP、可以集成数据库、缓存、消息等三方组件、支持restful应用程序开发。
Spring 框架被划分为多个模块。应用程序可以选择他们需要的模块。core 是核心容器的模块,包括一个配置模型和一个依赖注入机制。除此之外,Spring框架还为不同的应用架构提供了基础支持,包括消息传递、事务性数据和持久性以及Web。它还包括基于Servlet的 Spring MVC Web 框架,以及并行的Spring WebFlux 响应式 web 框架。
Core Container
Spring 框架的核心模块,也可以说是基础模块,主要提供 IoC 依赖注入功能的支持。Spring 其他所有的功能基本都需要依赖于该模块,我们从上面那张 Spring 各个模块的依赖关系图就可以看出来。
- spring-core:Spring 框架基本的核心工具类。
- spring-beans:提供对 bean 的创建、配置和管理等功能的支持。
- spring-context:提供对国际化、事件传播、资源加载等功能的支持。
- spring-expression:提供对表达式语言(Spring Expression Language) SpEL 的支持,只依赖于 core 模块,不依赖于其他模块,可以单独使用。
AOP
- spring-aspects:该模块为与 AspectJ 的集成提供支持。
- spring-aop:提供了面向切面的编程实现。
- spring-instrument:提供了为 JVM 添加代理(agent)的功能。 具体来讲,它为 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 方法)等等常用的测试框架支持的都比较好。
2. Spring,Spring MVC,Spring Boot 之间什么关系?
Spring MVC 是 Spring 中的一个很重要的模块,主要赋予 Spring 快速构建 MVC 架构的 Web 程序的能力。MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。
Spring 旨在简化 J2EE 企业应用程序开发。Spring Boot 旨在简化 Spring 开发。Spring Boot 只是简化了配置,真正做到开箱即用!
3. spring的好处
| 序号 | 好处 | 说明 |
|---|---|---|
| 1 | 轻量 | Spring 是轻量的,基本的版本大约2MB。 |
| 2 | 控制反转 | Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。 |
| 3 | 面向切面编程(AOP) | Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。 |
| 4 | 容器 | Spring 包含并管理应用中对象的生命周期和配置。 |
| 5 | MVC框架 | Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。 |
| 6 | 事务管理 | Spring 提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)。 |
| 7 | 异常处理 | Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常。 |
| 8 | 最重要的 | 用的人多!!! |
二、IOC容器
1. Spring IoC容器和Bean简介
IoC也被称为依赖注入(DI)。它是一个过程,将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。org.springframework.beans和 org.springframework.context 这两个包是 IoC 实现的基础
为什么叫控制反转?
控制反转怎么理解呢? 举个例子:"对象 a 依赖了对象 b,当对象 a 需要使用 对象 b 的时候必须自己去创建。但是当系统引入了 IOC 容器后, 对象 a 和对象 b 之间就失去了直接的联系。这个时候,当对象 a 需要使用 对象 b 的时候, 我们可以指定 IOC 容器去创建一个对象 b 注入到对象 a 中"。 对象 a 获得依赖对象 b 的过程,由主动行为变为了被动行为,控制权反转。
- 控制:指的是对象创建(实例化、管理)的权力
- 反转:控制权交给外部环境(Spring 框架、IoC 容器)
优点
- 对象之间的耦合度或者说依赖程度降低;
- 资源变的容易管理;
2. 容器概述
org.springframework.context.ApplicationContext 接口代表Spring IoC容器,负责实例化、配置和组装bean。容器通过读取配置元数据来获得关于要实例化、配置和组装哪些对象的指示。ApplicationContext接口包含 BeanFactory 的所有功能,同时又提供了更多的扩展功能。ApplicationContext 相比于 BeanFactory 有以下几个区别:
- 继承 MessageSource,提供国际化的标准访问策略。
- 继承 ApplicationEventPublisher ,提供强大的事件机制。
- 扩展 ResourceLoader,可以用来加载多个 Resource,可以灵活访问不同的资源。
- 对 Web 应用的支持。
2.1 元数据
- 基于XML文件的配置
- 基于注解的配置
- 基于Java的配置,@configuration、@bean
3. Bean
Bean 代指的就是那些被 IoC 容器所管理的对象。这些Bean是用你提供给容器的配置元数据创建的
3.1 将一个类声明为 Bean 的注解有哪些?
@Component:通用的注解,可标注任意类为Spring组件。如果一个 Bean 不知道属于哪个层,可以使用@Component注解标注。@Repository: 对应持久层即 Dao 层,主要用于数据库相关操作。@Service: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。@Controller: 对应 Spring MVC 控制层,主要用于接受用户请求并调用Service层返回数据给前端页面。
3.2 @Component 和 @Bean 的区别是什么?
@Component注解作用于类,而@Bean注解作用于方法。@Component通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用@ComponentScan注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。@Bean注解比@Component注解的自定义性更强,而且很多地方我们只能通过@Bean注解来注册 bean。比如当我们引用第三方库中的类需要装配到Spring容器时,则只能通过@Bean来实现。
4. 依赖注入
依赖注入(DI)是一个过程,对象仅通过构造参数、工厂方法的参数或在对象实例被构造或从工厂方法返回后在其上设置的属性来定义它们的依赖(即与它们一起工作的其它对象)。然后,容器在创建 bean 时注入这些依赖。
4.1 注入 Bean 的注解有哪些?
| Annotaion | Package | Source |
|---|---|---|
@Autowired | org.springframework.bean.factory | Spring 2.5+ |
@Resource | javax.annotation | Java JSR-250 |
@Inject | javax.inject | Java JSR-330 |
4.2 @Autowired 和 @Resource 的区别是什么?
Autowired属于 Spring 内置的注解,默认的注入方式为byType(根据类型进行匹配),也就是说会优先根据接口类型去匹配并注入 Bean (接口的实现类)。当一个接口存在多个实现类的话,byType这种方式就无法正确注入对象了,可以通过@Qualifier注解来显式指定名称而不是依赖变量的名称。@Resource属于 JDK 提供的注解,默认注入方式为byName。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为byType。
4.3 Bean Scope作用域
| Scope | 说明 |
|---|---|
| singleton | (默认情况下)为每个Spring IoC容器将单个Bean定义的Scope扩大到单个对象实例。 |
| prototype | 将单个Bean定义的Scope扩大到任何数量的对象实例。 |
| request | 将单个Bean定义的Scope扩大到单个HTTP请求的生命周期。也就是说,每个HTTP请求都有自己的Bean实例,该实例是在单个Bean定义的基础上创建的。只在Web感知的Spring ApplicationContext 的上下文中有效。 |
| session | 将单个Bean定义的Scope扩大到一个HTTP Session 的生命周期。只在Web感知的Spring ApplicationContext 的上下文中有效。 |
| application | 将单个Bean定义的 Scope 扩大到 ServletContext 的生命周期中。只在Web感知的Spring ApplicationContext 的上下文中有效。 |
| websocket | 将单个Bean定义的 Scope 扩大到 WebSocket 的生命周期。仅在具有Web感知的 Spring ApplicationContext 的上下文中有效。 |
4.4 Bean线程安全
- prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题。
- singleton 作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题(取决于 Bean 是否有状态)。如果这个 bean 是有状态的话,那就存在线程安全问题(有状态 Bean 是指包含可变的成员变量的对象)。大部分 Bean 实际都是无状态(没有定义可变的成员变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。
5. Bean的生命周期
4.1 初始化回调
org.springframework.beans.factory.InitializingBean 接口让Bean在容器对Bean设置了所有必要的属性后执行初始化工作。InitializingBean 接口指定了一个方法。
void afterPropertiesSet() throws Exception;
不要使用 InitializingBean 接口,因为它不必要地将代码与Spring耦合。另外,我们建议使用 @PostConstruct 注解或指定一个POJO初始化方法。
调用顺序:
- 注解了
@PostConstruct的方法。 afterPropertiesSet(),如InitializingBean回调接口所定义。- 一个自定义配置的
init()方法。
4.2 销毁回调
实现 org.springframework.beans.factory.DisposableBean 接口可以让Bean在包含它的容器被销毁时获得一个回调。DisposableBean 接口指定了一个方法。
void destroy() throws Exception;
调用顺序:
- 注解了
@PreDestroy的方法。 destroy(),正如DisposableBean回调接口所定义的那样。- 一个自定义配置的
destroy()方法。
4.3 ApplicationContextAware 和 BeanNameAware
Spring Aware接口是一组接口,用于在Spring容器中获取Spring相关的对象或功能。通过实现这些接口,可以让Bean感知到Spring容器的存在,从而获取容器中的其他Bean或Spring的一些功能。
-
BeanNameAware:通过实现该接口,可以获取当前Bean在容器中的名称。
-
ApplicationContextAware:通过实现该接口,可以获取当前Bean所在的ApplicationContext。
-
通过这个对象,我们可以获取Spring容器中的其他Bean实例或一些组件。常见的应用场景包括:
- 在Web项目中获取ServletContext
- 获取国际化信息
- 获取Scheduler等定时任务
4.4 bean的生命周期
4.5 基于注解的自动注入
@Primary表示,当多个Bean是自动注入到一个单值(single value)依赖的候选者时,应该优先考虑一个特定的Bean。如果在候选者中正好有一个主要(primary)Bean存在,它就会成为自动注入的值。@Qualifier注解用于解决依赖注入时的歧义性。当一个接口有多个实现类时,Qualifier 可以帮助 Spring 确定要注入的具体实现类。@Value通常用于注入外部化properties。
三、AOP面向切面的编程
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:
AOP 另辟蹊径,提出横向抽取机制,将横切逻辑代码和业务逻辑代码分离。横切逻辑代码存在的问题:
- 代码重复问题
- 横切逻辑代码和业务代码混杂在一起,代码臃肿,不便维护
| 术语 | 含义 |
|---|---|
| 目标(Target) | 被通知的对象 |
| 代理(Proxy) | 向目标对象应用通知之后创建的代理对象 |
| 连接点(JoinPoint) | 目标对象的所属类中,定义的所有方法均为连接点 |
| 切入点(Pointcut) | 被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点) |
| 通知(Advice) | 增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情 |
| 切面(Aspect) | 切入点(Pointcut)+通知(Advice) |
| Weaving(织入) | 将通知应用到目标对象,进而生成代理对象的过程动作 |
Spring AOP包括以下类型的advice。
- Before advice: 在连接点之前运行的Advice ,但它不具备以下能力 阻止执行流进行到 join point 的能力(除非它抛出一个异常)。
- After returning advice: 在一个连接点正常完成后运行的Advice (例如,如果一个方法返回时没有抛出一个异常)。
- After (finally) advice: 无论连接点以何种方式退出(正常或特殊返回),都要运行该advice。
- After Throwing Advice:当一个匹配的方法执行通过抛出异常退出时,After throwing advice 运行。
- Around advice: 围绕一个连接点的advice,如方法调用。这是最强大的一种advice。Around advice可以在方法调用之前和之后执行自定义行为。它还负责选择是否继续进行连接点或通过返回自己的返回值或抛出一个异常来缩短advice方法的执行。