简介
Spring 是一种轻量级开发框架,旨在提高开发人员的开发效率以及系统的可维护性。Spring 官网:https://spring.io/。
我们一般说 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 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 测试的支持。
组件详情
IOC
IoC(Inverse of Control:控制反转)是一种设计思想,就是 将原本在程序中手动创建对象的控制权,交由Spring框架来管理。 IoC 在其他语言中也有应用,并非 Spring 特有。 IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。
Spring IoC的初始化过程
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vQw6IHY2-1639742651263)(https://secure1.wostatic.cn/static/h1TUQeBJHQKwh3W314gnmF/image.png)]](https://img-blog.csdnimg.cn/5d3112e1a9c9442d8381846a59aee6b0.png)
IOC-注解驱动与组件扫描
注解驱动
在 xml 驱动的 IOC 容器中,咱使用的是 ClassPathXmlApplicationContext ,它对应的是类路径下的 xml 驱动。
对于注解配置的驱动,那自然可以试着猜一下,应该是 Annotation 开头的,ApplicationContext 结尾。那就是下面咱介绍的 AnnotationConfigApplicationContext 。
注解驱动需要的是配置类。一个配置类就可以类似的理解为一个 xml 。配置类没有特殊的限制,只需要在类上标注一个 @Configuration 注解即可。
在 xml 中,咱声明 Bean 是通过 <bean> 标签。
// @Configuration注解 == applicationContext.xml
@Configuration
public class QuickstartConfiguration {
// @Bean注解 == <bean>标签
@Bean("name")
public Person person() {
return new Person();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="com.bean.Person"/>
</beans>
| @Configuration | 等于 | xml |
|---|---|---|
| QuickstartConfiguration .class | 等于 | applicationContext.xml |
| @Bean | 等于 | |
| Person | 等于 | class=“com.bean.Person” |
| “name” | 等于 | id=“person” |
注意:如果指定了bean的Name则为其指定的,如果没有指定则默认@Bean注解下方法名的首字母小写。
组件注册与组件扫描
注解注册注解:@Component,即代表该类会被注册到 IOC 容器中作为一个 Bean 。
相当于xml中:
@Component
// 如果不指定组件名称则默认为person
public class Person {}
<bean class="com.bean.Person"/>
组件扫描
在配置类上额外标注一个 @ComponentScan ,并指定要扫描的路径,它就可以扫描指定路径包及子包下的所有 @Component 组件:
@Configuration
@ComponentScan("com.bean")
public class ComponentScanConfiguration {
}
如果不指定扫描路径,则默认扫描本类所在包及子包下的所有 @Component 组件。
springboot服务建议写在主启动类上面。
传统SSM服务可以写在xml上。
<context:component-scan base-package="com"/>
拓展:@Configuration注解内部也引用了@Component注解,所以它也会被加载到IOC容器,作为一个Bean。这就是与xml文件不同的区别。
xml驱动与注解驱动互通
目的是实现一方引用另一方。
xml引入注解
在 xml 中要引入注解配置,需要开启注解配置,同时注册对应的配置类:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启注解配置 -->
<context:annotation-config />
<bean class="com.linkedbear.spring.annotation.d_importxml.config.AnnotationConfigConfiguration"/>
</beans>
注解引入xml
在注解配置中引入 xml ,需要在配置类上标注 @ImportResource 注解,并声明配置文件的路径:
@Configuration
@ImportResource("classpath:annotation/beans.xml")
public class ImportXmlAnnotationConfiguration {
}
依赖查找
用户根据name OR type在IOC容器中获取组装好的Bean
先看一个对象交给IOC容器管理
public class MysqlDAOImpl implements DemoDAO {}
<bean id="mysqlDao" class="dao.Impl.MysqlDAOImpl"/>
上述代码表示将该对象交给IOC容器管理,成为了一个bean对象。
| 名称 | 解释 |
| id | bean的名称 |
| class | bean的类型 |
byName
根据bean的id取查找bean。
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MysqlDAOImpl mysqlDao = (MysqlDAOImpl) context.getBean("mysqlDao");
System.out.println(mysqlDao);
byType
根据bean的class去查找bean
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MysqlDAOImpl mysqlDao = (MysqlDAOImpl) context.getBean(MysqlDAOImpl.class);
System.out.println(mysqlDao)
ofType
如果一个接口有多个实现,而咱又想一次性把这些都拿出来,那 getBean 方法显然就不够用了,需要使用额外的方式。
而ofType可以实现传入一个接口 / 抽象类,返回容器中所有的实现类 / 子类。
<bean id="mysqlDao" class="dao.Impl.MysqlDAOImpl"/>
<bean id="oracleDao" class="dao.Impl.OracleDAOImpl"/>
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Map<String, DemoDAO> ofTypes = context.getBeansOfType(DemoDAO.class);
ofTypes.forEach((beanName,bean) -> {
System.out.println(beanName + " : " + bean.toString());
});
}
}
withAnnotation注解查找
IOC 容器除了可以根据一个父类 / 接口来找实现类,还可以根据类上标注的注解来查找对应的 Bean 。下面咱来测试包含注解的 Bean 如何被查找。
声明一个注解:@Color
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Color {
}
创建三个bean对象:Red,Blue,Dog。给Red,Blue添加上@Color注解
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xZqDKcSl-1639742651264)(https://secure1.wostatic.cn/static/uXW1U2iD1x2jWp4RJyXTBV/image.png)]](https://img-blog.csdnimg.cn/45c5b5aab5a24a4680dbcb968a4f7bcf.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6YKj5LmI5Zac5qyi5pWy5Luj56CB,size_20,color_FFFFFF,t_70,g_se,x_16)
测试:
public class MainTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Map<String, Object> beans = context.getBeansWithAnnotation(Color.class);
beans.forEach((beanName,bean) -> {
System.out.println("beanName:" + beanName + ", bean:" + bean);
});
}
}
获取IOC容器中的所有Bean
要用到 ApplicationContext 的另一个方法了:getBeanDefinitionNames 。
这个方法是通过id去查找bean的,而并非是name。
测试:
public class MainTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Map<String, Object> beans = context.getBeansWithAnnotation(Color.class);
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
}
延时查找
实现缺省策略:通过applicationContext去查询所获取的bean是否存在,存在则获取,不存在做后续操作,比如创建。
用途
我想获取一个 Bean 的时候,你可以先不给我报错,先给我一个包装让我拿着,回头我自己用的时候再拆开决定里面有还是没有,这样是不是就省去了 IOC 容器报错的麻烦事了呢?
实现
在 SpringFramework 4.3 中引入了一个新的 API :ObjectProvider ,它可以实现延迟查找。
ObjectProvider 中还有一个方法:getIfAvailable ,它可以在找不到 Bean 时返回 null 而不抛出异常。使用这个方法,就可以避免上面的问题了。
public class LazyLookupApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Cat cat = ctx.getBean(Cat.class);
System.out.println(cat);
// 下面的代码会报Bean没有定义 NoSuchBeanDefinitionException
// Dog dog = ctx.getBean(Dog.class);
// 这一行代码不会报错
ObjectProvider<Dog> dogProvider = ctx.getBeanProvider(Dog.class);
Dog dog = dogProvider.getIfAvailable();
if (dog == null) {
dog = new Dog();
}
}
}
依赖注入
创建的 Bean 都是不带属性的!如果我要创建的 Bean 需要一些预设的属性,那该怎么办呢?那就涉及到 IOC 的另外一种实现了,就是依赖注入。
还是延续 IOC 的思想,如果你需要属性依赖,不要自己去找,交给 IOC 容器,让它帮你找,并给你赋上值。
简单属性注入
public class Person {
private String name;
private Integer age;
// getter and setter ......
}
<bean id="person" class="com.linkedbear.spring.basic_di.a_quickstart_set.bean.Person">
<property name="name" value="test-person-byset"/>
<property name="age" value="18"/>
</bean>
通过依赖查找获取Person实例查询到的结果:
Person{name='test-person-byset', age=18}
Setter注入
@Bean
public Person person() {
Person person = new Person();
person.setName("test-person-anno-byset");
person.setAge(18);
return person;
}
<bean id="person" class="com.linkedbear.spring.basic_di.a_quickstart_set.bean.Person">
<property name="name" value="test-person-byset"/>
<property name="age" value="18"/>
</bean>
构造注入
@Bean
public Person person() {
return new Person("test-person-anno-byconstructor", 18);
}
<bean id="person" class="com.linkedbear.spring.basic_di.b_constructor.bean.Person">
<constructor-arg index="0" value="test-person-byconstructor"/>
<constructor-arg index="1" value="18"/>
</bean>
注解式属性注入
@Component下属性注入
@Component
public class Black {
@Value("black-value-anno")
private String name;
@Value("0")
private Integer order;
}
外部配置文件引入-@PropertySource
resources目录下创建red.properties文件
**(一)**
red.name=red-value-byproperties
red.order=1
**(三) **
@Value("${red.name}")
private String name;
@Value("${red.order}")
private Integer order;
**(二)**
@Configuration
// 顺便加上包扫描
@ComponentScan("com.bean")
@PropertySource("classpath:red.properties")
public class InjectValueConfiguration {
}
SpEL表达式
SpEL 全称 Spring Expression Language ,它从 SpringFramework 3.0 开始被支持,它本身可以算 SpringFramework 的组成部分,但又可以被独立使用。它可以支持调用属性值、属性参数以及方法调用、数组存储、逻辑计算等功能。
SpEL 的语法统一用 #{} 表示,花括号内部编写表达式语言。

![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IuJpOz5L-1639742651267)(https://secure1.wostatic.cn/static/mT2Vsoh3WXE97zTNSzusC2/image.png)]](https://img-blog.csdnimg.cn/e93c0576043145ad9edbfbe61fcf262c.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6YKj5LmI5Zac5qyi5pWy5Luj56CB,size_20,color_FFFFFF,t_70,g_se,x_16)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qe0JxCQP-1639742651269)(https://secure1.wostatic.cn/static/nWvzrUT4YvvwQVkDAgZjnu/image.png)]](https://img-blog.csdnimg.cn/45539c1310e349b7a80c9f967bfe23ed.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6YKj5LmI5Zac5qyi5pWy5Luj56CB,size_19,color_FFFFFF,t_70,g_se,x_16)
自动注入@Autowired
在 Bean 中直接在 属性 / setter 方法 上标注 @Autowired 注解,IOC 容器会按照属性对应的类型,从容器中找对应类型的 Bean 赋值到对应的属性上,实现自动注入。
使用该注解的前提是注入的属性必须是已经在IOC容器中所管理,否则在程序运行时会找不到该Bean,启动失败。如果想不让程序抛异常可以给@Autowired 注解上加一个属性:required = false 。
@Autowired(required = false)
private Person person;
@Qualifier:指定Bean注入的名称
我们IOC容器里出现两个Person的对象的时候,再注入时控制台会报错:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.linkedbear.spring.basic_di.d_complexfield.bean.Person' available: expected single matching bean but found 2: administrator,master
IOC 容器发现有两个类型相同的 Person ,它也不知道注入哪一个了,索性直接 “我选择死亡” ,就挂了。
解决方式:
@Qualifier 注解的使用目标是要注入的 Bean ,它配合 @Autowired 使用,可以显式的指定要注入哪一个 Bean :
@Autowired
@Qualifier("administrator")
private Person person;
同时我们也可能得出结论,@Autowired这个注解是根据Bean的类型去IOC容器中查找的。
JSR250-@Resource
@Resource 也是用来属性注入的注解,它与 @Autowired 的不同之处在于:@Autowired**** 是按照类型注入,@Resource 是直接按照属性名 / Bean的名称注入。
JSR330-@Inject
JSR330 也提出了跟 @Autowired 一样的策略,它也是按照类型注入。不过想要用 JSR330 的规范,需要额外导入一个依赖:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
剩下的使用方式就跟 SpringFramework 原生的 @Autowired + @Qualifier 一样了:
@Component
public class Cat {
@Inject // 等同于@Autowired
@Named("admin") // 等同于@Qualifier
private Person master;
}
回调注入
。。。
延时注入
setter的延迟注入
@Component
public class Dog {
private Person person;
@Autowired
public void setPerson(ObjectProvider<Person> person) {
// 有Bean才取出,注入
this.person = person.getIfAvailable();
}
构造器的延迟注入
@Component
public class Dog {
private Person person;
@Autowired
public Dog(ObjectProvider<Person> person) {
// 如果没有Bean,则采用缺省策略创建
this.person = person.getIfAvailable(Person::new);
}
属性字段的延迟注入
@Autowired
private ObjectProvider<Person> person;
@Override
public String toString() {
// 每用一次都要getIfAvailable一次
return "Dog{" + "person=" + person.getIfAvailable(Person::new) + '}';
}
Bean作用域
为什么会有作用域的概念:
说白了就是资源有限,如果一个资源同时被多个地方访问(如全局常量),那就可以把作用域提的很高;反之,如果一个资源伴随着一个时效性强的、带强状态的动作,那这个作用域就应该局限于一个动作,不能被这个动作之外干扰。
SpringFramework 中内置了 6 种作用域(5.x 版本):
| 作用域类型 | 概述 |
|---|---|
| singleton | 一个 IOC 容器中只有一个【默认值】 |
| prototype | 每次获取创建一个 |
| request | 一次请求创建一个(仅Web应用可用) |
| session | 一个会话创建一个(仅Web应用可用) |
| application | 一个 Web 应用创建一个(仅Web应用可用) |
| websocket | 一个 WebSocket 会话创建一个(仅Web应用可用) |
singleton:单实例Bean
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wWoQ2DHv-1639742651270)(https://secure1.wostatic.cn/static/j69VR1cVCA1xoNsUrP164L/image.png)]](https://img-blog.csdnimg.cn/197225da0de34314a1314fb7c2f1eb7b.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6YKj5LmI5Zac5qyi5pWy5Luj56CB,size_20,color_FFFFFF,t_70,g_se,x_16)
SpringFramework 中默认所有的 Bean 都是单实例的,即:一个 IOC 容器中只有一个。
prototype:原型Bean
Spring 官方的定义是:**每次对原型 Bean 提出请求时,都会创建一个新的 Bean 实例。**这里面提到的 ”提出请求“ ,**包括任何依赖查找、依赖注入的动作,都算做一次 ”**提出请求“ 。由此咱也可以总结一点:如果连续 getBean() 两次,那就应该创建两个不同的 Bean 实例;向两个不同的 Bean 中注入两次,也应该注入两个不同的 Bean 实例。SpringFramework 的官方文档中也给出了一张解释原型 Bean 的图:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TrQt4lrf-1639742651271)(https://secure1.wostatic.cn/static/3fPWPVimNvPYt9x6Q4AQoY/image.png)]](https://img-blog.csdnimg.cn/f19849be28024ca486550d5f902d7fc7.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6YKj5LmI5Zac5qyi5pWy5Luj56CB,size_20,color_FFFFFF,t_70,g_se,x_16)
Bean生命周期
生命周期阶段

- 创建 / 实例化阶段:此时会调用类的构造方法,产生一个新的对象
- 初始化阶段:此时对象已经创建好,但还没有被正式使用,可能这里面需要做一些额外的操作(如预初始化数据库的连接池)
- 运行使用期:此时对象已经完全初始化好,程序正常运行,对象被使用
- 销毁阶段:此时对象准备被销毁,已不再使用,需要预先的把自身占用的资源等处理好(如关闭、释放数据库连接)
- 回收阶段:此时对象已经完全没有被引用了,被垃圾回收器回收
在spring阶段我们能干预的就只有初始化和销毁两个阶段。
控制Bean生命周期的三种方式
| init-method & destroy-method | @PostConstruct & @PreDestroy | InitializingBean & DisposableBean | |
|---|---|---|---|
| 执行顺序 | 最后 | 最先 | 中间 |
| 组件耦合度 | 无侵入(只在 <bean> 和 @Bean 中使用) | 与 JSR 规范耦合 | 与 SpringFramework 耦合 |
| 容器支持 | xml 、注解原生支持 | 注解原生支持,xml需开启注解驱动 | xml 、注解原生支持 |
| 单实例Bean | √ | √ | √ |
| 原型Bean | 只支持 init-method | √ | √ |
对于原型 Bean 的生命周期,使用的方式跟上面是完全一致的,只是它的触发时机就不像单实例 Bean 那样了。
单实例 Bean 的生命周期是陪着 IOC 容器一起的,容器初始化,单实例 Bean 也跟着初始化(当然不绝对,后面会介绍延迟 Bean );容器销毁,单实例 Bean 也跟着销毁。原型 Bean 由于每次都是取的时候才产生一个,所以它的生命周期与 IOC 容器无关。