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

Spring IoCDI

目录

IoC&DI入门

Spring是什么?

什么是容器?

什么是IoC?【面试】

IoC介绍

传统程序开发

IoC程序开发

IoC优势

DI介绍

IoC&DI使用

IoC详解

Bean的存储

类注解【五大注解非常重要】

1)@Controller(控制器存储)

2)@Service(服务存储)

3)@Repository(仓库存储)

4)@Component(组件存储)

5)@Configuration(配置存储)​

五大注解直接的区别与联系

方法注解 @Bean

方法注解要配合类注解使用​

定义多个对象​

DI 详解​

1.属性注入

2.构造方法注入

3.Setter注入

​编辑

三种注入优缺点分析​【面试】

@Autowired存在问题

1)@Primary​

2)@Qualifier​

3)@Resource​

@Autowird 与 @Resource的区别

Autowired装配顺序 

总结​

Spring, Spring Boot 和Spring MVC的关系以及区别

bean 的命名​


IoC&DI入门

Spring  Spring Boot  Spring MVC

我们学习了Spring Boot和Spring MVC的开发,可以完成⼀些基本功能的开发了,但是什么是Spring呢?Spring,Spring Boot和SpringMVC⼜有什么关系呢?咱们还是带着问题去学习.我们先看什么是Spring?


Spring是什么?

Spring是包含了众多⼯具方法的IoC容器

什么是容器?

• List/Map->数据存储容器

• Tomcat->Web容器

什么是IoC?【面试】

IoC是Spring的核心思想;

IoC: Inversion of Control (控制反转),也就是说Spring是⼀个"控制反转"的容器.

什么是控制反转呢?也就是控制权反转.什么的控制权发⽣了反转?获得依赖对象的过程被反转了 也就是说,当需要某个对象时,传统开发模式中需要⾃⼰通过new创建对象,现在不需要再进⾏创 建,把创建对象的任务交给容器,程序中只需要依赖注⼊(Dependency Injection,DI)就可以了. 这个容器称为:IoC容器.Spring是⼀个IoC容器,所以有时Spring也称为Spring容器.


IoC介绍

下面小编将通过一个造车的示例来演示一下

传统程序开发

我们的实现思路是这样的:

先设计轮⼦(Tire),然后根据轮⼦的⼤⼩设计底盘(Bottom),接着根据底盘设计⻋⾝(Framework),最 后根据⻋⾝设计好整个汽⻋(Car)。这⾥就出现了⼀个"依赖"关系:汽⻋依赖⻋⾝,⻋⾝依赖底盘,底 盘依赖轮⼦.

代码如下:

轮胎:

package org.example.springioc.troditional;

public class Tire {
    private int size = 21;  // 轮胎的尺寸
    public Tire() {
        System.out.println("tire size"+size);
    }
}

底盘:

package org.example.springioc.troditional;

public class Bottom {
    private Tire tire;
    public Bottom() {
        tire = new Tire();
        System.out.println("tire init...");
    }
}

车身:

package org.example.springioc.troditional;

public class Framework {
    private Bottom bottom;   // 这是汽车的一个底盘

    /**
     * 构造函数
     */
    public Framework() {
        bottom = new Bottom();
        System.out.println("bottom init");
    }

}

车:

package org.example.springioc.troditional;

public class Car {

    private Framework framework;  // 车身

    /**
     * 无参的构造函数
     */
    public Car() {
        framework = new Framework();
        System.out.println("framework init...");
    }

    /**
     * 汽车需要跑起来
     */
    public void run(){
        System.out.println("car run...");
    }

}

主代码:

package org.example.springioc.troditional;

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

结果展示:

如上述这个流程,我们确实能够顺利得造一辆车出来。但是在现实的市场中,人们总是要求根据用户不同的需求进行造车。比如轮胎的尺寸,有的用户需求是20厘米,有的用户的需求是25厘米,有的用户需求是27厘米...有许许多多的用户,则会有许许多多的需求。既然有了轮胎尺寸的要求就会有对于汽车其他方面的需求,例如车身的颜色、底盘的高度等等。所以以这辆车为例,我们的轮胎尺寸希望是可以变动的,供用户选择的,所以我们对代码做出以下改动:

对于上述的改动,我们发现一个参数的改变把我们程序中的所有的代码几乎改了个遍,这种情况就是我们所说的高耦合,所以针对这种情况,我们对代码进行改动,即进行解耦


IoC程序开发

我们尝试换⼀种思路,我们先设计汽⻋的⼤概样⼦,然后根据汽⻋的样⼦来设计⻋⾝,根据⻋⾝来设计 底盘,最后根据底盘来设计轮⼦.这时候,依赖关系就倒置过来了:轮⼦依赖底盘,底盘依赖⻋⾝,⻋⾝依赖汽⻋。

此时,我们只需要将原来由⾃⼰创建的下级类,改为传递的⽅式(也就是注⼊的⽅式),因为我们不 需要在当前类中创建下级类了,所以下级类即使发⽣变化(创建或减少参数),当前类本⾝也⽆需修改任何代码,这样就完成了程序的解耦.

代码示例:

package org.example.springioc.novel;

public class Tire {
    private int size;   // 轮胎的尺寸
    private String color;   // 轮胎的颜色
    public Tire(int size,String color) {
        System.out.println("tire size"+size+",color:"+color);
    }
}
package org.example.springioc.novel;



public class Bottom {
    private Tire tire;
    public Bottom(Tire tire) {
        this.tire = tire;
        System.out.println("tire init...");
    }
}
package org.example.springioc.novel;



public class Framework {
    private Bottom bottom;   // 这是汽车的一个底盘

    /**
     * 构造函数
     */
    public Framework(Bottom bottom) {
        this.bottom = bottom;
        System.out.println("bottom init");
    }

}
package org.example.springioc.novel;



public class Car {

    private Framework framework;  // 车身

    /**
     * 无参的构造函数
     */
    public Car(Framework framework) {
        this.framework = framework;
        System.out.println("framework init...");
    }

    /**
     * 汽车需要跑起来
     */
    public void run(){
        System.out.println("car run...");
    }

}
package org.example.springioc.novel;

public class Main {
    public static void main(String[] args) {
        Tire tire = new Tire(10,"black");
        Bottom bottom = new Bottom(tire);
        Framework framework = new Framework(bottom);
        Car car = new Car(framework);
        car.run();

    }
}

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


IoC优势

在传统的代码中对象创建顺序是:Car->Framework->Bottom->Tire

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

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

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

即控制反转容器就是IoC容器。

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

资源不由使⽤资源的双⽅管理,⽽由不使⽤资源的第三⽅管理,这可以带来很多好处。第⼀,资源集中管理,实现资源的可配置和易管理。第⼆,降低了使⽤资源双⽅的依赖程度,也就是我们说的耦合度。

1. 资源集中管理: IoC容器会帮我们管理⼀些资源(对象等),我们需要使⽤时,只需要从IoC容器中去取 就可以了

2. 我们在创建实例的时候不需要了解其中的细节,降低了使⽤资源双⽅的依赖程度,也就是耦合度.

Spring就是⼀种IoC容器,帮助我们来做了这些资源管理.


DI介绍

DI:Dependency Injection(依赖注入)

容器在运⾏期间,动态的为应⽤程序提供运⾏时所依赖的资源,称之为依赖注⼊。

程序运⾏时需要某个资源,此时容器就为其提供这个资源.

从这点来看,依赖注⼊(DI)和控制反转(IoC)是从不同的⻆度的描述的同⼀件事情,依赖注⼊是 从应⽤程序的⻆度来描述,就是指通过引⼊IoC容器,利⽤依赖关系注⼊的⽅式,实现对象之间的解 耦.

以上述代码为例:

IoC是⼀种思想,也是"⽬标",⽽思想只是⼀种指导原则,最终还是要有可⾏的落地⽅案,⽽DI就属于 具体的实现。所以也可以说,DI是IoC的⼀种实现.


IoC&DI使用

对IoC和DI有了初步的了解,我们接下来具体学习Spring IoC和DI的代码实现.

既然Spring是⼀个IoC(控制反转)容器,作为容器,那么它就具备两个最基础的功能: • 存 • 取

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


IoC详解

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

Bean的存储

要把某个对象交给IOC容器管理,需要在类上添加⼀个注解。⽽Spring框架为了更好的服务web应⽤程序,提供了更丰富的注解.

类注解【五大注解非常重要】
1)@Controller(控制器存储)

代码示例:

package org.example.springioc.Controller;

import org.springframework.stereotype.Controller;

@Controller
public class UController {
    public void sayHi(){
        System.out.println("hi,UController...");
    }
}
@SpringBootApplication
public class SpringIocApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
        // 通过类型
        UController bean = context.getBean(UController.class);
        bean.sayHi();
    }
}

结果展示:

ApplicationContext和BeanFeactory关系与区别【重点!!】:

1.关系上,他们是父子关系,BeanFeactory有的功能,ApplicationContext都有。除此之外,ApplicationContext还具备环境、资源管理等功能。

2.性能上,BeanFeactory是懒加载.ApplicationContext是提前加载


获取bean的三种方式:

1.根据bean的名称获取bean

2.根据bean的名称和类型获取bean

3.根据bean的类型获取bean

代码展示:

package org.example.springioc.Controller;
import org.springframework.stereotype.Controller;


@Controller    // 通过这个注解已经存进来了[存进Spring【Spring是一个容器】中了]
public class HelloController {
    public void sayHi(){
        System.out.println("Hello userController...");
    }
}
@SpringBootApplication
public class SpringIocApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);

        // 通过类型
        HelloController bean = context.getBean(HelloController.class);
        System.out.println(bean);
        bean.sayHi();

        // 通过bean的名称
        HelloController helloController = (HelloController) context.getBean("helloController");
        System.out.println(helloController);
        helloController.sayHi();

        // 通过bean的名称+类型去拿
        HelloController helloController1 = context.getBean("helloController",HelloController.class);
        System.out.println(helloController1);
        helloController1.sayHi();
    }
}

结果展示:

如上图结果可知,通过这三种方式获取bean,得到的对象是同一个。


关注:以bean的名称获取到bean.

如上述代码所述,当我们以bean的名称获取bean的时候,bean的名称的首字母要小写,那究竟bean的命名规则是不是对于所有的bean,只要首字母小写就可以访问到了呢?我们下面通过一个代码来检验一下:

代码展示:

package org.example.springioc.Controller;

import org.springframework.stereotype.Controller;

@Controller
public class UController {
    public void sayHi(){
        System.out.println("hi,UController...");
    }
}
@SpringBootApplication
public class SpringIocApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
        UController uController = (UController) context.getBean("uController");
        uController.sayi();
    }
}

如上述代码我们把"UController"改成"uController"

结果展示:

唉。奇了怪了,明明上一个程序把bean的名称首字母小写能完成注入,怎么到了这个代码就NoSuchBeanDefinitionException了,我们一起听听官方的解释。


Bean的命名规则

1)命名约定使⽤Java标准约定作为实例字段名.也就是说,bean名称以⼩写字⺟开头,然后使⽤驼峰式⼤⼩写.

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


针对上面Bean的命名规则我们再进行一次修正,看一下最后的结果是否符合我们的预期。

代码展示:

@SpringBootApplication
public class SpringIocApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
        UController uController = (UController) context.getBean("UController");
        uController.sayi();
    }
}

结果展示:

如上述结果所示,我们顺利拿到我们所预期的结果。


2)@Service(服务存储)

代码展示:

package org.example.springioc.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {
    public void doService(){
        System.out.println("Hi,UserService...");
    }
}
@SpringBootApplication
public class SpringIocApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
         /**
         * 测试注解@Service的
         */
        UserService bean = context.getBean(UserService.class);
        bean.doService();
    }
}

结果展示:


3)@Repository(仓库存储)

代码展示:

package org.example.springioc.repo;

import org.springframework.stereotype.Repository;

@Repository
public class UserRepo {
    public void doRepo(){
        System.out.println("doRepo...");
    }
}
@SpringBootApplication
public class SpringIocApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
         /**
         * 测试@Repository
         */
        UserRepo bean3 = context.getBean(UserRepo.class);
        bean3.doRepo();
    }
}

结果展示:


4)@Component(组件存储)

代码展示:

package org.example.springioc.component;

import org.springframework.stereotype.Component;

@Component
public class UserComponent {
    public void doComponent(){
        System.out.println("do Component...");
    }
}
@SpringBootApplication
public class SpringIocApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
         /**
         * 测试注解Component的
         */
        UserComponent bean1 = context.getBean(UserComponent.class);
        bean1.doComponent();
    }
}

结果展示:


5)@Configuration(配置存储)​

代码展示:

package org.example.springioc.config;

import org.springframework.context.annotation.Configuration;

@Configuration
public class UserConfig {
    public void deConfig(){
        System.out.println("de Config...");
    }
}
@SpringBootApplication
public class SpringIocApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
         /**
         * 测试@Configuration
         */
        UserConfig bean2 = context.getBean(UserConfig.class);
        bean2.deConfig();
    }
}

结果展示:


五大注解直接的区别与联系

那我们为什么是需要五个注解?而不是一个统一的一个注解呢?毕竟这个注解的作用只是把这个类交给spring来进行管理【放到ioc容器】而已。

这个也是和咱们前面讲的应用分层是呼应的. 让程序员看到类注解之后,就能直接了解当前类的用途.​
@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 ​

方法注解要配合类注解使用​

代码示例:

package org.example.springioc.config;

import org.example.springioc.model.UserInfo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

/**
 * Bean的一些配置信息
 */
@Configuration
public class BeanConfig {
    @Bean
    public UserInfo userInfo(){
        return new UserInfo("zhangsan");
    }
}

注意方法注解的使用要与类注解搭配使用!不然就会报错!【在idea的专业版上,如果只是加了方法注解,而没有加类注解的话会直接爆红。但是社区版不会,只有运行起来才会发现错误。】


定义多个对象​

代码展示:

package org.example.springioc.config;

import org.example.springioc.model.UserInfo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

/**
 * Bean的一些配置信息
 */
@Configuration
public class BeanConfig {
    @Bean
    public UserInfo userInfo(){
        return new UserInfo("zhangsan");
    }

    @Bean
    public UserInfo userInfo1(){
        return new UserInfo("lisi");
    }

    @Bean
    public UserInfo UCInfo(){
        return new UserInfo("xiaoyun");
    }
}

测试代码:

@SpringBootApplication
public class SpringIocApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
         /**
         * 通过类型进行访问
         */
        UserInfo user = context.getBean(UserInfo.class);
        System.out.println(user);
    }
}

结果展示:

唉!报错了!那我们就来看看是什么导致了报错,从报错Cincinnati我们一个看出,报错信息中说Bean不唯一。那我们就得回想一下了我们是不是之前定义多个同样类型的bean对象了,导致程序不知道你想要哪个对象就会报错了。那怎么解决这个问题呢?

我们又发现了,虽然这个类型里面有三个bean,但是这三个bean的名称不一样啊!那我们是不是可以通过bean的名称进行访问呢?实践出真知,那我们就试验一下便知了。


(1)通过bean名称

改动后的测试代码:

@SpringBootApplication
public class SpringIocApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
         /**
         * @Bean就是通过方法名
         */
        UserInfo bean = (UserInfo) context.getBean("userInfo");
        System.out.println(bean);

        UserInfo bean1 = (UserInfo) context.getBean("userInfo1");
        System.out.println(bean1);

        UserInfo bean2 = (UserInfo) context.getBean("UCInfo");
        System.out.println(bean2);
    }
}

结果展示:

实践结果表明我们可以通过bean的名称可以获取到不同对象的信息。


那还有没有别的方法可以解决这个问题的,好像进入到spring的学习后,我们基本上都会利用注解去解决问题。经过查资料我们可以利用@Primary这个注解去解决“空指针异常”,还是实践一下

package org.example.springioc.config;

import org.example.springioc.model.UserInfo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

/**
 * Bean的一些配置信息
 */
@Configuration
public class BeanConfig {
    @Bean
    public UserInfo userInfo(){
        return new UserInfo("zhangsan");
    }
    @Primary
    @Bean
    public UserInfo userInfo1(){
        return new UserInfo("lisi");
    }

    @Bean
    public UserInfo UCInfo(){
        return new UserInfo("xiaoyun");
    }
}

如上图所示,我们把这个@Primary加到了第二个bean的上方,这个注解的意思就是默认是获取这个对象的意思,即当我们通过类型访问这个类型的所有对象的时候,默认返回第二个bean的对象。还是实践一下!

测试代码:

@SpringBootApplication
public class SpringIocApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
         /**
         * 通过类型进行访问
         */
        UserInfo user = context.getBean(UserInfo.class);
        System.out.println(user);
    }
}

测试结果:

唉!符合我们的预期,返回的就是第二bean的结果!


DI 详解​

上面我们讲解了控制反转IoC的细节,接下来呢,我们学习依赖注入DI的细节。​
依赖注入是一个过程,是指IoC容器在创建Bean时, 去提供运行时所依赖的资源,而资源指的就是对象.​在上面程序案例中,我们使用了 @Autowired 这个注解,完成了依赖注入的操作.​简单来说, 就是把对象【资源】取出来放到某个类的属性中. ​

关于依赖注入, Spring也给我们提供了三种方式:​
        1. 属性注入(Field Injection)​
        2. 构造方法注入(Constructor Injection)​
        3. Setter 注入(Setter Injection)​

以注入UserService为例

代码:

package org.example.springioc.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {
    public void doService(){
        System.out.println("Hi,UserService...");
    }
}

1.属性注入

代码展示:

package org.example.springioc.Controller;
import org.example.springioc.component.UserComponent;
import org.example.springioc.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;


@Controller    // 通过这个注解已经存进来了[存进Spring【Spring是一个容器】中了]
public class HelloController {
    // 将这个依赖注入进来
    @Autowired           // 属性注入
    private UserService userService;
    public void sayHi(){
        userService.doService();
        System.out.println("Hello userController...");
    }
}

测试代码:

​
@SpringBootApplication
public class SpringIocApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
         /**
         * 通过类型进行访问
         */
        UserInfo user = context.getBean(UserInfo.class);
        System.out.println(user);
    }
}

​

结果展示:

如上图所示,很顺利就访问到了!并输出了正确信息,说明注入成功!


2.构造方法注入

代码展示:

package org.example.springioc.Controller;
import org.example.springioc.component.UserComponent;
import org.example.springioc.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;


@Controller    // 通过这个注解已经存进来了[存进Spring【Spring是一个容器】中了]
public class HelloController {
    private UserService userService;
    private UserComponent userComponent;
    /**
     * 构造方法的注入
     *如果加了一个无参的构造函数,就会报错【空指针异常】
     */
    @Autowired    // 这个注解就是明确谁是默认的构造函数
    public HelloController(UserService userService, UserComponent userComponent) {
        this.userService = userService;
        this.userComponent = userComponent;
    }
    public void sayHi(){
        userService.doService();
        userComponent.doComponent();
        System.out.println("Hello userController...");
    }
}

测试代码与属性注入一样,几个注入的测试代码一样。

结果展示:

结果正确!


3.Setter注入

代码展示:

package org.example.springioc.Controller;
import org.example.springioc.component.UserComponent;
import org.example.springioc.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;


@Controller    // 通过这个注解已经存进来了[存进Spring【Spring是一个容器】中了]
public class HelloController {
    private UserService userService;
    private UserComponent userComponent;
    /**
     * Setter方法注入
     */
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    public void sayHi(){
        userService.doService();
     
        System.out.println("Hello userController...");
    }
}

结果展示:

结果正确!


三种注入优缺点分析​【面试】

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


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


• Setter注入(Spring 3.X推荐)​
◦ 优点: 方便在类实例之后, 重新对该对象进行配置或者注入​
◦ 缺点: ​
   ▪ 不能注入一个Final修饰的属性​
   ▪ 注入对象可能会被改变, 因为setter方法可能会被多次调用, 就有被修改的风险.​


@Autowired存在问题

当同一类型存在多个bean时, 使用@Autowired会存在问题​

代码展示:

package org.example.springioc.config;

import org.example.springioc.model.UserInfo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

/**
 * Bean的一些配置信息
 */
@Configuration
public class BeanConfig {
    @Bean
    public UserInfo userInfo(){
        return new UserInfo("zhangsan");
    }
    @Bean
    public UserInfo userInfo1(){
        return new UserInfo("lisi");
    }

    @Bean
    public UserInfo UCInfo(){
        return new UserInfo("xiaoyun");
    }
}
package org.example.springioc.Controller;

import jakarta.annotation.Resource;
import org.example.springioc.config.UserConfig;
import org.example.springioc.model.UserInfo;
import org.example.springioc.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {


    @Autowired
    private UserService userService;

    @Autowired
    private UserInfo user;


    public void sayHi() {
        System.out.println(user);
        System.out.println("hi UserController");
    }
}

结果展示:

结果发现报错了!报错信息里面说,找到三个这样的注入,不知道注入哪个好!


那对于上述出现的问题,我们有没有什么解决方案呢?答案是必须有!

Spring提供了以下几种解决方案:​
• @Primary​
• @Qualifier​
• @Resource​


1)@Primary​

BeanConfig中,使用@Primary注解:当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现.

如上图所示,当我们把这个注释加到第二个bean上是,就会默认是实现第二个bean.

结果展示;

结果返回时“lisi”,结果正确!解决方案生效!


2)@Qualifier​

使用@Qualifier注解:指定当前要注入的bean对象。 在@Qualifier的value属性中,指定注入的bean的名称

@Qualifier注解不能单独使用,必须配合@Autowired使用​

代码展示:

package org.example.springioc.Controller;

import jakarta.annotation.Resource;
import org.example.springioc.config.UserConfig;
import org.example.springioc.model.UserInfo;
import org.example.springioc.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    /**
     * 解决属性注入的问题方案1+方案2
     */
    @Qualifier("userInfo")
    @Autowired
    private UserInfo user;
    public void sayHi() {
        System.out.println(user);
        System.out.println("hi UserController");
    }
}

结果展示:

结果正确!方法生效!


3)@Resource​

使用@Resource注解:是按照bean的名称进行注入。通过name属性指定要注入的bean的名称。

代码展示:

package org.example.springioc.Controller;

import jakarta.annotation.Resource;
import org.example.springioc.config.UserConfig;
import org.example.springioc.model.UserInfo;
import org.example.springioc.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {

    /**
     * 解决属性注入的问题方案3
     */
    @Resource(name = "UCInfo")
    private UserInfo user;

    public void sayHi() {
        System.out.println(user);
        System.out.println("hi UserController");
    }
}

结果展示:

结果正确!方案生效!


@Autowird 与 @Resource的区别

• @Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解​
• @Autowired 默认是按照类型注入,而@Resource是按照名称注入. 相比于 @Autowired 来说,
@Resource 支持更多的参数设置,例如 name 设置,根据名称获取 Bean.​


Autowired装配顺序 

文字描述

@Qualifier优先级高于@Autowired

1.按照类型查找,如果只查到一个直接注入;如果没有查到,就抛出异常;如果查到多个就按照名称查找,查找到就注入;没有查找到就抛出异常;

2.@Qualifier按照名称和类型查找


总结​

Spring, Spring Boot 和Spring MVC的关系以及区别

八股文:

“Spring 是整个 Spring 家族的核心框架,它主要解决的是对象的创建和管理问题,核心包括 IOC(控制反转)和 AOP(面向切面编程)。通过这些机制,我们可以更好地实现代码的解耦和模块化,是整个后端架构的基础。

Spring MVC 是基于 Spring 的一个 Web 层框架模块,它实现了经典的前端控制器模式 DispatcherServlet,专门用来处理 Web 请求、参数绑定、视图渲染等功能。也就是说,当我们需要开发 Web 应用时,Spring MVC 提供了非常清晰的请求分发和控制逻辑。

Spring Boot 是在 Spring 基础上进一步封装的快速开发框架,它的核心目标是简化 Spring 应用的配置和部署。通过自动配置、starter 依赖管理、内嵌 Tomcat 等机制,Spring Boot 大大降低了 Spring 项目的上手和开发门槛。我们不用再像以前一样手动配置 XML 或者各种繁琐的 Bean,只需要引入相关 starter 就能直接用,非常适合微服务架构下的快速迭代开发。

总的来说,Spring 提供基础能力,Spring MVC 负责 Web 开发,而 Spring Boot 则帮我们快速整合和启动整个项目。这三者不是彼此替代的关系,而是上下协同、共同构建起现代 Java Web 开发的主流技术体系。”

(最后一句话总结: Spring MVC和Spring Boot都属于Spring,Spring MVC 是基于Spring的一个 MVC 框架,而Spring Boot 是基于Spring的一套快速开发整合包.)


bean 的命名​

1) 五大注解存储的bean​
 ① 前两位字母均为大写, bean名称为类名​
 ② 其他的为类名首字母小写
 ③ 通过 value属性设置 @Controller(value = "user") ​
 2) @Bean 注解存储的bean​
 ① bean名称为方法名​
 ②通过name属性设置 @Bean(name = {"u1","user1"}) ​


相关文章:

  • tomcat的负载均衡和会话保持
  • 微信小程序生成某个具体页面的二维码
  • JVM基础架构:内存模型×Class文件结构×核心原理剖析
  • 算法刷题记录——LeetCode篇(2.6) [第151~160题](持续更新)
  • 使用ExcelJS实现专业级医疗数据导出功能:从数据到Excel报表的完整指南
  • vscode调试vite项目断点(debugger)
  • 基于HAI应用,从零开始的NLP处理实践指南
  • 【区块链安全 | 第三十一篇】合约(五)
  • CVAT及其半自动标注安装(Windows)
  • SYN Flooding攻击原理
  • OpenCV--图像形态学
  • 第二章日志分析-mysql应急响应笔记
  • 【Linux网络】网络套接字socket
  • DeepSeek在互联网技术中的革命性应用:从算法优化到系统架构
  • Proteus vs Multisim:电路设计与仿真软件对比
  • 计算机专业English交流
  • 我用Cursor + DeepSeek + Claude-3.7-Sonnet + DevBox,10分钟开发了一个系统
  • ChatGPT之智能驾驶问题讨论
  • 网络Socket编程基于UDP协议模拟简易网络通信
  • 基于快速开发平台与智能手表的区域心电监测与AI预警系统(源码+论文+部署讲解等)
  • 倒票“黄牛”屡禁不绝怎么破?业内:强化文旅市场票务公开制度
  • 首映|《星际宝贝史迪奇》真人电影,不变的“欧哈纳”
  • 女生“生理期请病假要脱裤子证明”?高校回应:视频经处理后有失真等问题
  • 我使馆就中国公民和企业遭不公正待遇向菲方持续提出严正交涉
  • 美国务卿鲁比奥抵达会场,将参加俄乌会谈
  • 讲座|消逝之钟:《红楼梦》与《布登勃洛克一家》中的时间观