Spring IoC&DI

目录

1 IoC&DI

1.1 什么是容器

1.2 什么是IcO

2 IoC介绍

2.1 传统程序开发

2.2 问题分析 

2.3 解决方案

2.4 IoC优势

3 DI介绍 

4 IoC & DI使用

5 Bean的存储

5.1 @Controller(控制器存储)

5.2 Bean命名约定

5.3 @Service(服务存储) 

5.4 @Repository(仓库存储)

5.5  @Component(组件存储)

5.6 @Configuration(配置存储)

5.7 为什么需要这么多类注解

5.8 方法注解@Bean

 5.9 定义多个对象

5.10 @Bean传递参数

5.11 重命名Bean

5.12 扫描路径

6 DI详情

6.1 属性注入

6.2 构造方法注入

6.3 Setter注入

6.4 三种注入优缺点

6.5 @Autowired存在问题

6.5.1 使用@Primary注解

6.5.2 使用@Qualifier注解

6.5.3 使用@Resource注解

1 IoC&DI

1.1 什么是容器

我们知道Spring是包含了众多工具方法的IoC容器,那么什么是容器,什么是IoC容器,在此之前我们学过一些数据结构,其中List/Map—>数据存储容器,Tomcat->Web存储容器。

1.2 什么是IcO

IcO是Spring的核心思想,在类上面添加@RestController@Controller注解,就是把这个对象交给Spring管理,Spring框架启动时就会自动加载该类,把对象交给Spring管理,这就是IoC思想,IoC:Inversion of Control(控制反转),也就是说Spring是一个"控制反转"的容器。

2 IoC介绍

我们的需求是造一辆车

2.1 传统程序开发

先设计轮子,根据轮子的大小设计底盘,根据底盘设计车身,根据车身设计整个汽车,这时候就形成了一个依赖的关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子

public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        car.run();
    }
    static class Car {
        private Framework framework;
        public Car() {
            framework = new Framework();
            System.out.println("car init...");
        }
        public void run() {
            System.out.println("car run...");
        }
    }
    static class Framework {
        private Bottom bottom;
        public Framework() {
            bottom = new Bottom();
            System.out.println("framework init...");
        }
    }
    static class Bottom {
        private Tire tire;
        public Bottom() {
            tire = new Tire();
            System.out.println("bottom init...");
        }
    }
    static class Tire {
        private int size;
        public Tire() {
            this.size = 17;
            System.out.println("轮子尺寸:" + size);
        }
    }
}

2.2 问题分析 

上面的代码是没有问题的,但是维护性却很低,如果需求变更的话,随着对车的需求量越来越大,个性化需求也会越来越多,这个时候需要对代码进行修改。

当修改轮子的尺寸,整个代码都需要从头到尾修改,完整代码如下 

public class Main {
    public static void main(String[] args) {
        Car car = new Car(20);
        car.run();
    }
    static class Car {
        private Framework framework;
        public Car(int size) {
            framework = new Framework(size);
            System.out.println("car init...");
        }
        public void run() {
            System.out.println("car run...");
        }
    }
    static class Framework {
        private Bottom bottom;
        public Framework(int size) {
            bottom = new Bottom(size);
            System.out.println("framework init...");
        }
    }
    static class Bottom {
        private Tire tire;
        public Bottom(int size) {
            tire = new Tire(size);
            System.out.println("bottom init...");
        }
    }
    static class Tire {
        private int size;
        public Tire(int size) {
            this.size = size;
            System.out.println("轮子尺寸:" + size);
        }
    }
}

从上面代码的修改中可以看出,当最底层的代码修改之后,整个调用链上的代码都需要修改,这也说明了,程序的耦合是非常高的

2.3 解决方案

在上面的程序中,我们是根据轮子的尺寸设计底盘的,当轮子的尺寸修改后,底盘也需要修改,依次所有的东西都要改,因此我们先设计汽车,然后再根据汽车来设计车身,依次往下,这时候关系就翻转过来了。

实现方案:在每个类中自己创建下级类,如果自己创建下级类会发生改变操作,后面也要跟着修改。因此改为传递的方式(注入的方式),这样我们不需要在当前类创建下级类,所以下级类发生变化,当前类也无需修改任何代码了,这样就完成了程序的解耦

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();
    }
}
static 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 run");
    }
}
static public class Framework {
    private Bottom bottom;

    public Framework(Bottom bottom) {
        this.bottom = bottom;
        System.out.println("framework init....");
    }
}
static public class Bottom {
    private Tire tire;

    public Bottom(Tire tire) {
        this.tire = tire;
        System.out.println("bottom init....");
    }
}
static public class Tire {
    private int size;
    public Tire(int size) {
        this.size = size;
        System.out.println("tire init....size:" + size);
    }
}

代码经过上述调整,无论底层图和变化,整个调用链不需要做任何变化,这样就完成了代码之间的解耦,此时实现了更加灵活、通用的程序设计了

2.4 IoC优势

使用IoC设计,就是将控制权发生反转,不再是使用方对象创建并控制依赖对象了,而是把依赖对象注入当前对象中,依赖对象的控制权不再由当前类控制了,因此IoC容器就是控制反转容器。

IoC容器的优点:

1)资源集中管理:IoC容器会帮我们管理一些资源,当我们需要使用时,只需要从IoC容器中取就可以了

2)再创建实例的时候不需要了解其中的细节,降低了使用资源双方的依赖程度,也就是耦合度

3 DI介绍 

DI:Dependency Injection(依赖注入)

程序再运行时需要某个资源,此时容器就为其提供这个资源,就是通过引入IoC容器,利用依赖关系注入的方式,实现对象之间的解耦

IoC是一种思想,也就是"目标",而思想只是一种指导原则,最终还需要可行的方案,而DI就是具体的实现,所以说,DI是IoC的一种实现

4 IoC & DI使用

因为Spring是一个IoC容器,作为容器,那么他就具备两个最基础的功能:,Spring容器管理的主要是对象,这些对象我们称之为"Bean",把这些对象交给Spring管理,由Spring来负责对象的创建和销毁,只需要告诉Spring,哪些需要存,怎样从Spring中取对象

通过@Component存对象,@Autowired取对象

5 Bean的存储

Spring框架为了更好的服务web应用程序,提供了更丰富的注解

1)类注解:@Controller  @Service  @Repository  @Component  @Configuration

2)方法注解:@Bean

5.1 @Controller(控制器存储)

使用@Controller存储bean的代码如下

@Controller//将对象存储到Spring中
public class UserController {
    public void doController() {
        System.out.println("do Controller");
    }
}

接下来从Spring容器中获取对象

这里需要注意,需要选择的第一个接口,而不是下面的类

@SpringBootApplication
public class DemoApplication {

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

此时就能成功的从Spring中获取到Controller,并执行Controller的doController方法

如果把@Controller去掉,在观察

这个时候会出现报错,找不到UserController的Bean了 

5.2 Bean命名约定

在开发中不需要为bean指定名称,Spring容器将为该bean生成唯一的名称

命名约定使用Java标准约定作为实例字段名,bean名称以小写字母开头,然后使用驼峰式的大小写,如以下

类名:UserController,Bean的名称:userController

类名:AccountManager,Bean的名称为:accountManager

特殊情况,当有多个字符并且第一个和第二个字符都是大写,此时Bean的命名为类名,例如

类名:UController,Bean的名称:UController

Bean的获取有三种方法,获取代码如下

@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		//获取Spring上下文对象
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		//根据bean类型,从Spring上下文获取对象
		UserController userController1 = context.getBean(UserController.class);
		//根据bean名称,从Spring上下文获取对象
		UserController userController2 = (UserController) context.getBean("userController");
		//根据bean的类型+名称,从Spring上下文获取对象
		UserController userController3 = context.getBean("userController", UserController.class);
		System.out.println(userController1);
		System.out.println(userController2);
		System.out.println(userController3);
	}
}

我们观察运行结果,输出的地址是一样的,说明对象是一个

常见面试题

 ApplicationContent VS BeanFactory

从继承关系和功能来说:Spring容器有两个顶级接口ApplicationContentBeanFactory,其中BeanFactory提供了基础的访问容器能力,而ApplicationContent属于BeanFactory的子类,它继承了BeanFactory的所有功能

从性能方面来说:ApplicationContent是一次性加载并初始化所有的Bean对象,而BeanFactory更加轻量

5.3 @Service(服务存储) 

使用@Service存储bean的代码如下

@Service
public class UserService {
    public void doService() {
        System.out.println("do Service");
    }
}
@SpringBootApplication
public class DemoApplication {

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

5.4 @Repository(仓库存储)

使用@Repository存储bean的代码如下

@Repository
public class UserRepository {
    public void doRepository() {
        System.out.println("do Repository");
    }
}
@SpringBootApplication
public class DemoApplication {

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

5.5  @Component(组件存储)

使用@Component存储bean的代码如下

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

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

5.6 @Configuration(配置存储)

使用@Configuration存储bean的代码如下

@Configuration
public class UserConfiguration {
    public void doConfiguration() {
        System.out.println("do Configuration");
    }
}
@SpringBootApplication
public class DemoApplication {

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

5.7 为什么需要这么多类注解

每个注解都有自己的用途,注解多是让程序员看到类注解后,能够直接了解当前类的用途

@Controller:控制层,接收请求,返回响应

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

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

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

程序的应用分层,调用流程如下:

类注解之间的关系

查看@Controller   @Service  @Repository  @Configuration 等注解的源码发现:

这些注解里面都有一个注解@Component,说明它们本身就是@Component的子类,@Component是一个元注解,可以注解其他类注解,如@Controller   @Service  @Repository  @Configuration,它们也是@Component的衍生注解。在开发中,如果想在控制层使用@Component也是可以的,但是一般建议在控制层使用@Controller

5.8 方法注解@Bean

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

1)第三方jar包是没有办法添加五大注解

2)一个类,定义多个对象时,比如多个数据源

这种场景的话就需要使用@Bean注解

public class BeanConfig {
    @Bean
    public UserInfo userInfo() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setUserName("zhangsan");
        userInfo.setAge(10);
        return userInfo;
    }
}
@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		UserInfo userInfo = context.getBean(UserInfo.class);
		System.out.println(userInfo);
	}
}

 此时我们没法获取bean对象中的user,这是因为在Spring框架设计中,方法注解@Bean需要配合 类注解(五大注解)才能将对象正常存储到Spring容器中

@Configuration
public class BeanConfig {
    @Bean
    public UserInfo userInfo() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setUserName("zhangsan");
        userInfo.setAge(10);
        return userInfo;
    }
}

 5.9 定义多个对象

在同一个类中定义多个对象,根据类型获取对象,获取的是哪个对象?

@Configuration
public class BeanConfig {
    @Bean
    public UserInfo userInfo() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setUserName("zhangsan");
        userInfo.setAge(10);
        return userInfo;
    }
    @Bean
    public UserInfo userInfo2() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(2);
        userInfo.setUserName("lisi");
        userInfo.setAge(11);
        return userInfo;
    }
}

 此时报错信息显示:期望有一个匹配,结果发现了两个,userInfo,userInfo2

从报错信息中可以看出,@Bean注解的bean,bean的名称就是它的方法名,下面根据名称来获取Bean对象

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

可以看到,@Bean可以针对同一个类定义多个对象,当同一个类有多个对象时,通过bean的名称,也就是方法名来获取对象

5.10 @Bean传递参数

@Configuration
public class BeanConfig {
    @Bean
    public UserInfo userInfo(String name) {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setUserName(name);
        userInfo.setAge(10);
        return userInfo;
    }
}
@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		UserInfo userInfo = context.getBean(UserInfo.class);
		System.out.println(userInfo);
	}
}

控制台会出现"需要一个String类型的对象

@Configuration
public class BeanConfig {
    @Bean
    public String name() {
        return "zhangsan";
    }
    @Bean
    public UserInfo userInfo(String name) {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setUserName(name);
        userInfo.setAge(10);
        return userInfo;
    }
}

 

5.11 重命名Bean

可以通过设置name属性给Bean对象进行重命名操作

@Configuration
public class BeanConfig {
    @Bean("user1")
    public UserInfo userInfo() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setUserName("zhangsan");
        userInfo.setAge(10);
        return userInfo;
    }
}
@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		UserInfo user1 = (UserInfo) context.getBean("user1");
		System.out.println(user1);
	}
}

5.12 扫描路径

使用五大注解声明的bean想要生效,关键的一点时需要被Spring扫描

将DemoApplication放到configuration这个包里面,再次观察结果,发现没有找到bean对象

报错的原因就是使用五大注解声明的bean,要想生效,还需要配置扫描路径,让Spring扫描到这些类,通过 @ComponentScan来配置扫描路径

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

前面没有配置@ComponentScan注解也能扫描到,这是因为SpringBoot的特点是,约定大于配置,默认的扫描路径是启动类所在的目录以及子目录

最开始启动类所在的位置就是com.example.demo的路径下,因此它能扫描到这个目录下的子类

6 DI详情

上面说了控制反转IoC的细节,现在是依赖注入DI的细节

依赖注入是一个过程,是指IoC容器在创建Bean时,取提供运行所依赖的资源,资源就是对象,关于依赖的注入,Spring提供了三种方式

1)属性注入

2)构造方法注入

3)Setter注入

6.1 属性注入

使用@Autowired实现,将Service类注入到Controller类中

Service类的实现代码

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

Controller类的实现代码

@Controller
public class UserController {
    @Autowired
    private UserService userService;
    public void doController() {
        userService.doService();
        System.out.println("do Controller");
    }
}

获取Controller中的doController方法 

@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		UserController userController = context.getBean(UserController.class);
		userController.doController();
	}
}

6.2 构造方法注入

@Controller
public class UserController {
    private UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    public void doController() {
        userService.doService();
        System.out.println("do Controller");
    }
}

注意:如果类里面只有一个构造方法,那么 @Autowired注解可以省略,如果有多个构造方法,此时就需要添加@Autowired来明确指定到底使用哪个构造方法,有多个构造方法的时候,如果没有@Autowired注解,默认会调用无参的构造方法,此时会报空指针异常

6.3 Setter注入

Setter注入和属性的Setter方法实现类似,只不过在设置set方法的时候加上@Autowired注解

@Controller
public class UserController {
    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
    
    public void doController() {
        userService.doService();
        System.out.println("do Controller");
    }
}

去掉@Autowired注解的话会报空指针异常

6.4 三种注入优缺点

属性注入:

优点:简洁,使用方便

缺点:只能用于IoC容器,如果是非IoC容器不可用,并且只有在使用的时候才会出现空指针异常

           不能注入一个Final修饰的属性

构造方法注入:(Spring4.X推荐)

优点:可以注入Final修饰的属性

           注入的对象不会被修改

           依赖对象在使用前一定会被初始化,因为依赖在类的构造方法中执行,而构造方法是在类               加载阶段就会执行的方法

           通用性好,构造方法时JDK支持的,更换任何框架,都是适用的

缺点:注入多个对象时,代码比较繁琐

Setter注入:(Spring3.X推荐)

优点:方法在实例之后,重新对该对象进行配置或者注入

缺点:不能注入Final修饰的属性

           注入的对象可能会改变,因为Setter方法可能被多次调用,就有修改的风险

6.5 @Autowired存在问题

当同一类型存在多个bean,也就是多个对象时,使用@Autowired会存在问题

@Configuration
public class BeanConfig {
    @Bean("user1")
    public UserInfo userInfo() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setUserName("zhangsan");
        userInfo.setAge(10);
        return userInfo;
    }
    @Bean
    public UserInfo userInfo2() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(2);
        userInfo.setUserName("lisi");
        userInfo.setAge(11);
        return userInfo;
    }
}
@Controller
public class UserController {
    @Autowired
    //注入userInfo
    private UserInfo userInfo;
    public void doController() {
        System.out.println("do Controller");
        System.out.println(userInfo);
    }
}
@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		UserController userController = context.getBean(UserController.class);
		userController.doController();
	}
}

报错的原因是,非唯一的Bean对象

如何解决上述问题,Spring提供了以下几种解决方案:

1)@Primary             2)@Qualifier          3)@Resource

6.5.1 使用@Primary注解

当存在多个相同类型的Bean是注入时,加上@Primary注解,来确定默认的实现

@Configuration
public class BeanConfig {
    @Primary
    @Bean("user1")
    public UserInfo userInfo() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setUserName("zhangsan");
        userInfo.setAge(10);
        return userInfo;
    }
    @Bean
    public UserInfo userInfo2() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(2);
        userInfo.setUserName("lisi");
        userInfo.setAge(11);
        return userInfo;
    }
}
@Controller
public class UserController {
    @Autowired
    //注入userInfo
    private UserInfo userInfo;
    public void doController() {
        System.out.println("do Controller");
        System.out.println(userInfo);
    }
}
@SpringBootApplication
public class DemoApplication {
	public static void main(String[] args) {
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		UserController userController = context.getBean(UserController.class);
		userController.doController();
	}
}

6.5.2 使用@Qualifier注解

使⽤@Qualifier注解:指定当前要注⼊的bean对象。在@Qualifier的value属性中,指定注⼊的bean的名称,@Qualifier注解不能单独使用,必须配合@Autowired使用

@Controller
public class UserController {
    @Qualifier("userInfo2")
    @Autowired
    //注入userInfo
    private UserInfo userInfo;
    public void doController() {
        System.out.println("do Controller");
        System.out.println(userInfo);
    }
}

6.5.3 使用@Resource注解

使⽤@Resource注解:是按照bean的名称进⾏注⼊,通过name属性指定要注⼊的bean的名称

@Controller
public class UserController {

    @Resource(name = "user1")
    //注入userInfo
    private UserInfo userInfo;
    public void doController() {
        System.out.println("do Controller");
        System.out.println(userInfo);
    }
}

常见面试题:

@Autowird 与 @Resource的区别

1 @Autowired是Spring框架提供的注解,而@Resource是JDK提供的注解

2 @Autowired默认是按照类型注入的,如果同一个类型存在多个对象,按名称匹配,如果名称匹配补上,就会报错,而@Resource是按照名称注入,相比@Autowired来说,@Resource支持更多的参数,例如name属性,根据名称获取Bean

相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-30 20:22:03       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-30 20:22:03       106 阅读
  3. 在Django里面运行非项目文件

    2024-04-30 20:22:03       87 阅读
  4. Python语言-面向对象

    2024-04-30 20:22:03       96 阅读

热门阅读

  1. Spring中实现策略模式的几种方式

    2024-04-30 20:22:03       27 阅读
  2. Kafka集群搭建

    2024-04-30 20:22:03       36 阅读
  3. ndk编译android系统下运行的ffmpeg配置

    2024-04-30 20:22:03       34 阅读
  4. 使用通义千问,为汽车软件需求生成测试用例

    2024-04-30 20:22:03       29 阅读
  5. WebSocket 的封装

    2024-04-30 20:22:03       39 阅读
  6. Android Glide 获取动图的第一帧

    2024-04-30 20:22:03       33 阅读
  7. 原生小程序分页/上拉加载(通过页面生命周期)

    2024-04-30 20:22:03       30 阅读
  8. Czi.算法学习(三)

    2024-04-30 20:22:03       34 阅读