Spring IoC和DI

Spring是一个IoC容器。

IoC是什么?

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

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

java 复制代码
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的思想就可以将上述代码改为如下的调用方式:

java 复制代码
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中

java 复制代码
@Controller
public class Hello {
    public void hello() {
        System.out.println("Hello");
    }
}

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

java 复制代码
@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();
    }
}

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

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

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

java 复制代码
@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> T getBean(Class<T> var1, Object... var2) throws BeansException
    }
}

五大类注解的关系:

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

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

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

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

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

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

@Bean

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

java 复制代码
@Component
public class Hello {
    private String str;

    public Hello() {
    }

    public Hello(String str) {
        this.str = "你好";
    }

    public void hello() {
        System.out.println("Hello"+str);
    }
}
java 复制代码
@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();
    }
}

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

重命名Bean

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

java 复制代码
@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;
    }
}
java 复制代码
@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();
    }
}

DI是什么?

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

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

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

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

属性注入

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

java 复制代码
@RestController
public class Abc {
    @Autowired
    private Hello hello;
    
    @RequestMapping("/hello")
    public void hello() {
        hello.hello();
    }
}

构造方法注入

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

java 复制代码
@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注解

java 复制代码
@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;

java 复制代码
@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;
    }
}
java 复制代码
@RestController
public class Abc {
    @Autowired
    private Hello hello;

    @RequestMapping("/hello")
    public void hello() {
        hello.hello();
    }
}

@Qualifier

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

java 复制代码
@RestController
public class Abc {
    
    @Qualifier("bean2")
    @Autowired
    private Hello hello;

    @RequestMapping("/hello")
    public void hello() {
        hello.hello();
    }
}

@Resource

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

java 复制代码
@RestController
public class Abc {

    @Resource(name = "bean1")
    private Hello hello;

    @RequestMapping("/hello")
    public void hello() {
        hello.hello();
    }
}
相关推荐
松仔log1 小时前
JetPack——Paging3+Room
android·java·zoom
㳺三才人子6 小时前
初探 Flask
后端·python·flask·html
星栈独行6 小时前
我在 Rust 全栈项目里用 JWT 做无状态认证
开发语言·后端·rust·前端框架·开源·github·web
Lei活在当下6 小时前
先用起来,再理解,关于协程Coroutine应该知道的事
android·java·jvm
Java爱好狂.6 小时前
Java程序员体系化学习路线(2026最新版)
java·后端·java面试·java架构师·java程序员·java八股文·java学习路线
陈随易7 小时前
Redis 8.8发布,一定要更新
前端·后端·程序员
tongluowan0077 小时前
以ReentrantLock为例解释AQS的工作流程
java·模板方法模式·aqs·reentrantlock
装不满的克莱因瓶7 小时前
SpringBoot 如何将 lib 目录中jar包打包进最终的jar包里面
spring boot·后端·maven·jar·mvn
ltl8 小时前
Transformer 原论文实验结果:为什么 28.4 BLEU 足以改写路线图
后端
身如柳絮随风扬8 小时前
Java 项目打包与部署完全指南:JAR vs WAR,从构建到运行
java·firefox·jar