自学内容网 自学内容网

[Spring] Spring IoC与DI注入

🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:
🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482
🍕 Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀Java EE(96平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
🍃 Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
在这里插入图片描述

1. IoC与DI入门

1.1 IoC

通过前面的学习,我们知道了Spring是⼀个开源框架,他让我们的开发更加简单.他支持⼴泛的应用场景,有着活跃而庞大的社区,这也是Spring能够长久不衰的原因.
但是这个概念相对来说,还是比较抽象.
我们用⼀句更具体的话来概括Spring,那就是:Spring是包含了众多工具方法的IoC容器.

1.1.1 什么是容器

容器是用来容纳某种物品的(基本)装置.想我们生活中的水杯,垃圾桶等都是容器.那么我们想一想,我们之前的学习中,都有学习过哪些容器.

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

1.1.2 什么是IoC

IoC其实我们在之前学习Spring的阶段就使用过.在类上面添加@RestController@Controller 注解,就是在Spring中创建出一个类的对象,并把这个对象交给Spring管理,Spring框架启动时就会加载该类.把对象交给Spring管理,就是IoC思想.这个交给Spring管理的对象我们统称为Bean.
所谓IoC,翻译过来就是Inversion of Control (控制反转),也就是说Spring是⼀个"控制反转"的容器,什么是控制翻转呢?也就是控制权反转.什么是控制权翻转?就是对象控制权反转,此时对象不是由创建对象的类管理的,而是由容器管理的.当我们需要一个对象的时候,我们在传统开的模式中,我们需要通过自己new对象来实现.但是现在在创建对象的时候,是交给容器来创建的,程序中只需要写一个依赖注入,就可以完成对象的引用.这个所谓的容器,就是IoC容器.有时也叫做Spring容器.

控制反转在生活中也处处可见,比如在企业的招聘中,员工的入职,离职,解雇等控制权,均不是由老板来控制,而是由HR来控制的.

1.1.3 举例说明控制反转(IoC)

接下来我们通过案例来说明一下什么是IoC.我们来造一辆小汽车.

  • 传统程序开发
    我们的实现思路是这样的:先设计轮子Tire),然后根据轮子的大小设计底盘(Bottom),接着根据底盘设计车身(Framework),最后根据车身设计好整个汽车(Car)。这里就出现了⼀个"依赖"关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子.
    在这里插入图片描述
    程序实现:
public class Car {
    public Framework framework;

    public Car() {
        this.framework = new Framework();
        System.out.println("car loading...");
    }
    public void run(){
        System.out.println("car is running...");
    }
}
public class Framework {
    public Bottom bottom;

    public Framework() {
        this.bottom = new Bottom();
        System.out.println("bottom loading...");
    }
}
public class Bottom {
    public Tire tire;

    public Bottom() {
        this.tire = new Tire();
        System.out.println("Bottom loading...");
    }
}
public class Tire {
    private int size;

    public Tire() {
        this.size = 17;
        System.out.println("轮胎的尺寸为:" + this.size);
    }
}
  • 问题分析
    上面这样的代码其实可维护性非常低,我们如果我们想要对轮胎的大小自定义,那么我们除了要修改Tire类的代码以外,我们后续的代码也需要全部修改.
public class Tire {
    private int size;

    public Tire(int size) {
        this.size = size;
        System.out.println("轮胎的尺寸为:" + this.size);
    }
}
public class Bottom {
    public Tire tire;

    public Bottom(int size) {
        this.tire = new Tire(size);
        System.out.println("Bottom loading...");
    }
}
public class Framework {
    public Bottom bottom;

    public Framework(int size) {
        this.bottom = new Bottom(size);
        System.out.println("bottom loading...");
    }
}
public class Car {
    public Framework framework;

    public Car(int size) {
        this.framework = new Framework(size);
        System.out.println("car loading...");
    }
    public void run(){
        System.out.println("car is running...");
    }
    public static void main(String[] args) {
      Car car = new Car(17);
      car.run();
  }
}

上面的代码反映出的最大的问题就是:当底层代码有一点点改动,整个调用链上的代码就都需要修改.程序的耦合度非常高.

  • 解决方案
    我们尝试换⼀种思路,我们先设计汽车的大概样子,然后根据汽车的样子来设计车身,根据车身来设计底盘,最后根据底盘来设计轮子.这时候,依赖关系就倒置过来了:轮子依赖底盘,底盘依赖车身,车身依赖汽车.

这就类似我们打造⼀辆完整的汽车,如果所有的配件都是自己造,那么当客户需求发生改变的时候,比如轮胎的尺寸不再是原来的尺寸了,那我们要自己动手来改了,但如果我们是把轮胎外包出去,那么即使是轮胎的尺寸发生改变了,我们只需要向代理工厂下订单就行了,我们自身是不需要出力的.

在这里插入图片描述

那么如何来实现呢?

  • 注入式程序开发
    我们可以尝试在每一个类中不创建下一级的类,可以采用注入传递的方式进行.我们在每次创建对象的时候,只需要传入上一个所依赖的对象即可.
public class Tire {
    private int size;

    public Tire(int size) {
        this.size = size;
        System.out.println("轮胎的尺寸为:" + this.size);
    }
}
public class Bottom {
    public Tire tire;

    public Bottom(Tire tire) {
        this.tire = tire;
        System.out.println("Bottom loading...");
    }
}
public class Framework {
    public Bottom bottom;

    public Framework(Bottom bottom) {
        this.bottom = bottom;
        System.out.println("bottom loading...");
    }
}
public class Car {
    public Framework framework;

    public Car(Framework framework) {
        this.framework = framework;
        System.out.println("car loading...");
    }
    public void run(){
        System.out.println("car is running...");
    }

    public static void main(String[] args) {
        Tire tire = new Tire(17);
        Bottom bottom = new Bottom(tire);
        Framework framework1 = new Framework(bottom);
        Car car = new Car(framework1);
        car.run();
    }
}

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

1.1.4 IoC的优点与IoC容器

通过上述两种编程的方式,我们可以看出,类的创建顺序是反的,传统代码是Car控制并创建了Framework,Framework创建并创建了Bottom,依次往下,而改进之后的控制权发生的反转,不再是使用方对象创建并控制依赖对象了,而是把依赖对象注入将当前对象中,依赖对象的控制权不再由当前类控制了.这种实现方式就是IoC的实现方式.
那么什么是IoC容器呢?
在这里插入图片描述
上面的一部分代码就是IoC容器中所做的事情.
从上面也可以看得出来,IoC容器具备以下优点:
资源不由使用资源的双方管理,而由不使用资源的第三方管理,也就是把这个对象外包出去了.这可以带来很多好处。第⼀,资源集中管理,实现资源的可配置和易管理。第二,降低了使用资源双方的依赖程度,也就是我们说的耦合度.

1.2 DI

那么什么是DI呢?DI翻译过来就是:Dependency Injection(依赖注入).容器在运行期间(只不过上面那个对汽车的实现,main方法是容器而已),动态的为应用程序提供运行时所依赖的资源,就是依赖注入.
在这里插入图片描述

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

总的来说,IoC是一种思想,就是让对象的控制权发生改变,把它的控制权交给第三方来管理.而具体如何实现IoC思想,也就是如何实现控制权反转,就是由依赖注入的方式来实现的.

2. IoC与DI在Spring环境下的具体使用

我们接下来学习Spring IoC和DI的代码实现
既然Spring是IoC的一个容器,作为容器,那么它就具备两个最基本的功能,存和取.在Spring中,管理的主要是对象,这些对象我们称为Bean.我们把这些对象交给Spring管理.由Spring负责创建和销毁.
那么怎么存和取呢?
我们暂时先试用注解@Component来存,使用注解@Autowired来取.下面我们还是以图书管理系统为例子.

  1. 把Data交给Spring来管理.
@Component//交给Spring容器来管理Data
public class Data {
    //向业务逻辑端提供数据
    public List<BookInfo> mockData(){
        List<BookInfo> list2 = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            BookInfo bookInfo = new BookInfo();
            bookInfo.setId(i);
            bookInfo.setBookName("Java编程思想"+i);
            bookInfo.setCount(1);
            bookInfo.setPublish("机械工业出版社");
            bookInfo.setPrice(new Random().nextInt(100));
            bookInfo.setAuthor("高斯林");
            bookInfo.setStatus(1);
            list2.add(bookInfo);
        }
        return list2;
    }
}
  1. 把BookService交给Spring管理,由Spring来管理对象.
@Component
public class BookService {
    public List<BookInfo> getList(){
        Data data = new Data();
        List<BookInfo> list = data.mockData();
        for (BookInfo bookInfo:list){
            if (bookInfo.status == 1){
                bookInfo.setStatusCN("可借阅");
            }else{
                bookInfo.setStatusCN("不可借阅");
            }
        }
        return list;
    }
}
  1. 删除创建Data的代码,使用@Autowired进行注入.
@Component
public class BookService {
    @Autowired
    public Data data;
    public List<BookInfo> getList(){
        List<BookInfo> list = data.mockData();
        for (BookInfo bookInfo:list){
            if (bookInfo.status == 1){
                bookInfo.setStatusCN("可借阅");
            }else{
                bookInfo.setStatusCN("不可借阅");
            }
        }
        return list;
    }
}
  1. 删除BookService的创建代码,使用@Autowired进行注入.
@RequestMapping("/book")
@RestController
public class BookController {
    @Autowired
    public BookService bookService;
    @RequestMapping("/getList")
    public List<BookInfo> getList(){
        List<BookInfo> bookInfos = new ArrayList<>();
        bookInfos = bookService.getList();
        return bookInfos;
    }
}

3. IoC详解

接下来,我们来系统学习SpringIoC和DI操作.

3.1 Bean的存储

在之前的案例中,我们要想在把某个对象交给IoC容器来管理,我们需要在类上添加一个注解:@Component,而Spring框架为了更好的服务Web应用程序,提供了更丰富的注解.
共有两类注解可以实现:

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

3.3.1 @Controller

这个注解一般用于控制器的存储.一般用于三层架构的表现层.@Controller存储Bean的代码如下.

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

如何观察这个对象已经放入容器中了呢?我们需要从Spring中的运行类中获取对象.

@SpringBootApplication
public class DemoApplication {

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

}

我们想要获取到Spring容器中的对象,就需要先获取到Spring的上下文.也就是通过applicationContext context = SpringApplication.run(DemoApplication.class, args);来获取.之后我们便可以从上下文中拿到我们注入IoC容器中的对象.我们使用getBean(类对象)方法来获取其中的对象.我们来运行程序.
在这里插入图片描述
我们发现,程序成功输出了我们期望的内容.

  • 关于获取对象的方法,除了通过上述方法获取,还有其他的 方法可以获取:
    ApplicationContext获取Bean对象的功能是接口BeanFactory提供的功能.下面我们通过注释来说明:
public interface BeanFactory {
    String FACTORY_BEAN_PREFIX = "&";
    //通过Bean的名字获取Bean
    Object getBean(String name) throws BeansException;
    //根据Bean的名称和类型获取Bean
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;
    //暂时不用了解
    Object getBean(String name, Object... args) throws BeansException;
    //根据类型获取Bean
    <T> T getBean(Class<T> requiredType) throws BeansException;
    //暂时不用了解
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
}

那么既然还有通过名称来获取Bean的方法,那么在Spring容器中,Bean的名称是怎么样的呢?

  • Bean的命名约定
    命名约定使用Java标准约定作为实例字段名.也就是说,bean名称以小写字母开头,然后使用驼峰式大小写.类名是大驼峰的,在IoC容器中的Bean就是小驼峰,前两个字母都大写的类名在IoC容器中不变.
    例如:类名:UserController,Bean的名称为:userController.类名:UController,Bean的名称为:UController
    接下来,我们根据这个命名规则来获取Bean.
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
        //从Spring中获取上下文对象
        UserController userController = context.getBean(UserController.class);
        userController.sayHi();
        UserController userController1 = (UserController) context.getBean("userController");
        userController1.sayHi();
        //这个方法需要注意的是,返回值的类型是Object类型,需要强制转换为指定类型
        UserController userController2 = context.getBean("uerController",UserController.class);
        userController2.sayHi();
    }
}

运行结果:
在这里插入图片描述
说明可以成功拿到Bean对象.
接下来我们把这三个不同方法拿到的对象打印出来.

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
        //从Spring中获取上下文对象
        UserController userController = context.getBean(UserController.class);
        userController.sayHi();
        UserController userController1 = (UserController) context.getBean("userController");
        userController1.sayHi();
        //这个方法需要注意的是,返回值的类型是Object类型,需要强制转换为指定类型
        UserController userController2 = context.getBean("userController",UserController.class);
        userController2.sayHi();
        System.out.println(userController);
        System.out.println(userController1);
        System.out.println(userController2);
    }
}

在这里插入图片描述
我们运行发现,地址是一样的,说明是同一个对象.所以在Bean对象放入IoC容器中的时候,使用的是单例模式,一个类的对象在IoC容器中只有一个.

面试八股文:ApplicationContext VS BeanFactory

  • 从继承关系和功能方面的角度来说:BeanFactory提供了基础访问容器的能力,而ApplicationContext是BeanFactory的子类,它除了继承了BeanFactory的所有功能之外,还拥有独特的特性,添加了对国际化的支持,资源访问支持等.
  • 从性能方面来说:AppliactionContext是一次性加载并初始化了所有的Bean对象,而BeanFactory只去加载需要的那个,会更加的轻量.

3.3.2 @Service

@Service一般用于服务的存储,一般用于三层架构的业务逻辑层,具体代码如下:

@Service
public class UserService {
    public void sayHi(){
        System.out.println("hi,UserService");
    }
}
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
        UserService userService = context.getBean(UserService.class);
        userService.sayHi();
    }
}

观察运行结果我们发现,成功获取到了UserService的Bean对象,并成功调用了sayHi方法.
在这里插入图片描述

3.3.3 @Repository

@Repository注解一般用于数据的存储,在三层架构中一般用于数据层:

@Repository
public class UserRepository {
    public void sayHi(){
        System.out.println("Hi,UserRepository");
    }
}
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
        UserRepository userRepository = context.getBean(UserRepository.class);
        userRepository.sayHi();
    }
}

运行结果:
在这里插入图片描述
剩余的@Component@configuration注解同理.其中@Component用于组建的存储,@configuration用于配置的存储.这里不再演示.

3.2 为什么要有这么多注解

这个和我们之前的应用分层是相互对应的.让程序员在看到注解之后就可以知道其中的用途.
• @Controller:控制层,接收请求,对请求进行处理,并进行响应.在这几个类注解中,只有他加上@RequestMapping注解之后,可以通过浏览器访问,其他都不可以.
• @Servie:业务逻辑层,处理具体的业务逻辑.
• @Repository:数据访问层,也称为持久层.负责数据访问操作
• @Configuration:配置层.处理项目中的⼀些配置信息.

就像一个省中的不同市车牌号都是不同字母开头一样,比如山西省,太原市是晋A,大同市是晋B,晋中市是晋K.

在这里插入图片描述

  • 类注解之间的关系
    下面我们来查看@Controller @Servie @Repository @Configuration的源码:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";

    boolean proxyBeanMethods() default true;

    boolean enforceUniqueMethods() default true;
}

我摸观察上述源码发现,他们都有@Component注解.这说明@Component是一个元注解,也就是可以注解其他注解的一个注解,@Controller @Servie @Repository @Configuration这些注解被称为@Component的衍生注解.

你当然可以不按照规范使用这些注解,除了@Controller之外,其他任何注解使用之后在效果上是等同的,当然我们不建议这么做.就像我们不建议用洗脸盆洗脚,用洗脚盆洗脸一样.

3.3 方法注解@Bean

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

  1. 要是使用外部包里的类,没有办法对只读文件添加注解.
  2. 一个类,需要多个对象,比如多个数据源(就是一个项目需要多个数据库来支持,这时候就需要多个数据源)
    对于这种场景,我们就需要使用方法注解@Bean.

3.3.1 单例使用@Bean

我们首先来看看在一个类只有一个对象的时候如何使用.

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Setter
@Getter
@ToString
public class User {
    public String name;
    public Integer age;
}

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

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

[注意] 方法注解必须搭配五大类注解使用,否则在IoC容器中拿不到想要的对象.
运行结果:成功从IoC容器中拿到对象.
在这里插入图片描述
这时候在IoC容器中只有一个User类型的对象,是单例模式,所以直接可以通过User的类对象拿到,如果在容器中有多个对象的时候就不可以通过一个类的类对象拿到了.

3.3.2 定义多个对象使用@Bean

对于同一个类,如果这个类有多个对象,那么如何定义呢?
接下来我们使用@Bean解决.

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

如果我们还使用User的类对象获取User对象.
在这里插入图片描述
我们看到报错信息是:不唯一的Bean,说明不可以使用类对象来获取Bean.
在这里插入图片描述
从报错信息中,我们还可以看出,@Bean注解的方法在容器中的Bean,就是它自身的方法名.
接下来我们根据名称来获取Bean对象.

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

运行结果:
在这里插入图片描述

3.3.3 重命名Bean

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

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

运行结果:
在这里插入图片描述
也可以一个Bean对象拥有多个name.

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

运行结果:
在这里插入图片描述
这里的重命名和前面的后端参数重命名@RequestParam有异曲同工之妙.

3.4 扫描路径

加上注解之后,就一定会被获取到吗?不一定,还需要被Spring扫描到.
我们现在把Spring的启动类放在other目录中.
在这里插入图片描述
运行之后我们发现,启动类没有在IoC容器中找到这样一个对象:
在这里插入图片描述
其实,启动类默认的扫描路径是SpringBoot启动类所在包及其子包.
如果我们想自定义,扫描路径的话,我们就需要使用@ComponentScan来配置扫描路径.我们还是把Spring的启动类放入other目录之下:

@SpringBootApplication
@ComponentScan("com.example.demo")
public class DemoApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
        User user = (User) context.getBean("u");
        System.out.println(user);
        User user2 = (User) context.getBean("u0");
        System.out.println(user2);
    }
}

运行结果:
在这里插入图片描述
我们发现,加上扫描路径之后,运行结果依然符合预期,但是我们一般不建议这样做,我们还是建议放在要扫描的路径之下,使用默认的扫描路径.

4. DI详解

依赖注入是⼀个过程,是指IoC容器在创建Bean时,去提供运行时所依赖的资源,而资源指的就是对象.
在上面程序案例中,我们使用了 @Autowired 这个注解,完成了依赖注如的操作.
关于依赖注入,Spring也为我们提供了三种方式:

  1. 属性注入(Field Injection)
  2. 构造方法注入(Constructor Injection)
  3. Setter注入(Setter Injection)

4.1 属性注入

我们前面所展示的案例就是属性注入,也就是在一个类的属性上加上@Autowired注解.这里不再赘述.

4.2 构造方法注入

构造方法注入是在类的构造方法的上面加上@Autowired实现注入.
具体代码如下:

@Service
public class UserService2 {
    public void sayHi(){
        System.out.println("Hi,UserService2");
    }
}
@Controller
public class UserController2 {
    public UserService2 userService2;
    @Autowired
    public UserController2(UserService2 userService2) {
        this.userService2 = userService2;
    }
    public void sayHi(){
        userService2.sayHi();
        System.out.println("Hi,UserController2");
    }
}
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
        UserController2 userController2 = context.getBean(UserController2.class);
        userController2.sayHi();
    }
}

在这里插入图片描述

4.3 Setter注入

在一个属性的Setter方法上面加上@Autowired实现注入.

@Controller
public class UserController2 {
    public UserService2 userService2;
    @Autowired
    public void setUserService2(UserService2 userService2) {
        this.userService2 = userService2;
    }
    public void sayHi(){
        userService2.sayHi();
        System.out.println("Hi,UserController2");
    }
}

剩余代码与上面的一样.
运行结果:
在这里插入图片描述

面试八股文:三种注入方式优缺点的分析
• 属性注入
◦ 优点:简洁,使用方便;
◦ 缺点:
▪ 只能用于IoC容器,如果是非IoC容器不可用,并且只有在使用的时候才会出现NPE(空指针异常)
▪ 不能注入⼀个Final修饰的属性
• 构造函数注入(Spring4.X推荐)
◦ 优点:
▪ 可以注入final修饰的属性
▪ 注入的对象不会被修改
▪ 依赖对象在使⽤前⼀定会被完全初始化,因为依赖是在类的构造方法法中执行的,而构造方法
是在类加载阶段就会执行的方法.
▪ 通用性好,构造方法是JDK支持的,所以更换任何框架,他都是适用的
◦ 缺点:
▪ 注入多个对象时,代码会比较繁琐
• Setter注入(Spring3.X推荐)
◦ 优点:方便在类实例之后,重新对该对象进行配置或者注入
◦ 缺点:
▪ 不能注入⼀个Final修饰的属性
▪ 注⼊对象可能会被改变,因为setter方法可能会被多次调用,就有被修改的风险

4.4 @Autowired存在的问题

当一个类型存在多个Bean的时候,使用@Autowired就会出现问题.

@Component
public class BeanConfig {
    @Bean(name = "u")
    public User user(){
        User user = new User();
        user.setAge(18);
        user.setName("zhangsan");
        return user;
    }
    @Bean(name = "u1")
    public User user1(){
        User user1 = new User();
        user1.setName("lisi");
        user1.setAge(18);
        return user1;
    }
}
@Controller
public class UserController2 {
    public UserService2 userService2;
    @Autowired
    public User user;
    @Autowired
    public void setUserService2(UserService2 userService2) {
        this.userService2 = userService2;
    }

    public void sayHi(){
        userService2.sayHi();
        System.out.println("Hi,UserController2");
    }
}

我们发现,由于注入时,IoC容器中的User类型的Bean不是唯一的,所以报错.
在这里插入图片描述
那么如何解决上述问题呢?Spring提供了如下的解决方案:
• @Primary
• @Qualifier
• @Resource

  • 首先@Primary注解,当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现.
@Component
public class BeanConfig {
    @Bean(name = "u")
    @Primary
    public User user(){
        User user = new User();
        user.setAge(18);
        user.setName("zhangsan");
        return user;
    }
    @Bean(name = "u1")
    public User user1(){
        User user1 = new User();
        user1.setName("lisi");
        user1.setAge(18);
        return user1;
    }
}
@Controller
public class UserController2 {
    public UserService2 userService2;
    @Autowired
    public User user;
    @Autowired
    public void setUserService2(UserService2 userService2) {
        this.userService2 = userService2;
    }

    public void sayHi(){
        userService2.sayHi();
        System.out.println(this.user);
        System.out.println("Hi,UserController2");
    }
}

运行结果:
我们看到拿到的是加了@Primary的张三.
在这里插入图片描述

  • @Qualifier(翻译:限定符),此注解不可以单独使用,必须配合@Autowired注解使用.在后面的参数中指定Bean对象的名称即可指定注入对象.(注:是Bean的名称,不是类或者方法的名称)
@Component
public class BeanConfig {
    @Bean(name = "u")
    public User user(){
        User user = new User();
        user.setAge(18);
        user.setName("zhangsan");
        return user;
    }
    @Bean(name = "u1")
    public User user1(){
        User user1 = new User();
        user1.setName("lisi");
        user1.setAge(18);
        return user1;
    }
}
@Controller
public class UserController2 {
    public UserService2 userService2;
    @Autowired
    @Qualifier("u1")
    public User user;
    @Autowired
    public void setUserService2(UserService2 userService2) {
        this.userService2 = userService2;
    }

    public void sayHi(){
        userService2.sayHi();
        System.out.println(this.user);
        System.out.println("Hi,UserController2");
    }
}
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
        UserController2 userController2 = context.getBean(UserController2.class);
        userController2.sayHi();
    }
}

运行结果:
在这里插入图片描述

  • @Resource(翻译:资源)
    @Resource后面把name参数设置为IoC容器中Bean的名字即可.
@Controller
public class UserController2 {
    public UserService2 userService2;
    @Resource(name = "u1")
    public User user;
    @Autowired
    public void setUserService2(UserService2 userService2) {
        this.userService2 = userService2;
    }

    public void sayHi(){
        userService2.sayHi();
        System.out.println(this.user);
        System.out.println("Hi,UserController2");
    }
}

其他代码和上面一样.
运行结果:
在这里插入图片描述

常见面试题:
• @Autowired是spring框架提供的注解,而@Resource是JDK提供的注解
• @Autowired默认是按照类型注⼊,而@Resource是按照名称注入.相比于@Autowired来说,@Resource支持更多的参数设置,例如name设置,根据名称获取Bean。
一般情况下,一个类有多个对象的时候我们习惯用@Resource,是单例的时候,我们习惯用@Autowired


原文地址:https://blog.csdn.net/2301_80050796/article/details/140531072

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!