从框架源码中学习创建型设计模式
文章目录
- 从框架源码中解读创建型设计模式
- 工厂模式
- 案例一:RocketMQ源码-创建Producer生产者
- 案例二:RocketMQ源码-创建过滤器工厂
- 抽象工厂
- 案例一:Dubbo源码-创建缓存的抽象工厂
- 案例二:RocketMQ源码-创建日志对象的抽象工厂
- 单例模式
- 面试官:单例有几种写法?
- 案例一:dubbo源码-饿汉式
- 案例二:RocketMQ源码-懒汉式-非线程安全
- 案例三:双重检查锁
- 案例四:线程安全synchronized方法
- 案例五:枚举类
- 案例六:静态内部类
- 建造者模式
- 案例:dubbo源码使用
- 原型模式
- 案例:RocketMQ源码-拷贝数组对象
从框架源码中解读创建型设计模式
概念:创建型设计模式顾名思义是用来创建对象的同时隐藏创建逻辑的方式,而不是通过new关键字直接实例化对象,使得程序判别某个对象是否需要创建时更加灵活。
工厂模式
工厂模式是最常见的设计模式。通过工厂封装对象创建逻辑提供一个接口供调用者创建对象。
案例一:RocketMQ源码-创建Producer生产者
public class ProducerFactory {public static DefaultMQProducer getRMQProducer(String ns) {DefaultMQProducer producer = new DefaultMQProducer(RandomUtil.getStringByUUID());producer.setInstanceName(UUID.randomUUID().toString());producer.setNamesrvAddr(ns);try {producer.start();} catch (MQClientException e) {e.printStackTrace();}return producer;}
}
每调用一次都会获得新的对象实例。
案例二:RocketMQ源码-创建过滤器工厂
public class FilterFactory {public static final FilterFactory INSTANCE = new FilterFactory();protected static final Map<String, FilterSpi> FILTER_SPI_HOLDER = new HashMap<String, FilterSpi>(4);static {FilterFactory.INSTANCE.register(new SqlFilter());}public void register(FilterSpi filterSpi) {if (FILTER_SPI_HOLDER.containsKey(filterSpi.ofType())) {throw new IllegalArgumentException(String.format("Filter spi type(%s) already exist!", filterSpi.ofType()));}FILTER_SPI_HOLDER.put(filterSpi.ofType(), filterSpi);}public FilterSpi unRegister(String type) {return FILTER_SPI_HOLDER.remove(type);}public FilterSpi get(String type) {return FILTER_SPI_HOLDER.get(type);}}
没创建完将对象缓存到本地内存中,之后从内存中获取对象
抽象工厂
用来创建工厂的工厂。
案例一:Dubbo源码-创建缓存的抽象工厂
先定义一个创建工厂的接口(@SPI:表示这个接口是基于SPI拓展接口)。
@SPI("lru")
public interface CacheFactory {@Adaptive("cache")Cache getCache(URL url, Invocation invocation);
}
定义一个抽象工厂实现通用的getCache()方法,并将对象管理逻辑定义在抽象类中并提供一个createCache()方法让工厂的实现类不用关系内部对象管理逻辑只需要实现createCache()方法即可。
public abstract class AbstractCacheFactory implements CacheFactory {private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();@Overridepublic Cache getCache(URL url, Invocation invocation) {url = url.addParameter(METHOD_KEY, invocation.getMethodName());String key = url.toFullString();Cache cache = caches.get(key);if (cache == null) {caches.put(key, createCache(url));cache = caches.get(key);}return cache;}protected abstract Cache createCache(URL url);}
实现抽象工厂的工厂实现类,缓存有多种实现方式有LRU、LFU等等
public class LruCacheFactory extends AbstractCacheFactory {@Overrideprotected Cache createCache(URL url) {return new LruCache(url);}}
public class LfuCacheFactory extends AbstractCacheFactory {@Overrideprotected Cache createCache(URL url) {return new LfuCache(url);}}
过期时间缓存实现工厂
public class ExpiringCacheFactory extends AbstractCacheFactory {@Overrideprotected Cache createCache(URL url) {return new ExpiringCache(url);}
}
案例二:RocketMQ源码-创建日志对象的抽象工厂
public abstract class InternalLoggerFactory {public static final String LOGGER_SLF4J = "slf4j";public static final String LOGGER_INNER = "inner";public static final String DEFAULT_LOGGER = LOGGER_SLF4J;private static String loggerType = null;//缓存容器private static ConcurrentHashMap<String, InternalLoggerFactory> loggerFactoryCache = new ConcurrentHashMap<String, InternalLoggerFactory>();//通过类获取对象实例public static InternalLogger getLogger(Class clazz) {return getLogger(clazz.getName());}//通过日志类型获取内部日志对象实例public static InternalLogger getLogger(String name) {return getLoggerFactory().getLoggerInstance(name);}//获取内部工厂对象private static InternalLoggerFactory getLoggerFactory() {InternalLoggerFactory internalLoggerFactory = null;if (loggerType != null) {internalLoggerFactory = loggerFactoryCache.get(loggerType);}if (internalLoggerFactory == null) {internalLoggerFactory = loggerFactoryCache.get(DEFAULT_LOGGER);}if (internalLoggerFactory == null) {internalLoggerFactory = loggerFactoryCache.get(LOGGER_INNER);}if (internalLoggerFactory == null) {throw new RuntimeException("[RocketMQ] Logger init failed, please check logger");}return internalLoggerFactory;}//设置当前日志类型public static void setCurrentLoggerType(String type) {loggerType = type;}//程序启动默认注册Slf4j工厂和内部日志工厂到loggerFactoryCache容器中static {try {new Slf4jLoggerFactory();} catch (Throwable e) {//ignore}try {new InnerLoggerFactory();} catch (Throwable e) {//ignore}}//注册工厂逻辑protected void doRegister() {String loggerType = getLoggerType();if (loggerFactoryCache.get(loggerType) != null) {return;}loggerFactoryCache.put(loggerType, this);}protected abstract void shutdown();//获取内部日志对象protected abstract InternalLogger getLoggerInstance(String name);//获取日志类型protected abstract String getLoggerType();
}
通过抽象工厂实现Slf4jLoggerFactory工厂
public class Slf4jLoggerFactory extends InternalLoggerFactory {public Slf4jLoggerFactory() {LoggerFactory.getILoggerFactory();doRegister();}@Overrideprotected String getLoggerType() {return InternalLoggerFactory.LOGGER_SLF4J;}@Overrideprotected InternalLogger getLoggerInstance(String name) {return new Slf4jLogger(name);}@Overrideprotected void shutdown() {}public static class Slf4jLogger implements InternalLogger {private Logger logger = null;public Slf4jLogger(String name) {logger = LoggerFactory.getLogger(name);}@Overridepublic String getName() {return logger.getName();}@Overridepublic void debug(String s) {logger.debug(s);}@Overridepublic void info(String s) {logger.info(s);}@Overridepublic void warn(String s) {logger.warn(s);}@Overridepublic void warn(String s, Throwable throwable) {logger.warn(s, throwable);}@Overridepublic void error(String s) {logger.error(s);}@Overridepublic void error(String s, Throwable throwable) {logger.error(s, throwable);}}
}
单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。该类负责创建自己的对象,同时确保只有单个对象被创建,同时提供了一种访问其唯一对象的方式。
面试官:单例有几种写法?
- 饿汉式:类加载时就初始化,浪费内存
- 懒汉式-非线程安全:用到的时候才初始化
- 懒汉式-线程安全synchronized方法,每次都需要加载性能慢
- 懒汉式-双重检查锁,这种方式比第三种性能更高,但是每次都需要做判断,而且书写麻烦个人建议还不如用静态内部类方式
- 枚举:这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。推荐使用
- 静态内部类:在主类中提供一个静态内部类在初始化时候创建对象,主类提供获取单例对象方法,用到的时候初始化,通过ClassLoader机制保证只有一个线程创建实例。推荐使用
案例一:dubbo源码-饿汉式
public class ShutdownHookCallbacks {public static final ShutdownHookCallbacks INSTANCE = new ShutdownHookCallbacks();private final List<ShutdownHookCallback> callbacks = new LinkedList<>();ShutdownHookCallbacks() {loadCallbacks();}public ShutdownHookCallbacks addCallback(ShutdownHookCallback callback) {synchronized (this) {this.callbacks.add(callback);}return this;}public Collection<ShutdownHookCallback> getCallbacks() {synchronized (this) {sort(this.callbacks);return this.callbacks;}}public void clear() {synchronized (this) {callbacks.clear();}}private void loadCallbacks() {ExtensionLoader<ShutdownHookCallback> loader =ExtensionLoader.getExtensionLoader(ShutdownHookCallback.class);loader.getSupportedExtensionInstances().forEach(this::addCallback);}public void callback() {getCallbacks().forEach(callback -> execute(callback::callback));}
}
案例二:RocketMQ源码-懒汉式-非线程安全
public class MQClientManager {private final static InternalLogger log = ClientLogger.getLog();private static MQClientManager instance = new MQClientManager();private AtomicInteger factoryIndexGenerator = new AtomicInteger();private ConcurrentMap<String/* clientId */, MQClientInstance> factoryTable =new ConcurrentHashMap<String, MQClientInstance>();private MQClientManager() {}public static MQClientManager getInstance() {return instance;}public MQClientInstance getOrCreateMQClientInstance(final ClientConfig clientConfig) {return getOrCreateMQClientInstance(clientConfig, null);}//懒汉式public MQClientInstance getOrCreateMQClientInstance(final ClientConfig clientConfig, RPCHook rpcHook) {String clientId = clientConfig.buildMQClientId();MQClientInstance instance = this.factoryTable.get(clientId);if (null == instance) {instance =new MQClientInstance(clientConfig.cloneClientConfig(),this.factoryIndexGenerator.getAndIncrement(), clientId, rpcHook);MQClientInstance prev = this.factoryTable.putIfAbsent(clientId, instance);if (prev != null) {instance = prev;log.warn("Returned Previous MQClientInstance for clientId:[{}]", clientId);} else {log.info("Created new MQClientInstance for clientId:[{}]", clientId);}}return instance;}public void removeClientFactory(final String clientId) {this.factoryTable.remove(clientId);}
}
特别注意:使用这种方式获取单例非线程安全的,那RocketMQ这样使用不是有错呢?如果只是单纯这样使用肯定是有错的,但是上层调用加了synchronized就没有问题,如下
public class DefaultMQPullConsumerImpl implements MQConsumerInner {...public synchronized void start() throws MQClientException {switch (this.serviceState) {case CREATE_JUST:this.serviceState = ServiceState.START_FAILED;this.checkConfig();this.copySubscription();if (this.defaultMQPullConsumer.getMessageModel() == MessageModel.CLUSTERING) {this.defaultMQPullConsumer.changeInstanceNameToPID();}this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQPullConsumer, this.rpcHook);...
}
案例三:双重检查锁
public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; }
}
案例四:线程安全synchronized方法
public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }
}
案例五:枚举类
public enum Singleton {INSTANCE;public void doSomething() {System.out.println("doSomething");}}
案例六:静态内部类
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; }
}
建造者模式
使用多个简单的对象一步步构成复制对象。
案例:dubbo源码使用
package org.apache.dubbo.config.bootstrap.builders;import org.apache.dubbo.config.RegistryConfig;import java.util.Map;/*** This is a builder for build {@link RegistryConfig}.** @since 2.7*/
public class RegistryBuilder extends AbstractBuilder<RegistryConfig, RegistryBuilder> {/*** Register center address*/private String address;/*** Username to login register center*/private String username;/*** Password to login register center*/private String password;/*** Default port for register center*/private Integer port;/*** Protocol for register center*/private String protocol;/*** Network transmission type*/private String transporter;private String server;private String client;private String cluster;...public static RegistryBuilder newBuilder() {return new RegistryBuilder();}public RegistryBuilder id(String id) {return super.id(id);}public RegistryBuilder address(String address) {this.address = address;return getThis();}public RegistryBuilder username(String username) {this.username = username;return getThis();}public RegistryBuilder password(String password) {this.password = password;return getThis();}...public RegistryConfig build() {RegistryConfig registry = new RegistryConfig();super.build(registry);registry.setCheck(check);registry.setClient(client);registry.setCluster(cluster);registry.setDefault(isDefault);registry.setDynamic(dynamic);registry.setExtraKeys(extraKeys);registry.setFile(file);registry.setGroup(group);registry.setParameters(parameters);registry.setPassword(password);registry.setPort(port);registry.setProtocol(protocol);registry.setRegister(register);registry.setServer(server);registry.setSession(session);registry.setSimplified(simplified);registry.setSubscribe(subscribe);registry.setTimeout(timeout);registry.setTransporter(transporter);registry.setUsername(username);registry.setVersion(version);registry.setWait(wait);registry.setUseAsConfigCenter(useAsConfigCenter);registry.setUseAsMetadataCenter(useAsMetadataCenter);registry.setAccepts(accepts);registry.setPreferred(preferred);registry.setWeight(weight);registry.setAddress(address);return registry;}@Overrideprotected RegistryBuilder getThis() {return this;}
}
原型模式
用来拷贝对象,通过实现Cloneable接口中的clone()方法,使用时注意深拷贝、浅拷贝问题。
案例:RocketMQ源码-拷贝数组对象
package org.apache.rocketmq.filter.util;/*** Wrapper of bytes array, in order to operate single bit easily.*/
public class BitsArray implements Cloneable {private byte[] bytes;private int bitLength;public static BitsArray create(int bitLength) {return new BitsArray(bitLength);}private BitsArray(int bitLength) {this.bitLength = bitLength;// init bytesint temp = bitLength / Byte.SIZE;if (bitLength % Byte.SIZE > 0) {temp++;}bytes = new byte[temp];for (int i = 0; i < bytes.length; i++) {bytes[i] = (byte) 0x00;}}private BitsArray(byte[] bytes) {if (bytes == null || bytes.length < 1) {throw new IllegalArgumentException("Bytes is empty!");}this.bitLength = bytes.length * Byte.SIZE;this.bytes = new byte[bytes.length];System.arraycopy(bytes, 0, this.bytes, 0, this.bytes.length);}...public BitsArray clone() {byte[] clone = new byte[this.byteLength()];System.arraycopy(this.bytes, 0, clone, 0, this.byteLength());return create(clone, bitLength());}
}
从框架源码中学习创建型设计模式相关推荐
- 从框架源码中学习结构型设计模式
文章目录 从框架源码学习结构型设计模式 适配器模式 应用实例 案例一:dubbo框架日志适配器 Logger接口 日志实现类 Logger适配器接口 LoggerAdapter实现类 Logger日志 ...
- rust墙壁升级点什么_分享:如何在阅读Rust项目源码中学习
今天做了一个Substrate相关的小分享,公开出来. 因为我平时也比较忙,昨天才选定了本次分享的主题,准备比较仓促,细节可能不是很充足,但分享的目的也是给大家提供一个学习的思路,更多的细节大家可以在 ...
- 「从源码中学习」面试官都不知道的Vue题目答案
前言 当回答面试官问及的Vue问题,我们除了照本宣科的回答外,其实还可以根据少量的源码来秀一把,来体现出你对Vue的深度了解. 本文会陆续更新,此次涉及以下问题: "new Vue()做了什 ...
- MVVM架构~knockoutjs系列之从Knockout.Validation.js源码中学习它的用法
说在前 有时,我们在使用一个插件时,在网上即找不到它的相关API,这时,我们会很抓狂的,与其抓狂,还不如踏下心来,分析一下它的源码,事实上,对于JS这种开发语言来说,它开发的插件的使用方法都在它的源码 ...
- android源码使用方法,android源码中使用到的设计模式(创建型)
1.单例模式 1.1定义 确保某个类只有一个实例,而且自行实例化并向整个系统提供者个实例. 1.2单例的形式 饿汉模式:第一次就加载,用空间换时间. public class SingleTon { ...
- MyBatis学习总结(26)——Mybatis源码中使用了哪些设计模式?
分享一个大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到人工智能的队伍中来!点击浏览教程 虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开发中很少遇到,Myba ...
- 手把手带你撸一把springsecurity框架源码中的认证流程
提springsecurity之前,不得不说一下另外一个轻量级的安全框架Shiro,在springboot未出世之前,Shiro可谓是颇有统一J2EE的安全领域的趋势. 有关shiro的技术点 1.s ...
- 从shiro源码角度学习工厂方法设计模式
绪论 shiro是一个简单易用,功能强大的Java安全框架,学习其源码设计思想对我们的编码水平的提高大有裨益.现在,就从源码角度带大家学习一下shiro里面的工厂方法模式. 这里的前提是读者有过使用s ...
- glide源码中包含了那种设计模式_推荐一个好用的拍照选图库,致敬Glide
本文作者 作者:思忆_GeorgeQin 链接: https://juejin.cn/post/6907620425837051917 本文由作者授权发布. 1内容提要 本文内容较长,包含一个功能整个 ...
最新文章
- 独家 | 手把手教你用Python进行时间序列分解和预测
- Our Proof : Page Scraping : Website Data Extraction : Data Mining Analytics : Connotate.com
- unity3d Update()和FixedUpdate()的区别
- 为什么有些xpath绝对路径拿不到数据_Python爬虫,登陆神器Selenium之xpath的使用
- myeclipse无法启动tomcat的一种情况
- 谈谈怎样提高炼丹手速
- mysql 单向复制_Mysql 复制(Replication)实现
- 用 Lucene 构建文档数据库
- html5图片自动轮播纯代码,HTML5的自定义data-*实现图片切换或者轮播(代码实例)...
- 大话机器人之云端架构
- 参考文献格式字号字体_参考文献标准格式字体
- android图片添加文字,Android给图片加文字和图片水印
- UE4自定义资源和编辑器(二):创建自定义编辑器
- matlab排队系统仿真,MM1排队系统仿真matlab实验报告
- unity调用安卓手机物理返回键和home键
- Java HotSpot(TM) 64-Bit Server VM warning:Options -Xverify:none and -noverify were deprecated in ..
- redis介绍命令性能数据及监控缓存穿透
- android培训课程
- java早晚班值班表_节日值班表(安排好)_***121237
- DSP CCS 12.00 芯片:TMS320F28335 建立工程 ,使LED 灯闪烁
热门文章
- mysql 报错 Specified key was too long; max key length is 767 bytes,开启系统变量:innodb_large_prefix
- 谷歌离开游览器不触发_谷歌游览器
- C++为什么空格无法输出_OOP 面向对象编程:由 C 到 C++
- 搜狗手机浏览器_搜狗推广开户费多少钱?【搜狗开户代理商】
- scratch 素材_scratch书籍免费领
- python程序设计第一章答案_Python《学习手册:第一章-习题》
- 笨方法python_笨方法学习Python(11-20)
- python中try命令_Python 异常处理 Python 基础教程 try..except
- 经典Java编程面试题分析
- ebs 供应商地点信息_供应商,地址,业务实体,地点关联银行账户