SpringSpring 入门介绍
Posted 吞吞吐吐大魔王
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringSpring 入门介绍相关的知识,希望对你有一定的参考价值。
文章目录
- 1. Spring 介绍
- 2. Spring 项目的创建和使用
- 3. Spring 更简单的读取和存储对象方式
- 4. Bean 的作用域
- 5. Bean 的原理分析
1. Spring 介绍
1.1 Spring 是什么?
Spring(Spring Framework) 是一个开源的轻量级框架,是包含了众多工具方法的 IoC 容器。
那么什么 是 IoC 容器呢?
容器 本身的含义是用来容纳某种物品的装置,而在之前的学习中,我们就应该接触过一些容器了,比如存储数据的容器 List、Map、Set 等等,Web 容器 Tomcat 等等。
那么 IoC 是什么呢?
1.2 IoC 是什么?
IoC(Inversion of Control),译为控制反转,它不是什么技术,而是一种思想。在 Java 开发中,IoC 意味着你将设计好的依赖对象 B 交给容器控制,而不是按照传统的方式,在你需要使用依赖对象 B 的对象 A 内部直接控制。
IoC 既然译为控制反转,那么我们就要理解谁控制谁,控制什么?要理解为何要反转,反转了什么?
谁控制谁,控制什么?
按照传统的设计思想,如果我们想要在 A 对象内部使用 B 对象,那么我们就会在 A 中直接 new 一个 B,这是我们程序员主动去创建的依赖对象;而 IoC 容器能够存储你需要的对象 B,当你要使用这个依赖对象 B 时,IoC 容器就会来控制依赖对象的创建,而程序只需要引入这个已经创建好的对象 B 就行。IoC 容器主要是控制了外部资源的获取(不只是包含要依赖的对象,还包括文件等等)。
为何要反转,反转了什么?
按照传统的设计思想,如果我们想要在 A 对象内部使用 B 对象,那么我们就会在 A 中直接 new 一个 B,这是我们程序员主动去创建的依赖对象;而反转则是使容器来帮忙创建并注入依赖对象,它使得对象 A 不需要再主动去创建对象 B 了,而是被动的接受由容器已经创建好的依赖对象,即对象创建、管理的控制权被反转了,依赖对象的获取被反转了。
IoC 的作用:
IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了 IoC 容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试。除此之外,将对象存储在 IoC 容器相当于将以后可能⽤到的所有⼯具制作好后都放到仓库中,需要的时候直接取出就⾏,⽤完再把它放回到仓库,利于复用。⽽ new 对象的⽅式相当于,每次需要⼯具时,才现做,⽤完就扔掉了也不会保存,下次再⽤的时候还得重新做。
注意:
并不是只有 Spring 的容器才叫 IoC 容器,基于 IoC 容器的框架还有很多,并非是 Spring 特有的
到这里 IoC 已经差不多介绍完了,从上面的介绍可知 IoC 它只是一种思想,那么 IoC 思想具体的实现是怎么样的呢?我们就不得不提到 DI。
1.3 DI 是什么?
DI(Dependency Injection),译为依赖注入,它是指 IoC 容器在运行期间,动态地将某种依赖关系注入到对象之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
DI 既然译为依赖注入,那么我们就要理解谁依赖谁,为什么需要依赖,谁注入谁,注入了什么?
- 谁依赖谁:应用程序依赖 IoC 容器。
- 为什么需要依赖:应用程序需要 IoC 容器来提供对象所需的外部资源。
- 谁注入谁:IoC 容器注入应用程序某个对象。
- 注入了什么:注入了某个对象所需的外部资源(包括对象、资源、常量数据等等)。
IoC 和 DI 的关系:
IoC 和 DI 其实是同一个概念的不同角度的描述,IoC 是一种思想,而 DI 是具体的实现,通过引入 IoC 容器,利用依赖关系注入的方式,就能实现对象之间的解耦。
1.4 Spring 的核心功能
回归主题,Spring 是包含了众多工具方法的 IoC 容器,我们已经介绍了 IoC 是什么,那么如何理解“Spring 就是一个 IoC 容器”呢?
上面介绍了很多关于 IoC 的知识,但是 Spring 的本质上是一个容器,容器才是 Spring 框架实现功能的核心,既然 Spring 是一个容器,它就会具备容器的两个最基础的功能:
- 将对象存入到容器
- 从容器中取出对象
也就是说 Spring 最核心的功能 就是将对象存入到 Spring 中,再从 Spring 中获取到对象。又因为 Spring 是⼀个 IoC 容器,那么除了它本身具备的存储对象和获取对象的能⼒,还具有管理对象的创建和销毁的权利。
1.5 Spring 的应用上下文
我们已经了解了 Spring 是一个 IoC 容器,但是容器只是一个提供给需要被管理的对象的空间,还需要通过 Spring 的应用上下文,来向容器中添加我们需要的被管理的对象。
Spring 的应用上下文 可以简单的理解成将你需要 Spring 帮你管理的对象放入到容器的容器对象,它是 Spring 容器的一种抽象化表述。
常用的 Spring 上下文对象为 ApplicationContext
,它本质上就是一个维护 Bean 定义以及对象之间协作关系的高级接口,它是继承了上下文对象 BeanFactory
后派生而来的应用上下文的抽象接口。在功能上,相比于 BeanFactory
只能提供基本的 DI 功能,它能提供更多的企业级的服务,比如对国际化支持、资源访问、以及事件传播等等。在性能上,ApplicationContext
是⼀次性加载并初始化所有的 Bean 对象(在获取上下文时,就会创建初始化所有的 Bean对象),所以调用快,⽽ BeanFactory
是需要哪个 Bean 才去加载哪个 Bean(在 getBean 时才会创建初始化指定的 Bean 对象),因此更加轻量,但调用慢。
对于上述两种上下文抽象接口,Spring 提供了多种类型的容器实现,用于不同场景的使用
ClassPathXmlApplicationContext
:从类路径下的一个或多个 xml 配置文件中加载上下文,适用于 xml 配置的方式。AnnotationConfigApplicationContext
:从基于一个或多个 Java 的配置类中加载上下文,适用于 Java 注解的方式。FileSystemXmlApplicationContext
:从文件系统下的一个或多个 xml 配置文件中加载上下文,从系统盘符中加载 xml 配置文件。AnnotationConfigWebApplicationContext
:专门为 web 应用准备的,适用于注解方式。XmlWebApplicationContext
:从 web 应用下的一个或多个 xml 配置文件加载上下文,适用于 xml 配置方式。
2. Spring 项目的创建和使用
在 Java 语言中对象也叫做 Bean,因此以下遇到的对象将以 Bean 著称。
2.1 创建 Maven 项目
-
创建一个 Maven 项目(在 IDEA 中创建好 Maven 项目)
-
添加 Spring 框架支持(在项目的
pom.xml
文件中添加 Spring 框架的支持,配置如下)Spring 项目需要添加的依赖有
spring-context
(Spring 上下文)和spring-beans
(管理对象的模块)。可以去 Maven 中央仓库搜索,也可以直接使用下面的依赖。<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>5.2.3.RELEASE</version> </dependency> </dependencies>
-
添加启动类(在
/src/main/java
目录下创建一个启动类,类名自定义,包含 main 方法即可)
2.2 存储 Bean 对象
-
创建好需要的 Bean(可以创建一个包,用于存放要创建的 Bean)
存储 Bean 分为两个步骤,先创建好需要的 Bean,再将创建好的 Bean 注册到 Spring 容器中
-
将 Bean 注册到 Spring 容器中
先在
resources
目录下创建一个xml
文件(文件名没有要求),用于存放 Spring 的配置文件,配置文件是固定的内容,内容如下。<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
将创建好的 Bean 添加到 Spring 配置文件中。在原有的 beans 标签内再创建一个 beans 标签,里面用于存放要注册的 Bean,然后在创建好的 beans 标签内创建一个 bean 标签,表示要注册的某个 Bean,该 bean 标签内添加一个 id 属性表示存储对象在 Spring 中的身份标识,即 Bean 对象的名字,再添加一个 class 属性表示要存储的对象(包含包名和类名)
补充: Spring 存储 Bean 时的命名规则
- 当 bean 标签中主动设置 id 属性来命名 Bean 对象的名字时,Spring 中存储该 Bean 的名字即为 id 的值。(源码如下)
- 当 bean 标签没有设置 id 属性来命名 Bean 对象的名字时,Spring 中存储该 Bean 时生成的命名规则为:当类名的长度大于1,且第一个字符和第二个字符都为大写时,则直接命名为类名;否则将类名的第一个字符转成小写后,再返回新生成的字符。(源码如下)
2.3 获取并使用 Bean 对象
-
创建 Spring 上下文(这里使用
ApplicationContext
作为上下文对象,也可以使用BeanFactory
)// 得到 Spring 的上下⽂对象,创建的时候需要配置 Spring 配置信息 ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
-
获取指定的 Bean 对象(这里使用上文正已经注册到 Spring 容器中的 Bean 对象 User)
getBean
方法用于加载上下文中的 Bean 对象,它有多种重载的方法-
Object getBean(Bean 对象的名字)
-
T getBean(Bean 对象的名字, 要加载的类对象)
-
T getBean(要加载的类对象)
注意: 如果当前的一个类型被重复注册到 Spring 的配置文件中,那么不能使用第三种方式去获取 Bean 对象,需要根据名称来获取。
// 方式一 User user = (User) context.getBean("user"); // 方式二 User user = (User) context.getBean("user", User.class); // 方式三 User user = (User) context.getBean(User.class);
-
-
使用 Bean
user.say();
注意:Spring 中的 Bean 默认是单例模式。可以再创建一个 User 实例,打印两个实例得到结论
3. Spring 更简单的读取和存储对象方式
上面实现了基本的 Spring 存储和读取对象的操作,但是在操作过程中并不是那么的简单,接下来将介绍更简单的存储和读取 Bean 对象的方法,即使用注解。
3.1 存储 Bean 对象
之前将 Bean 存储到 Spring 中的方法,是在 Spring 的配置文件中直接添加 bean 注册内容,示例如下:
而通过使用注解,就无需在存储一个 Bean 对象时,在 Spring 的配置文件中再添加一个注册内容,只要在 Spring 的配置文件中配置一个扫描路径即可实现 Bean 的批量注册。
3.1.1 配置扫描路径
Spring 引入了组件自动扫描机制,它可以在类路径底下,扫描配置的 base-package
包下所有标注了@Component
、@Service
、@Controller
、@Repository
、@Configuration
注解的类,并把这些类纳入进Spring 容器中管理。
在 Spring 配置文件中添加如下配置:
<?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:content="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
https://www.springframework.org/schema/context/spring-context.xsd">
<content:component-scan base-package="com.t4.beans">
</content:component-scan>
</beans>
3.1.2 注册 Bean 相关的注解介绍
想要将对象存储在 Spring 中,有两种类型的注解可以实现:
-
类注解: 注解在类上,用于标识组件的类型
注解 描述 @Controller
组合了 @Component
注解,应用在 mvc 层(控制层),DispatcherServlet
会自动扫描注解了此注解的类,然后将 web 请求映射到注解了@RequestMapping
的方法上。@Service
组合了 @Component
注解,应用在 service 层(业务逻辑层)。@Repository
组合了 @Component
注解,应用在 dao 层(数据访问层)。@Component
表示该类是一个“组件”,成为 Spring 管理的 Bean。当使用基于注解的配置和类路径扫描时,这些类被视为自动检测的候选对象。同时 @Component
还是一个元注解。@Configuration
声明当前类为配置类,其中内部组合了 @Component
注解,表明这个类是一个 Bean。 -
方法注解: 注解在方法上
注解 描述 @Bean
声明当前方法的返回值是一个 Bean,返回的 Bean 对应的类中可以定义 init()
方法和destroy()
方法。
为何说 @Controller
、@Service
、@Repository
、@Configuration
都组合了 @Component
注解呢?
通过源码发现,这些注解本身都带了一个注解
@Component
,即它们本身就属于@Component
的“子类”
3.1.3 添加 @Controller 注解存储 Bean 对象
示例代码:
package com.t4.beans;
import org.springframework.stereotype.Controller;
@Controller
public class UserController
public void say()
System.out.println("你好 Controller!");
3.1.4 添加 @Service 注解存储 Bean 对象
示例代码:
package com.t4.beans;
import org.springframework.stereotype.Service;
@Service
public class UserService
public void say()
System.out.println("你好 Service!");
3.1.5 添加 @Repository 注解存储 Bean 对象
示例代码:
package com.t4.beans;
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository
public void say()
System.out.println("你好 Repository!");
3.1.6 添加 @Component 注解存储 Bean 对象
示例代码:
package com.t4.beans;
import org.springframework.stereotype.Component;
@Component
public class UserComponent
public void say()
System.out.println("你好 Component!");
3.1.7 添加 @Configuration 注解存储 Bean 对象
示例代码:
package com.t4.beans;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfiguration
public void say()
System.out.println("你好 Configuration!");
3.1.8 添加 @Bean 注解存储 Bean 对象
注解 @Bean
需要与类注解搭配使用,例如常与注解 @Configuration
结合使用。带有 @Bean
注解的方法返回的对象会存到 Spring 容器中,Bean 对象的名字为方法名。
虽说使用 @Bean
注解要搭配类注解使用,看似要多出一笔,但是加上了类注解后能大大加快扫描的性能。
示例代码:
package com.t4.beans;
import com.t4.model.UserInfo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserBeans
@Bean
public UserInfo getUserInfo()
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setName("张三");
userInfo.setPassword("1234");
return userInfo;
重命名 Bean:
由于方法名不能很直观的显示 Bean 名,为此可以重命名 Bean。重命名方式为:在 @Bean
注解后面增加一个 name 属性,该属性可以含多个值,其中 name =
都可以省略,只带新的 Bean 名。
注意1: 重命名后,如果起了多个名字,则使用任意一个都可以。但是不能再使用原方法名去获取 Bean
注意2: 重命名后,如果起了多个名字,获取的对象都是同一个
3.2 获取 Bean 对象
获取 Bean 对象也叫做对象装配或者对象注入,是把对象从容器中取出来放到某个类中。
之前我们介绍了先通过获取 Spring 应用上下文,再从中获取 Bean 对象的方法去得到 Bean,但是整体显得比较麻烦。接下来将介绍三种更简单的对象注入的方法:
- 方法一:属性注入
- 方法二:构造方法注入
- 方法三:Setter 注入
3.2.1 注入 Bean 相关注解介绍
这里介绍三种和注入 Bean 相关的注解:
注解 | 描述 | 包含的属性 |
---|---|---|
@Autowired | 可以标注在属性、构造方法和 Setter 方法上,用于完成自动装配。默认通过 Bean 类型进行查询,如果查询不到则会再通过 Bean 对象名进行查询,为 Spring 提供的注解。 | required |
@Resource | 可以标注在属性和 Setter 方法上,用于完成自动装配。默认通过 Bean 名进行查询,为 JDK 提供的注解。 | name 、type 等 |
@Qualifier | 用于指定需要注入的 Bean 对象的名字 | value |
- 使用
@Autowired
自动注入的时候,可以在属性或参数加上@Qualifier("xxx")
指定注入到对象; - 使用
@Autowired
注解时,当 Spring 无法找到匹配的 Bean 装配,它会抛出异常。要解决这个问题,可以通过@Autowired
的required
属性设置为 false 来禁用此检查功能。 - Spring 将
@Resource
注解的 name 属性的值解析为为 Bean 的名字,type 属性的值解析为为 Bean 的类型。 - 使用
@Resource
注解默认通过 Bean 名进行查询装配。如果使用 name 属性,则使用 Bean 名 自动注入策略;如果使用 type 属性,则使用 Bean 类型自动注入策略。 @Autowired
注解用于构造方法注入时,如果只有一个构造方法,可以省略使用@Autowired
,如果有多个注解则不能省略。@Autowired
可以用于数组和使用泛型的集合类型,然后 Spring 会将容器中所有类型符合的 Bean 注入进来。@Autowired
标注作用于 Map 类型时,如果 Map 的 key 为 String 类型,则 Spring 会将容器中所有类型符合 Map 的 value 对应的类型的 Bean 增加进来,用 Bean 的 id 或 name 作为 Map 的 key。@Autowired
标注作用于普通方法时,会产生一个副作用,就是在容器初始化该 Bean 实例的时候就会调用该方法。当然,前提是执行了自动装配,对于不满足装配条件的情况,该方法也不会被执行。
3.2.2 属性注入 Bean 对象
在需要的类中先创建一个要被注入的对象(该对象可以是一个集合,那么得到的结果就是所有匹配的 Bean 的集合),并在该对象上加上 @Autowired
或 @Resource
注解。
代码情景:
创建了一个 UserService 类,里面包含了 name 和 password 两个字段,将这个类存储到 Spring 中。在上述代码中的 UserController 类中通过 @Autowired 注解来将 UserService 这个对象注入到该类中,并创建了一个 getUserService 方法打印获取到的 Bean 对象。而 UserController 类本身又在启动类中被获取后调用 getUserService 方法。
示例代码1: 使用 @Autowired
注解
@Controller
public class UserController
@Autowired
private UserService userService;
public void getUserService()
System.out.println(userService);
示例代码2: 使用 @Resource
注解
@Controller
public class UserController
@Resource
private UserService userService;
public void getUserService()
System.out.println(userService);
3.2.3 构造方法注入 Bean 对象
在需要的类中先创建一个要被注入的对象,然后创建该类的构造方法,将要被注入的对象进行赋值,并在构造方法上加上 @Autowired
注解。
代码情景:
创建了一个 UserService 类,里面包含了 name 和 password 两个字段,将这个类存储到 Spring 中。在上述代码中的 UserController 类中通过 @Autowired 注解来将 UserService 这个对象注入到该类中,并创建了一个 getUserService 方法打印获取到的 Bean 对象。而 UserController 类本身又在启动类中被获取后调用 getUserService 方法。
示例代码: 使用 @Autowired 注解
@Controller
public class UserController
private UserService userService;
@Autowired
public UserController(UserService userService)
this.userService = userService;
public void getUSerService()
System.out.println(userService);
3.2.4 Setter 注入 Bean 对象
在需要的类中先创建一个要被注入的对象,然后创建该类的 Setter 方法,将要被注入的对象进行赋值,并在 Setter方法上加上 @Autowired
或 @Resource
注解。
代码情景:
创建了一个 UserService 类,里面包含了 name 和 password 两个字段,将这个类存储到 Spring 中。在上述代码中的 UserController 类中通过 @Autowired 注解来将 UserService 这个对象注入到该类中,并创建了一个 getUserService 方法打印获取到的 Bean 对象。而 UserController 类本身又在启动类中被获取后调用 getUserService 方法。
示例代码1: 使用 @Autowired
注解
@Controller
public class UserController
private UserService userService;
@Autowired
public void setUserService(UserService userService)
this.userService = userService;
public void getUSerService()
System.out.println(userService);
示例代码2: 使用 @Resource
注解
@Controller
public class UserController
private UserService userService;
@Resource
public void setUserService(UserService userService)
this.userService = userService;
public void getUSerService()
System.out.println(userService);
3.2.5 三种注入方式的优缺点
注入方式 | 优点 | 缺点 |
---|---|---|
属性注入 | 写法最简单,可读性高。 | 只适应 IoC 容器,移植性差。 |
构造方法注入 | 可以适应非 IoC 容器,通用性好,代码移植性高。 | 如果有多个注入,代码会显得比较臃肿。 |
Setter 注入 | 只有对象是需要被注入时,才会注入依赖,而不是在这个类初始化的时候就注入。可以适应非 IoC 容器,通用性好,代码移植性高。 | 不能将对象设为 final。 |
3.2.6 同一类型多个 @Bean 报错问题
当出现多个 Bean 返回的对象类型都相同时,如果注入的注解不设置具体注入的哪个 Bean,则会报错。对于这个问题有两种解决方式:
- 对于 @Autowired 注解,可以搭配一个 @Qualifuer 来指定要注入的 Bean
- 对于 @Resource 注解,可以设置一个 name 属性来指定要注入的 Bean
代码情景:
创建了一个 User 类,里面包含了 name 和 password 两个字段。再创建一个 UserService 类这个类通过两个 @Bean 注解的方法注册两个类型相同,名字不同的 Bean 存储到 Spring 中。在上述代码中的 UserController 类中通过 @Autowired 注解来将 User 这个对象注入到该类中,并创建了一个 printUser 方法打印获取到的 Bean 对象。而 UserController 类本身又在启动类中被获取后调用 printUser 方法。
示例代码1: 对于 @Autowired
的解决方式
@Controller
public class UserController
@Autowired
@Qualifier(value = "user1"Spring入门案例