• 设计模式 笔记11 | 创建型设计模式在源码中的应用 总结篇(一)



    关于创建型设计模式在源码中的应用的笔记

    笔记1:设计模式 笔记1 | 五个创建型模式 | Java 语言实现 | 工厂方法设计模式 | 抽象工厂模式 |建造者模式 | 单例模式 | 原型模式_程序喵 尤Ni的博客-CSDN博客

    一、创建型设计模式 知识回顾

    创建型设计模式 主要有五种,分别是:

    • 工厂方法设计模式
    • 抽象工厂模式
    • 建造者模式
    • 单例模式
    • 原型模式

    接下来用简要的一句话概括这些设计模式的定义和主要组成部分:

    1.1 工厂方法设计模式

    定义:提供符合开闭原则的工厂对象,支持获取同一类型的产品。
    特点:工厂和产品都有抽象和具体实现两种。
    主要组成:

    1.2 抽象工厂模式

    定义:为访问类提供一组相关的接口,支持获取多种类型的产品。
    特点:工厂方法的升级版,符合开闭原则。
    主要组成:

    1.3 建造者模式

    定义:将创建对象的过程根据其属性分成多个步骤,每一个步骤支持单独处理部分的属性。
    特点:可以和工厂方法模式结合使用,封装性好
    主要组成:

    其中抽象建造者和具体建造者可以合并,直接放在产品角色中,即静态内部类的方式实现建造者模式,这种方式比较常用,其组成图如下:

    1.4 单例模式

    定义:只有一个单例对象,通常使用 static 关键字实现
    特点:分为懒汉式 和 饿汉式两种,前者在多线程环境下需要用到 synchronized 同步,防止多线程抢占资源
    主要组成:只有一个单例对象,贴一个不使用 synchronized ,而用 Java类加载机制完美实现懒汉式的代码:

    public class Singleton {
        private Singleton(){} // 私有化无参构造
    
        private static class Holder{ // 静态内部类
            // 根据类加载特性, 仅使用Singleton类时,不会对静态内部类进行初始化
            private final static Singleton INSTANCE = new Singleton();
        }
        public static Singleton getInstance(){
            return Holder.INSTANCE; // 直接获取内部类的静态实例
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    1.5 原型模式

    定义:用已创建的对象实例作为原型,通过复制该原型对象来创建一个和原型相同货类似的新对象。
    特点:分为浅克隆和深克隆两种,Object类的clone() 属于 浅克隆,比直接new快
    主要组成:

    二、创建型设计模式在源码中的应用总结

    2.1 工厂方法模式 logback

    参考笔记:https://blog.csdn.net/Unirithe/article/details/125911608
    logback** **是一种日志管理框架,由 log4j (流行的日志管理框架)创始人推出,是 log4j 的升级版,同时也是 SpringBoot 默认的日志管理框架

    日志级别分类以及优先级为:ERROR > WARN > INFO > DEBUG >TRACE

    logback 架构:

    • logback-core,框架代码核心,其他两个模块依赖此模块
    • logback-classic,log4j优化版本,实现了原生log4j
    • logback-access,可集成到现有的Servlet容器,如Tomcat、Jetty(按需引入)

    主要的三个接口:

    • Logger,属于 logback-classic 模块,最常用的接口,用于获取日志对象
    • Appender 和 Layout 接口,属于 logback-core 模块

    使用到工厂模式的类:LoggerFactory.java

    public final class LoggerFactory {
        private LoggerFactory() {}		// 构造方法私有化
        public static Logger getLogger(Class<?> clazz) {
            ...
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    LoggerFactory 是 logback-classic 提供的 日志工厂类,我们一般是通过这个工厂里的方法来获取日志对象 Logger 的。
    getLogger 是 日志工厂提供的静态方法,是重载的方法,最常见的有 String 和 Class两个参数:

    public static Logger getLogger(Class<?> clazz) {
        // 获取字节码的名称,调用重载方法获取 Logger
        Logger logger = getLogger(clazz.getName());
        ...
        return logger;
    }
    
    public static Logger getLogger(String name) {
        // 获取工厂实现类
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }
    public static ILoggerFactory getILoggerFactory() {...}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    通过源码可以看出,getLogger 是日志工厂里可以获取日志的方法,,其本质吊用了日志工厂对应的抽象工厂,即 iLoggerFactory 接口的 getLogger方法。

    2.2 抽象工厂模式 Spring

    参考笔记:

    Spring 是 Java EE 编程领域的一个轻量级开源框架,集成了各类型的工具,通过核心的 BeanFactory 实现了底层的类的实例化和生命周期的管理

    Spring特点:IOC 控制反转,DI 依赖注入,AOP 面向切面编程

    Spring中的抽象工厂模式:

    BeanFactory 接口声明了一系列关于获取 Bean 的方法:

    package org.springframework.beans.factory;
    
    import org.springframework.beans.BeansException;
    import org.springframework.core.ResolvableType;
    import org.springframework.lang.Nullable;
    
    public interface BeanFactory {
    
        // Bean 自带的前缀
    	String FACTORY_BEAN_PREFIX = "&";
        // 1. 根据名称获取 Bean
    	Object getBean(String name) throws BeansException;
        // 2. 根据名称获取 Bean, 并转化为指定的对象
    	<T> T getBean(String name, Class<T> requiredType) throws BeansException;
        // 3. 根据名称获取 Bean, 并传入构造该Bean的参数,用于有参构造
    	Object getBean(String name, Object... args) throws BeansException;
        // 4. 根据字节码获取 Bean
    	<T> T getBean(Class<T> requiredType) throws BeansException;
        // 5. 根据字节码获取 Bean,并传入构造该Bean的参数,用于有参构造
    	<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
        // 6. 根据字节码获取指定 Bean 的提供者
    	<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
        // 7. 根据指定的解析类型获取 Bean 的提供者
    	<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
        // 8. 判断工厂是否包含 Bean
    	boolean containsBean(String name);
        // 9. 根据名称判断 Bean 是否为单例的对象
    	boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
        // 10. 根据名称判断 Bean 是否为原型对象
    	boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
        // 11. 根据名称判断 Bean 是否能被指定的可解析类型解析
    	boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
        // 12. 根据名称判断 Bean 是否被指定的字节码对应的类进行解析
    	boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
        // 13. 根据名称获取 Bean 的类型对应的字节码
    	@Nullable
    	Class<?> getType(String name) throws NoSuchBeanDefinitionException;
        // 14. 根据名称获取 Bean 的类型对应的字节码,并设置是否允许Bean初始化
    	@Nullable
    	Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;
        // 15. 获取 Bean的别名
    	String[] getAliases(String name);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    Spring 提供的 BeanFacory接口,具有多个抽象的实现类,可视为抽象工厂:
    在这里插入图片描述

    其中比较眼熟的就是 AbstractApplicatoinContext,我们获取 Spring 中容器里的 Bean时就是通过这个应用上下文的实现类,其定义为:

    public abstract class AbstractApplicationContext extends DefaultResourceLoader
    		implements ConfigurableApplicationContext {
    }
    
    • 1
    • 2
    • 3

    传统方式获取 Spring 容器中的 Bean:

    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    User user = (User) context.getBean(User.class);
    
    • 1
    • 2

    其中 ClassPathXmlApplicationContext 类就是之前 AbstractApplicationContext 的实现子类,不过并不是直接的实现子类
    在这里插入图片描述

    而 ApplicationContext 其实就是 BeanFactory 的子接口

    在这里插入图片描述

    所以之前获取 Bean 的方法getBean(User.class),本质上就是调用了 BeanFactory 接口声明的方法。

    User user = (User) context.getBean(User.class);
    
    • 1

    总结:

    Spring 底层定义了 BeanFactory 工厂接口,用于声明关于获取 Bean 的方法,该接口有若干个 AbastractBeanFactory 抽象工厂的子类,包括AbstractApplicationContext 子类,这个子类实现了 ApplicationContext接口, 而后者这个接口就是 BeanFactory 的子接口,即 AbstractApplicationContext间接实现了 BeanFactory 接口。

    以上的设计模式是标准的抽象工厂方法的设计模式。

    2.3 建造者模式在源码中的应用 StringBuilder + Mybatis

    2.3.1 StringBuilder 建造者模式

    java.lang.StringBuilder是 JDK 源码提供的支持 String 类型一系列操作的类

    在创建 StringBuilder() 对象时,可以使用 append 方法添加字符串内容,也可以做创建后调用 append() ,例如:

    String s = new StringBuilder()
        .append("hello, ")
        .append("Wolrd!")
        .toString();
    
    • 1
    • 2
    • 3
    • 4

    toString() 可视为建造者的 build () 方法
    StringBuilder 拓展: 部分源码

    public final class StringBuilder
        extends AbstractStringBuilder
        implements Serializable, CharSequence
    {
        @Override
        public StringBuilder append(Object obj) {
            return append(String.valueOf(obj));
        }
        ...
        @Override
        public String toString() {
            // 创建拷贝对象, 不分享内部的字符数组
            return new String(value, 0, count);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    StringBuilder 内部类存储字符串的变量为 char[] value
    这个字符串数组是存在其父类 AbstactStringBuilder中的,在 StringBuilder 的无参构造中,初始化该字符数组的长度为 16

    public StringBuilder() {
        super(16);
    }
    
    • 1
    • 2
    • 3

    2.3.2 StringBuilder 扩容机制

    SpringBuilder 是有扩容机制的,初始化的字符长度为16,当长度超过这个值时,则会调用 ensureCapacityInternal() 方法来进行扩容,这个方法是在其抽象父类 AbstractStringBuilder中实现的:

    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                                  newCapacity(minimumCapacity));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    接下来可以通过调试观察其扩容过程:
    测试代码:

    String s = new StringBuilder()
        .append("1234567890")
        .append("1234567")
        .toString();
    
    • 1
    • 2
    • 3
    • 4

    调试过程:

    在这里插入图片描述

    2.3.2 MyBatis 建造者模式

    Mybatis 提供了 SqlSessionFactoryBuilder 类用于创建 SqlSessionFactory,即创建 Sql 会话工厂对象。这里是建造者模式和工厂模式的综合使用

    public class SqlSessionFactoryBuilder {
        ...
        public SqlSessionFactory build(Configuration config) {
            return new DefaultSqlSessionFactory(config);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    build () 方法根据 Configuration 配置对象创建一个默认的工厂对象。

    在 SqlSessionFactoryBuilder 类中有几个重载的 build 方法,其中主要的实现为:

    public class SqlSessionFactoryBuilder {
        
        public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
            try {
                // 创建建造者 XMLConfigBuilder的 实例
                XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
                // XMLConfigBuilder 的 parse() 创建 Configuration 实例
                return build(parser.parse());
            } catch (Exception e) {
                // 捕获异常
                throw ExceptionFactory.wrapException("Error building SqlSession.", e);
            } finally {
                // 重置错误
                ErrorContext.instance().reset();
                try {
                    // 释放资源
                    inputStream.close();
                } catch (IOException e) {
                    // IO 错误不进行处理
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    这里的 XMLConfigurBuilder 是用于创建 Configuration 的建造者,可以视为建造者模式中的具体建造者。

    2.4 单例模式在源码中的应用

    2.4.1 Runtime

    java.lang.Runtime是从 JDK 1.0 开始出现的,用于启动应用程序的类
    【例1】使用 Runtime 打开系统的记事本

    Runtime.getRumtime().exec("notepad");
    
    • 1

    【例2】使用 Runtime 实现 10秒后 自动关机

    Runtime.getRuntime().exec("shut down -s -t 10")
    
    • 1

    在 Runtime 源码实现中,使用了 饿汉式的单例模式,部分源码如下:

    public class Runtime{
        private static Runtime currentRuntime = new Runtime();
        public static Runtime getRuntime(){
             return currentRuntime;   
        }
        private Rumtime(){}
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.4.2 Integer 基本类型的引用类型

    Java 提供了 boolean、byte、int 、float、double、char[] 等基本类型对应的引用类型,分别是 Boolean、Byte、Integer、Float、Double、String,本次只用了解 Integer

    1. 自动拆箱与自动装箱

    JDK 8 中 Integer 是 int 类型的引用类型,它们之间可以互相转换,这个过程称作自动装箱和自动拆箱,例:

    public class Demo{
         public static void main(String[] args) {
             Integer a = 10; 
             int b = a;
             System.out.println(a + "=" + b);
         }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Integer ->int 称为 自动拆箱,本质上调用的是 Integer 的 intValue() 方法

    public int intValue() {
        return value;
    }
    
    • 1
    • 2
    • 3

    int -> Integer 称为自动装箱,本质上调用的是 Integer 的 valueOf () 方法:

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2. Integer 内部缓存数据

    Integer 默认提供了 [ -128 , 127 ] 数据之间的 IntegerCache 缓存数组
    当我们定义的 Integer 型变量属于这个范围时,valueOf () 方法会直接返回 IntegerCache数组里的 Integer 对象

    3. IntegerCache 饿汉式

    IntegerCache是 Integer 的静态内部类

    package java.lang;
    public final class Integer extends Number implements Comparable<Integer> {
        ...
        private static class IntegerCache {
                static final int low = -128;
                static final int high;
                static final Integer cache[];
        
                static {
                    // high value may be configured by property
                    int h = 127;
                    String integerCacheHighPropValue =
                        sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
                    if (integerCacheHighPropValue != null) {
                        try {
                            int i = parseInt(integerCacheHighPropValue);
                            i = Math.max(i, 127);
                            // Maximum array size is Integer.MAX_VALUE
                            h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                        } catch( NumberFormatException nfe) {
                            // If the property cannot be parsed into an int, ignore it.
                        }
                    }
                    high = h;
        
                    cache = new Integer[(high - low) + 1];
                    int j = low;
                    for(int k = 0; k < cache.length; k++)
                        cache[k] = new Integer(j++);
        
                    // range [-128, 127] must be interned (JLS7 5.1.7)
                    assert IntegerCache.high >= 127;
                }
        
            private IntegerCache() {}
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    通过源码可以看出,IntegerCache 是单例模式中饿汉式的实现,其主要特点有:

    • 构造方法私有化,private IntegerCache() {}
    • 内部变量均以 static 修饰
    • 使用 static 代码块 初始化内部的 cache 数组

    2.4.3 Spring

    Spring 中,Bean 一般有两种定义方式,分别是 prototype(支持多例)和 singleton (单例)

    • prototype,对 bean 的每次请求都会创建一个新的实例
    • singleton,对 bean 的每次请求都会返回唯一的实例

    接下来,通过几个例子体会其中的单例模式
    【例1】在 SpringBoot 中,往 IOC 容器注册单例模式的 Bean

    @SpringBootApplication
    public class WebDemoApplication {
        @Bean
        @Scope("singleton")
        public String uuid(){
            return UUID.randomUUID().toString();
        }
        public static void main(String[] args) {
            ApplicationContext context = SpringApplication.run(WebDemoApplication.class, args);
            String id1 = context.getBean(String.class);
            String id2 = context.getBean(String.class);
            System.out.println("id1:" + id1);
            System.out.println("id2:" + id2);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    运行结果: 输出了相同的id
    id1:8b58cfd1-ba2c-4759-96d9-fbc44f8a66b5
    id2:8b58cfd1-ba2c-4759-96d9-fbc44f8a66b5

    【例2】SpringBoot 往 IOC 容器注册多例模式 的 Bean

    @Bean
    @Scope("prototype")
    public String uuid(){
        return UUID.randomUUID().toString();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Spring 注入 Bean 时默认是 单例模式

    2.5 原型模式在源码中的应用

    2.5.1 ArrayList

    java.util.ArrayList 中重写了 祖先类 Object 的 clone() 方法,但本质上还是浅克隆

    package java.util;
    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
       public Object clone() {
            try {
                // 1. 直接克隆整个列表
                ArrayList<?> v = (ArrayList<?>) super.clone();
                // 2. 调用 Ararys.copyof 克隆列表的每个元素
                v.elementData = Arrays.copyOf(elementData, size);
                // 3. 初始化modCount, 表示列表在结构上被修改的次数
                v.modCount = 0;
                return v;
            } catch (CloneNotSupportedException e) {
                // this shouldn't happen, since we are Cloneable
                throw new InternalError(e);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2.5.2 基于字节流处理的深克隆

    ArrayList 虽然重写了 clone方法,但是依然无法克隆列表里的引用类型,这里推荐使用下面的流处理方式来实现深克隆,使用这种方式要求列表里的元素都实现 Serializable 接口

    // 1. 创建字节数组输出流
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    // 2. 转化为对象输出流
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    // 3. 将深克隆的对象写入到对象输出流
    oos.writeObject(src);
    // 4. 将 字节数组输出流 读取为 字节数组输入流
    ByteArrayInputStream bai = new ByteArrayInputStream(bos.toByteArray());
    // 5. 转化为对象输入流
    ObjectInputStream ois = new ObjectInputStream(bai);
    // 6. 读取对象
    return (List) ois.readObject();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    三、总结


    工厂方法设计模式 logback
    抽象工厂模式 Spring
    建造者模式 StringBuilder、Mybatis
    单例模式 Runtime、Integer、Spring
    原型模式 Object.clone() ,

  • 相关阅读:
    ENVI手动地理配准栅格图像的方法
    Mac M1芯片Java开发环境搭建 · 排错处理
    nginx的基本使用
    Solon Java 应用开发框架 v2.7.5 发布
    Seata
    Win11如何开启移动热点?Win11开启移动热点的方法
    解决ffmpeg的播放摄像头的延时优化问题(项目案例使用有效)
    ES6~ES13新特性(一)
    【C++高阶】3.2 vector容器
    reactnative使用七牛云上传图片
  • 原文地址:https://blog.csdn.net/Unirithe/article/details/126060399