Spring基础篇:动手感受一下松耦合

简介: 从思想上丝滑过渡到Spring的核心:如何让程序解耦。结合UML图介绍工厂+反射的方式创建Bean工厂。

介绍

Spring的核心在于控制反转,组件之间不再通过new来进行关联。而是以面向接口的方式编程,利用容器或者框架来建立组件(Class)之间的关系,最终实现松耦合。

本文从一个简单的main方法程序,逐渐过渡到工厂的思想,通过该思想模拟出Spring的核心:“控制反转”。

一个简单程序

一个名为HelloWorld的Class,里面有一个主函数,使用PrintStream打印一句Hello World

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

该程序写完后,执行的功能是单一的,它只能调用System.out去打印Hello World

缺点:

  1. System.out是固定的,如果我们想改写成System.err,需要重新编码。
  2. Hello World是固定的,它至少应该是一个消息类,以后可以通过某种消息中间件去接收消息(超纲)。

我们改写一下

接口编程

接口的思想就是,我先设计一个模板,先不做具体实现。接着上面的内容,我现在需要:

  1. 一个专门产生消息的类,调用方法获得消息。
public interface MessageSource {
    public String getMessage();
}
  1. 一个打印消息的类,做一个打印打印的动作。
public interface MessageDestination {
    public void write(String msg);
}

程序现在的UML结构应该是这样的,它解决了固定的问题,因为消息和打印体我可以变动,但实际还是硬编码,程序依然耦合

image.png

public class HelloWorld {
    public static void main(String[] args) {
        PrintMessage printMessage = new PrintMessageImpl();
        MessageSource messageSource = new MessageSourceImpl("Hello World");
        printMessage.print(System.out, messageSource.getMessage());
    }
}

好像程序不但耦合了,而且变得复杂了,但是至少它的可维护性在增加(从工程的角度上,我把一个整块的程序在做拆分,变成一个个模块)。这里面就是我们开头提到的一个问题:组件之间是通过new来关联的,是硬编码,紧耦合的关系。接下去我们准备消除这个new,但是为了引出工厂的优点,我们再次完善一下UML的结构。

image.png

这次改写接口PrintMessage,不再传入打印流了,而是由两个实现类分别去做不同的事。

工厂 + 反射

消除这个new,也就是需要我们在运行的过程中才去创建实例对象

  1. 使用反射这个技术,可以实现在运行过程中去创建实例对象。
  2. 结合工厂模式可以解决,根据某些规则去选择实例化对象的方法。

所以将这两种技术结合可以将代码进行解耦。

我先来画个图,解释一下:工厂+反射。

image.png

大致就是说,BeanFactory是用来专门创建类对象的,根据一定的规则(该规则我们可以通过读取配置文件的方式,将传入的key找到类的全类路径)去创建对象(创建的方式可以使用反射的方式创建)。

完整的UML图如下:

image.png

具体代码如下:

工厂接口

public interface BeanFactory {
    // 通过某种名称就拿到一个Bean
    public Object getBean(String name);
}

工厂实现类

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * @ClassName
 * @Description
 * @Author:chengyunlai
 * @Date
 * @Version 1.0
 **/
public class BeanFactoryImpl implements BeanFactory {
    private static Map<String,String> beanDefinitions;
    // 使用Properties作为配置文件,希望再构造该工厂的时候传入配置,加载配置文件
    public BeanFactoryImpl(String config) {
        readBeanDefinitions(config);
    }

    private static void readBeanDefinitions(String config) {
        Properties properties = new Properties();
        System.out.println(BeanFactoryImpl.class.getClassLoader());
        InputStream resourceAsStream = BeanFactoryImpl.class.getClassLoader().getResourceAsStream(config);
        if (resourceAsStream == null) {
            throw new RuntimeException("文件不存在");
        }

        try {
            properties.load(resourceAsStream);
            resourceAsStream.close();
            beanDefinitions = new HashMap<>();
            for (Map.Entry<Object, Object> objectObjectEntry : properties.entrySet()) {
                beanDefinitions.put(objectObjectEntry.getKey().toString(),objectObjectEntry.getValue().toString());
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Object getBean(String name){
        System.out.println(name);
        String beanClassPath = this.beanDefinitions.get(name);
        System.out.println(beanClassPath);
        if (beanClassPath == null){
            return null;
        }
        try {
            // 反射
            return Class.forName(beanClassPath).newInstance();
        } catch (Exception  e) {
            throw new RuntimeException("无法创建该bean:" + beanClassPath);
        }
    }
}

主函数测试

public class FactoryTest {
    public static void main(String[] args) {
        BeanFactory beanFactory = new BeanFactoryImpl("factory.properties");
        MessageSource messageSource = (MessageSource) beanFactory.getBean("messageSource");
        ErrPrintMessage errMessageImpl = (ErrPrintMessage) beanFactory.getBean("errMessageImpl");
        errMessageImpl.print(messageSource.getMessage());
    }
}

配置文件

#为了方便回头取这些类的全限定名,我给每一个类名都起一个“小名”(别名),这样我就可以根据小名来找到对应的全限定类名了。
messageSource=top.chengyunlai.spingPro.sping_02_Introduction.code2_4.impl.MessageSourceImpl
errMessageImpl=top.chengyunlai.spingPro.sping_02_Introduction.code2_4.impl.ErrPrintMessage
outMessageImpl=top.chengyunlai.spingPro.sping_02_Introduction.code2_4.impl.OutPrintMessage

看看Spring是怎么做的

导入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.24</version>
    </dependency>
</dependencies>

Properties方式

Spring也提供了工厂和读取Properties文件的类,看看它是怎么做的:

  1. DefaultListableBeanFactory:即工厂。
  2. BeanDefinitionReader:即读取配置文件。
public class DISpringHelloWorld {
    public static void main(String[] args) {
        DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
        BeanDefinitionReader reader = new PropertiesBeanDefinitionReader(bf);
        reader.loadBeanDefinitions(new ClassPathResource("factory.properties"));
        ErrPrintMessage errMessageImpl = (ErrPrintMessage) bf.getBean("errMessageImpl");
        errMessageImpl.print("你好");
    }
}

配置文件内容

其实就是有点小特殊,在key那额外加了.(class)

messageSource.(class)=top.chengyunlai.spingPro.sping_02_Introduction.code2_4.impl.MessageSourceImpl
errMessageImpl.(class)=top.chengyunlai.spingPro.sping_02_Introduction.code2_4.impl.ErrPrintMessage
outMessageImpl.(class)=top.chengyunlai.spingPro.sping_02_Introduction.code2_4.impl.OutPrintMessage

额外的不展开了,平时用的不多。

XML方式

这个方式,我们接触的最多,即使用ClassPathXmlApplicationContext("xml路径"),直接拿到容器上下文。

配置类方式

这个方式也接触的很多,即使用AnnotationConfigApplicationContext(XXX.class),拿到容器的上下文。

目录
相关文章
|
9月前
|
XML 设计模式 Java
依赖注入艺术:探索Spring如何实现松耦合的神奇
依赖注入艺术:探索Spring如何实现松耦合的神奇
122 0
依赖注入艺术:探索Spring如何实现松耦合的神奇
|
9月前
|
Java 数据库连接 数据库
[Spring ~松耦合的设计神器]`SPI`
[Spring ~松耦合的设计神器]`SPI`
|
Java 测试技术 Spring
探究Spring Boot中的IoC容器:实现松耦合的依赖管理
在现代的软件开发中,松耦合和可维护性是极其重要的设计原则。Spring Boot作为一款流行的Java框架,借助于控制反转(IoC)的机制,实现了高度松耦合的组件之间的依赖管理,提高了代码的可维护性和可测试性。本文将深入介绍Spring Boot中的IoC容器,探讨IoC的基本概念、Spring Boot的IoC支持,以及如何在实际应用中利用IoC构建强大的应用。
256 0
|
29天前
|
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的端口配置不会生效。
233 17
Spring Boot 两种部署到服务器的方式
|
29天前
|
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框架,让大家快速实现无状态登陆和接口权限认证主体框架,具体业务细节未实现,大家按照实际项目补充。
87 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等信息。
369 12
|
2月前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
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