SpringBoot 的两大核心
  1. IOC:控制反转
  2. AOP:面向切面编程

基本概念

三层架构:

  1. Controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据。
  2. Service:业务逻辑层,处理具体的业务逻辑。
  3. Dao:数据访问层(Data Access Object)(持久层),负责数据访问操作,包括数据的增、删、改、查。

分层解耦:

  • 内聚:软件中各个功能模块内部的功能联系。
  • 耦合:衡量软件中各个层/模块之间的依赖、关联的程度。
  • 软件设计原则:高内聚低耦合

重要概念:

  • 控制反转:Inversion Of Control,简称 IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转。这个容器我们称为 IOC 容器,或者 Spring 容器。
  • 依赖注入:Dependency Injection,简称 Dl。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。
  • Bean 对象:IOC 容器中创建、管理的对象,称之为 bean。

IOC/DI 的使用

基础使用:

  • @Component:将当前类(Service 层、Dao 层)交给 IOC 容器管理,成为 IOC 容器中的 bean。实现控制反转。
  • @Autowired:(为 Controller 和 Service 注入)运行时,IOC 容器会提供该类型的 bean 对象,并赋值给该变量。实现依赖注入。

所以说,切换业务实现类时,直接把需要用的实现类上加上 @Component 就行。

SpringBoot 中 Bean 的声明:

注解 说明 位置
@Component 声明 bean 的基础注解 不属于以下三类时,用此注解(比如一些工具类)
@Controller @Component 的衍生注解 标注在控制器类上
@Service @Component 的衍生注解 标注在业务类上
@Repository @Component 的衍生注解 标注在数据访问类上(由于与 mybatis 整合,用的少)

声明 bean 的时候,可以通过 value 属性指定 bean 的名字,如果没有指定,默认为类名首字母小写。

使用以上四个注解都可以声明 bean,但是在 Springboot 集成 web 开发中,声明控制器 bean 只能用 @Controller

@RestController 中包含 @Controller 注解。

Bean 组件扫描:

  • 前面声明 bean 的四大注解,要想生效,还需要被组件扫描注解 @Componentscan 扫描。@ComponentScan 注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解 @SpringBootApplication 中,默认扫描的范围是启动类所在包及其子包。
  • 【不推荐】我们可以在启动类上增加 @ComponentScan 注解,手动设置扫描包的范围。注意检查有没有覆盖掉默认的薮猫范围,可以自己手动添加上。

Bean 注入:

  • @Autowired 注解,默认是按照类型进行。也就是在 IOC 容器中找这个类型的 Bean 对象然后再注入。
  • 如果存在多个相同类型的 bean,将会报错,可以使用以下方案解决:
    • @Primary。在 Bean 上面再加上这个注解,可以让该 Bean 优先生效。
    • @Autowired+@Qualifier("<beanName>")。使该名字的 Bean 生效。
    • @Resource(name="<beanName>")。使该名字的 Bean 生效。
【高频面试题】@Resource@Autowired 区别:
  • @Autowired 是 Spring 框架提供的注解,而 @Resource 是 JDK 提供的注解,
  • @Autowired 默认是按照类型注入,而 @Resource 默认是按照名称注入。

示例工程

一个工程目录示例:

image.png

示例 Mapper:

1
2
3
4
5
6
@Mapper
public interface DeptMapper{
// 基于注解方式编写SQL语句
@Select("select * from user")
List<dept> list();
}

示例 Service 接口类:

1
2
3
public interface DeptService{
List<Dept> list();
}

示例 Service 的实现类:

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class DeptServiceImpl implements DeptService{
// 注入Mapper
@Autowired
private DeptMapper deptMapper;

// 实现接口
@Override
public List<Dept> list(){
return deptMapper.list();
}
}

示例 Controller 的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Slf4j // 使用日志
@RestController
public class DeptController{
// @Slf4j 将省略下面这一行
// private static Logger log = LoggerFactory.getLogger(DeptController.class);

// Service的注入
@Autowired
private DeptService deptService;

// @GetMapping 作用和下面这条相同
// @RequestMapping(vaue = "/depts",method = RequestMethod.GET)
@GetMapping("/depts")
public Result list(){
log.info("查询部门数据");
List<Dept> deptList = deptService.list();
return Result.success(deptList); // 结果类Result是自己定义的
}
}

Bean 管理

获取 Bean

1
2
3
// 拿到 IOC 容器的方法
@Autowired
private ApplicationContext applicationContext; // IOC容器对象

默认情况下,Spring 项目启动时,会把 bean 都创建好放在 IOC 容器中 [1],如果想要主动获取这些 bean,可以通过如下方式使用 ApplicationContext 的方法:

  • 根据 name 获取 bean:Object getBean(String name)
  • 根据类型获取 bean:<T> T getBean(Class<T> requiredType)
  • 根据 name 获取 bean(带类型转换):<T> T getBean(String name, Class<T> requiredType)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@SpringBootTest
class ApplicationTests{

@Autowired
private ApplicationContext applicationContext; // IOC容器对象

@Test
public void testGetBean(){

//根据bean的名称获取
DeptController bean1 = (DeptController) applicationContext.getBean("deptController");
System.out.println(bean1);

//根据bean的类型获取
DeptController bean2 = applicationContext.getBean(DeptController.class);
System.out.println(bean2);

//根据bean的名称及类型获取
DeptController bean3 = applicationContext.getBean("deptController",DeptController.class);
System.out.println(bean3);
}

输出结果发现三个 bean 对象都是同一个对象,说明此时 bean 是单例的。

Bean 的作用域

Spring 支持五种作用域,后三种在 web 环境才生效:

作用域 说明
singleton 容器内同名称的 bean 只有一个实例(单例)(默认)
prototype 每次使用该 bean 时会创建新的实例(非单例)
request 每个请求范围内会创建新的实例(web 环境中,了解即可)
session 每个会话范围内会创建新的实例(web 环境中,了解即可)
application 每个应用范围内会创建新的实例(web 环境中,了解即可)

可以通过 @Scope 注解来进行配置作用域:@Scope("prototype")

默认 singleton 的 bean,在容器启动时被创建,可以使用 @Lazy 注解来延迟初始化(延迟到第一次使用时),prototype 的 bean,每一次使用该 bean 的时候都会创建一个新的实例。

实际开发当中,绝大部分的 Bean 是单例的,也就是说绝大部分 Bean 不需要配置 scope 属性。

第三方 Bean

如果要管理的 bean 对象来自于第三方(不是自定义的),是无法用 @Component 及衍生注解声明 bean 的(第三方某些类的源码定义中我们无法改动),就需要用到 @Bean 注解。若要管理的第三方 bean 对象,建议对这些 bean 进行集中分类配置,可以通过 @Configuration 注解声明一个配置类。

@Configuration 底层也是 @Component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 方法一:【不推荐】定义在启动类中
// 这样导致启动类不纯粹
@SpringBootApplication
public class SpringbootWebConfig2Application{
@Bean //将方法返回值交给IOC容器管理,成为IOC容器的bean对象
public MyBean myBean(){
return new MyBean();
}
}

// 方法二:声明一个配置类
// 通过@Bean注解的name/value属性指定bean名称,如果未指定,默认是方法名
@Configuration
public class CommonConfig{
@Bean
public MyBean myBean(){
return new MyBean();
}
}

使用 Bean 时直接注入即可:

1
2
@Autowired
private MyBean mybean;

如果第三方 bean 需要依赖其它 bean 对象,直接在 bean 定义方法中设置形参即可,容器会根据类型自动装配。

1
2
3
4
5
6
7
8
9
10
@Configuration
public class CommonConfig{

// SpringBoot容器会根据参数类型MyService自动装配Bean对象
@Bean
public MyBean myBean(MyService myService){
// 省略一些使用myService的操作
return new MyBean();
}
}

@Component 及衍生注解与 @Bean 注解使用场景?

  • 项目中自定义的,使用 @Component 及其衍生注解
  • 项目中引入第三方的,使用 @Bean 注解

后记

下面你可以立即移步这篇文章加深上面所学知识的印象:SpringBoot 的原理以及写一个自定义 Starter

本文参考


  1. 这还会受到作用域及延迟初始化影响,这里主要针对于默认的单例非延迟加载的 bean 而言。 ↩︎