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

Spring IoC:解耦与控制反转的艺术

目录

  • Spring IoC 从初识到精通
    • 一、IoC(控制反转)
    • 二、Bean的存储
      • 2.1 `@Controller`(控制器存储)
      • 2.2 `@Service` 服务存储
      • 2.3 `@Repository`(仓库存储)
      • 2.4 `@Component`(组件存储)
      • 2.5`@Configuration`
    • 三、为什么要这么多的注解?
    • 四、 ` @Bean` 方法注解
    • 五、总结

Spring IoC 从初识到精通

一、IoC(控制反转)

其实IoC我们在前面已经使用了, 我们在前面讲到, 在类上面添加@RestController 和@Controller 注解, 就是把这个对象交给Spring管理, Spring 框架启动时就会加载该类. 把对象交给Spring管理, 就是IoC思想。

接下来我们通过案例来了解一下什么是IoC
需求: 造⼀辆车:
传统程序开发
我们的实现思路是这样的:
先设计轮子(Tire),然后根据轮子的大小设计底盘(Bottom),接着根据底盘设计车身(Framework),最后根据车身设计好整个汽车(Car)。这里就出现了一个"依赖"关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子。
如图所示:
在这里插入图片描述

public class NewCar {
    public static void main(String[] args) {
        Car car=new Car();
        car.run();
    }

   static class Car {
        private Framework framework;
        public Car(){
            framework=new Framework();
            System.out.println("car...init...");
        }
        void run(){
            System.out.println("car....run...");
        }
    }

    static class Framework {
        private Bottom bottom;
        public Framework(){
            bottom=new Bottom();
            System.out.println("car....bottom..");
        }
        static class Bottom {
            private Tire tire;
            public Bottom(){
                tire=new Tire();
                System.out.println("car...tire");
            }
            static class Tire {
                private int size;
                public Tire() {
                    this.size = 17;
                    System.out.println("轮胎尺寸" + size);
                }
            }
        }
    }
}

在这里插入图片描述

这样的设计看起来没问题,但是可维护性却很低.
接下来需求有了变更: 随着对的车的需求量越来越大,个性化需求也会越来越多,我们需要加工多种尺寸的轮胎。
那这个时候就要对上面的程序进行修改了,修改后的代码如下所示:

public class NewCar {
    public static void main(String[] args) {
        Car car=new Car(50);
        car.run();
    }

   static class Car {
        private Framework framework;
        public Car(int size){
            framework=new Framework(30);
            System.out.println("car...init...");
        }
        void run(){
            System.out.println("car....run...");
        }
    }

    static class Framework {
        private Bottom bottom;
        public Framework(int size){
            bottom=new Bottom(20);
            System.out.println("car....bottom..");
        }
        static class Bottom {
            private Tire tire;
            public Bottom(int size){
                tire=new Tire(25);
                System.out.println("car...tire");
            }
            static class Tire {
                private int size;
                public Tire(int size) {
                    this.size = 17;
                    System.out.println("轮胎尺寸" + size);
                }
            }
        }
    }
}

从以上代码可以看出,以上程序的问题是:当最底层代码改动之后,整个调用链上的所有代码都需要修改。
程序的耦合度非常高(修改⼀处代码, 影响其他处的代码修改)。


解决方案
在上⾯的程序中, 我们是根据轮子的尺寸设计的底盘,轮子的尺寸一改,底盘的设计就得修改. 同样因为我们是根据底盘设计的车身,那么车身也得改,同理汽车设计也得改, 也就是整个设计几乎都得改
我们尝试换⼀种思路, 我们先设计汽车的大概样子,然后根据汽车的样子来设计车身,根据车身来设计底盘,最后根据底盘来设计轮子. 这时候,依赖关系就倒置过来了:轮子依赖底盘, 底盘依赖车身,车身依赖汽车这就类似我们打造⼀辆完整的汽车, 如果所有的配件都是自己造,那么当客户需求发生改变的时候,比如轮胎的尺寸不再是原来的尺寸了,那我们要自己动手来改了,但如果我们是把轮胎外包出去,那么即使是轮胎的尺寸发生改变了,我们只需要向代理工厂下订单就行了,我们自身是不需要出力的。

如何来实现呢:

我们可以尝试不在每个类中自己创建下级类,如果自己创建下级类就会出现当下级类发生改变操作,自己也要跟着修改。
此时,我们只需要将原来由自己创建的下级类,改为传递的方式(也就是注入的方式),因为我们不需要在当前类中创建下级类了,所以下级类即使发生变化(创建或减少参数),当前类本身也无需修改任何代码,这样就完成了程序的解耦。


修改后的代码:

public class NewCar {
    public static void main(String[] args) {
        Tire tire=new Tire(20);
       Bottom bottom=new Bottom(tire);
       Framework framework=new Framework(bottom);
       Car car=new Car(framework);
        car.run();
    }

   static class Car {
        private Framework framework;
        public Car(Framework framework){
            this.framework=framework;
            System.out.println("car...init...");
        }
        void run(){
            System.out.println("car....run...");
        }
    }

    static class Framework {
        private Bottom bottom;

        public Framework(Bottom bottome) {
            this.bottom = bottome;
            System.out.println("car....bottom..");
        }

    }
        static class Bottom {
            private Tire tire;

            public Bottom(Tire tire) {
                this.tire = tire;
                System.out.println("car...tire");
            }
        }
            static class Tire {
                private int size;
                public Tire(int size) {
                    this.size = 17;
                    System.out.println("轮胎尺寸" + size);
                }
            }
}

代码经过以上调整,无论底层类如何变化,整个调用链是不用做任何改变的,这样就完成了代码之间的解耦,从而实现了更加灵活、通用的程序设计。


IoC 优势

在传统的代码中对象创建顺序是:Car -> Framework -> Bottom -> Tire
改进之后解耦的代码的对象创建顺序是:Tire -> Bottom -> Framework -> Car

在这里插入图片描述

我们发现了⼀个规律,通用程序的实现代码,类的创建顺序是反的,传统代码是 Car 控制并创建了Framework,Framework 创建并创建了Bottom,依次往下,而改进之后的控制权发生的反转,不再是使用方对对象创建并控制依赖对象了,而是把依赖对象注入将当前对象中,依赖对象的控制权不再由当前类控制了.
这样的话, 即使依赖类发生任何改变,当前类都是不受影响的,这就是典型的控制反转,也就是 IoC 的实现思想。


到这里, 我们⼤概就知道了什么是控制反转了, 那什么是控制反转容器呢, 也就是IoC容器。
在这里插入图片描述
这部分代码, 就是IoC容器做的工作.
从上面也可以看出来, IoC容器具备以下优点:
资源不由使用资源的双方管理,而由不使用资源的第三方管理,这可以带来很多好处。第一,资源集中管理,实现资源的可配置和易管理。第二,降低了使用资源双方的依赖程度,也就是我们说的耦合度。

  1. 资源集中管理: IoC容器会帮我们管理⼀些资源(对象等), 我们需要使用时, 只需要从IoC容器中去取就可以了。
  2. 我们在创建实例的时候不需要了解其中的细节, 降低了使用资源双方的依赖程度, 也就是耦合度.Spring 就是⼀种IoC容器, 帮助我们来做了这些资源管理。

前面我们提到IoC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象。也就是bean的存储。

二、Bean的存储

在之前的案例中,要把某个对象交给IOC容器管理,需要在类上添加⼀个注解: @Component而Spring框架为了更好的服务web应用程序, 提供了更丰富的注解。
共有两类注解类型可以实现:
1. 类注解:@Controller@Service@Repository@Component@Configuration.
2. 方法注解@Bean.
接下来我们分别来看:

2.1 @Controller(控制器存储)

使用 @Controller 存储 bean 的代码如下所示:

@Controller
public class HelloController {
 public void print(){
      System.out.println("HelloController!!!");
  }
}

如何观察这个对象已经存在Spring容器当中了呢?
接下来我们学习如何从Spring容器中获取对象:
先学习一下Bean的命名约束
Bean的命名约束:
程序开发⼈员不需要为bean指定名称(BeanId), 如果没有显式的提供名称(BeanId),Spring容器将为该bean生成唯⼀的名称.
命名约定使用Java标准约定作为实例字段名. 也就是说,bean名称以小写字母开头,然后使用驼峰式大小写。

比如
类名: UserController, Bean的名称为: userController
类名: AccountManager, Bean的名称为: accountManager
类名: AccountService, Bean的名称为: accountService

也有⼀些特殊情况, 当有多个字符并且第⼀个和第⼆个字符都是⼤写时, 将保留原始的大小写. 这些规则与java.beans.Introspector.decapitalize (Spring在这里使用的)定义的规则相同.

比如
类名: UController, Bean的名称为: UController
类名: AManager,Bean的名称为:AManager

根据这个命名规则我们来获取Bean:

@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
		//上下文管理器
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		HelloController bean1 = context.getBean(HelloController.class);
		bean1.print();
		}
}

在这里插入图片描述


获取Bean的方法很多,这里就不一个一个写了。
在这里插入图片描述


2.2 @Service 服务存储

@Service
public class UserService {;
    public void print(){
        System.out.println("do Service");
    }
}

获取Bean:

@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
	UserService bean=context.getBean(UserService.class);
		bean.print();
	}
}

在这里插入图片描述


2.3 @Repository(仓库存储)

使用@Repository的方法存储bean:

import org.springframework.stereotype.Repository;

@Repository
public class UserRepository {
    public void print(){
        System.out.println("UserRepository!!!");
    }
}

获取Bean:

@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
	ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		UserRepository bean=context.getBean(UserRepository.class);
		bean.print();
	}
}

在这里插入图片描述


2.4 @Component(组件存储)

使用@Component的方法存储Bean:

@Configuration
public class UserComponent {
      public void print(){
        System.out.println("do UserCompoent!");
    }
}

获取Bean:

@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
	ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		UserRepository UserComponent bean1 = context.getBean(UserComponent.class);
		bean1.print();
	}
}

在这里插入图片描述


2.5@Configuration

使用@Configuration的方法存储Bean

@Configuration
public class UserConfig {
    public void print(){
        System.out.println("do config");
    }
}

获取Bean:

@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
	ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
UserConfig bean1 = context.getBean(UserConfig.class);
		bean1.print();
	}
}

在这里插入图片描述


三、为什么要这么多的注解?

这个也是和咱们前⾯讲的应用分层是呼应的. 让程序员看到类注解之后,就能直接了解当前类的用途。
@Controller:控制层, 接收请求, 对请求进行处理, 并进行响应。
• @Servie:业务逻辑层, 处理具体的业务逻辑。
• @Repository:数据访问层,也称为持久层. 负责数据访问操作。
• @Configuration:配置层. 处理项目中的⼀些配置信息。
在这里插入图片描述

查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发现:
在这里插入图片描述

其实这些注解里面都有⼀个注解 @Component ,说明它们本⾝就是属于 @Component 的"子类".
@Component 是⼀个元注解,也就是说可以注解其他类注解,如 @Controller , @Service ,@Repository 等. 这些注解被称为 @Component 的衍生注解.
@Controller , @Service 和 @Repository ⽤于更具体的⽤例(分别在控制层, 业务逻辑层, 持久化层), 在开发过程中, 如果你要在业务逻辑层使⽤ @Component 或@Service,显然@Service是更好的选择。

四、 @Bean 方法注解

类注解是添加到某个类上的, 但是存在两个问题:

  1. 使用外部包里的类, 没办法添加类注解
  2. 一个类, 需要多个对象, 比如多个数据源
    这种场景, 我们就需要使用方法注解 @Bean
    我们先来看看方法注解如何使用:
@Data
public class Student {
    public String name;
    public int age;
}

public class StudentComponent {
    @Bean
    public Student s1(){
        Student student=new Student();
        student.setAge(18);
        student.setName("张三");
        return student;
    }
}

获取Bean对象的student:

@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
		//上下文管理器
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		Student student =context.getBean(Student.class);
		//使用对象
		System.out.println(student);
	}
}

在这里插入图片描述
这里显示没有找到,那是什么原因?
在 Spring 框架的设计中,方法注解 @Bean 要配合类注解才能将对象正常的存储到 Spring 容器中
如下代码所示:

@Component
public class StudentComponent {
    @Bean
    public Student s1(){
        Student student=new Student();
        student.setAge(18);
        student.setName("张三");
        return student;
    }
}

在这里插入图片描述

可以看到获取Bean对象当中的student成功了。


定义多个对象

对于同一个类, 如何定义多个对象呢?

比如多数据源的场景, 类是同一个, 但是配置不同, 指向不同的数据源。

代码:

@Component
public class StudentComponent {
    public Student s1(){
        Student student=new Student();
        student.setAge(18);
        student.setName("张三");
        return student;
    }
   @Bean
    public Student s2(){
       Student student=new Student();
       student.setAge(20);
       student.setName("李四");
        return student;
    }
}

获取Bean对象的信息:

@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
		//上下文管理器
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		Student student =context.getBean(Student.class);
		//使用对象
		System.out.println(student);
	}
}

在这里插入图片描述
只希望匹配一个但是看到找到了两个,然后报错了,分不清student是哪个,那怎么解决呢?

@Primary注解 指定默认的bean
把上面的代码修改一下:

@Component
public class StudentComponent {
@Primary
    public Student s1(){
        Student student=new Student();
        student.setAge(18);
        student.setName("张三");
        return student;
    }
   @Bean
    public Student s2(){
       Student student=new Student();
       student.setAge(20);
       student.setName("李四");
        return student;
    }
}

在这里插入图片描述

可以看到成功了!!!


重命名 Bean

@Configuration
public class AppConfig {
    @Bean(name = "customUserService")
    public UserService userService() {
        return new UserService();
    }
}

userService进行重命名。


五、总结

总结语:
Spring IoC(控制反转)是Spring框架的核心思想之一,它通过将对象的创建和管理权交给Spring容器,实现了代码的解耦与灵活性。IoC不仅简化了开发流程,还提高了系统的可测试性和可维护性。通过控制反转,开发者可以更专注于业务逻辑的实现,而不是对象的创建与管理。掌握Spring IoC的思想与实践,是每一位Spring开发者必须具备的核心能力,也是构建高质量、可扩展应用的重要基石。


希望本文能帮助你理解 Spring IoC 的核心概念,并在实际项目中灵活运用!


http://www.kler.cn/a/581606.html

相关文章:

  • hive 中各种参数
  • 大语言模型基础—语言模型的发展历程--task1
  • python的sql解析库-sqlparse
  • OpenCV 拆分、合并图像通道方法及复现
  • JAVA的权限修饰符
  • 前瞻技术新趋势:改变未来生活方式的技术探索
  • Flink测试环境Standalone模式部署实践
  • 关于Vue/React中Diffing算法以及key的作用
  • C/S架构与B/S架构
  • C++设计模式-观察者模式:从基本介绍,内部原理、应用场景、使用方法,常见问题和解决方案进行深度解析
  • 【算法】BFS(最短路径问题、拓扑排序)
  • ScanPy - Preprocessing and clustering 3k PBMCs (legacy workflow)工作复现
  • 【亲测有效】Mac系统升级或降级Node.js版本,Mac系统调整node.js版本
  • alibaba EasyExcel的使用说明
  • 基于Ollama平台部署的Qwen大模型实现聊天机器人
  • git规范提交之commitizen conventional-changelog-cli 安装
  • 《灵珠觉醒:从零到算法金仙的C++修炼》卷三·天劫试炼(27)混元幡遮天机 - 第一个错误版本(二分边界)
  • golang从入门到做牛马:第十四篇-Go语言结构体:数据的“定制容器”
  • CSS中相对定位使用详情
  • 力扣热题 100:贪心算法专题经典题解析