Spring学习前置知识

Spring作用

Spring是一个轻量级的开源框架,是为解决企业级应用开发的复杂性而创建的,通过核心的Bean Factory实现了底层类的实例化和生命周期的管理;

Spring的根本使命:简化Java开发

OCP开闭原则

开放封闭原则(OCP,Open Closed Principle)是所有面向对象原则的核心。软件设计本身所追求的目标就是封装变化、降低耦合,而开闭原则正是这一目标的直接体现,其他的设计原则,很多时候都是未实现这一目标服务的

开:对扩展开放 闭:对修改关闭

开闭原则的核心思想是对系统的抽象进行编程,通过定义抽象接口或者抽象类,使得系统具有一定的灵活性和扩展性。在需要修改系统的功能时,可以通过添加新的实现类或者子类来扩展系统的功能,而不是直接修改已有的实现类或者基类,从而降低代码的耦合性,提高代码的可维护性和可扩展性。

开闭原则的优点包括:

a. 可扩展性:通过添加新的代码或者类,可以扩展系统的功能,满足不同的需求。

b. 可维护性:不需要修改已有的代码,减少了代码的修改次数和维护成本。

c. 可复用性:通过抽象化和接口编程,可以提高代码的复用性。

d. 可测试性:由于代码的耦合性降低,可以更容易地进行单元测试和集成测试。

也就是说,如果在进行功能扩展的时候,添加额外的类是没问题的,但因为功能扩展而修改之前运行正常的程序,这是忌讳的,不被允许的。因为一旦修改之前运行正常的程序,就会导致项目整体要进行全方位的重新测试。这是相当麻烦的过程。导致以上问题的主要原因是:代码和代码之间的耦合度太高。如下图所示:

可以明显的看出,上层依赖下层的。UserController依赖UserServiceImpl,而UserServiceImpl依赖UserDaoImplForMySql,这样就会导致下面只要改动,上面必然会受牵连(跟着也会改动),所谓牵一发而动全身。这样也就违背了另一个开发原则:依赖倒置原则。

依赖倒置原则DIP

依赖导致原则(Dependency Inversion Principle, DIP)的定义总结为:

高层模块不应该依赖于底层模块,两者都应该依赖于抽象

抽象不应该依赖于细节,细节应该依赖于抽象

依赖倒置原则的核心思想就是通过抽象接口和依赖倒置来实现代码的松耦合,降低模块之间的依赖性,提高代码的可维护性、可扩展性和可复用性。

依赖倒置原则的实现方式包括:

(一) 抽象接口:定义抽象接口,将高层模块和底层模块解耦,使得它们只依赖于抽象接口而不依赖于具体实现

(二)接口隔离原则:定义多个专门的接口,而不是一个大而全的接口,避免出现不必要的依赖关系

(三)依赖注入:通过依赖注入(Dependency Injection)的方式,将依赖关系从高层模块中抽离,交由外部容器或者框架注入

(四)依赖查找:通过依赖查找(Dependency Look)的方式,使得高层模块可以从容器或者框架中查找需要的底层模块,实现模块之间的解耦

2.1 核心:倡导面向接口编程、面向抽象编程,不要面向具体编程

2.2 目的:降低程序的耦合性,提高扩展力

控制反转IOC

控制反转(Inversion of Control, IoC)是一种设计模式,它是面向对象编程中依赖倒置原则的一种实现方式。控制反转是指将程序的控制权由程序员转移给框架或者容器,由它来控制对象的创建、销毁、依赖关系的维护等操作。在传统的程序开发过程中,程序员自己负责对象的创建和维护,导致代码的耦合度较高,而控制反转则可以解决这个问题,它将通过将对象的创建和维护交给框架或容器来实现代码的耦合。

控制反转的核心思想是通过定义抽象接口和依赖注入来实现代码的松耦合,它包括以下几个步骤:

  1. 定义抽象接口:定义抽象接口,描述对象的行为和功能,由程序员来编写
  2. 实现具体类:实现抽象接口的具体类,由框架或容器来创建和维护,由程序员配置
  3. 注册对象:将具体类注册到容器中,告诉容器哪些对象需要创建和维护
  4. 依赖注入:将对象的依赖关系交给容器来维护,容器在创建对象时,自动将对象所需要的依赖注入到对象中,避免程序员手动维护依赖关系。
    控制反转常见的实现方式:依赖注入
    控制反转是一种思想,依赖注入是这种思想的具体实现,常见方式:
    第一种: set注入 (执行set方法给属性赋值)
    第二种:构造方法注入 (执行构造方法给属性赋值)
    依赖注入 中 "依赖"是什么意思?"注入"是什么意思?
    ● 依赖:A对象和B对象的关系
    ● 注入:是一种手段,通过这种手段,可以让A对象和B对象产生关系
    ● 依赖注入:对象A和对象B之间的关系,靠注入的手段来维护,而注入的方法包括:set注入和构造方法注入
    控制反转是一种新型的设计模式,但是因为比较新,没有被纳入GOF 23种设计模式中

优质程序代码的书写原则

程序的耦合和内聚

耦合与内聚的介绍:

内聚:内聚指的是一个模块(类或者函数等)内部各个元素(方法、属性等)之间关联的紧密程度。高内聚意味着各个元素都紧密相关。共同为为实现一个单一、清晰、定义良好的目标而努力。这样的模块更易于理解、维护和复用。

高内聚的例子:一个类只负责处理用户信息的验证,包括用户名、密码、邮箱等的验证逻辑。这个类中所有的方法、变量都是紧密围绕着用户信息验证这一主题。

○ 低内聚的例子:一个模块中既有添加数据的逻辑,又有更新数据的逻辑,还可能有其他不相关的功能,这样的模块就显得杂乱无章,内聚度低。

耦合:耦合指的是不同模块之间相互依赖的程度。每个模块都可以独立地修改/新增而不修改其他的模块。 ○

低耦合的例子:两个模块通过定义良好的接口进行通信,一个模块的改变不会影响到另一个模块,除非接口本身发生变化。 ○

高耦合的例子:一个模块直接访问另一个模块的内部数据或方法,或者两个模块之间存在复杂的、难以理解的依赖关系。 耦合与内聚的关系

耦合与内聚在定义上是独立的,但它们之间存在着一种微妙的关系,一般来说,追求高内聚的同时,也倾向于实现低耦合。因为高耦合的模块通常具有清晰的职责边界,这样有助于减少模块间的依赖,从而降低耦合度。

在通常情况下,我们会努力在两者之间找到一个平衡点,为了提高系统的整体性能或满足特定的设计需求,可能会在一定程度上牺牲内聚或耦合,以实现既易于维护又高效的系统。

内聚代码演示

java 复制代码
//反例: 一个方法内实现了太多的计算功能,不符合高内聚思想 弊端:不方便维护,扩展性差
public int compute(int i, int j, String label){
  if ("+".equals(label)){
    return i + j;
  }else if ("-".equals(label)){
    return i - j;
  }else{
    // do something
  }
  return 0;
}

-----------------------------------------------------------------------------------
//高内聚例子: 好处:扩展性好 开闭原则:向扩展开发,向修改闭合
public int add(int i, int j){
  return i + j;
}

public int sub(int i, int j){
  return i - j;
}

耦合代码演示

MVC三层架构演示

java 复制代码
public interface UserDao {
    void add();
    void update();
}

public class UserDapImpl implements UserDao{

    @Override
    public void add() {
        System.out.println("Mysql数据库正在新增用户....");
    }

    @Override
    public void update() {
        System.out.println("Mysql数据库正在修改用户....");
    }
}
public interface UserService {
    void add();
    void update();
}

public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        // UserServiceImpl 依赖于UserDapImpl,如果UserDapImpl的add方法变更,那么这里也需要变更,我们称之为耦合度高
        UserDao userDao = new UserDapImpl();
        userDao.add();
    }

    @Override
    public void update() {

    }
}

耦合的弊端

耦合弊端 说明
维护成本高 以UserService调用UserDao为例,如果DAO实现方案修改,则需要做硬编码调整,维护性差;
独立性差 以UserService调用UserDao为例,UserService下功能脱离UserDao无法独立实现功能;
可复用性不高 因为模块间存在耦合,导致很难被有效引用;比如:B与C耦合,而A仅仅想引用B,但实际情况是A间接与C也耦合了,说明B的复用性较差;

工厂模式解耦

【1】原始方案

服务层与持久层存在耦合,一旦替换持久层实现方案,则需要硬编码修改代码,维护性很差;

【2】工厂模式降低耦合

思考:服务层与持久层存在耦合,说白了就是在服务层直接创建持久层对象,存在强引用,也就是紧耦合,如何解决?

我们可以把创建持久层对象的功能封装到一个工具类下,也就是说这个工具类专门用来创建持久层对象,将来方案修改,也仅仅修改工具类即可,这也就是工厂模式的雏形;

工厂模式解耦代码实例

java 复制代码
public class DaoFactory {

    /**
     * UserDao 工厂对象实例化方法
     */
    public static UserDao UserDaoGetInstance(){
        // 解决ServiceImpl和Dao的耦合问题:这里引用UserDao的实现类
        // 但是出现新的耦合问题:工厂与创建对象的耦合
        return new UserDaoImpl2();
    }
}

public class UserServiceImpl implements UserService{
    @Override
    public void update() {
        // 调用工厂类,这样UserServiceImpl与UserDao就没有了直接依赖关系,完成解耦
        UserDao userDao = DaoFactory.UserDaoGetInstance();
        userDao.update();
    }
}

引入工厂模式的好处与弊端:

[1] 好处:当持久层或者服务层方案修改时,仅仅修改工厂类即可完成实现方案的切换,维护性相对较好。 [2]

弊端:加入工厂模式后,服务层与持久层完成解耦,但是又引入了新的耦合,即工厂类与持久层存在紧耦合。

Spring雏形(基于工厂模式)

工厂模式的思考

Spring是基于工厂模式创建的

工厂类与工厂生产的对象之间如何实现的解耦:

a. 将工厂生产的对象类信息配置到静态文件中

b. 工厂类通过I/O加载静态资源,获取配置信息,然后通过反射创建对象

好处:将工厂创建的对象静态配置化,这样即使对象实现方案进行了切换,也仅仅修改静态文件,无需修改工厂代码

最终解决方案:工厂 + 静态配置文件 + 反射

在Spring中工厂维护的对象称为 Bean。

工厂模式Bean实现

创建Bean.properties配置文件,其中key为使用时的参数,value是需要引用的实现类的全限定类名,之所以要全限定类名,是因为在反射创建对象的时候需要使用到。

yaml 复制代码
userDao=com.powernode.Dao.UserDaoImpl
UserService=com.powernode.Service.UserServiceImpl

工厂模式Bean多例模式实现

定义

java 复制代码
package com.powernode.factory;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;

/**
 * @Author: 
 * @Description: 工厂 + 静态配置 + 反射
 *              定义创建对象的工厂,实现bean多实例创建
 * @DateTime: 2024/9/19 18:54
 */
public class BeanFactory {
    // 专门存放bean的定义对象
    private static Map<String, String> beanInformation = new HashMap<>();
    // 使用静态代码块,实现Bean.properties文件资源 io只需要走一次
    static {
        // 1、读取配置文件
        ResourceBundle bundle = ResourceBundle.getBundle("Bean");
        // 2、获取配置文件中的key
        Enumeration<String> keys = bundle.getKeys();
        // 3、循环将对象信息以Map的形式存储
        while (keys.hasMoreElements()){
            String key = keys.nextElement();
            String className = bundle.getString(key);
            beanInformation.put(key, className);
        }
    }

    // 获取bean对象
    public static Object getBean(String beanName) {
        // 1、通过beanName获取到配置文件中内容
        String className = beanInformation.get(beanName); // 全限定类名
        Object bean = null;
        // 2、通过反射 根据全限定类名创建对象
        try{
            Class<?> aClass = Class.forName(className);
            bean = aClass.newInstance();
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("获取bean失败");
        }
        // 3、返回Bean
        return bean;
    }
}

使用

java 复制代码
package com.powernode.Service;

import com.powernode.Dao.UserDao;
import com.powernode.Dao.UserDaoImpl;
import com.powernode.factory.BeanFactory;
import com.powernode.factory.BeanSingletonFactory;

/**
 * @Author: 
 * @Description: TODO
 * @DateTime: 2024/9/19 15:19
 */
public class UserServiceImpl implements UserService{
    UserDao userDao = (UserDao) BeanFactory.getBean("userDao");

    @Override
    public void add() {
        UserDao userDao = (UserDao) BeanFactory.getBean("userDao");
        System.out.println("add:userDao:" + userDao);
        userDao.add();
    }

    @Override
    public void update() {
        UserDao userDao = (UserDao) BeanFactory.getBean("userDao");
        System.out.println("update:userDao:" + userDao);
        userDao.update();
    }
}

public class Main {
    public static void main(String[] args) {
        UserService userService = (UserService) BeanFactory.getBean("UserService");
        userService.add();
        userService.update();
    }
}

运行效果

java 复制代码
add:userDao:com.powernode.Dao.UserDaoImpl@573f2bb1
Mysql数据库正在新增用户....
update:userDao:com.powernode.Dao.UserDaoImpl@5ae9a829
Mysql数据库正在修改用户....

工厂模式Bean单例模式实现

定义

java 复制代码
package com.powernode.factory;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;

/**
 * @Author: 
 * @Description: 工厂 + 静态配置 + 文件 生成Bean
 *              一个key只能对应一个对象 单例模式
 * @DateTime: 2024/9/19 19:15
 */
public class BeanSingletonFactory {
    // 定义Bean信息
    private static Map<String, String> beanInformation = new HashMap<>();
    // 定义存储实例化Bean的Map
    private static Map<String, Object> singletonBean = new HashMap<>();
    // 读取配置文件
    static {
        ResourceBundle bundle = ResourceBundle.getBundle("Bean");
        Enumeration<String> keys = bundle.getKeys();
        while (keys.hasMoreElements()){
            String key = keys.nextElement();
            String className = bundle.getString(key);
            beanInformation.put(key, className);
        }
    }
    // 获取Bean
    public static Object getBean(String className){
        // 1、先从集合中获取bean
        Object bean = singletonBean.get(className);
        //2、如果bean集合中不存在则重新创建
        if (bean == null){
            String info = beanInformation.get(className);
            try {
                Class<?> aClass = Class.forName(info);
                bean = aClass.newInstance();

                // 将bean保存到Bean集合中,方便下次直接获取
                singletonBean.put(className, bean);
            }catch (Exception e){
                e.printStackTrace();
                System.out.println("获取bean失败");
            }
        }

        return bean;
    }
}

使用

java 复制代码
package com.powernode.Service;

import com.powernode.Dao.UserDao;
import com.powernode.Dao.UserDaoImpl;
import com.powernode.factory.BeanFactory;
import com.powernode.factory.BeanSingletonFactory;

/**
 * @Author: 
 * @Description: TODO
 * @DateTime: 2024/9/19 15:19
 */
public class UserServiceImpl implements UserService{

    @Override
    public void add() {
        UserDao userDao2 = (UserDao) BeanSingletonFactory.getBean("userDao");
        System.out.println("add:userDao:" + userDao2);
        userDao2.add();
    }

    @Override
    public void update() {
        UserDao userDao2 = (UserDao) BeanSingletonFactory.getBean("userDao");
        System.out.println("update:userDao:" + userDao2);
        userDao2.update();
    }
}

public class Main {
    public static void main(String[] args) {
        UserService userService = (UserService) BeanFactory.getBean("UserService");
        userService.add();
        userService.update();
    }
}

可以从最终的运行效果可以看到,虽然都调用了getBean方法,但是对象其实并没有改变
运行效果

java 复制代码
add:userDao:com.powernode.Dao.UserDaoImpl@573f2bb1
Mysql数据库正在新增用户....
update:userDao:com.powernode.Dao.UserDaoImpl@573f2bb1
Mysql数据库正在修改用户....

Spring的发展历程

工厂模式+静态配置+反射: 最终没有消除耦合,只是降低了耦合 静态配置文件与资源的耦合依旧存在;

静态配置文件修改后无需重复编译,耦合降到最低; 程序间的耦合是无法彻底消除的,只能尽可能的降低耦合。

详情跳转:参考

相关推荐
2401_857636399 分钟前
Spring Boot环境下的知识分类与检索
java·spring boot·后端
小趴菜不能喝13 分钟前
spring boot 3.x 整合Swagger3
java·spring boot·swagger
微服务技术分享25 分钟前
专为成长型企业打造的Java CRM系统源码:CRM客户关系管理系统技术解析与功能构建
java·crm客户关系管理系统源码·鸿鹄crm客户关系管理系统·鸿鹄crm客户关系管理系统源码
琪露诺大湿26 分钟前
JavaEE-多线程初阶(4)
java·开发语言·jvm·java-ee·基础·1024程序员节·原神
Java程序员-小白34 分钟前
Spring Shell——快速构建终端应用,自定义终端命令
java·后端·spring
想做白天梦40 分钟前
LeetCode :150. 逆波兰表达式求值(含求后缀表达式和中缀转后缀表达式)
java·前端·算法
远望樱花兔43 分钟前
【d63】【Java】【力扣】141.训练计划III
java·开发语言·leetcode
九圣残炎1 小时前
【从零开始的LeetCode-算法】3254. 长度为 K 的子数组的能量值 I
java·算法·leetcode
那你为何对我三笑留情1 小时前
六、Spring Boot集成Spring Security之前后分离项目认证流程最佳方案
java·spring boot·分布式·后端·spring·spring security
土小帽软件测试1 小时前
jmeter基础01-2_环境准备-Mac系统安装jdk
java·测试工具·jmeter·macos·软件测试学习