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

【spring】IOC与DI

💐个人主页:初晴~

📚相关专栏:程序猿的春天


一、IOC(Inversion of Control)

1、概念

IOC(Inversion of Control,控制反转)是一种设计原则,它将对象的控制权从程序代码中转移到外部容器中。比如在类上⾯添加 @RestController 和 @Controller 注解,就是把这个对象交给Spring管理,Spring 框架启动时就会加载该类。把对象交给Spring管理,就是IoC思想。

2、具体分析

比如我们有一个需求是:造一辆车

(1)传统开发过程

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

代码如下:

public class CarExample {
    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() {
            this.tire = new Tire();
            System.out.println("Bottom init...");
        }
    }
    /**
     * 轮胎类
     */
    static class Tire {
        // 尺⼨
        private int size;
        public Tire(){
            this.size = 17;
            System.out.println("轮胎尺⼨:" + size);
        }
    }
}

这样设计确实可以实现我们的需求。但是可维护性非常低。当我们需要更改某一个需求时,就很有可能会“牵一发而动全身”。

比如当我们需要定制各种尺寸的轮胎时,轮胎的尺寸就需要修改为变量:

但是由于这种开发方式的耦合调用关系,只改轮胎的代码肯定会导致其它依赖的程序出现报错,就需要继续修改:

由以上例子我们不难看出,传统的开发方式各个类的耦合程度过高,各个类直接相互依赖,当某个类需要发生改变时,整个调⽤链上的所有代码都需要修改

(2)IOC开发方式

我们可以先设计汽⻋的⼤概样⼦,然后根据汽⻋的样⼦来设计⻋⾝,根据⻋⾝来设计底盘,最后根据底盘来设计轮⼦。这时候,依赖关系就倒置过来了:轮⼦依赖底盘, 底盘依赖⻋⾝,⻋⾝依赖汽⻋:

如图,改进之后的控制权发生了翻转,不再是使用方创建并控制对象,而是把依赖对象注入到当前的对象,依赖对象的控制权不再由当前类控制,而是交由一个统一的类进行管理:

public class Main {
    public static void main(String[] args) {
        Tire tire=new Tire(20);
        Bottom bottom=new Bottom(tire);
        Framework framework=new Framework(bottom);
        Car car = new Car(framework);
        car.run();
    }
}
public class Tire {
    // 尺⼨
    private int size;
    public Tire(int size){
        this.size = size;
        System.out.println("轮胎尺⼨:" + size);
    }
}
public class Bottom {
    private Tire tire;
    public Bottom(Tire tire) {
        this.tire = tire;
        System.out.println("Bottom init...");
    }
}
public class Framework {
    private Bottom bottom;
    public Framework(Bottom bottom) {
        this.bottom = bottom;
        System.out.println("Framework init...");
    }
}
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...");
    }
}

3、优势

1、降低耦合度

  • 通过将对象的创建和依赖关系的管理交给外部容器,减少了代码之间的直接依赖,使得系统更加模块化。

2、提高代码可维护性

  • 由于对象之间的依赖关系是通过配置文件或注解定义的,修改这些依赖关系时不需要修改代码,只需要调整配置,这使得代码更容易维护。

3、提高代码的可重用性

  • 由于对象的创建和依赖关系管理被外部化,相同的对象可以在不同的上下文中重用,而不需要修改代码。

4、Spring中运用IOC

Spring 就是⼀种IoC容器,帮助我们来做了这些资源管理。一般就是通过注解的形式, 将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对 象。
(1)类注解:
  • @Controller:控制层,接收请求,对请求进⾏处理,并进⾏响应
  • @Servie:业务逻辑层,处理具体的业务逻辑
  • @Repository:数据访问层,也称为持久层。负责数据访问操作
  • @Configuration:配置层。处理项⽬中的⼀些配置信息

其实这几个注解的作用基本都是一致的,之所以要进行区分,主要是为了对类的用途进行分类,让代码结构更加清晰:

观察这几个注解的源码我们会发现:

它们底层都有一个 @Component 注解,这是一个元注解,是真正完成控制反转的注解。理论上,上述所有注解都可以用 @Component 直接代替。不过,为了让开发更加规范,还是建议运用对应的注解比较合适。

(2)方法注解

类注解是添加到某个类上的, 但是存在两个问题:
1. 使⽤外部包⾥的类, 没办法添加类注解
2. ⼀个类, 需要多个对象, ⽐如多个数据源
这种场景, 我们就需要使⽤⽅法注解 @Bean
需要注意的是,⽅法注解 @Bean 也要搭配 类注解进行使用:
@Component
public class BeanConfig {
    @Bean()
    public User user(){
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }
}

运行结果:

在获取bean时也可以有两种方式:
  • 根据类型获取bean
User user = context.getBean(User.class);
  • 根据名称获取bean
User user1 = (User) context.getBean( "user1" );
还可以通过设置name属性来给 Bean 对象进行重命名操作:
@Component
public class BeanConfig {
    //@Bean(name={"u1","user1"}) //完整写法
    //@Bean({"u1","user1"})      //可省略“name=”
    @Bean("u1")                  //当重命名参数只有一个时,还可省略{}
    public User user1(){
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }
}

二、DI(Dependency Injection)

1、概念

DI,即依赖注入(Dependency Injection),是一种实现控制反转(IOC)原则的技术手段。

容器在运⾏期间, 动态的为应⽤程序提供运⾏时所依赖的资源,称之为依赖注⼊。
可以这样理解:IOC是一种思想,而DI则是具体实现

2、实现方式

(1)属性注入

属性注⼊是使⽤ @Autowired 实现的,将 Service 类注⼊到 Controller 类中
@Controller
    public class UserController {
     //注⼊⽅法1: 属性注⼊
     @Autowired
     private UserService userService;
 
     public void sayHi(){
         System.out.println("hi,UserController...");
         userService.sayHi();
     }
}
(2)构造⽅法注⼊
构造⽅法注⼊是在类的构造⽅法中实现注⼊,如下代码所⽰:
@Controller
public class UserController2 {
     //注⼊⽅法2: 构造⽅法
     private UserService userService;
 
     @Autowired
     public UserController2(UserService userService) {
     this.userService = userService;
     }
 
     public void sayHi(){
         System.out.println("hi,UserController2...");
         userService.sayHi();
     }
}
注意: 如果类只有⼀个构造⽅法,那么 @Autowired 注解可以省略;如果类中有多个构造⽅法,那么需要添加上 @Autowired 来明确指定到底使⽤哪个构造⽅法。

(3)Setter 注⼊

Setter 注⼊和属性的 Setter ⽅法实现类似,只不过在设置 set ⽅法的时候需要加上 @Autowired 注

解 ,如下代码所⽰:
@Controller
public class UserController3 {
     //注⼊⽅法3: Setter⽅法注⼊
     private UserService userService;
 
     @Autowired
     public void setUserService(UserService userService) {
         this.userService = userService;
     }
 
     public void sayHi(){
         System.out.println("hi,UserController3...");
         userService.sayHi();
     }
}

3、三种注⼊优缺点分析

(1)属性注⼊

优点:
  • 简洁,使⽤⽅便;

缺点:

  • 只能⽤于 IoC 容器,如果是⾮ IoC 容器不可⽤,并且只有在使⽤的时候才会出现 NPE(空指 针异常)
  • 不能注⼊⼀个Final修饰的属性
(2)构造函数注⼊(Spring 4.X推荐)

优点:

  • 可以注⼊final修饰的属性
  • 注⼊的对象不会被修改
  • 依赖对象在使⽤前⼀定会被完全初始化,因为依赖是在类的构造⽅法中执⾏的,⽽构造⽅法是在类加载阶段就会执⾏的⽅法.
  • 通⽤性好, 构造⽅法是JDK⽀持的, 所以更换任何框架,他都是适⽤的
缺点:
  • 注⼊多个对象时, 代码会⽐较繁琐
(3)Setter注⼊(Spring 3.X推荐)
优点:
  • ⽅便在类实例之后, 重新对该对象进⾏配置或者注⼊
缺点:
  • 不能注⼊⼀个Final修饰的属性
  • 注⼊对象可能会被改变, 因为setter⽅法可能会被多次调⽤, 就有被修改的⻛险

4、@Autowired存在问题

当同⼀类型存在多个bean时, 使⽤@Autowired会存在问题
@Component
public class BeanConfig {
     @Bean("u1")
     public User user1(){
         User user = new User();
         user.setName("zhangsan");
         user.setAge(18);
         return user;
     }
     @Bean
     public User user2() {
         User user = new User();
         user.setName("lisi");
         user.setAge(19);
         return user;
     }
}
 
@Controller
public class UserController {
    //注入user
    @Autowired
    private User user;

    public void sayHi(){
         System.out.println("hi,UserController...");
         System.out.println(user);
    }
}
运行结果:

报错的原因是,⾮唯⼀的 Bean 对象。
对此,Spring有三种解决方式:
(1) 使⽤@Primary注解
当存在多个相同类型的Bean注⼊时,加上@Primary注解,来确定默认的实现
@Component
public class BeanConfig {
    @Primary //指定该bean为默认bean的实现
    @Bean("u1")
    public User user1(){
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }
    @Bean
    public User user2() {
        User user = new User();
        user.setName("lisi");
        user.setAge(19);
        return user;
    }
}

(2)使用@Qualifier注解

指定当前要注⼊的bean对象。 在@Qualifier的value属性中,指定注⼊的bean的名称。
注意: @Qualifier注解不能单独使⽤,必须配合@Autowired使⽤
@Controller
public class UserController {
    //注⼊user
    @Qualifier("user2")
    @Autowired
    private User user;
    public void sayHi(){
        System.out.println("hi,UserController...");
        System.out.println(user);
    }
}

(3)使⽤@Resource注解

按照bean的名称进⾏注⼊。通过name属性指定要注⼊的bean的名称
@Controller
public class UserController {
    //注⼊user
    @Resource(name = "user2")
    private User user;
    public void sayHi(){
        System.out.println("hi,UserController...");
        System.out.println(user);
    }
}

5、@Autowird 与 @Resource的区别

  • @Autowired是spring框架提供的,@Resource是JDK提供的注解
  • @Autowired默认是按类型注入的,而@Resource优先是按照名称注入的,@Resource提供更多的参数设置
@Autowired装配顺序


那么本篇文章就到此为止了,如果觉得这篇文章对你有帮助的话,可以点一下关注和点赞来支持作者哦。如果有什么讲的不对的地方欢迎在评论区指出,希望能够和你们一起进步✊


http://www.kler.cn/a/383809.html

相关文章:

  • linux操作系统的开机引导过程及系统日志
  • 软件测试面试题及答案
  • 微服务的注册中心Nacos
  • 单元测试怎么做
  • Python并发编程——multiprocessing
  • [CKS] K8S Admission Set Up
  • mysql字段是datetime如何按照小时来统计
  • Ai练习过程当中的注意事项 Ⅱ
  • 【AI日记】24.11.06 我对投资的一点浅见
  • 问:Redis为什么这么快?
  • Magentic-One:微软推出多智能体系统,用于解决跨领域的复杂网络和文件任务
  • yolov8-seg目标分割理论及代码运行实践
  • 【1个月速成Java】基于Android平台开发个人记账app学习日记——第7天,申请阿里云SMS短信服务SDK
  • 代码随想录之字符串刷题总结
  • redis与本地缓存
  • [MySQL]视图
  • 大数据中的Kafka, Zookeeper,Flume,Nginx, Sqoop与ETL
  • 一文读懂:AIOps 从自动化运维到智能化运维
  • C#如何快速获取P/Invoke方法签名
  • 【ChatGPT】让ChatGPT生成跨语言翻译的精确提示
  • iOS灵动岛动画小组件怎么播放动画
  • Python实例:爱心代码
  • PySpark本地开发环境搭建
  • 【Pytorch】基本语法
  • ssm052游戏攻略网站的设计与实现+vue(论文+源码)-kaic
  • Hyperledger Fabric 入门笔记(十六)Fabric V2.5 测试网络部署补充 - 手动从通道中移除组织