从依赖倒置原则看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
136 0
|
缓存 Java Spring
从依赖倒置原则看Spring(三)
从依赖倒置原则看Spring
188 0
|
XML 前端开发 Java
从依赖倒置原则看Spring(二)
从依赖倒置原则看Spring
164 0
|
28天前
|
XML Java 应用服务中间件
Spring Boot 两种部署到服务器的方式
本文介绍了Spring Boot项目的两种部署方式:jar包和war包。Jar包方式使用内置Tomcat,只需配置JDK 1.8及以上环境,通过`nohup java -jar`命令后台运行,并开放服务器端口即可访问。War包则需将项目打包后放入外部Tomcat的webapps目录,修改启动类继承`SpringBootServletInitializer`并调整pom.xml中的打包类型为war,最后启动Tomcat访问应用。两者各有优劣,jar包更简单便捷,而war包适合传统部署场景。需要注意的是,war包部署时,内置Tomcat的端口配置不会生效。
224 17
Spring Boot 两种部署到服务器的方式
|
28天前
|
Dart 前端开发 JavaScript
springboot自动配置原理
Spring Boot 自动配置原理:通过 `@EnableAutoConfiguration` 开启自动配置,扫描 `META-INF/spring.factories` 下的配置类,省去手动编写配置文件。使用 `@ConditionalXXX` 注解判断配置类是否生效,导入对应的 starter 后自动配置生效。通过 `@EnableConfigurationProperties` 加载配置属性,默认值与配置文件中的值结合使用。总结来说,Spring Boot 通过这些机制简化了开发配置流程,提升了开发效率。
61 17
springboot自动配置原理
|
1月前
|
XML JavaScript Java
SpringBoot集成Shiro权限+Jwt认证
本文主要描述如何快速基于SpringBoot 2.5.X版本集成Shiro+JWT框架,让大家快速实现无状态登陆和接口权限认证主体框架,具体业务细节未实现,大家按照实际项目补充。
83 11
|
1月前
|
缓存 安全 Java
Spring Boot 3 集成 Spring Security + JWT
本文详细介绍了如何使用Spring Boot 3和Spring Security集成JWT,实现前后端分离的安全认证概述了从入门到引入数据库,再到使用JWT的完整流程。列举了项目中用到的关键依赖,如MyBatis-Plus、Hutool等。简要提及了系统配置表、部门表、字典表等表结构。使用Hutool-jwt工具类进行JWT校验。配置忽略路径、禁用CSRF、添加JWT校验过滤器等。实现登录接口,返回token等信息。
359 12
|
1月前
|
Java 测试技术 应用服务中间件
Spring Boot 如何测试打包部署
本文介绍了 Spring Boot 项目的开发、调试、打包及投产上线的全流程。主要内容包括: 1. **单元测试**:通过添加 `spring-boot-starter-test` 包,使用 `@RunWith(SpringRunner.class)` 和 `@SpringBootTest` 注解进行测试类开发。 2. **集成测试**:支持热部署,通过添加 `spring-boot-devtools` 实现代码修改后自动重启。 3. **投产上线**:提供两种部署方案,一是打包成 jar 包直接运行,二是打包成 war 包部署到 Tomcat 服务器。
47 10
|
1月前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
82 8
|
2月前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)