
本篇博客给大家带来的是SpringBoot的知识点, 本篇 接续上篇, 介绍Spring IoC & DI 相关知识. 由于续 上篇文章的内容来讲, 所以目录从 3 开始.
🐎文章专栏: JavaEE进阶
👉gitte链接: 薯条不要番茄酱
🚀若有问题 评论区见
❤ 欢迎大家点赞 评论 收藏 分享
如果你不知道分享给谁,那就分享给薯条.
你们的支持是我不断创作的动力 .
王子,公主请阅🚀
- 要开心
- [3. loC详解](#3. loC详解)
-
- [3.1 Bean的存储](#3.1 Bean的存储)
-
- [3.1.1 @Controller(控制器存储)](#3.1.1 @Controller(控制器存储))
- [3.1.2 @Service(服务存储)](#3.1.2 @Service(服务存储))
- [3.1.3 @Repository(仓库存储)](#3.1.3 @Repository(仓库存储))
- [3.1.4 @Component(组件存储)](#3.1.4 @Component(组件存储))
- [3.1.5 @Configuration(配置存储)](#3.1.5 @Configuration(配置存储))
- [3.2 为什么需要那么多类注解.](#3.2 为什么需要那么多类注解.)
- [3.3 方法注解 @Bean](#3.3 方法注解 @Bean)
-
- [3.3.1 方法注解要配合类注解使用](#3.3.1 方法注解要配合类注解使用)
- [3.3.2 定义多个对象](#3.3.2 定义多个对象)
- [3.3.3 重名命 Bean](#3.3.3 重名命 Bean)
- [3.4 扫描路径](#3.4 扫描路径)
- [4. DI 详解](#4. DI 详解)
-
- [4.1 属性注入](#4.1 属性注入)
- [4.2 构造方法注入](#4.2 构造方法注入)
- [4.3 Setter 注入](#4.3 Setter 注入)
- [4.4 三种注入的优缺点(面试题)](#4.4 三种注入的优缺点(面试题))
- [4.5 @Autowired存在问题](#4.5 @Autowired存在问题)
-
- [4.5.1 @Autowird 与 @Resource的区别?(常见面试题)](#4.5.1 @Autowird 与 @Resource的区别?(常见面试题))
- [5. 总结](#5. 总结)
-
- [5.1 Spring, Spring Boot 和Spring MVC的关系以及区别](#5.1 Spring, Spring Boot 和Spring MVC的关系以及区别)
- [5.2 常见注解有哪些? 作用分别是什么? (常见面试题)](#5.2 常见注解有哪些? 作用分别是什么? (常见面试题))
-
要开心
要快乐
顺便进步
3. loC详解
知道了 Spring IoC 和 DI 的基本操作, 接下来我们来系统的学习Spring IoC 和 DI 的操作. 前面我们提到 IoC 控制反转,就是将对象的控制权交给Spring的IOC容器,由IoC容器创建及管理对象。这其实就是bean的存储.
3.1 Bean的存储
在前面的入门案例中,要把某个对象交给 IoC 容器管理,需要在类上添加一个注解: @Component
而Spring框架为了更好的服务web应用程序, 提供了更丰富的注解.
共有两类注解类型可以实现:
- 类注解:@Controller、@Service、@Repository、@Component、@Configuration.
- 方法注解: @Bean
3.1.1 @Controller(控制器存储)
使用 @Controller 存储 bean 的代码如下所示:
java
@Controller // 将对象存储到 Spring 中
public class HelloController {
public void sayHi() {
System.out.println("hi, userController...");
}
}
如何观察这个对象已经存在Spring容器当中了呢?
java
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
HelloController bean = context.getBean(HelloController.class);
System.out.println(bean);
bean.sayHi();
}
}
ApplicationContext 翻译过来就是: Spring 上下文.因为对象都交给 Spring 管理了,所以获取对象要从 Spring 中获取,那么就得先得到 Spring 的上下文.
运行程序,并观察运行结果:

发现成功从Spring 中获取到 Controller 对象,并执行 Controler.sayHi 方法.
如果把@Controller 删掉, 再观察运行结果

报错提示: 找不到类型为: com.bite.ioc.controller.HelloController 的bean.
获取bean对象的其他方式
上述代码是根据类型来查找对象, 如果Spring容器中, 同一个类型存在多个bean的话, 怎么来获取呢?
ApplicationContext 也提供了其他获取bean的方式, ApplicationContext 获取bean对象的功能, 是父类BeanFactory提供的功能.
java
public interface BeanFactory {
//...
// 1. 根据bean名称获取bean
Object getBean(String var1) throws BeansException;
// 2. 根据bean名称和类型获取bean
<T> T getBean(String var1, Class<T> var2) throws BeansException;
// 3. 按bean名称和构造函数参数动态创建bean,只适⽤于具有原型(prototype)作⽤域的bean
Object getBean(String var1, Object... var2) throws BeansException;
// 4. 根据类型获取bean
<T> T getBean(Class<T> var1) throws BeansException;
// 5. 按bean类型和构造函数参数动态创建bean, 只适⽤于具有原型(prototype)作⽤域的bean
<T> T getBean(Class<T> var1, Object... var2) throws BeansException;
//...
}
常用的是上述1,2,4种, 这三种方式,获取到的bean是一样的。其中1,2种都涉及到根据名称来获取对象. bean的名称是什么呢?
Spring bean 是 Spring框架 在运行时管理的对象, Spring 会给管理的对象起一个名字. 比如学校管理学生, 会给每个学生分配一个学号, 根据学号, 就可以找到对应的学生.
Spring也是如此, 给每个对象起一个名字, 根据Bean的名称(BeanId)就可以获取到对应的对象.
Bean 命名约定 , 官方文档是这样说明的 Bean官方文档👇

程序开发人员不需要为bean指定名称(BeanId), 如果没有显式的提供名称(BeanId),Spring容器将为该
bean生成唯一的名称.
一般来说, bean名称以小写字母开头,然后使用驼峰式大小写
比如:
类名: UserController, Bean的名称为: userController
类名: AccountManager, Bean的名称为: accountManager
类名: AccountService, Bean的名称为: accountService
也有一些特殊情况, 当有多个字符并且第一个和第二个字符都是大写时, 将保留原始的大小写.
比如
类名: UController, Bean的名称为: UController
类名: AManager, Bean的名称为: AManager
根据命名规则, 我们来获取Bean, 顺便验证第一,二,四种方法获取的Bean对象是否相同
java
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
HelloController bean = context.getBean(HelloController.class);
HelloController bean1 = context.getBean("helloController", HelloController.class);
HelloController bean2 = (HelloController)context.getBean("helloController");
System.out.println(bean);
System.out.println(bean1);
System.out.println(bean2);
}
}
程序运行结果如下:

可以看到地址一样, 说明对象是一个。
ApplicationContext 和 BeanFactory的区别(常见面试题)
① 继承关系和功能方面来说:Spring 容器有两个顶级的接口:BeanFactory 和
ApplicationContext。其中 BeanFactory 提供了基础的访问容器的能力,而
ApplicationContext 属于 BeanFactory 的子类,它除了继承了 BeanFactory 的所有功能之外,它还拥有独特的特性,还添加了对国际化支持、资源访问支持、以及事件传播等方面的支持.
② 从性能方面来说:ApplicationContext(饿汉) 是一次性加载并初始化所有的 Bean 对象,而BeanFactory(懒汉) 是需要那个才去加载那个,因此更加轻量. (空间换时间)
3.1.2 @Service(服务存储)
使用 @Service 存储 bean 的代码如下所示:
java
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserService bean = context.getBean(UserService.class);
bean.doService();
}
}
读取 bean 的代码:
java
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserService bean = context.getBean(UserService.class);
bean.doService();
}
}
获取成功
3.1.3 @Repository(仓库存储)
使用 @Repository 存储 bean 的代码如下所示:
java
@Repository
public class UserRepo {
public void doRepo() {
System.out.println("do Repo...");
}
}
读取 bean 的代码:
java
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserRepo bean3 = context.getBean(UserRepo.class);
bean3.doRepo();
}
}
观察运行结果, 发现成功从Spring 中获取到 UserRepo 对象, 并执行 UserRepo 的 doRepo方法.
3.1.4 @Component(组件存储)
使用 @Repository 存储 bean 的代码如下所示:
java
@Component
public class UserComponent {
public void doComponent() {
System.out.println("do component...");
}
}
读取的代码:
java
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserComponent bean1 = context.getBean(UserComponent.class);
bean1.doComponent();
}
}
观察运行结果, 发现成功从Spring中获取到UserComponent 对象 , 并执行UserRepository 的 doComponent 方法.

3.1.5 @Configuration(配置存储)
使用 @Configuration 存储 bean 的代码如下所示:
java
@Configuration
public class UserConfig {
public void doConfig() {
System.out.println("do Config...");
}
}
读取bean 代码:
java
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserConfig bean2 = context.getBean(UserConfig.class);
bean2.doConfig();
}
}
观察运行结果, 发现成功从Spring中获取到 UserConfig 对象, 并执行 doConfig 方法.

3.2 为什么需要那么多类注解.
这和前面讲的应用分层是呼应的. 让程序员看到类注解之后,就能直接了解当前类的用途.
@Controller:控制层, 接收请求, 对请求进行处理, 并进行响应.
@Service: 业务逻辑层, 处理具体的业务逻辑.
@Repository:数据访问层,也称为持久层. 负责数据访问操作.
@Configuration:配置层. 处理项目中的一些配置信息.

上述类注解之间的关系:
查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发现:
实这些注解里面都有一个注解 @Component 它们本身就是属于 @Component 的"子类". @Component 是一个元注解,也就是说可以注解其他类注解.
@Controller , @Service ,@Repository 等. 这些注解被称为 @Component 的衍生注解.
在使用的时候, @Component 可以代替上述四个注解,但是不建议这么做。不然分层不就白分了.同时代码可读性不高.
3.3 方法注解 @Bean
类注解是添加到某个类上的, 但是存在两个问题:
- 使用外部包里的类, 没办法添加类注解。
- 一个类, 需要多个对象, 比如多个数据源。
这种场景, 我们就需要使用方法注解 @Bean
方法注解@Bean的使用
java
public class BeanConfig {
@Bean
public UserInfo UCInfo() {
return new UserInfo("wangwu");
}
}
读取bean代码
java
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserInfo bean = (UserInfo) context.getBean("UCInfo");
System.out.println(bean);
}
}
运行程序,发现报错了, 报错信息: A component required a bean named 'UCInfo' that could not be found. 找不到该 bean. 这又是为什么?
3.3.1 方法注解要配合类注解使用
在 Spring 框架的设计中,方法注解 @Bean 要配合类注解才能将对象正常的存储到 Spring 容器中,如下代码所示:
java
@Configuration
public class BeanConfig {
@Bean
public UserInfo UCInfo() {
return new UserInfo("wangwu");
}
}
运行程序, 结果如下:

3.3.2 定义多个对象
对于同一个类, 如何定义多个对象呢?
java
@Configuration
public class BeanConfig {
@Bean
public UserInfo userInfo() {
return new UserInfo("zhangsan");
}
@Bean
public UserInfo userInfo1() {
return new UserInfo("lisi");
}
@Bean
public UserInfo UCInfo() {
return new UserInfo("wangwu");
}
}
定义了多个对象的话, 我们根据类型获取对象, 获取的是哪个对象呢?
java
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserInfo beanConfig = context.getBean(UserInfo.class);
System.out.println(beanConfig);
}
}
运行结果:

报错信息显示: 期望只有一个匹配,结果发现了三个 userInfo,userInfo1,UCInfo,可以看出来, @Bean 注解的bean, bean的名称就是它的方法名
接下来我们根据名称来获取bean对象。
java
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserInfo beanConfig = (UserInfo) context.getBean("userInfo");
UserInfo beanConfig1 = (UserInfo) context.getBean("userInfo1");
UserInfo beanConfig2 = (UserInfo) context.getBean("UCInfo");
System.out.println(beanConfig);
System.out.println(beanConfig1);
System.out.println(beanConfig2);
}
}
运行结果:

3.3.3 重名命 Bean
可以通过设置 name 属性给 Bean 对象进行重命名操作,如下代码所示:
java
@Configuration
public class BeanConfig {
@Bean(name={"u1","userInfo"})
public UserInfo userInfo() {
return new UserInfo("zhangsan");
}
}
此时我们使用 u1 就可以获取到 User 对象了,如下代码所示:
java
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserInfo beanConfig = (UserInfo) context.getBean("u1");
System.out.println(beanConfig);
}
}
3.4 扫描路径
启动类默认扫描的范围是SpringBoot启动类所在包及其子包 。
在上述 重命名Bean代码写完之后,保持代码不变, 将启动类放到 controller 目录下,再次启动程序,有如下结果:

报错信息: 找不到名为 UCInfo 的Bean对象。
再把启动类 放到 UCInfo bean对象所在的目录, 启动程序:

发现程序运行成功.
那问题来了, 如果我就是想把启动类放在 UCInfo bean对象 不在的目录里, 还想要程序正常执行, 怎么办?
给启动类加上 @ComponentScan 注解. 就可以做到了. 当然, 实际开发中不建议这么做. 老老实实把启动类的位置放好就行. 胡搞八搞的, 显得有些花里胡哨。
前面没有给启动类配置 @ComponentScan注解为什么程序也可以正常执行呢? 点过去看启动类注解的源码👇

发现: @ComponentScan 注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解
@SpringBootApplication 中了
推荐做法: 启动类放在我们希望扫描的包的路径下, 这样我们定义的bean就都可以被扫描到 。

4. DI 详解
依赖注入是一个过程,是指IoC容器在创建Bean时, 去提供运行时所依赖的资源,而资源指的就是对象.
在下面程序案例中,使用了 @Autowired 这个注解,完成了依赖注入的操作.
简单来说, 就是把对象取出来放到某个类的属性中.
关于依赖注入, Spring也给我们提供了三种方式:
- 属性注入(Field Injection).
- 构造方法注入(Constructor Injection).
- Setter 注入(Setter Injection).
4.1 属性注入
使用 @Autowired 将 Service 类注入到 Controller 类中。
Service 类的实现代码如下:
java
@Service
public class UserService {
public void doService() {
System.out.println("do Service...");
}
}
Controller 类的实现代码如下:
java
@Controller
public class UserController {
@Autowired
private UserService userService;
public void sayHi() {
userService.doService();
System.out.println("hi,user Controller");
}
}
获取 Controller 中的 sayHi 方法:
java
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserController bean = context.getBean("userController",UserController.class);
bean.sayHi();
}
}

去掉@Autowired , 再运行一下程序看看结果。

4.2 构造方法注入
如果添加构造函数,一定要把无参构造函数显示添加。
构造方法注入是在类的构造方法中实现注入,如下代码所示:
java
@Controller
public class UserController {
//构造方法注入
private UserService userService;
public UserController() {}
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
public void sayHi() {
System.out.println("hi, UserController...");
userService.doService();
}
}
注意事项:如果类只有一个构造方法,那么 @Autowired 注解可以省略;如果类中有多个构造方法,那么需要添加上 @Autowired 来明确指定到底使用哪个构造方法。 如果未指定,默认使用无参的构造函数。
4.3 Setter 注入
Setter 注入和属性的 Setter 方法实现类似,只不过在设置 set 方法的时候需要加上 @Autowired 注解 ,如下代码所示:
java
@Controller
public class UserController {
//setter方法注入
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void sayHi() {
System.out.println("hi, UserController...");
userService.doService();
}
}
4.4 三种注入的优缺点(面试题)
Ⅰ 属性注入:
优点: 简洁,使用方便;
缺点: 只能用于 IoC 容器,如果是非 IoC 容器不可用。不能注入一个Final修饰的属性。
Ⅱ 构造函数注入(Spring 4.X推荐)
优点: 可以注入final 修饰的属性; 注入的对象不会被修改; 构造方法是在类加载阶段就会执性的方法.依赖对象在使用前一定会被完全初始化; 构造方法是JDK支持的, 更换任何框架,他都是适用的, 所以通用性好。
缺点: 注入多个对象时, 代码会比较繁琐。
Ⅲ Setter注入 (Spring 3.X推荐)
优点: 方便在类实例之后, 重新对该对象进行配置或者注入。
缺点: 不能注入一个Final修饰的属性
▪ 注入对象可能会被改变, 因为 setter 方法可能会被多次调用, 就有被修改的风险.
4.5 @Autowired存在问题
当同一类型存在多个bean时,单独 使用@Autowired会存在找不到Bean对象的问题.
java
@Configuration
public class BeanConfig {
@Bean(name={"u1","userInfo"})
public UserInfo userInfo() {
return new UserInfo("zhangsan");
}
@Bean
public UserInfo userInfo1() {
return new UserInfo("lisi");
}
@Bean
public UserInfo UCInfo() {
return new UserInfo("wangwu");
}
}
java
@Service
public class UserService {
//注入UserInfo
@Autowired
private UserInfo userInfo;
public void doService() {
System.out.println("do Service...");
System.out.println(userInfo);
}
}
运行程序,查看结果.

如果想要获取到某一个 bean对象, 就得用到 注解了.
总共有三个注解:
① @Primary
② @Qualifier
③ @Resource
使用 @Primary 注解:当存在多个相同类型的Bean注入时,加上@Primary 注解,来确定默认的实现.
java
@Configuration
public class BeanConfig {
@Bean(name={"u1","userInfo"})
public UserInfo userInfo() {
return new UserInfo("zhangsan");
}
@Primary
@Bean
public UserInfo userInfo1() {
return new UserInfo("lisi");
}
@Bean
public UserInfo UCInfo() {
return new UserInfo("wangwu");
}
}
使用@Qualifier 注解:指定当前要注入的 bean 对象。 在@Qualifier的value属性中,指定注入的bean的名称。
• @Qualifier 注解不能单独使用,必须配合。@Autowired 使用 。
• @Qualifier 的优先级高于 @Autowired , 如果查找 @Autowired 对应的类型,只有一个,但是名称和 @Qualifier 名称不一样,注入也是失败的 。
java
@Service
public class UserService {
//注入UserInfo
@Qualifier("userInfo1")//指定userInfo1
@Autowired
private UserInfo userInfo;
public void doService() {
System.out.println("do Service...");
System.out.println(userInfo);
}
}
使用 @Resource 注解:是按照bean的名称进行注入。通过name属性指定要注入的bean的名称。
java
@Service
public class UserService {
//注入UserInfo
@Resource(name = "UCInfo")//指定 UCInfo
@Autowired
private UserInfo userInfo;
public void doService() {
System.out.println("do Service...");
System.out.println(userInfo);
}
}
4.5.1 @Autowird 与 @Resource的区别?(常见面试题)
@Autowired 是spring框架提供的注解,而 @Resource 是JDK提供的注解。
@Autowired 默认是按照类型注入,而 @Resource 是按照名称+类型注入. 相比于 @Autowired 来说,@Resource 支持更多的参数设置,例如 name 设置,根据名称获取 Bean。
5. 总结
5.1 Spring, Spring Boot 和Spring MVC的关系以及区别
Spring: 简单来说, Spring 是一个开发应用框架,什么样的框架呢,有这么几个标签:轻量级、一站式、模块化,其目的是用于简化企业级应用程序开发.
Spring的主要功能: 管理对象,以及对象之间的依赖关系, 面向切面编程, 数据库事务管理, 数据访问, web框架支持等.
Spring具备高度可开放性, 并不强制依赖Spring, 开发者可以自由选择Spring的部分或者全部, Spring可以无缝继承第方框架, 比如数据访问框架(Hibernate , JPA), web框架(如Struts、JSF)。
Spring MVC: Spring MVC是Spring的一个子框架, Spring诞生之后, 大家觉得很好用, 于是按照MVC模式设计了一个 MVC框架(一些用Spring 解耦的组件), 主要用于开发 Web 应用和网络接口,所以, Spring MVC 是一个Web框架.
Spring MVC基于Spring进行开发的, 天生的与Spring框架集成. 可以让我们更简洁的进行Web层开发, 支持灵活的 URL 到页面控制器的映射, 提供了强大的约定大于配置的契约式编程支持, 非常容易与其他视图框架集成,如 Velocity, FreeMarker等
Spring Boot: Spring Boot 是对 Spring 的一个封装, 为了简化Spring应用的开发而出现的,中小型企业,没有成本研究自己的框架, 使用 Spring Boot 可以更加快速的搭建框架, 降级开发成本, 让开发人员更加专注于 Spring 应用的开发,而无需过多关注 XML 的配置和一些底层的实现.
Spring Boot 是个脚手架, 插拔式搭建项目, 可以快速的集成其他框架进来.
比如想使用 SpringBoot 开发 Web 项目, 只需要引入 Spring MVC框架即可, Web开发的工作是 SpringMVC 完成的, 而不是 SpringBoot, 想完成数据访问, 只需要引入 Mybatis框架 即可.
Spring Boot 只是辅助简化项目开发的, 让开发变得更加简单, 甚至不需要额外的 web服务器, 直接生成 jar包 执行即可
一句话总结: Spring MVC 和 Spring Boot都属于Spring,Spring MVC 是基于Spring的一个MVC 框架,而 Spring Boot 是基于Spring的一套快速开发整合包.
5.2 常见注解有哪些? 作用分别是什么? (常见面试题)
上一篇文章和这篇文章讲过的所有注解都是答案.
本篇博客到这里就结束啦, 感谢观看 ❤❤❤
🐎期待与你的下一次相遇😊😊😊