本文已参与「新人创作礼」活动,一起开启掘金创作之路。
Spring 全家桶
Spring 框架源于 Rod Johnson (Spring Framework 创始人) 在其著作《Expert one on one J2EE design and development》中阐述的部分理念和原型衍生而来. ( 看到这个大佬, 想起一句话 "我变秃了, 也变强了" )
Spring 的出现方便开发者简化基于 POJO 的 Java 应用程序的开发. Spring 框架为 POJO 提供的各种服务共同组成了 Spring 的大家族.
Spring 框架经过多年的发展, 已经成为了一个庞大的家族, 出现在 Java 开发中的各个位置, 比如 充当 MVC 框架的 SpringMVC, 比如和数据访问有关的 Spring JDBC / Spring Data. 这些模块相互独立却又可以相互依赖.
Spring 框架最核心的便是 Spring Core 模块了, 它是整个 Spring 框架的基石, 整个 Spring 框架都构建在 Core 核心模块之上. 在该模块中, Spring 为开发者提供了一个 IoC 容器(IoC Container)实现, 用于帮助开发者以依赖注入的方式管理对象之间的依赖关系. 除此之外, Core 核心模块中还包括框架内部使用的各种工具类, 比如 Spring 的基础 IO 工具类等.
Spring 大家族:
- Spring Core : Spring 核心, IOC容器
- Spring AOP : 面向切面编程, 实现无耦合的日志记录, 性能统计, 安全控制等.
- Spring JDBC, Spring Data : Spring 提供的数据访问模块. 同时支持与其他 ORM 框架整合
- Spring TX : Spring 对事务管理的抽象模块. 是 Spring AOP 的最佳实践, 直接构建在 Spring AOP 的基础之上.
- SpringMVC : Spring 提供的 MVC 模块.
- SpringBoot : 可以快速搭建项目的 MVC 模块.
- Spring Cloud : Spring 提供的微服务框架.
- Spring Batch 批处理模块.
- Spring Security 安全框架
- Spring Test 测试模块
- .....
总结 : 从狭义上来讲, 什么是Spring? Spring 就是一个 IOC 容器. 从广义上来讲, Spring 是很多模块的集合.
Spring 框架的优势 :
- Spring 除了不能帮我们写业务逻辑外, 其余的事 Spring 几乎都可以简化开发者的工作量.
- Spring 可以低侵入, 低耦合地根据配置文件创建及组装对象之间的依赖关系.
- Spring 面向切面编程能帮助开发者无耦合的实现日志记录, 性能统计, 安全控制等.
- Spring 有非常简单的且强大的声明式事务管理.
- Spring 提供了与第三方数据访问框架(如 Hibernate, JPA)无缝集成, 且自己也提供了一套 JDBC 模板来方便数据库访问.
- Spring 提供与第三方 Web(如 Struts, SF)框架无缝集成, 且自己也提供了一套 Spring MVC 框架, 来方便 Web 层搭建.
容器 IOC DI
前面我们讲了, 从狭义的角度看, Spring 是一个轻量级的 DI/IOC 容器的开源框架.
什么是容器
什么是容器 Container :可以简单理解为一个装着其他对象的更大的对象, 当然了, 容器还需要对外提供获取指定对象, 存入指定对象, 并对容器中的对象的生命周期进行管理的功能.
比如 Tomcat 就是Servlet 和 JSP 的容器. Tomcat 会负责 Servlet 对象的创建, 使用, 销毁的整个过程.
什么是 IOC
IOC 是 Inversion on Control 的缩写, 意思是控制反转. 它是一种设计思想, 即将原来程序中由开发者创建对象的任务, 交给 Spring 去完成.
举个例子, 一般的 web 项目分为 三层, 即 conroller, service, dao 层. 需要在 controller 层调用 service 层. 在没有 IOC 思想之前, 创建对象的过程如下 :
UserDao dao = new UserDao(); //先创建 dao 层的类
UserService service = new UserService(); //再创建 service 层的类
service.setDao(dao); //然后把 dao 层的类赋值给 service 层中 dao 层的引用
controller.setService(service); //然后需要把 service 层的类赋值给 controller 层中 service 层的引用
//在 controller 层调用 service
即整个流程是 创建对象 + 手动组装依赖的对象.
而在 IOC 思想中, 创建对象的过程如下 :
UserService service = IOC容器.getBean(...); //由 IOC 容器负责创建对象,并设置其依赖关系.
//在 controller 层调用 service
UserService service = IOC容器.getBean(...); 这一步中, IOC 容器进行了如下操作 :
- 创建 UserService 对象.
- 发现 UserService 对象需要 UserDao 对象.
- 创建 UserDao 对象, 并设置给 UserService.
- 由 IOC 容器来管理对象的生命周期.
这便是 IOC 控制反转的含义.
什么是 DI
DI 是 Dependency Injection 的缩写, 意思是依赖注入. DI 是指 Spring 在创建对象时, 会将对象依赖的属性 ( 对象, 常量, 集合 ) 通过配置设置给该对象.
IOC 和 DI 其实意思都是一样的. loC 从字面意思上很难体现出由谁来维护对象之间的依赖关系, 所以 Martin Fowler 提出一个新的概念 DI, 更明确描述了 被注入对象(Service对象)依赖 loC 容器来配置依赖对象(DAO对象).
Spring 的 IOC 容器
IOC 是一种思想, Spring IOC 则是对这种思想进行了具体的实现.
Spring IOC 容器负责创建, 配置和管理 bean. 被 SpringIOC 容器管理的对象称之为 bean 对象.
注意区分 Java 里的对象和 Spring 的 bean 对象. bean 对象肯定是 Java 对象, 但是 Java 对象不一定是 Bean 对象, 因为这个 Java 对象可能并不是 Spring 创建的, 也就是没有经历过 Spring 中 bean 的生命周期历程.
哪些 Bean 需要被管理
Spring IOC 容器如何知道它需要管理哪些 bean 对象呢 ?
可以通过配置, Spring loC 容器通过读取配置中的配置元数据, 通过元数据对各个对象进行实例化及装配.
元数据的配置有三种方式:
- XML-based configuration : XML 文件的方式
- Java-based configuration : Java 代码的方式
- Annotation-based configuration : 注解的方式
Spring 中两种类型的 IOC 容器
(1) BeanFactory
BeanFactory 是最基本的 IoC 容器, 是 Spring 最底层的接口, 只提供了的 lOC 功能, 负责创建, 组装, 管理 bean.
在实际开发中, 一般不使用 BeanFactory, 而使用其子容器 ApplicationContext.
BeanFactory 就是创建 Bean 的工厂. 作为 Spring 提供的基本的 IOC 容器, BeanFactory 可以完成作为 IOC Service Provider 的所有任务, 包括对象的注册, 创建和对象依赖关系的绑定.
BeanFactory 是接口, 它的常用实现类是 XmlBeanFactory.
BeanFactory 接口中提供了如下一些方法:
Object getBean(String name) // 根据名字获取 bean 对象.
T getBean(Class requiredType) //根据类型获取 bean 对象。
T getBean(String name, Class requiredType) // 根据名字和类型获取 bean 对象。
boolean containsBean(String name) // 判断 bean 是否存在。
(2) ApplicationContext
ApplicationContext 接口继承自 BeanFactory, 是在 BeanFactory 基础上构建的更高级的 IOC 容器实现. 除了 IOC 外, 还提供 AOP 集成, 国际化处理, 事件传播, 统一资源等功能.
ApplicationContext 同样是接口, 有如下几个常用的实现 :
- org.springframework.context.support.
ClassPathXmlApplicationContext. 从 Classpath 加载 bean 定义以及相关资源 - org.springframework.web.context.support.
XmlWebApplicationContext. Spring 提供的用于 Web 应用程序 - org.springframework.web.context.support.
AnnotationConfigApplicationContext. 注解方式初始化的 IOC 容器.
(3) 两种 IOC 容器的区别
除了提供的功能多少不同外, 这两种 IOC 容器在创建对象的时候, 也有所不同 :
- BeanFactory 容器
默认延迟加载( lazy-load ) , 即只有当使用到该 bean 对象时, 才会去创建该 bean 对象, 并配置依赖关系. 所以容器启动速度相对快. - ApplicationContext 容器
默认不延迟加载, 它所管理的对象, 在该容器启动之后, 会全部初始化并配置依赖关系. 所以容器启动速度相对慢一点.
当然了, 既然有默认值, 那也可以使用 lazy-init 参数手动指定这两种容器创建 bean 时是否延迟加载.
IOC 容器的基本使用
在 Spring IOC 容器创建 bean 对象之前, 必须先对 IOC 容器进行实例化, 也就是必须先创建容器, 只有容器创建之后, 方可创建 bean 实例. ( 不然创建出来的 bean 对象放哪里呢? )
① 引入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
② 要创建的实体类:
需要注意的是, 交给 Spring 管理的 对象必须提供无参数的构造方法, 否则 Spring 无法创建该对象.
public class User {
private int id;
private String name;
//实体五连
}
③ 配置元数据
这里使用 xml 的方式来配置元数据, 配置文件放置在 classpath 路径之下, 文件名一般为 applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="user" class="it.entity.User">
<property name="id" value="001" /> //对应 user 类的 setId() 方法
<property name="name" value="艾AA" /> //对应 user 类的 setName() 方法
</bean>
</beans>
这样我们就配置好了要让 Spring 帮我们创建管理的一个 Bean 对象.
④ 创建 IOC 容器
这里我们创建 ApplicationContext 容器 :
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public static void main(String[] args) {
// 1.创建IOC容器. 注意配置文件的路径, 包名/配置文件名
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2.从容器中获取 bean 对象, 根据 <bean> 节点的 id 属性来获取.
User user = (User) ac.getBean("user");
System.out.println(user);
}
整个 IOC 的使用如上, 我们来分析一下具体的步骤 :
- 首先创建 IOC 容器.
- 然后通过 Resource 加载指定的 xml 配置文件
- 然后解析 xml 文件中的 bean 标签的配置, 利用
反射机制, 调用该对象无参数的构造方法, 对其实例化. ( 因为是使用了反射 所以和无参数构造器的访问权限修饰符无关, 即使它是 private 的 ) - 如果这个 bean 标签还有 property 节点, 那么就调用 property 节点中 name 属性所对应的该对象的 setXXX() 方法, 为该对象的属性赋值.
- 从容器中获取指定名称的 bean, 就是拿到了这个类的实例.
- 使用 bean 对象.
容器中获取 bean 对象的几种方式
- User user = (User) ac.getBean("user"); : 根据 bean 标签
节点的 id属性值返回对应的 bean. 这要求该配置文件中, bean 标签节点的 id 属性值必须是唯一的, 当然了, 该属性只能是唯一的, 如果重复的话, xml 会检测出来, 直接报错. - User user2 = ac.getBean(User.class); : 根据
bean 的类型返回对应的 bean. 这要求该配置文件中, 该类型的 bean 标签节点只能有一个. - User user3 = ac.getBean("user", User.class); 根据
name 属性和类型, 可以返回一个唯一的对象.
获取 Bean 对象时, 可能发生的错误 :
- 错误1:NoSuchBeanDefinitionException:No bean named xxx'
原因 : 按照 bean 名称去获取 bean 时, 不存在名称为 xxx 的 bean.
- 错误2:BeanDefinitionParsingException:Configuration problem:Bean name xxx is already used in this beans element Offending resource:class path resource[applicationContext.xml]
原因 : 在 applicationContext.xml 文件中, 多个 bean 元素的名称都是 xxx
- 错误3:NoUnique BeanDefinitionException:No qualifying bean of type 'it.com.entity.xxx'available:expected single matching bean but found 2:xxxx1, xxxx2
原因 : 按照 Xxx 类型去获取 bean 时, 期望找到该类型唯一的一个 bean, 可是此时找到了两个.
\