Spring IoC和DI
作者:mmseoamin日期:2024-04-27

Spring是一个IoC容器。

IoC是什么?

IoC:控制反转。什么是控制反转?在我们平时的代码中对象的创建和销毁都是由我们程序员自己来进行控制,而IoC就是将这个过程给翻转过来,即:对象的创建和销毁交给第三方来进行管理,程序员自己不用关心对象的创建和销毁的细节。这样可以降低代码的耦合程度,那么它是如何降低的呢?

例如: A依赖B,B依赖C;

class A{
    private B b;
    public A() {
        b = new B();
        System.out.println("A创建完成");
    }
}
class B{
    private C c;
    public B() {
        c = new C();
        System.out.println("B创建完成");
    }
}
class C{
    public C() {
        System.out.println("C创建完成");
    }
}
//此时想要获取A
public class Abc {
    public static void main(String[] args) {
        A a = new A();
    }
}

此时可以看到如果想要给C类的构造方法中添加一个参数,那么A和B也必须跟着改。如果此时我们引入IoC的思想就可以将上述代码改为如下的调用方式:

class A{
    private B b;
    public A(B b) {
        this.b = b;
        System.out.println("A创建完成");
    }
}
class B{
    private C c;
    public B(C c) {
        this.c = c;
        System.out.println("B创建完成");
    }
}
class C{
    public C() {
        System.out.println("C创建完成");
    }
}
//此时想要获取A
public class Abc {
    public static void main(String[] args) {
        C c = new C();
        B b = new B(c);
        A a = new A(b);
    }
}

此时可以看到如果想要给C类的构造方法中添加一个参数,就只需要更改C这一个类。而main()方法中的代码都是IoC容器来帮我们完成的。

IoC就可以想象为一个存放对象的仓库。而Spring提供了6种将对象放入仓库的注解

五大类注解:@Controller ; @Component ; @Repository ; @Configuration ; @Service

一个方法注解:@Bean

在Spring中可以通过这些注解将对象交给Spring来进行管理。

通过@Controller将对象存入Spring中

@Controller
public class Hello {
    public void hello() {
        System.out.println("Hello");
    }
}

存进去之后我们该如何证明它是否存进去了呢?

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        //获取Spring的上下文
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
        //可以通过getBean()方法来获取Spring容器中的Bean对象
        Hello h = context.getBean(Hello.class);
        h.hello();
    }
}

Spring IoC和DI,第1张

此时如果将Hello类上的@Controller注解删除:

Spring IoC和DI,第2张

执行出现异常:没有找到Hello类的Bean对象。

getBean()方法还有其他几种重载的方法(Bean的名称默认是类名的首字母缩写,如果类名前几个字母都是大写,那么Bean名称就是类名,也可以通过value属性设置@Controller(value = "user")):

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        //获取Spring的上下文
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
        //根据类型来获取Bean对象
        Hello h1 = context.getBean(Hello.class);
        //根据对象名称来获取Bean对象
        Hello h2 = (Hello) context.getBean("hello");
        //根据类型和名称来获取Bean对象
        Hello h3 = context.getBean(Hello.class, "hello");
        h1.hello();
        h2.hello();
        h3.hello();
        //按bean名称和构造函数参数动态创建bean,只适⽤于具有原型(prototype)作⽤域的bean
        //Object getBean(String var1, Object... var2) throws BeansException
        // 5. 按bean类型和构造函数参数动态创建bean, 只适⽤于具有原型(prototype)作⽤域的bean
        // T getBean(Class var1, Object... var2) throws BeansException
    }
}

 Spring IoC和DI,第3张

五大类注解的关系:

其他的四个类注解其实和@Controller注解没有本质的区别,他们更多的是赋予的字面意义不同,使得程序员看到注解就可以大概明白该类是干什么的。

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

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

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

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

那么 @Component 呢?这个注解可以称为是上面四个注解的 “父类” 因为其他四个注解都被 @Component 注解所注解。

Spring IoC和DI,第4张

@Bean

该注解是一个方法注解,可以将方法返回的对象放入Spring容器中(这个类也必须交给Spring来管理)Bean对象的名称默认为方法名

@Component
public class Hello {
    private String str;
    public Hello() {
    }
    public Hello(String str) {
        this.str = "你好";
    }
    public void hello() {
        System.out.println("Hello"+str);
    }
}
@Component//这个类也必须交给Spring来管理
public class Tmp {
    @Bean
    public Hello bean1() {
        Hello h = new Hello("你好");
        return h;
    }
    @Bean
    public Hello bean2() {
        Hello h = new Hello();
        return h;
    }
}
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        //获取Spring的上下文
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
        //根据对象名称来获取Bean对象
        Hello h2 = (Hello) context.getBean("bean1");
        h2.hello();
    }
}

Spring IoC和DI,第5张

现在我们使用@Bean创建出了两个Bean对象,此时就不适合使用类型来获取Bean实例了,如果此时使用类型来获取Bean实例就会发生异常(因为程序不知道你想要那一个Bean对象):

Spring IoC和DI,第6张

重命名Bean

可以在使用@Bean注解时设置name属性的值来指定该Bean对象的名称

@Component
public class Tmp {
    @Bean(name = "qq")
    public Hello bean1() {
        Hello h = new Hello("你好");
        return h;
    }
    @Bean
    public Hello bean2() {
        Hello h = new Hello();
        return h;
    }
}
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        //获取Spring的上下文
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
        Hello h2 = (Hello) context.getBean("qq");
        h2.hello();
    }
}

Spring IoC和DI,第7张

DI是什么?

DI:依赖注入。容器在运行期间,动态的为应用程序提供运行时所依赖的资源,依赖注入(DI)和控制反转(IoC)是从不同的角度来描述同⼀件事情,就是指通过引入IoC容器,利用依赖关系注入的方式,实现对象之间的解耦。

简单来说,就是把对象取出来放到某个类的属性中。

Spring也给我们提供了三种依赖注入的方式:

  • 属性注入
  • 构造方法注入
  • Setter 注入

    属性注入

    使用@Autowired注解实现属性注入

    @RestController
    public class Abc {
        @Autowired
        private Hello hello;
        
        @RequestMapping("/hello")
        public void hello() {
            hello.hello();
        }
    }

    构造方法注入

    使用@Autowired注解实现构造方法注入

    @RestController
    public class Abc {
        private Hello hello;
        
        @Autowired
        public Abc(Hello hello) {
            this.hello = hello;
        }
        @RequestMapping("/hello")
        public void hello() {
            hello.hello();
        }
    }

    Setter方法注入

    Setter 注入和普通的 Setter 方法实现类似,只不过在设置 set 方法的时候需要加@Autowired注解

    @RestController
    public class Abc {
        private Hello hello;
        
        @Autowired
        public void setHello(Hello hello) {
            this.hello = hello;
        }
        
        @RequestMapping("/hello")
        public void hello() {
            hello.hello();
        }
    }

    三种注入方式的区别:

    属性注入:

    • 优点:简洁,使用方便;
    • 缺点:只能用于IoC容器,如果是非IoC容器不可用,并且只有在使用的时候才会出现空指针异常,不能注入一个Final修饰的属性。

      构造方法注入:

      • 优点:可以注入final修饰的属性,注入的对象不会被修改依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类加载阶段就会执行的方法。通用性好,构造方法是JDK支持的,所以更换任何框架,他都是适用的;
      • 缺点:注入多个对象时,代码会比较繁琐。

        Setter注入:

        • 优点:方便在类实例之后重新对该对象进行配置或者注入;
        • 缺点:不能注入一个Final修饰的属性,注入对象可能会被改变,因为setter方法可能会被多次调用,就有被修改的风险。

          @Autowired

          @Autowired注解的注入逻辑:先根据被修饰属性的类型来获取Bean对象,当找到了一个就直接注入;当找到了多个对象就根据属性名来寻找如果没找到就报错,如果找到了就注入。

          @Autowired是Spring提供的注解

          @Primary

          当存在多个同类型的Bean时可以使用该注解来指定默认注入的Bean;

          @Component
          public class Tmp {
              @Primary
              @Bean
              public Hello bean1() {
                  Hello h = new Hello("你好");
                  return h;
              }
              @Bean
              public Hello bean2() {
                  Hello h = new Hello();
                  return h;
              }
          }
          @RestController
          public class Abc {
              @Autowired
              private Hello hello;
              @RequestMapping("/hello")
              public void hello() {
                  hello.hello();
              }
          }

          Spring IoC和DI,第8张

          @Qualifier

          在@Qualifier的value属性中,可以指定注入的bean的名称。@Qualifier注解不能单独使用,必须配合@Autowired使用。

          @RestController
          public class Abc {
              
              @Qualifier("bean2")
              @Autowired
              private Hello hello;
              @RequestMapping("/hello")
              public void hello() {
                  hello.hello();
              }
          }

          Spring IoC和DI,第9张

          @Resource

          @Resource注解:是按照bean的名称进行注入。@Resource注解是JDK提供的。

          @RestController
          public class Abc {
              @Resource(name = "bean1")
              private Hello hello;
              @RequestMapping("/hello")
              public void hello() {
                  hello.hello();
              }
          }

          Spring IoC和DI,第10张