【JavaEE】IOC和DI

发布于:2025-03-17 ⋅ 阅读:(15) ⋅ 点赞:(0)


博客结尾附有此篇博客的全部代码!!!

一、认识IOC

Spring IoC(Inversion of Control,控制反转)是Spring框架的核心概念之一,它是一种设计原则,用于实现对象之间的解耦和依赖关系管理。

什么是控制反转呢?
在这里插入图片描述
举个例子:以上面造车为例,汽车Run()起来需要依赖于车身(Framework),而车身依赖于底盘(Bottom),底盘依赖于轮胎(Tire)
代码实现:

public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        car.run();
    }
}
public class Car {
    private Framework framework;
    public Car() {
        Framework framework=new Framework();
        System.out.println("Car init...");
    }
    public void run(){
        System.out.println("Car is running");
    }
}

public class Framework {
    private Bottom bottom;
    public Framework(){
        Bottom b = new Bottom();
        System.out.println("Framework init...");
    }
}

public class Bottom {
    private Tire tire;
    public Bottom(){
        Tire tire = new Tire();
        System.out.println("Bottom init...");
    }
}
public class Tire {
    private String color;
    private Integer size;
    public Tire() {
        this.color = "彩色";
        this.size = 17;
        System.out.println("color:"+color+",size:"+size);
    }

}

这段代码我们发现,它的耦合性太高了,如果我们想要自己设置轮胎大小和颜色,我们就需要将整个程序的代码改一遍。

public class Bottom {
    private Tire tire;
    public Bottom(String color,Integer size){
        Tire tire = new Tire(color,size);
        System.out.println("Bottom init...");
    }
}
public class Tire {
    private String color;
    private Integer size;
    public Tire(String color, Integer size) {
        System.out.println("color:"+color+",size:"+size);
    }

}

解决方案:
这里我们可以将轮胎,底盘和车身外包给其他公司,他们根据客户的需要制造出零件,我们负责组装。
实现代码:我们将创建子类的方式,改为传递注入

public class Main {
    public static void main(String[] args) {
        Tire tire = new Tire("红色",17);
        Bottom bottom = new Bottom(tire);
        Framework framework = new Framework(bottom);
        Car car = new Car(framework);
        car.run();
    }
}
public  class Car {
    private Framework framework;
    public Car(Framework framework) {
        this.framework = framework;
        System.out.println("Car init...");
    }
    public void run(){
        System.out.println("Car is running");
    }
}
public class Framework {
    private Bottom bottom;
    public Framework(Bottom bottom) {
        this.bottom = bottom;
        System.out.println("Framework init...");
    }
}
public class Bottom {
    private Tire tire;
    public Bottom(Tire tire){
        this.tire = tire;
        System.out.println("Bottom init...");
    }
}

public class Tire {
    private String color;
    private Integer size;
    public Tire(String color, Integer size ) {
        System.out.println("color:"+color+",size:"+size);
    }

}

在这里插入图片描述

通过实现程序我们发现:类的创建顺序是反的,传统代码是 Car 控制并创建了Framework,Framework 创建并创建了Bottom,依次往下,⽽改进之后的控制权发⽣的反转,不再是使⽤⽅对象创建并控制依赖对象了,⽽是把依赖对象注⼊将当前对象中,依赖对象的控制权不再由当前类来控制。

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

Spring IOC的核心思想:将对象的创建和依赖关系的管理从代码中分离出来,交给一个专门的外部容器(如Spring容器)来完成

二、认识DI

DI:Dependency Injection(依赖注⼊)

DI是实现IoC(Inversion of Control,控制反转)的一种具体技术手段。它的核心思想是通过外部机制(如框架或容器)将依赖关系注入到对象中,而不是由对象自己去创建或查找依赖。这样可以降低对象之间的耦合度,提高代码的可维护性和可测试性。

依赖注入的核心思想是:由外部机制(如Spring容器)负责创建依赖对象(如B),并将依赖对象注入到需要它的对象(如A)中

在这里插入图片描述

三、详解IOC

Spring 是⼀个 IoC(控制反转)容器,作为容器, 那么它就具备两个最基础的功能:
• 存
• 取
Spring 容器管理的主要是对象, 这些对象, 我们称之为"Bean". 我们把这些对象交由Spring管理, 由Spring来负责对象的创建和销毁.我们程序只需要告诉Spring, 哪些需要存, 以及如何从Spring中取出对象。

3.1 Bean的存储

Bean在上面我们也说了,就是Spring管理起来的对象。

实现将对象交给Spring管理,
共有两类注解类型可以:

  1. 类注解:@Controller、@Service、@Repository、@Component、@Configuration.
  2. ⽅法注解:@Bean.

3.2 @Controller(控制器存储)

@Controller
public class ControllerDemo {
    public void controllerMethod() {
        System.out.println("controllerMethod");
    }
}

3.3 获取Bean的方法

从Spring容器中获取Bean对象:

  1. 先获取Spring上下⽂对象
  2. 从Spring上下⽂中获取对象

Spring提供的方法:在这里插入图片描述
传Bean对象名注意:

  1. 如果Bean对象名是大驼峰,则改成小驼峰 (ControllerMethod–>controllerMethod)
  2. 如果Bean 对象前两个单词是大写的,则Bean对象名不变(COntrollerMethod–>COntrollerMethod)
通过类名获得
    public static void main(String[] args) {
//获取Spring上下文对象
        ConfigurableApplicationContext context =
                SpringApplication.run(SpringIocApplication.class, args);
        //从Spring上下文获取Bean对象
        ControllerDemo bean = context.getBean(ControllerDemo.class);
        bean.controllerMethod();
    }

在这里插入图片描述

通过Bean对象名称
ControllerDemo bean1 = (ControllerDemo)context.getBean("controllerDemo");
        bean1.controllerMethod();

返回的Bean 对象类型是Object,这里需要自己强转一下。
演示一下类名前两个字母都为大写:

HElloController bean3 = (HElloController)context.getBean("HElloController");
        bean3.controllerMethod2();

在这里插入图片描述
当将类名第二个字母改为小写会报错误:
在这里插入图片描述

通过Bean对象名称和类名
ControllerDemo bean2 = context.getBean("controllerDemo", ControllerDemo.class);
        bean2.controllerMethod();

3.4 @Service(服务存储)

@Service
public class ServiceDemo {
    public void ServiceMethod() {
        System.out.println("Service Method");
    }
}
ServiceDemo bean =(ServiceDemo) context.getBean("serviceDemo");
        bean.ServiceMethod();

3.5 @Repository(仓库存储)

@Repository
public class RepositoryDemo {
    public void RepositoryMethod() {
        System.out.println("RepositoryMethod");
    }
}
RepositoryDemo bean2 =(RepositoryDemo) context.getBean("repositoryDemo");
        bean2.RepositoryMethod();

3.6 @Component(组件存储)

@Component
public class ComponentDemo {
    public void ComponentMethod() {
        System.out.println("ComponentMethod");
    }
}
ComponentDemo bean1 = context.getBean(ComponentDemo.class);
        bean1.ComponentMethod();

3.7 @Configuration(配置存储)

@Configuration
public class ConfigurationDemo {
    public void ConfigurationMethod(){
        System.out.println("ConfigurationMethod");
    }
}
ConfigurationDemo bean3 = context.getBean(ConfigurationDemo.class);
        bean3.ConfigurationMethod();

3.8 五大类注释的对比

  • @Controller代表控制层。接收参数返回响应,控制层一定要使用@Controller
  • @Service代表服务层。专门用于标记服务层组件的注解。通常用于业务逻辑层。
  • @Repository代表数据层。专门用于标记数据访问层组件的注解。通常用于DAO(数据访问对象)层。
  • @Configuration代表配置层。
  • @Component代表组件层。通用的注解,用于将类标记为Spring管理的组件。
    在这里插入图片描述
    这五大注解都是Spring的组件(组件(Component)是一个泛指,它通常用于描述任何由Spring IoC容器管理的对象)

3.9 ⽅法注解 @Bean

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

  1. 使⽤外部包⾥的类, 没办法添加类注解
package com.thirdparty;
//这个是第三方库,你不能进行修改
public class ThirdPartyService {
    public void performService() {
        System.out.println("Service performed by ThirdPartyService");
    }
}
//用过Bean注解将第三方库中ThirdPartyService实例交给Spring管理
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.thirdparty.ThirdPartyService;

@Configuration
public class ThirdPartyConfig {

    @Bean
    public ThirdPartyService thirdPartyService() {
        return new ThirdPartyService();
    }
}
  1. ⼀个类, 需要多个对象, ⽐如多个数据源

当一个应用需要连接多个数据源时,可能需要同一个接口的多个实现。如果使用类注解,Spring容器会为每个标记了相同注解的类创建一个Bean,但无法区分这些Bean。

@Configuration
public class BeanDemo {
    @Bean
    public User user1(){
        User user=new User();
        user.setName("张三");
        user.setAge(18);
        return user;
    }
}

User bean = context.getBean(User.class);
        System.out.println(bean);

@Bean注解需要搭配五大类注解使用,否则会出现错误!

@Configuration
public class BeanDemo {
    @Bean
    public User user1(){
        User user=new User();
        user.setName("张三");
        user.setAge(18);
        return user;
    }

    @Bean
    public User user2(){
        User user=new User();
        user.setName("lisi");
        user.setAge(20);
        return user;
    }
}

在这里插入图片描述

@Configuration
public class BeanDemo {
    @Bean(name = "user1")
    public User user1(){
        User user=new User();
        user.setName("张三");
        user.setAge(18);
        return user;
    }

    @Bean(value = "user2")
    public User user2(){
        User user=new User();
        user.setName("lisi");
        user.setAge(20);
        return user;
    }
}

在这里插入图片描述

四、扫描路径

使用@ComponentScan注解:
当我们将启动类放到demo2包下,发现刚才执行的代码报错:在这里插入图片描述

在这里插入图片描述
这里我们通过@ComponentScan指定Spring扫描com.abcdefg.springioc这个路径下的包。
在这里插入图片描述

五、DI详解

依赖注⼊是⼀个过程,是指IoC容器在创建Bean时, 去提供运⾏时所依赖的资源,⽽资源指的就是对象。
简单来说:就是将实例的对象取出,放入到某个类的属性中。
创建依赖注入的三种方法:

  • 属性注⼊(Field Injection)
  • 构造⽅法注⼊(Constructor Injection)
  • Setter 注⼊(Setter Injection)
    @Autowired注解是实现依赖注入 的一种常用方式。

5.1 属性注入

@Service
public class Demo1 {
    public void demo1() {
        System.out.println("demo1");
    }
}

@Controller
public class Demo2 {
    //属性注入
    @Autowired
    private Demo1 demo1;
    public void demo2() {
        demo1.demo1();
        System.out.println("demo2");
    }
}

@SpringBootApplication
public class SpringIocApplication {

    public static void main(String[] args) {
//获取Spring上下文对象
        ConfigurableApplicationContext context =
                SpringApplication.run(SpringIocApplication.class, args);
        Demo2 bean = context.getBean(Demo2.class);
        bean.demo2();
        }
}

5.2 构造方法注入

@Controller
public class Demo1 {
    private String msg;
    private String msg2;

    public Demo1() {
        System.out.println("无参的demo1");
    }

    public Demo1(String msg, String msg2) {
        this.msg = msg;
        this.msg2 = msg2;
        System.out.println("两个参数的demo1");
    }

    public Demo1(String msg) {
        this.msg = msg;
        System.out.println("一个参数的demo1");
    }

    public void demo1() {
        System.out.println("demo1");
    }
}


@Controller
public class Demo2 {

    private Demo1 demo1;

    @Autowired
    public Demo2(Demo1 demo1) {
        this.demo1 = demo1;//无参的demo1这段话会在这里打印出来
    }

    public void demo2() {
        System.out.println("Demo2");
        demo1.demo1();
    }
}

运行结果会发现,虽然我们给了三个构造函数,但是@Autowired还是会调用无参的构造函数。

当我们注掉无参的构造函数,这段代码就会报错!
在这里插入图片描述
如果我们想使用有参的构造函数,则需要给我们想使用的构造函数前面加上@Autowired,他就会调用我们想使用的构造函数。
这里的话,上述代码就不能作为演示了,因为msg和msg2是我们定义的基本数据类型,@Autowired不支持基本数据类型注入。
在这里插入图片描述

所以这里我用已经注入到Spring容器中的类来演示。

    //构造方法注入
    private ControllerDemo controller;
    private ComponentDemo component;
    public Demo1() {}
    @Autowired
    public Demo1(ControllerDemo controller, ComponentDemo component) {
        this.controller = controller;
        this.component = component;
    }
    public Demo1(ControllerDemo controller) {
        this.controller = controller;
    }

    public void demo1(){
        System.out.println("demo1");
        controller.controllerMethod();
        component.ComponentMethod();
    }

5.3 setter方法注入

        private ControllerDemo controller;
    private ComponentDemo component;
@Autowired
    public void setController(ControllerDemo controller) {
        this.controller = controller;
    }
@Autowired
    public void setComponent(ComponentDemo component) {
        this.component = component;
    }

    public void demo1(){
        System.out.println("demo1");
        controller.controllerMethod();
        component.ComponentMethod();
    }

5.4 @Autowired存在问题

  • 属性注⼊

优点:

  1. 简洁,使⽤⽅便;

缺点:

  1. 只能⽤于 IoC 容器,如果是⾮ IoC 容器不可⽤,并且只有在使⽤的时候才会出现NPE(空指针异常)
  2. 不能注⼊⼀个Final修饰的属性
  • 构造函数注⼊(Spring 4.X推荐)

优点:

  1. 可以注⼊final修饰的属性
  2. 注⼊的对象不会被修改
  3. 依赖对象在使⽤前⼀定会被完全初始化,因为依赖是在类的构造⽅法中执⾏的,⽽构造⽅法是在类加载阶段就会执⾏的⽅法.
  4. 通⽤性好,构造⽅法是JDK⽀持的, 所以更换任何框架,他都是适⽤的

缺点:

  1. 注⼊多个对象时, 代码会⽐较繁琐
  • Setter注⼊(Spring 3.X推荐)

优点:

  1. ⽅便在类实例之后, 重新对该对象进⾏配置或者注⼊

缺点:

  1. 不能注⼊⼀个Final修饰的属性
  2. 注⼊对象可能会被改变, 因为setter⽅法可能会被多次调⽤,就有被修改的⻛险

解决方法:

  • @Primary
  • @Qualifier
  • @Resource
    当我们注入对象User对象:
@Configuration
public class BeanDemo {
    @Bean(name = {"user1","hahh"})
    public User user1(){
        User user=new User();
        user.setName("张三");
        user.setAge(18);
        return user;
    }

    @Bean(value = "user2")
    public User user2(){
        User user=new User();
        user.setName("lisi");
        user.setAge(20);
        return user;
    }
}

@Service
public class Demo2 {
    @Autowired
    private User user;

    public void demo2(){
        System.out.println("demo2");
        user.toString();
    }
}

在这里插入图片描述
修改方法:

  1. 加入@Primary注解(package org.springframework.context.annotation)
    在这里插入图片描述
  2. 使用@Qualifier注解(package org.springframework.beans.factory.annotation)
    在这里插入图片描述
  3. 使用@Resource(package jakarta.annotation)
    在这里插入图片描述
@Autowird 与 @Resource的区别
  • @Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解
  • @Autowired 默认是按照类型注入,而@Resource是按照名称注入.相比于 @Autowired 来说,@Resource 支持更多的参数设置,例如name设置,根据名称获取 Bean.

此篇博客的全部代码!!!