0


【Spring】Spring IOC&DI:架构旋律中的“依赖交响”与“控制华章”

前言

🌟🌟本期讲解关于Spring IOC&DI的详细介绍~~~

🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客

**🔥 你的点赞就是小编不断更新的最大动力 **

🎆那么废话不多说直接开整吧~~

📚️1.IOC&DI入门

1.1什么是Spring

通过前⾯的学习, 我们知道了Spring是⼀个开源框架, 他让我们的开发更加简单. 他⽀持⼴泛的应⽤场景, 有着活跃⽽庞⼤的社区, 这也是Spring能够⻓久不衰的原因.但是这个概念相对来说, 还是⽐较抽象.

我们⽤⼀句更具体的话来概括Spring, 那就是: Spring 是包含了众多⼯具⽅法的 IoC 容器

1.2什么是IOC

在前⾯讲到, 在类上⾯添加 @RestController 和@Controller 注解, 就是把这个对象交给Spring管理, Spring 框架启动时就会加载该类. 把对象交给Spring管理, 就是IoC思想

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

控制反转:

当需要某个对象时, 传统开发模式中需要⾃⼰通过 new 创建对象, 现在不需要再进⾏创建, 把创建对象的任务交给容器, 程序中只需要依赖注⼊ (Dependency Injection,DI)就可以了

总结:

IOC就是一个创建任务对象的容器;

DI就是我们后面需要注入的依赖

1.3IOC的介绍

1.3.1传统的开发过程

假如我们在实现创建一个车子的时候,我们知道他是依赖于车身,轮胎,底盘...那么就有如下的依赖的情况:

那么我们在程序实现的时候就是如下所示的:

  1. public class Car {
  2. //车依赖于车身
  3. private Framwork framwork;
  4. public Car(int size) {
  5. framwork = new Framwork(size);
  6. System.out.println("Car init....");
  7. }
  8. public void run(){
  9. System.out.println("car run....");
  10. }
  11. }

** 解释:**

这就是车在制造的时候,依赖车身,所以就会创建一个车身的对象,那但是此时车身依赖由于底盘,就会在车身类进行对象的构造;

  1. public class Framwork {
  2. private Bottem bottem;
  3. public Framwork(int size){
  4. bottem=new Bottem(size);
  5. System.out.println("framwork init....");
  6. }
  7. }

解释:

此时我们就会发现,车身的制造依赖于底盘,那么就要创建底盘的对象,那么此时一直到最后一环指定就是轮胎;

  1. public class Tire {
  2. private int size;
  3. public Tire(int size){
  4. this.size=size;
  5. System.out.println("tire init...");
  6. }
  7. }

解释:

如果这里我们这里,最后一环的情况下,假如我们要规定颜色和轮胎的尺寸大小,此时就会发现一个问题;

以上程序的问题是:当最底层代码改动之后,整个调⽤链上的所有代码都需要修改.程序的耦合度⾮常⾼(修改⼀处代码, 影响其他处的代码修改)

1.3.2IOC程序开发

此时我们就可以使用IOC程序开发的思想进行改进,将整个对象进行统一管理,然后就可以大大降低耦合性;

  1. public static void main(String[] args) {
  2. Tire tire = new Tire(20);
  3. Bottom bottom = new Bottom(tire);
  4. Framework framework = new Framework(bottom);
  5. Car car = new Car(framework);
  6. car.run();
  7. }
  8. static class Car {
  9. private Framework framework;
  10. public Car(Framework framework) {
  11. this.framework = framework;
  12. System.out.println("Car init....");
  13. }
  14. public void run() {
  15. System.out.println("Car run...");
  16. }
  17. }

** 解释:**

这里小编只展示了一小部分,其余的写法基本是一致的;代码经过以上调整,⽆论底层类如何变化,整个调⽤链是不⽤做任何改变的,这样就完成了代码之间的解耦,从⽽实现了更加灵活、通⽤的程序设计了

1.3.3IOC的优势

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

*改进之后的控制权发⽣的反转,不再是使⽤⽅对象创建并控制依赖对象了,⽽是把依赖对象注⼊将当前对象中,依赖对象的控制权不再由当前类控制了 *

那么此时就可以看做是如下所示的情况:

此时优点就是如下:

  1. 资源集中管理: IoC容器会帮我们管理⼀些资源(对象等), 我们需要使⽤时, 只需要从IoC容器中去取就可以了
  2. 我们在创建实例的时候不需要了解其中的细节, 降低了使⽤资源双⽅的依赖程度, 也就是耦合度.

1.4DI的介绍

DI: Dependency Injection(依赖注⼊)

容器在运⾏期间, 动态的为应⽤程序提供运⾏时所依赖的资源,称之为依赖注⼊。程序运⾏时需要某个资源,此时容器就为其提供这个资源.

就是一个依赖的注入;

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

📚️2.IOC&DI的使用

既然 Spring 是⼀个 IoC(控制反转)容器,作为容器, 那么它就具备两个最基础的功能:
• 存
• 取
Spring 容器 管理的主要是对象, 这些对象, 我们称之为"Bean". 我们把这些对象交由Spring管理, 由
Spring来负责对象的创建和销毁. 我们程序只需要告诉Spring, 哪些需要存, 以及如何从Spring中取出对象

此时我们就会使用@component来进行管理Bean,使用@Autowired来进行依赖的注入,那么代码如下:

  1. @Component
  2. public class BookDao {
  3. public List<BookInfo> mockData(){
  4. List<BookInfo> books=new ArrayList<>();
  5. for (int i = 0; i < 5; i++) {
  6. BookInfo book = new BookInfo();
  7. book.setId(i);
  8. book.setBookName("书籍" + i);
  9. book.setAuthor("作者" + i);
  10. book.setCount(i * 5 + 3);
  11. book.setPrice(new BigDecimal(new Random().nextInt(100)));
  12. book.setPublish("出版社" + i);
  13. book.setStatus(1);
  14. books.add(book);
  15. }
  16. return books;
  17. }
  18. }

我们将这个数据对象交给spring管理,就是加上注解@Component;

  1. @Component //交给spring进行管理
  2. public class BookService {
  3. @Autowired
  4. private BookDao bookDao;
  5. public List<BookInfo> getBookList(){
  6. List<BookInfo> books=bookDao.mockData();
  7. for (BookInfo book : books) {
  8. if (book.getStatus() == 1) {
  9. book.setStatusCN("可借阅");
  10. } else {
  11. book.setStatusCN("不可借阅");
  12. }
  13. }
  14. return books;
  15. }
  16. }

这里就是将逻辑处理的对象交给spring管理,由于这里要使用数据,所以要引入依赖,这个依赖就可以将数据的对象传过来;

  1. @RequestMapping("/book")
  2. @RestController
  3. public class BookController {
  4. @Autowired
  5. private BookService bookService;
  6. @RequestMapping("/getList")
  7. public List<BookInfo> getList() {
  8. //获取数据
  9. //交给spring注入依赖
  10. List<BookInfo> books = bookService.getBookList();
  11. //处理⻚⾯展⽰
  12. return books;
  13. }
  14. }

最后在controller表现层操作时,将引入逻辑处理层的对象,此时就可以spring管理的对象拿出来,就是依赖的引入;

📚️3.IOC详解

通过上⾯的案例, 我们已经知道了Spring IoC 和DI的基本操作, 接下来我们来系统的学习Spring IoC和DI的操作.前⾯我们提到IoC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象。也就是bean的存储

3.1Bean的存储

在之前的⼊⻔案例中,要把某个对象交给IOC容器管理,需要在类上添加⼀个注解:@Component⽽Spring框架为了更好的服务web应⽤程序, 提供了更丰富的注解

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

那么下面小编来示范一下;

3.2类注解实现

这里小编就只使用controller进行操作,通过controller注解来进行存储Bean;

代码如下所示:

  1. @Controller
  2. public class UserController {
  3. public void userController(){
  4. System.out.println("userController start...");
  5. }
  6. }

此时我们就将这里的对象给spring进行管理了;

如何从Spring容器中获取对象:

  1. @SpringBootApplication
  2. public class SpringIocDemoApplication {
  3. public static void main(String[] args) {
  4. //获取Spring上下⽂对象
  5. ApplicationContext context =
  6. SpringApplication.run(SpringIocDemoApplication.class, args);
  7. //从Spring上下⽂中获取对象
  8. UserController userController = context.getBean(UserController.class);
  9. //使⽤对象
  10. userController.sayHi();
  11. }
  12. }

解释:

ApplicationContext 翻译过来就是: Spring 上下⽂因为对象都交给 Spring 管理了,所以获取对象要从 Spring 中获取,那么就得先得到 Spring 的上下⽂

这个上下⽂, 就是指当前的运⾏环境, 也可以看作是⼀个容器, 容器⾥存了很多内容, 这些内容是当前运⾏的环境

可以看到此时的对象就是拿到了的,即输出了usercontroller start这句话~~~

然后我们将这个controller进行删除试试:

此时就是出现异常了:就是这不到这个类型对象的存在,所以就抛出了异常;

3.3获取Bean的其他方式

上述就是通过类型来进行获取Bean的;

上述代码是根据类型来查找对象, 如果Spring容器中, 同⼀个类型存在多个bean的话, 怎么来获取呢?

ApplicationContext 也提供了其他获取bean的⽅式, ApplicationContext 获取bean对象的功能, 是⽗类BeanFactory提供的功能.

  1. public interface BeanFactory {
  2. String FACTORY_BEAN_PREFIX = "&";
  3. Object getBean(String name) throws BeansException;
  4. <T> T getBean(String name, Class<T> requiredType) throws BeansException;
  5. Object getBean(String name, Object... args) throws BeansException;
  6. <T> T getBean(Class<T> requiredType) throws BeansException;
  7. <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

可以看到这里可以根据类型和名字进行对象的获取,那么Bean的名字是啥?

Spring bean是Spring框架在运⾏时管理的对象, Spring会给管理的对象起⼀个名字,给每个对象起⼀个名字, 根据Bean的名称(BeanId)就可以获取到对应的对象.

Bean的命名

官方的文档就是如下所示:

具体的内容就是:

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

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

当然也有特殊的情况,前面两个单词都是大写:

类名: UController, Bean的名称为: UController
类名: AManager, Bean的名称为: AManager

还有就是三个大写字母,的Bean的命名就是:

类名:UserControllerDI,Bean的名称;usercontrollerDI

此时我们使用三种获取Bean的方式进行获取Bean对象:

  1. public static void main(String[] args) {
  2. ApplicationContext context= SpringApplication.run(SpringIocApplication.class, args);
  3. UserController bean = context.getBean(UserController.class);
  4. bean.userController();
  5. UserController bean1 = (UserController) context.getBean("userController");
  6. bean1.userController();
  7. UserController bean2 = context.getBean("userController",UserController.class);
  8. bean2.userController();
  9. }

解释:

此时我们这里第一种获取Bean的方式就是通过类型进行获取,然后第二种就是通过bean的名称进行获取,第三种就是前面两种的合并的方式;

此时我们进入运行状态:

ApplicationContext与BeanFactory

继承关系和功能⽅⾯来说:Spring 容器有两个顶级的接⼝:BeanFactory 和ApplicationContext。其中 BeanFactory 提供了基础的访问容器的能⼒,⽽ApplicationContext 属于 BeanFactory 的⼦类,它除了继承了 BeanFactory 的所有功能之外,它还拥有独特的特性,还添加了对国际化⽀持、资源访问⽀持、以及事件传播等⽅⾯的⽀持

从性能⽅⾯来说:ApplicationContext 是⼀次性加载并初始化所有的 Bean 对象,⽽
BeanFactory 是需要那个才去加载那个,因此更加轻量. (空间换时间

这里小编就不再演示其他几个了,基本代码和思想都是一样的~~~

3.4多种注解含义

• @Controller:控制层, 接收请求, 对请求进⾏处理, 并进⾏响应.
• @Servie:业务逻辑层, 处理具体的业务逻辑.
• @Repository:数据访问层,也称为持久层. 负责数据访问操作
• @Configuration:配置层. 处理项⽬中的⼀些配置信息

类注解之间的关系查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发
现:

这些注解⾥⾯都有⼀个注解 @Component ,说明它们本⾝就是属于 @Component 的"⼦类".
@Component 是⼀个元注解,这些其他的注解就是@Component的衍生类;

3.5方法注解Bean

1.使⽤外部包⾥的类, 没办法添加类注解
2. ⼀个类, 需要多个对象, ⽐如多个数据源

3.5.1方法注解要配合注解类

代码如下所示:

  1. @Configuration
  2. public class BeanConfig {
  3. @Bean
  4. public UserInfo userInfo(){
  5. return new UserInfo("zhangsan");
  6. }
  7. }

那么此时就将这个对象进行了管理了;

那么我们就可以拿到这个对象:

  1. UserInfo bean = context.getBean("UserInfo.class");
  2. System.out.println(bean);
3.5.2存在多个对象

代码如下:

  1. @Configuration
  2. public class BeanConfig {
  3. @Bean
  4. public UserInfo user1(){
  5. return new UserInfo("zhangsan");
  6. }
  7. //@Primary
  8. @Bean
  9. public UserInfo user2(){
  10. return new UserInfo("lisi");
  11. }
  12. }

那么此时我们使用类型进行获取时候;

那么此时我们就要通过bean的名称来进行获取了;

  1. User user1 = (User) context.getBean("user1");
  2. User user2 = (User) context.getBean("user2")

📚️4.DI详解

依赖注⼊是⼀个过程,是指IoC容器在创建Bean时, 去提供运⾏时所依赖的资源,⽽资源指的就是对象.在上⾯程序案例中,我们使⽤了 @Autowired 这个注解,完成了依赖注⼊的操作

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

4.1属性的注入

代码如下:

  1. @Controller
  2. public class UsercontrollerDI {
  3. @Autowired
  4. private UserServiceDI userServicedi;
  5. //private final UserServiceDI userServiceDI=new UserServiceDI();
  6. public void start(){
  7. userServicedi.start();
  8. }
  9. }

这里就是通过属性的注入的方式进行依赖的注入;

此时我们进行对象的获取和打印:

  1. UsercontrollerDI bean3 = context.getBean(UsercontrollerDI.class);
  2. bean3.start();

去掉@Autowired , 再运⾏⼀下程序看看结果:

就是说明了这里由于没有注入依赖,那么就直接为空了(这个对象)

4.2构造方法注入

代码如下所示:

  1. @Controller
  2. public class UserControllerDI2 {
  3. private UserServiceDI userServiceDI;
  4. public UserControllerDI2(){
  5. }
  6. @Autowired
  7. public UserControllerDI2(UserServiceDI userServiceDI){
  8. this.userServiceDI=userServiceDI;
  9. }
  10. public void start(){
  11. userServiceDI.start();
  12. }
  13. }

解释:

这就是通过构造方法来进行操作的,当然这里有点问题就是当存在两个构造方法的时候,默认就是无参的构造方法;

只有一个构造方法的时候,默认是使用这一个

若存在两个构造方法,此时就要指定使用那个构造方法

4.3setter方式注入

这里就和get与set方式基本一致了:

  1. @Controller
  2. public class UserSet {
  3. private UserServiceDI userServiceDI;
  4. @Autowired
  5. public void setUserServiceDI(UserServiceDI userServiceDI) {
  6. this.userServiceDI = userServiceDI;
  7. }
  8. public void start(){
  9. userServiceDI.start();
  10. }
  11. }

这里小编就不在过多的解释了;

4.4几种方式的优缺

属性注入

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

构造函数注入

优点:
▪ 可以注⼊final修饰的属性
▪ 注⼊的对象不会被修改
▪ 依赖对象在使⽤前⼀定会被完全初始化,因为依赖是在类的构造⽅法中执⾏的,⽽构造⽅法
是在类加载阶段就会执⾏的⽅法.
▪ 通⽤性好, 构造⽅法是JDK⽀持的, 所以更换任何框架,他都是适⽤的
◦ 缺点:
▪ 注⼊多个对象时, 代码会⽐较繁琐

setter方式注入

优点: ⽅便在类实例之后, 重新对该对象进⾏配置或者注⼊
◦ 缺点:
▪ 不能注⼊⼀个Final修饰的属性
▪ 注⼊对象可能会被改变, 因为setter⽅法可能会被多次调⽤, 就有被修改的⻛险

4.5@Autowired的缺点

当出现多个同种类型的对象的时候:

  1. public class BeanConfig {
  2. @Bean
  3. public UserInfo userInfo(){
  4. return new UserInfo("zhangsan");
  5. }
  6. //@Primary
  7. @Bean
  8. public UserInfo userInfo1(){
  9. return new UserInfo("lisi");
  10. }
  11. }

此时我们进行注入,就会出现如下的情况:

  1. @Autowired
  2. private UserInfo user;
  3. public void start(){
  4. System.out.println(user);
  5. }

直接出错;

很明显就是分不清楚,那个是要注入的Bean;

那么就有下面几种方法;

4.5.1使用@Primary

代码如下:

  1. @Bean
  2. public UserInfo userInfo(){
  3. return new UserInfo("zhangsan");
  4. }
  5. @Primary
  6. @Bean
  7. public UserInfo userInfo1(){
  8. return new UserInfo("lisi");
  9. }

此时我们默认这个对象;

4.5.2使用@Qualifier

注意这里要和autowired进行搭配使用

  1. @Qualifier("userInfo1")
  2. @Autowired
  3. private UserInfo user;

这里就要指定那个;

4.5.3使用@Resource

这里和上面一样,只不过不用搭配@Autowired了;

  1. @Sesource("userInfo1")
  2. private UserInfo user;

•@Autowird 与 @Resource的区别
• @Autowired 是spring框架提供的注解,⽽@Resource是JDK提供的注解
• @Autowired 默认是按照类型注⼊,⽽@Resource是按照名称注⼊. 相⽐于 @Autowired 来说,@Resource ⽀持更多的参数设置,例如 name 设置,根据名称获取 Bean.

📚️5.总结

本期小编主要讲解了关于Spring IOC和DI的详细介绍,从入门讲解概念到实际的应用,都有涉及,当然其中还涉及比较重要的面试题哦~~~

🌅🌅🌅~~~~最后希望与诸君共勉,共同进步!!!


💪💪💪以上就是本期内容了, 感兴趣的话,就关注小编吧。

** 😊😊 * 期待你的关注~~~***

标签: 架构 java Spring IOC

本文转载自: https://blog.csdn.net/GGBond778/article/details/144150355
版权归原作者 GGBondlctrl 所有, 如有侵权,请联系我们删除。

“【Spring】Spring IOC&DI:架构旋律中的“依赖交响”与“控制华章””的评论:

还没有评论