从依赖倒置原则看Spring(一)

简介: 从依赖倒置原则看Spring

前言

知其然,更应知其所以然。

案例

我们先来看一个案例:有一个小伙,有一辆吉利车, 平常就开吉利车上班

代码实现:

public class GeelyCar {
    public void run(){
        System.out.println("geely running");
    }
}
public class Boy {
    // 依赖GeelyCar
    private final GeelyCar geelyCar = new GeelyCar();
    public void drive(){
        geelyCar.run();
    }
}

有一天,小伙赚钱了,又买了辆红旗,想开新车。

简单,把依赖换成HongQiCar

代码实现:

public class HongQiCar {
    public void run(){
        System.out.println("hongqi running");
    }
}
public class Boy {
    // 修改依赖为HongQiCar
    private final HongQiCar hongQiCar = new HongQiCar();
    public void drive(){
        hongQiCar.run();
    }
}

新车开腻了,又想换回老车,这时候,就会出现一个问题:这个代码一直在改来改去

很显然,这个案例违背了我们的依赖倒置原则(DIP):程序不应依赖于实现,而应依赖于抽象

优化

现在我们对代码进行如下优化:

img

Boy依赖于Car接口,而之前的GeelyCarHongQiCarCar接口实现

代码实现:

定义出Car接口

public interface Car {
    void run();
}

将之前的GeelyCarHongQiCar改为Car的实现类

public class GeelyCar implements Car {
    @Override
    public void run(){
        System.out.println("geely running");
    }
}

HongQiCar相同

Person此时依赖的为Car接口

public class Boy {
    // 依赖于接口
    private final Car car;
    public Boy(Car car){
        this.car = car;
    }
    public void drive(){
        car.run();
    }
}

此时小伙想换什么车开,就传入什么参数即可,代码不再发生变化。

局限性

以上案例改造后看起来确实没有什么毛病了,但还是存在一定的局限性,如果此时增加新的场景:

有一天小伙喝酒了没法开车,需要找个代驾。代驾并不关心他给哪个小伙开车,也不关心开的是什么车,小伙就突然成了个抽象,这时代码又要进行改动了,代驾依赖小伙的代码可能会长这个样子:

private final Boy boy = new YoungBoy(new HongQiCar());

随着系统的复杂度增加,这样的问题就会越来越多,越来越难以维护,那么我们应当如何解决这个问题呢?

思考

首先,我们可以肯定:使用依赖倒置原则是没有问题的,它在一定程度上解决了我们的问题。

我们觉得出问题的地方是在传入参数的过程:程序需要什么我们就传入什么,一但系统中出现多重依赖的类关系,这个传入的参数就会变得极其复杂。

或许我们可以把思路反转一下:我们有什么,程序就用什么!

当我们只实现HongQiCarYoungBoy时,代驾就使用的是开着HongQiCarYoungBoy

当我们只实现GeelyCarOldBoy时,代驾自然而然就改变成了开着GeelyCarOldBoy

这其实就是Spring的控制反转思想

而应该如何实现这个反转的需求呢?

需求分析

需求描述:我们有什么,程序就用什么。

分析:

  • 程序怎么知道我们有什么?或者我们应该如果告知程序我们有什么?
  • 程序怎么去使用?

借鉴生产消费模型看这个问题。

生产者把生产的物品放入容器中,消费中从容器中将物品取出。

我们是否也可以使用这样的模式?

我们将对象放入容器里,程序在容器里面取这个对象,有啥对象就用啥对象。

比如我们往容器里放HongQiCar, 那程序就用HongQiCar, 往容器里放GeelyCar, 那程序就用GeelyCar

需求实现:

  • 程序怎么知道我们有什么?
    我们往一个容器里面放对象,放了什么就是什么。
  • 程序怎么去使用?
    程序从容器里面取对象,拿到什么就用什么。

简单的实现:

容器的选定:容器可以用Map,key为接口的名称,value为对应的实现。如:car:HongQiCar

代码:

public class SimpleMain {
    private static Map<String, Object> map = new ConcurrentHashMap<>();
    public static void main(String[] args) {
        // 放
        map.put("car", new HongQiCar());
        // 使用
        new Boy((Car) map.get("car")).drive();
    }
}

这样的实现在系统简单的时候还好,但是对象多了,那改起来就非常复杂了。

有没有一种方法,能让程序自己去寻找使用的对象,我们只给要用的对象打个标识。

比如这个注解@JsonIgnore大家肯定都用过,它表示被标识的字段是否进行json序列化, 标识了就不进行序列化

@JsonIgnore
private String name;
if(field.isAnnotationPresent(JsonIgnore.class)){
  // 不序列化
}

显然这是一份通用的代码,我们只需要给字段上加@JsonIgnore注解就好

借鉴这个方法,我们同样可以给类加上注解,让程序去找寻项目里有该注解的类,有就自己把它put到Map中。

分析:

Q:给类加上注解,什么注解?

A:注解我们就用@Component注解


Q:让程序去找寻项目里有该注解的类,怎么找?

A:指定一个包路径,让程序去扫描包下的类,判断类是否被@Component注解标识


Q:有就自己把它put到Map中,怎么put?

A:通过反射将该类进行实例化,然后以类名为key, 实例为value,put到Map中

Q:如何扫描包下的类?

A:其实就是遍历文件目录

流程:

由于代码比较复杂,这里不做展示,见源码中:com.my.spring.auto.AutoMain

现在,我们可以随意的通过更换注解的方式,让程序使用不同的类,但是还剩下一个问题,就是这个

new Boy((Car) map.get("car")).drive();

回到案例的问题,此时加上代驾小哥,代码就会变成这个样子

Boy boy = map.get("boy");
boy.setCar(map.get("car"));
new DaiJia(boy).drive();

还是挺复杂的,所以能不能让程序把这个组装过程也完成了呢?

相当于让程序自己帮我检测包下的类之间的依赖关系,并且帮我组装好,我最后只要这样写代码:

map.get("daijia").drive();

岂不美哉!

现在,让我们结合之前的需求做一个整体的分析

需求:扫描指定包下面的类,进行实例化,并根据依赖关系组合好

步骤分解:

扫描指定包下面的类 -> 如果这个类标识了Component注解(是个Bean) -> 把这个类的信息存起来

进行实例化 -> 遍历存好的类信息 -> 通过反射把这些类进行实例化

根据依赖关系组合 -> 解析类信息 -> 判断类中是否有需要进行依赖注入的字段 -> 对字段进行注入

Q:为什么现在需要把类存起来?之前的实现都是直接把类实例化后放到map里的。

A:这是因为要查找依赖关系,比如A类依赖了B类,那么在实例化A时,去哪里找B呢,一个办法是将包再扫描一次,但是这样效率太低了,所以只要在第一次扫描时将类都存起来,后面找的时候只要从map找就行了。

Q:怎么判断类中是否有需要进行依赖注入的字段?

A:还是用注解,这里用@Autowired注解

为了更灵活,这里将扫描指定包下面的类也通过注解@ComponentScan实现

流程:

方案实现

定义注解

首先我们需要定义出需要用到的注解:ComponentScan,Component,Autowired

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
    String basePackages() default "";
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
    String value() default "";
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}


目录
相关文章
|
设计模式 Java Spring
【设计模式】依赖倒置原则与工厂方法模式与spring
【设计模式】依赖倒置原则与工厂方法模式与spring
131 0
|
缓存 Java Spring
从依赖倒置原则看Spring(三)
从依赖倒置原则看Spring
182 0
|
XML 前端开发 Java
从依赖倒置原则看Spring(二)
从依赖倒置原则看Spring
159 0
|
4月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
3月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
258 2
|
12天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
19天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
70 14
|
2月前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
58 1
SpringBoot入门(7)- 配置热部署devtools工具
|
2月前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
55 2
 SpringBoot入门(7)- 配置热部署devtools工具
|
2月前
|
监控 Java 数据安全/隐私保护
如何用Spring Boot实现拦截器:从入门到实践
如何用Spring Boot实现拦截器:从入门到实践
54 5