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

Spring IoC DI入门

一、Spring,Spring Boot和Spring MVC的联系及区别

Spring是另外两个框架的基础,是Java生态系统的核心框架,而SpringMVC是Spring 的子模块,专注于 Web 层开发,基于 MVC 设计模式(模型-视图-控制器)。Spring MVC 依赖 Spring 的 IOC 和 AOP,是 Spring 生态中处理 Web 请求的模块。而Spring Boot是Spring 的“快速启动器”,旨在简化 Spring 应用的配置和开发流程。Spring Boot 基于 Spring,但默认配置了 Spring MVC、Spring Data 等模块,更好上手。

二、什么是IoC

IoC是Spring的核心思想,其实IoC我们已经使用过了,在类上添加@Rest Controller和@Controller注解,就是将这个对象交给Spring管理,Spring框架启动时就会加载该类,把对象交给Spring处理,就是IoC思想。

概念:

IoC (Inversion of Control) 即控制反转,是⾯向对象编程中的⼀种设计原则。主要是通过第三⽅IoC容
器,对Bean对象进⾏统⼀管理,及组织对象之间的依赖关系。获得依赖对象的过程,由原本程序⾃⼰控
制,变为了IoC容器来主动注⼊,控制权发⽣了反转,所以叫做IoC,控制反转。
IoC⼜叫做DI:由于控制反转概念⽐较含糊(可能只是理解为容器控制对象这⼀个层⾯,很难让⼈想到
谁来维护对象关系),相对 IoC ⽽⾔,依赖注⼊实际上给出了实现IoC的⽅法:注⼊。所谓依赖注⼊,
就是由 IoC 容器在运⾏期间,动态地将某种依赖关系注⼊到对象之中。
依赖注⼊(DI)和控制反转(IoC)是从不同的⻆度的描述的同⼀件事情,就是指通过引⼊IoC容器,利⽤依赖
关系注⼊的⽅式,实现对象之间的解耦。
DI是IoC的实现⽅式之⼀。⽽DI 的实现⽅式主要有两种:构造⽅法注⼊和属性Setter注⼊。

三、一个关于IoC的实例

如果实现一辆车,需要以下结构:

       它们是相互依赖的,Car依赖底盘,底盘依赖于轮胎。

如果我们按照以前的方式来写,是这样的。

package com.example.ioc;

public class NewCar {
    public static void main(String[] args) {
        Car car = new Car(20);
        car.run();
    }
static class Car{
    private Bottom bottom;
    public Car(int size) {
        this.bottom = new Bottom(size);
        System.out.println("Car init....");
    }
    public void run() {
            System.out.println("Car run....");
        }
    }
    static class Bottom {
        private Tire tire;
        public Bottom(int size) {
             this.tire = new Tire(size);
            System.out.println("Bottom init....");
        }
    }
    static class Tire{
        private int size;
        public Tire(int size) {
            this.size = size;
            System.out.println("轮胎尺寸为"+size);
        }
    }
}

这样写的问题是:当代码最底层改动后,整个调用链上的代码都要改动,对于这段代码来说就是轮胎参数改变后,整个代码都要改。

解决方法:

我们可以将所有的配件都外包出去,如果轮胎尺寸更改后,我们只需要向代工厂提要求就行了,自身不需要修改。

此时,我们只需要将自己原本创建的下级类,改为传递的方式,也就是注入的方式,因为不需要在当前的类中创建下级类了,所以即使下级类发生改变,当前类也无需修改,这样就完成了程序的解耦合。

通过IoC思路修改后的代码:

package com.example.ioc;

public class NewCar {
    public static void main(String[] args) {
        Tire tire = new Tire(20);
        Bottom bottom = new Bottom(tire);
        Car car = new Car(bottom);
        car.run();
    }
static class Car{
    private Bottom bottom;
    public Car(Bottom bottom) {
        this.bottom = bottom;
        System.out.println("Car init....");
    }
    public void run() {
            System.out.println("Car run....");
        }
    }
    static class Bottom {
        private Tire tire;
        public Bottom(Tire tire) {
             this.tire = tire;
            System.out.println("Bottom init....");
        }
    }
    static class Tire{
        private int size;
        public Tire(int size) {
            this.size = size;
            System.out.println("轮胎尺寸为"+size);
        }
    }
}

IoC优势:

通过IoC思想修改后的代码,类的创建方式是反的,传统代码是Car控制并创建了Bottom,而Bottom控制并创建了Tire,依次往下,而改进后的控制权发生反转不是使用方对象控制并创建依赖对象了,而是将依赖对象注入到当前对象中,依赖对象的控制权不再由当前类控制了。

这样的话,即使依赖类发生任何改变,当前类都是不受影响的,这就是经典的控制反转,也是IoC的实现思想。

四、IoC容器

上述这段代码就是IoC容器做的工作

        Tire tire = new Tire(20);
        Bottom bottom = new Bottom(tire);
        Car car = new Car(bottom);

从上面可以看出,IoC容器具备以下优点:

资源不由使用资源的双方管理,而是由不使用资源的第三方管理。这样可以带来很多好处。第一:资源集中管理,实现资源的可配置和易于管理。第二:降低了使用资源双方的依赖程度,也就是降低了耦合度。

五、DI介绍

DI,中文翻译为依赖注入。

容器在运行时间,动态的为应用程序提供运行时所依赖的资源,称为依赖注入。

从这方面来看,DI就是IoC思想的一种实现方式。

六、IoC&DI的使用步骤

Spring容器中存储的主要是对象,这些对象,我们称之为“Bean”,我们把这些对象交由Spring管理,我们把这些对象交由Spring管理,由Spring来负责对象的创建和销毁,我们程序只需要告诉Spring,哪些需要存,以及如何从Spring中取出对象。

1、Bean的存储和获取

要将某个对象交给IoC容器管理,需要在类上添加注解@Compent,而Spring框架为了更好的服务web应用程序,提供了更多的注解。

共有两类注解可以实现:

类注解:@Controller、@Service、@Repository、@Component、@Configuration。

方法注解:@Bean

1>@Controller(控制器存储)

使用@Controller存储Bean的代码如下:

@Controller//将对象存储到Spring中
    public class UserController {
        public void sayHi(){
            System.out.println("Hi");
        }
    }

如何得知对象已经存储到了Spring容器中呢?

接下来是从Spring容器中获取对象:

@SpringBootApplication
public class IoCApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
        UserController usercontroler = context.getBean(UserController.class);
        //从Spring上下文中获取对象
        usercontroler.sayHi();
    }

}

ApplicationContext翻译过来就是:Spring上下文

因为对象都交给Spring管理了,所以获取对象要从Spring中获取,首先就要先得到Spring的上下文。(这个上下文,就是指当前的运行环境,也可以看作一个容器,容器里存了很多内容,这些内容是当前运行的环境)

获取Bean的其他方式:

Spring容器将会为存入进去的Bean起一个名字,起名字的规则如下:

1.bean名称以小驼峰方式起名:

例如:类名:UserController,Bean名称:userController

类名:AccountManager,Bean名称:accountManager。

2.特殊情况,第一个和第二个都是大写。

例如:UController,Bean名称:UController。

类名:AManager,Bean名称:AManager

根据这个规则来获取Bean:

@SpringBootApplication
public class IoCApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
        UserController usercontroller1 = context.getBean(UserController.class);//根据Bean的类型获取
        UserController usercontroller2 = (UserController) context.getBean("userController");//根据Bean名称获取
        UserController usercontroller3 = context.getBean("userController", UserController.class);
        //根据Bean名称+类型获取
        System.out.println(usercontroller1);
        System.out.println(usercontroller2);
        System.out.println(usercontroller3);

    }

}

我们看运行结果:

后缀都一样,说明地址相同,地址相同,说明是同一个。

获取bean对象,是父类BeanFactory提供的功能。

ApplicationContext和BeanFactory的联系:

继承关系和功能方面来说:Spring有两个顶级的接口:BeanFactory和ApplicationContext,其中BeanFactory提供了访问容器的基础能力,而ApplicationContext属于BeanFactory的子类,它继承了BeanFactory的所有功能之外,它还有对国际化支持,资源访问支持,以及事件传播等方面的支持。

从性能方面来说:ApplicationContext是一次性加载并初始化所有的Bean对象(空间换时间),而BeanFactory是需要哪个就去加载哪个,因此更轻量化。

2>@Service(服务存储)

使用@Service存储bean的代码如下:

@Service
public class UserService {
    public void sayHello(String name) {
        System.out.println("Hello " + name);
    }
}

读取bean的代码:

@SpringBootApplication
public class IoCApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
       UserService userService = context.getBean(UserService.class);
       userService.sayHello("World");

    }

}

当然@Service也可以使用Bean的名称或者名称+类型来获取。

3>@Repository(仓库存储)

使用@Repository存储Bean的代码:

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

读取Bean的代码:

@SpringBootApplication
public class IoCApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
       UserRepository userRepository = context.getBean(UserRepository.class);
       UserRepository userRepository2 = context.getBean("userRepository", UserRepository.class);
       userRepository2.Hello();
       userRepository.Hello();
    }

}

4>@Component(组件存储)

@Component
public class UserComponent {
    public void sayHello() {
        System.out.println("Hello");
    }
}

读取Bean

@SpringBootApplication
public class    IoCApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
        UserComponent userComponent = context.getBean(UserComponent.class);
        userComponent.sayHello();
    }

}

5>@Configuration(配置存储)

存储Bean

@Configuration
public class UserConfiguration {
    public void Hello(){
        System.out.println("Hello");
    }
}

获取Bean

@SpringBootApplication
public class    IoCApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
        UserConfiguration userConfiguration = context.getBean(UserConfiguration.class);
        userConfiguration.Hello();
    }

}

2、各个注解的使用场景:

@Controller:控制层,接受请求,对请求进行处理,并进行响应。

@Service:业务逻辑层,处理具体的业务逻辑。

@Repository:数据访问层,也称为持久层,负责数据访问操作。

@Configuration:配置层,处理项目中的一些配置信息。

它们关系图:

查看源码可知,除了Component外,其他四个注解里面都有@Component,说明它们本身就是属于@Component的“子类”,这好比如,@Component是一个普通的杯子,而其他注解则是喝水杯,漱口杯等。

3、方法注解@Bean

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

1.使用外部包的类,没办法添加类注解。

2.一个类,需要多个对象,比如多个数据源。

在这些场景,我们就需要使用方法注解@Bean。

1.使用外部包的类

方法注解的使用方法:

错误使用方法:

public class BeanConfig {
    @Bean
    public User user() {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }
}
@SpringBootApplication
public class    IoCApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
        User user = context.getBean("user", User.class);
        System.out.println(user);
    }

}

运行后会报错:

这是因为方法注解要配合类注解使用

在Spring框架的设计中,方法注解@Bean要配合类注解才能将对象正常的存储到Spring容器中,如下代码所示:

@Component
public class BeanConfig {
    @Bean
    public User user() {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }
}
@SpringBootApplication
public class    IoCApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
        User user = context.getBean("user", User.class);
        System.out.println(user);
    }

}

这时代码运行就正常了

2.定义多个对象

对于同一个类,如果我们要定义多个对象,就要使用@Bean,因为有时候类是同一个,但是配置不同,指向不同的数据源。

例如下列场景:

@Component
public class BeanConfig {
    @Bean
    public User user1() {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }
    @Bean
    public User user2() {
        User user = new User();
        user.setName("lisi");
        user.setAge(19);
        return user;
    }
}

但是当定义了多个对象后,根据类型获取对象,获取的是哪个对象呢?

@SpringBootApplication
public class    IoCApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
        User user = context.getBean( User.class);
        System.out.println(user);
    }

}

这时会报错:

报错信息上显示:期望只有一个匹配,结果发现了两个:user1,user2。

从中可以看出,@Bean注解的bean的名称就是它的方法名。

可以根据名称来获取bean对象:

@SpringBootApplication
public class    IoCApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
        User user1 = (User) context.getBean( "user1");
        User user2 = (User) context.getBean( "user2");
        
        System.out.println(user1);
        System.out.println(user2);
    }

}

运行结果显示如下:

可以看出,@Bean可以针对一个类来定义多个对象。

3.重命名Bean

可以通过设置name属性给Bean对象进行重命名操作,代码如下:

 public User user1() {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }

其中name = 可以省略。

@Bean({"zhangsan","user1"})
    public User user1() {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }

通过zhangsan来获取User对象:

@SpringBootApplication
public class    IoCApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
        User user1 = (User) context.getBean( "zhangsan");
        User user2 = (User) context.getBean( "user1");
        System.out.println(user1);
        System.out.println(user2);

    }

}

此时”user1“和”zhangsan“都表示user1,如果只需要一个名字,可以删除一个,同时”{}“也可以删除:

  @Bean("zhangsan")
    public User user1() {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }

4.扫描路径

@Bean要想生效,需要被Spring扫描到,如果将Spring类移到某个包中,就会出现错误。

此时再运行就会报错:

意为没有找到bean的名称为zhangsan。

这是因为使用五大注解声明的bean,要想生效,还需要配置扫描路径,让Spring扫描到这些注解,通过@ComponentScan来配置路径。


@ComponentScan({"com.example.ioc"})
@SpringBootApplication
public class    IoCApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(IoCApplication.class, args);
        User user1 = (User) context.getBean( "zhangsan");
       // User user2 = (User) context.getBean( "user1");
        System.out.println(user1);
      //  System.out.println(user2);

    }

}

此时就不会报错了,同时”{}“里面可以配置多个包路径。

之前没有配置也可以使用是因为@ComponentScan实际上已经包含在了启动类声明注解中了。

默认的扫描范围是SpringBoot启动类所在包及其子包。

相关文章:

  • QT-LINUX-Bluetooth蓝牙开发
  • 解释 TypeScript 中的类型保护(type guards),如何使用类型保护进行类型检查?
  • 麦肯锡外运集团企业卓越采购供应链管理体系规划(117页PPT)(文末有下载方式)
  • 阿里云平台Vue项目打包发布
  • 识别并脱敏上传到deepseek/chatgpt的文本文件中的护照信息
  • 晶鑫股份迈向敏捷BI之路,永洪科技助力启程
  • 天梯赛 L2-011 玩转二叉树
  • 使用uniapp的vite版本进行微信小程序开发,在项目中使用mqtt连接、订阅、发布信息
  • 若依(RuoYi)框架新手使用指南
  • Bilve 搭建手册
  • L2TP的LAC拨号模式实验
  • 【SpringBoot】你不能不会的SpringBoot图形验证码生成
  • 自学Python创建强大AI:从入门到实现DeepSeek级别的AI
  • bootstrap介绍(前端框架)(提供超过40种可复用组件,从导航栏到轮播图,从卡片到弹窗)bootstrap框架
  • 1688商品数据实战:API搜索接口开发与供应链分析应用
  • Linux--进程创建
  • CTF类题目复现总结-[WUSTCTF2020]girlfriend 1
  • wpa_supplicant驱动初始化源码分析
  • Gin框架学习
  • 【sgFloatDialog】自定义组件:浮动弹窗,支持修改尺寸、拖拽位置、最大化、还原、最小化、复位
  • 美国失去最后的AAA主权评级,继标普、惠誉后再遭穆迪降级
  • 圆桌丨全球化博弈与人工智能背景下,企业如何落地合规体系
  • 新版城市规划体检评估解读:把城市安全韧性摆在更加突出位置
  • 河南信阳:对违规吃喝问题不遮丑不护短,露头就打、反复敲打
  • 中国—美国经贸合作对接交流会在华盛顿成功举行
  • 诠释微末处的丰盈:“上海制造佳品汇”首届海外专场即将亮相日本大阪