当前位置: 首页 > news >正文

【Spring Boot】深入浅出Spring Boot中的控制反转与依赖注入

文章目录

  • 前言
  • 一、控制反转
    • 1.1 为什么需要控制反转?
    • 1.2 Spring Boot里的IOC管理
      • 1.2.1 Bean注册
      • 1.2.2 Bean扫描
  • 二、依赖注入
    • 2.1 依赖注入的意义·
    • 2.2 Spring Boot里实现依赖注入
    • 2.3 Bean优先级
  • 三、Spring Boot是如何启动一个容器
  • 总结


前言

一个复杂且具备高可维护性的系统,离不开对模块架构的精细化设计。单个模块内部功能联系紧密,围绕核心功能高度统一。同时,各个模块之间需保持适度的独立性,通过标准化的接口实现必要协作。实现低依赖。这种高内聚、低耦合的设计思想,同样是现代编程中控制反转(IOC)与依赖注入(DI)模式的核心出发点。

控制反转(Inversion of Control ,IOC)和依赖注入(Dependency Injection ,DI)是两个紧密相关的设计概念,常被一同提及但又容易混淆。依赖注入是控制反转实现思想的实现方式。依赖注入的提出是为了简化模块的组装过程,降低模块之间的耦合度。


一、控制反转

1.1 为什么需要控制反转?

传统项目我们常常会大量的重复new一个对象,控制权在自身。这样当依赖变化时,必须修改对象的代码。若修改依赖的实现,所有使用该依赖的类都需修改代码,牵一发而动全身,随着项目的持续推进要修改的代码量会呈现指数型上升。重复创建对象也会存在浪费内存的现象和资源泄漏的风险。并且高层模块直接依赖低层模块,而不是抽象接口,也违背了依赖倒置原则。毕竟相较于相对于低层实现类细节的多变性,抽象的东西要稳定的多。

如果要修改对日志(LogOperation)依赖的实现 ,则所有使用该依赖的类都需修改代码,耦合度非常高

@RequestMapping("/users/{id}")public String deleteUser(@PathVariable int id) {LogOperation logOperation = new LogOperation();// 执行删除操作boolean result = userService.deleteUser(id);if (result) {logOperation.log("用户删除成功,ID: " + id);return "删除成功";} else {logOperation.log("用户删除失败,ID: " + id);return "删除失败,用户不存在";}}

1.2 Spring Boot里的IOC管理

1.2.1 Bean注册

在Spring Boot中要将某个对象交给IOC容器管理分成的简洁,可以通过@Componet注解声明这个对象交由IOC容器管理。

@Component
public class UserServiceImpl implements UserService {
}

交由IOC容器管理的对象称之为bean。我们可以借助idea观测到。将程序运行起来,下方调试窗口里找到Beans。这里面就包含注册到IOC容器里的对象,可以看到有很多是系统自动注册的,如果我们仔细观测,会发现Controller本身也被作为bean对象交由IOC容器管理。
在这里插入图片描述

但是回想一下在Spring Boot开发中,我们的控制器上一般只会声明一个@RestController注解,并没有@Component注解。这是因为@RestController注解本身又使用到了Controller注解,定位到Controller注解里,就能见到Component注解本身。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {@AliasFor(annotation = Controller.class)String value() default "";
}@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {@AliasFor(annotation = Component.class)String value() default "";
}

其实在Spring Boot中有三种@Component的衍生注解,分别是:

  • @Controller:控制器层
  • @Service:业务服务层
  • @Repository:数据访问层类
    这三种衍生注解仅用于明确类的职责分层,功能上与@Component一致

1.2.2 Bean扫描

在主程序入口上一个@SpringBootApplication注解

@SpringBootApplication
public class SpringbootWebApplication {public static void main(String[] args) {SpringApplication.run(SpringbootWebApplication .class, args);}
}

其内部有个@ComponentScan注解,这个用于指定Spring容器扫描组件的范围,将标注了 @Component、@Controller、@Service、@Repository 等注解的类注册为 IOC 容器中的 Bean,默认扫描当前类所在包及其子包。

@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}
), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
public @interface ComponentScan {@AliasFor("basePackages")String[] value() default {};@AliasFor("value")String[] basePackages() default {};Class<?>[] basePackageClasses() default {};

最终是ComponentScanAnnotationParser方法调用@ComponentScan里定义的属性,比方说basePackages和basePackageClasses,如果都是空默认扫描当前类所在包及其子包。

二、依赖注入

2.1 依赖注入的意义·

控制反转的提出本质上是为了提升系统灵活性和扩展性。忽略细节,依赖抽象。体现在实际开发中便是一个控制权的转变, 从 如何创建一个对象改变成我要一个对象,无需关心这个对象生成的细节。

在Spring中,依赖注入是实现控制反转的具体方式:容器在创建其管理的对象(Bean)时,会自动将该对象所依赖的、同样由容器管理的其他对象(Bean)注入到当前对象中。

一般而言只有容器内的Bean之间,才能享受自动注入的能力。若 A 依赖 B,且 A 由容器管理(是 Bean),则 B 必须也是容器中的 Bean 才能被注入;若 B 还依赖 C,则 C 也需是 Bean,以此类推,形成依赖链的 “传染性”。
可以简单的说依赖注入具有传染性,这一点和.NET里的ServiceProvider很相似。一条依赖链上的对象,往往会因为前一个对象需要被注入,而传染到后一个对象也需要被容器管理。

2.2 Spring Boot里实现依赖注入

在Spring Boot中实现依赖注入一般分为三种方式,分别是构造函数注入,属性注入和Setter注入。下面分别演示下。

  • 构造函数注入(最推荐)。通过在构造函数使用@Autowired注解,将定义好的私有不可变字段通过构造函数实现依赖注入,这样就能直接获得一个实例。通过final关键字修饰依赖字段,一旦注入后无法被修改,非常的安全。但是如果依赖是非必需的,仍需在构造函数中传入null
@RestController
public class UserController {private final UserService userService ;@Autowiredpublic UserController(UserService userService) {this.userService = userService;}
  • 属性注入。这种方式是最为简洁的注入方式,但是不直观,也是需要@Autowired注解。字段无法用 final 修饰,可能被意外修改,破坏对象稳定性。并且因为不依赖于构造函数,从类的外部无法直观得知依赖关系
public class UserController {@Autowiredprivate UserService userService ;
  • Setter注入。Setter注入是通过一个setXXX()方法注入依赖。支持可选依赖(可通过 @Autowired(required = false) 标记),但是需手动调用 Setter 方法设置依赖,步骤稍多
@RestController
public class UserController {private UserService userService ;@Autowired public void setUserService (UserService userService) {this.userService = userService;}

2.3 Bean优先级

想象一下,一个接口的实现类有多种。每个实现类都被我们在IOC容器里注册成为bean对象。那么在依赖注入的时候Spring框架是如何区分待注入的bean对象呢。在Spring中,我们可以依据两个注解来实现Bean的优先级

  • @Primary。设置Bean的优先级
//接口
public interface UserService {}//实现类1
@Service
@Primary
public class UserServiceImpl1 implements UserService {}//实现类2
@Service
public class UserServiceImpl2 implements UserService {}//注入时,因为UserServiceImpl1有Primary注解
@Autowired
private UserService userService; // 所以实际注入的是UserServiceImpl1
  • @Qualifier。注解Qualifier里有一个参数,运行通过指定Bean 的名称来精确选择要注入的 Bean。比起@Primary注解,它能更加精细化的控制
@Autowired
@Qualifier("userServiceImpl2 ") 
private UserService userService;

一般情况下bean对象名称和注入类的名称一致,但是首字母小写

三、Spring Boot是如何启动一个容器

在springboot里实现控制反转依赖于一个容器,这个容器用于存储对象,被存储的就称之为bean对象。
在我们启动springboot服务的时候。
Spring boot SpringbootWeb1Application主方法会调用run方法:

@SpringBootApplication
public class SpringbootWebApplication{public static void main(String[] args) {SpringApplication.run(SpringbootWebApplication.class, args);}}

实际重载执行SpringApplication内部的run方法:

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class<?>[] { primarySource }, args);
}public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {return (new SpringApplication(primarySources)).run(args);
}

run() 方法会创建SpringApplication实例并触发容器初始化,核心逻辑在SpringApplication的run方法中:

public ConfigurableApplicationContext run(String... args) {/***省略***/try {/***省略***///创建应用程序上下文触发容器初始化context = this.createApplicationContext();context.setApplicationStartup(this.applicationStartup);//准备应用程序上下文this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);//刷新应用程序上下文this.refreshContext(context);this.afterRefresh(context, applicationArguments);/***省略***/} catch (Throwable ex) {throw this.handleRunFailure(context, ex, listeners);}}

在createApplicationContext里会根据当前应用的类型生成不同的ApplicationContext,比如AnnotationConfigServletWebServerApplicationContext。最后都是基础自GenericApplicationContext这个类,在这个GenericApplicationContext类里就包含着一个极为重要的BeanFactory。

public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {private final DefaultListableBeanFactory beanFactory;@Nullableprivate ResourceLoader resourceLoader;private boolean customClassLoader;private final AtomicBoolean refreshed;public GenericApplicationContext() {this.customClassLoader = false;this.refreshed = new AtomicBoolean();this.beanFactory = new DefaultListableBeanFactory();}

BeanFactory是 IOC 容器的底层接口,负责 Bean 的创建、依赖注入等核心功能。


总结

通过IOC容器管理对象和依赖关系,实现 “高内聚、低耦合”,提升系统的可维护性和扩展性。

http://www.dtcms.com/a/520553.html

相关文章:

  • 去年做那个网站致富商业网站在规划与设计时应着重考虑哪些因素
  • 苏州网站建设 凡仕臣网络台州自助建站公司
  • vscode json
  • 奥威BI:AI数据分析重塑客户体验的实战指南
  • 公司网站开发费摊销大连建设安全网站
  • 东莞教育网站建设浙江建设厅网站查询
  • 深圳公司网站设计企业虹口建设机械网站
  • Rust 所有权与借用机制深度剖析:原理、常见陷阱与实战优化
  • golang学习笔记:标准库encoding
  • Go语言实战教学:从一个混合定时任务调度器(Crontab)深入理解Go的并发、接口与工程哲学
  • 如何做原创小说网站广州商城网站制作网站
  • 让线程按指定顺序运行
  • 定制手机网站开发显示网站翻页代码
  • CoAlbum:多级缓存与性能对比
  • 【算法专题训练】27、树的层序遍历
  • 网站导入wordpress基层建设是哪个网站的
  • 第6章—手动移植创建STM32工程
  • Android Bluetooth 蓝牙通信
  • 简述一般网站开发方式广州三合一企业网站哪家好
  • 网站建设邀标函专业的外贸建站公司
  • C++ STL(标准模板库)深度解析:从基础到实践
  • 压缩与缓存调优实战指南:从0到1根治性能瓶颈(二)
  • Linux小课堂: SSH 配置文件详解之全局与局部 Config 文件的语义梳理与技术深化
  • 6-2〔O҉S҉C҉P҉ ◈ 研记〕❘ 客户端攻击▸利用WORD宏让客户端执行命令
  • 网站页面设计报价xampp做网站设置
  • 制作 网站 盈利怎么看网站到期时间
  • Qt6.10 | Qt Bluetooth 蓝牙
  • 网站自动化开发无极官方网站下载
  • 基于 docker compose 进行部署PandaWiki
  • 哪里建设网站设计服务