предисловие
Шаблоны проектирования — это типичные решения общих проблем проектирования программного обеспечения, которые можно настроить для решения конкретных проблем проектирования в коде.
В Интернете есть много объяснений шаблонов проектирования. Но большинство из них являются демонстрационными примерами, после прочтения которых вы, возможно, так и не узнаете, как ими пользоваться.
В этой статье автор начнет с шаблонов проектирования и рассмотрит различные сценарии применения шаблонов проектирования в превосходных фреймворках и промежуточных продуктах Java.
1. Одноэлементный режим
Одноэлементный шаблон — один из самых простых шаблонов проектирования в Java, который обеспечивает оптимальный способ создания объектов. Этот шаблон включает в себя один класс, который отвечает за создание своих собственных объектов, обеспечивая при этом создание только одного объекта. Этот класс предоставляет способ прямого доступа к своему единственному объекту без создания экземпляра объекта класса.
Хотя паттерн singleton очень прост, в нем много хитростей, давайте рассмотрим их по порядку.
1. Голодный китаец
Стиль Hungry Han, как следует из названия, заключается в том, что я очень голоден и не могу ждать. Независимо от того, использует кто-то его или нет, я создам его первым.
Например, этот код в Dubbo создает менеджер конфигурации.
public class ConfigManager {
private static final ConfigManager configManager = new ConfigManager();
private ConfigManager() {}
public static ConfigManager getInstance() {
return configManager;
}
}
Или в RocketMQ при создании экземпляра клиента MQ.
public class MQClientManager {
private static MQClientManager instance = new MQClientManager();
private MQClientManager() {}
public static MQClientManager getInstance() {
return instance;
}
}
2. Ленивый человек
Стиль ленивого человека соответствует стилю голодного человека. Он предназначен для инициализации при первом вызове, чтобы избежать пустой траты памяти. Но для безопасности потоков и производительности обычно создаются блокировки с двойной проверкой.
Давайте посмотрим на фреймворк Seata и создадим таким образом класс конфигурации.
public class ConfigurationFactory{
private static volatile Configuration CONFIG_INSTANCE = null;
public static Configuration getInstance() {
if (CONFIG_INSTANCE == null) {
synchronized (Configuration.class) {
if (CONFIG_INSTANCE == null) {
CONFIG_INSTANCE = buildConfiguration();
}
}
}
return CONFIG_INSTANCE;
}
}
3. Статический внутренний класс
Как видите, создавая объект Singleton с двойной проверкой замок более сложный. Он снова заблокирован, и он судим дважды, и он должен быть изменен волатильным.
Использование статических внутренних классов позволяет добиться того же эффекта, что и блокировка с двойной проверкой, но реализация проще.
Во фреймворке Seata при создании обработчика событий RM для создания одноэлементного объекта используется статический внутренний класс.
public class DefaultRMHandler extends AbstractRMHandler{
protected DefaultRMHandler() {
initRMHandlers();
}
private static class SingletonHolder {
private static AbstractRMHandler INSTANCE = new DefaultRMHandler();
}
public static AbstractRMHandler get() {
return DefaultRMHandler.SingletonHolder.INSTANCE;
}
}
Существует также метод перечисления для создания объекта singleton, но этот метод не получил широкого распространения, по крайней мере, автор не видел его в распространенных фреймворках с открытым исходным кодом, поэтому я не буду его перечислять.
Некоторые люди говорят, что голодный режим HALLOCK не является хорошим, не может быть отложено, отработанная память. Но автор считает, что, кажется, слишком много, чтобы получить деликатес, на самом деле в самых открытых источниках, наиболее использование этого способа.
Если вы явно хотите добиться отложенной загрузки, вы можете рассмотреть возможность использования статических внутренних классов; если есть другие особые требования, такие как громоздкий процесс создания объектов, вы можете использовать блокировки с двойной проверкой.
Вторая, заводская модель
Фабрика шаблона является одним из наиболее часто используемых шаблонов дизайна в Java. Этот тип структуры дизайна представляет собой творческий шаблон, который обеспечивает оптимальный способ создания объектов.
Проще говоря, в шаблоне factory это шаблон для создания экземпляров конкретных классов вместо новых.
1. Простая фабрика
Простая фабрика действительно относительно проста, ее функция заключается в том, чтобы поместить создание объектов в класс фабрики и создавать различные объекты через параметры.
В среде распределенных транзакций Seata при возникновении исключения требуется двухэтапный откат.
Его процесс заключается в том, чтобы найти запись undoLog по идентификатору транзакции, затем проанализировать содержащиеся в ней данные для генерации SQL и отменить SQL, выполненный за один этап.
Проблема в том, что тип SQL содержит такие вещи, какINSERT、UPDATE、DELETE
, поэтому их процесс анти-парсинга отличается, поэтому для парсинга нужны разные исполнители.
В Seata есть абстрактный исполнитель отмены, который генерирует одиночный SQL.
public abstract class AbstractUndoExecutor{
//生成撤销SQL
protected abstract String buildUndoSQL();
}
Затем есть фабрика, которая получает исполнителя отмены и в соответствии с типом SQL создает различные типы исполнителей и возвращает их.
public class UndoExecutorFactory {
public static AbstractUndoExecutor getUndoExecutor(String dbType, SQLUndoLog sqlUndoLog) {
switch (sqlUndoLog.getSqlType()) {
case INSERT:
return new MySQLUndoInsertExecutor(sqlUndoLog);
case UPDATE:
return new MySQLUndoUpdateExecutor(sqlUndoLog);
case DELETE:
return new MySQLUndoDeleteExecutor(sqlUndoLog);
default:
throw new ShouldNeverHappenException();
}
}
}
При использовании исполнитель получается напрямую через фабричный класс.
AbstractUndoExecutor undoExecutor = UndoExecutorFactory.getUndoExecutor(dataSourceProxy.getDbType(),sqlUndoLog);
undoExecutor.executeOn(conn);
Преимущества простой заводской модели, думаю, всем понятны, повторяться не будем. Но есть у него и небольшой недостаток:
После появления нового класса реализации необходимо изменить реализацию фабрики, что может сделать логику фабрики слишком сложной, что не способствует расширению и обслуживанию системы.
2. Заводской метод
Шаблон фабричного метода решает указанную выше проблему. Он может создать фабричный интерфейс и несколько фабричных классов реализации, так что если вы добавите новые функции, вам нужно будет добавить только новый фабричный класс без изменения предыдущего кода.
Кроме того, шаблон фабричного метода также можно комбинировать с шаблоном метода шаблона, чтобы извлечь их общую базовую логику в родительский класс, а остальную часть реализовать дочернему классу.
В Dubbo есть дизайн про кеш, который отлично отражает паттерн фабричный метод + паттерн шаблонного метода.
Во-первых, есть интерфейс кеша, который предоставляет два метода установки кеша и получения кеша.
public interface Cache {
void put(Object key, Object value);
Object get(Object key);
}
Затем есть фабрика кеша, которая возвращает реализацию кеша.
public interface CacheFactory {
Cache getCache(URL url, Invocation invocation);
}
Из-за комбинации шаблона метода шаблона Dubbo создала абстрактный класс фабрики кеша, который реализует интерфейс фабрики кеша.
public abstract class AbstractCacheFactory implements CacheFactory {
//具体的缓存实现类
private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();
@Override
public Cache getCache(URL url, Invocation invocation) {
url = url.addParameter(Constants.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);
}
Здесь общая логика заключается в создании класса реализации кеша с помощью getCahce(), и подкласс должен решить, какой класс реализации кеша создать.
Таким образом, каждый подкласс представляет собой определенный класс фабрики кэшей, например:
ExpiringCacheFactory, JCacheFactory, LruCacheFactory, ThreadLocalCacheFactory.
У этих фабричных классов есть только один способ — создать конкретный класс реализации кеша.
public class ThreadLocalCacheFactory extends AbstractCacheFactory {
@Override
protected Cache createCache(URL url) {
return new ThreadLocalCache(url);
}
}
ThreadLocalCache здесь — это конкретный класс реализации кэша, например, он реализует функцию кэширования через ThreadLocal.
public class ThreadLocalCache implements Cache {
private final ThreadLocal<Map<Object, Object>> store;
public ThreadLocalCache(URL url) {
this.store = new ThreadLocal<Map<Object, Object>>() {
@Override
protected Map<Object, Object> initialValue() {
return new HashMap<Object, Object>();
}
};
}
@Override
public void put(Object key, Object value) {
store.get().put(key, value);
}
@Override
public Object get(Object key) {
return store.get().get(key);
}
}
Когда клиент его использует, объект кеша все равно получается через фабрику.
public static void main(String[] args) {
URL url = URL.valueOf("http://localhost:8080/cache=jacache&.cache.write.expire=1");
Invocation invocation = new RpcInvocation();
CacheFactory cacheFactory = new ThreadLocalCacheFactory();
Cache cache = cacheFactory.getCache(url, invocation);
cache.put("java","java");
System.out.println(cache.get("java"));
}
Есть два преимущества для этого.
Во-первых, если вы добавляете новую реализацию кеша, вам нужно только добавить новый класс фабрики кеша, и больше ничего менять не нужно.
Во-вторых, с помощью шаблона метода шаблона константная часть инкапсулируется, а переменная расширяется. Извлеките общий код для простоты обслуживания.
Кроме того, в Dubbo получение реестра также осуществляется фабричным методом.
3. Абстрактная фабрика
Абстрактный фабричный шаблон, который может создавать ряд связанных объектов без указания их конкретных классов.
Метод и модель растений абстрактный заводский режим, максимальная разница между ними состоит в том, что:
- Фабричный метод узор имеет только один абстрактный класс продукта, а бетонный заводский класс может создать только экземпляр бетонного класса продукта;
- Шаблон абстрактной фабрики имеет несколько абстрактных классов продуктов, а конкретные классы фабрик могут создавать экземпляры нескольких конкретных классов продуктов.
Мы берем пример с вышеуказанного кеша, чтобы продолжить.
Если теперь у нас есть программа доступа к данным, которая должна одновременно работать с кэшем и базой данных, для нее потребуется несколько абстрактных продуктов и несколько реализаций конкретных продуктов.
Классы продуктов, связанных с кэшем, уже доступны, и мы создадим реализации продуктов, связанных с базами данных.
Во-первых, есть интерфейс базы данных, который представляет собой абстрактный класс продукта.
public interface DataBase {
void insert(Object tableName, Object record);
Object select(Object tableName);
}
Затем мы создаем два класса бетонных продуктов MySQLDatabase и Oracledatabase.
public class MysqlDataBase implements DataBase{
Map<Object,Object> mysqlDb = new HashMap<>();
@Override
public void insert(Object tableName, Object record) {
mysqlDb.put(tableName,record);
}
@Override
public Object select(Object tableName) {
return mysqlDb.get(tableName);
}
}
public class OracleDataBase implements DataBase {
Map<Object,Object> oracleDb = new HashMap<>();
@Override
public void insert(Object tableName, Object record) {
oracleDb.put(tableName,record);
}
@Override
public Object select(Object tableName) {
return oracleDb.get(tableName);
}
}
Во-вторых, создайте абстрактный заводский класс, который может вернуть кэшированные объекты и объекты базы данных.
public interface DataAccessFactory {
Cache getCache(URL url);
DataBase getDb();
}
Наконец, существует определенный класс фабрик, который может произвольно комбинировать каждый конкретный продукт в соответствии с реальными потребностями.
Например, нам нужна реализация кэша на основе ThreadLocal и объекты базы данных на основе Mysql.
public class DataAccessFactory1 implements DataAccessFactory {
@Override
public Cache getCache(URL url) {
return new ThreadLocalCache(url);
}
@Override
public DataBase getDb() {
return new MysqlDataBase();
}
}
Если вам нужна реализация кэша на основе Lru и объекты базы данных на основе Oracle.
public class DataAccessFactory2 implements DataAccessFactory {
@Override
public Cache getCache(URL url) {
return new LruCache(url);
}
@Override
public DataBase getDb() {
return new OracleDataBase();
}
}
Как видите, шаблон абстрактной фабрики изолирует создание конкретных классов, поэтому клиентам не нужно знать, что было создано. Из-за этой изоляции становится относительно легко заменить конкретную фабрику, все конкретные фабрики реализуют те общедоступные интерфейсы, которые определены в абстрактной фабрике, поэтому простое изменение экземпляра конкретной фабрики может в некоторой степени изменить все программное обеспечение и поведение системы. .
В-третьих, режим метода шаблона
В шаблоне шаблона абстрактный класс предоставляет способ/шаблон, который определяет его методы для выполнения. Его подклассы могут при необходимости переопределить реализацию метода, но вызовы будут выполняться так, как определено в абстрактном классе.
Короче говоря, существуют методы, общие для нескольких подклассов с одинаковой логикой, которые можно рассматривать как методы-шаблоны.
В приведенном выше примере кэша Dubbo мы видели применение шаблона метода шаблона. Но это в сочетании с шаблоном фабричного метода, мы найдем применение шаблона метода шаблона отдельно.
Мы знаем, что когда в нашем приложении Dubbo есть несколько поставщиков услуг, потребителю услуг необходимо выбрать одну из услуг для вызова с помощью алгоритма балансировки нагрузки.
Во-первых, есть интерфейс LoadBalance, который возвращает Invoker.
public interface LoadBalance {
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}
Затем определите абстрактный класс AbstractLoadBalance, реализующий интерфейс LoadBalance.
public abstract class AbstractLoadBalance implements LoadBalance {
@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
if (invokers == null || invokers.isEmpty()) {
return null;
}
if (invokers.size() == 1) {
return invokers.get(0);
}
return doSelect(invokers, url, invocation);
}
//抽象方法,由子类选择一个Invoker
protected abstract <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation);
}
Вот два общих логических суждения: коллекция вызывающих элементов пуста или это только один пример. Если это так, нет необходимости вызывать подкласс напрямую.
Существует четыре конкретных реализации балансировки нагрузки:
- RandomLoadBalance на основе взвешенного случайного алгоритма
- LeastActiveLoadBalance на основе алгоритма наименее активных вызовов
- ConsistentHashLoadBalance на основе согласованности хэша
- RoundrobinLoadBalance на основе взвешенного алгоритма опроса
public class RandomLoadBalance extends AbstractLoadBalance {
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
//省略逻辑....
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
}
В зависимости от их алгоритма, чтобы вернуть конкретный объект Invoker.
В-четвертых, режим конструктора
Шаблон Constructor использует несколько простых объектов для пошагового построения сложного объекта. Этот тип шаблона проектирования является шаблоном создания, который обеспечивает оптимальный способ создания объектов.
Этот режим распространен при построении сложного объекта, который может содержать некоторую бизнес-логику, такую как проверка, преобразование атрибутов и т. д. Если все вручную установить на стороне клиента, будет сгенерировано много избыточного кода. Затем в это время мы можем рассмотреть возможность использования шаблона конструктора.
Например, в Mybatis процесс создания MappedStatement использует шаблон конструктора.
Мы знаем, что каждый тег SQL в файле XML будет генерировать объект MappedStatement, который содержит множество атрибутов, и объект, который мы хотим построить, также является им.
public final class MappedStatement {
private String resource;
private Configuration configuration;
private String id;
private SqlSource sqlSource;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
//.....省略大部分属性
}
Затем есть внутренний класс Builder, который отвечает за завершение построения объекта MappedStatement.
Во-первых, этот класс Builder, через конструктор по умолчанию, сначала завершите конфигурацию объекта MapPaptatement, часть карты.
public static class Builder {
private MappedStatement mappedStatement = new MappedStatement();
public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
mappedStatement.configuration = configuration;
mappedStatement.id = id;
mappedStatement.sqlSource = sqlSource;
mappedStatement.statementType = StatementType.PREPARED;
mappedStatement.resultSetType = ResultSetType.DEFAULT;
//.....省略大部分过程
}
}
Затем с помощью ряда методов вы можете установить определенные свойства и вернуть этот класс Builder, методы здесь подходят для обработки некоторой бизнес-логики.
public static class Builder {
public Builder parameterMap(ParameterMap parameterMap) {
mappedStatement.parameterMap = parameterMap;
return this;
}
public Builder resultMaps(List<ResultMap> resultMaps) {
mappedStatement.resultMaps = resultMaps;
for (ResultMap resultMap : resultMaps) {
mappedStatement.hasNestedResultMaps = mappedStatement.hasNestedResultMaps || resultMap.hasNestedResultMaps();
}
return this;
}
public Builder statementType(StatementType statementType) {
mappedStatement.statementType = statementType;
return this;
}
public Builder resultSetType(ResultSetType resultSetType) {
mappedStatement.resultSetType = resultSetType == null ? ResultSetType.DEFAULT : resultSetType;
return this;
}
}
Наконец, просто предоставьте метод сборки и верните сконструированный объект.
public MappedStatement build() {
assert mappedStatement.configuration != null;
assert mappedStatement.id != null;
assert mappedStatement.sqlSource != null;
assert mappedStatement.lang != null;
mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);
return mappedStatement;
}
При использовании его на стороне клиента сначала создайте Builder, затем вызовите кучу методов в цепочке и, наконец, снова вызовите метод build(), и нужный нам объект уже есть — это применение шаблона конструктора.
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
5. Режим адаптера
Шаблон адаптера действует как мост между двумя несовместимыми интерфейсами. Этот тип шаблона проектирования представляет собой структурный шаблон, который сочетает в себе функциональность двух отдельных интерфейсов.
Шаблон адаптера обычно используется для защиты взаимодействия между бизнес-логикой и сторонними службами или различия между новым и старым интерфейсами.
Мы знаем, что в Dubbo все данные передаются через Netty, и тут возникает проблема кодирования и декодирования сообщений.
Итак, в первую очередь у него есть интерфейс кодека, который отвечает за кодирование и декодирование.
@SPI
public interface Codec2 {
@Adaptive({Constants.CODEC_KEY})
void encode(Channel channel, ChannelBuffer buffer, Object message) throws IOException;
@Adaptive({Constants.CODEC_KEY})
Object decode(Channel channel, ChannelBuffer buffer) throws IOException;
enum DecodeResult {
NEED_MORE_INPUT, SKIP_SOME_INPUT
}
}
Затем есть несколько классов реализации, таких как DubboCountCodec, DubboCodec, ExchangeCodec и т. д.
Это связано с тем, что кодек Netty должен реализовать интерфейс ChannelHandler, чтобы он был объявлен как обрабатывающий компонент Netty. Например, как MessageToByteEncoder, ByteToMessageDecoder.
Ввиду этого Dubbo сделал адаптер для адаптации к интерфейсу кодека.
final public class NettyCodecAdapter {
private final ChannelHandler encoder = new InternalEncoder();
private final ChannelHandler decoder = new InternalDecoder();
private final Codec2 codec;
private final URL url;
private final org.apache.dubbo.remoting.ChannelHandler handler;
public NettyCodecAdapter(Codec2 codec, URL url, org.apache.dubbo.remoting.ChannelHandler handler) {
this.codec = codec;
this.url = url;
this.handler = handler;
}
public ChannelHandler getEncoder() {
return encoder;
}
public ChannelHandler getDecoder() {
return decoder;
}
private class InternalEncoder extends MessageToByteEncoder {
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
org.apache.dubbo.remoting.buffer.ChannelBuffer buffer = new NettyBackedChannelBuffer(out);
Channel ch = ctx.channel();
NettyChannel channel = NettyChannel.getOrAddChannel(ch, url, handler);
codec.encode(channel, buffer, msg);
}
}
private class InternalDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf input, List<Object> out) throws Exception {
ChannelBuffer message = new NettyBackedChannelBuffer(input);
NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);
//解码对象
codec.decode(channel, message);
//省略部分代码...
}
}
}
В приведенном выше коде мы видим, что класс NettyCodecAdapter адаптируется к интерфейсу Codec2, передает класс реализации через конструктор, а затем определяет реализацию внутреннего кодировщика и реализацию декодера, и оба они являются ChannelHandlers.
В этом случае логика кодирования и декодирования во внутреннем классе действительно вызывает интерфейс Codec2.
Наконец, давайте посмотрим, как называется адаптер.
//通过SPI方式获取编解码器的实现类,比如这里是DubboCountCodec
Codec2 codec = ExtensionLoader.getExtensionLoader(Codec2.class).getExtension("dubbo");
URL url = new URL("dubbo", "localhost", 22226);
//创建适配器
NettyCodecAdapter adapter = new NettyCodecAdapter(codec, url, NettyClient.this);
//向ChannelPipeline中添加编解码处理器
ch.pipeline()
.addLast("decoder", adapter.getDecoder())
.addLast("encoder", adapter.getEncoder())
Выше показано применение кодека в режиме адаптера в Dubbo.
6. Модель цепочки ответственности
Режим ответственной цепочки создает цепочку объекта-получателя для запроса. Позволяет отправлять запросы по цепочке процессоров. После получения запроса каждый обработчик может обработать запрос или передать его следующему процессору в цепочке.
Давайте посмотрим на пример в Netty. Мы знаем, что сервер обрабатывается в Netty, вам нужно добавить один или несколько ChannelHandler. Затем перенос этих CHANNELHANDALER является CHANNELPIPELINE, что отражает применение режима ответственной цепи.
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel channel) {
channel.pipeline()
.addLast(new ChannelHandler1())
.addLast(new ChannelHandler2())
.addLast(new ChannelHandler3());
}
});
Что вам нужно знать, так это то, что во всей структуре Netty соединение соответствует каналу, и каждому вновь созданному каналу будет назначен новый ChannelPipeline.
ChannelPipeline хранит объект ChannelHandLercontext, который является объектом контекста, связанного с каналом, который содержит обработчик ChannelHandler, который мы определены.
В зависимости от происхождения события событием ввода-вывода будет процесс ChannelInboundHandler или ChannelOutboundHandler. Впоследствии, вызывая ChannelHandlerContext, он будет переадресован следующему ChannelHandler того же супертипа.
1. Обработчик канала
Во-первых, мы смотрим на ответственность интерфейса процессора, Netty ChannelHandler, он действует как логика приложения всех входящих и исходящих контейнеров данных.
public interface ChannelHandler {
//当把 ChannelHandler 添加到 ChannelPipeline 中时被调用
void handlerAdded(ChannelHandlerContext ctx) throws Exception;
//当从 ChannelPipeline 中移除 ChannelHandler 时被调用
void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
//当处理过程中在 ChannelPipeline 中有错误产生时被调用
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}
Затем Netty определяет следующие два важных субинтерфейса ChannelHandler:
- ChannelInboundHandler, который обрабатывает входящие данные и различные изменения состояния;
public interface ChannelInboundHandler extends ChannelHandler {
//当 Channel 已经注册到它的 EventLoop 并且能够处理 I/O 时被调用
void channelRegistered(ChannelHandlerContext ctx) throws Exception;
//当 Channel 从它的 EventLoop 注销并且无法处理任何 I/O 时被调用
void channelUnregistered(ChannelHandlerContext ctx) throws Exception;
//当 Channel 处于活动状态时被调用;Channel 已经连接/绑定并且已经就绪
void channelActive(ChannelHandlerContext ctx) throws Exception;
//当 Channel 离开活动状态并且不再连接它的远程节点时被调用
void channelInactive(ChannelHandlerContext ctx) throws Exception;
当从 Channel 读取数据时被调用
void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;
//当 Channel上的一个读操作完成时被调用
void channelReadComplete(ChannelHandlerContext ctx) throws Exception;
void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;
void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}
- ChannelOutboundHandler, который обрабатывает исходящие данные и позволяет перехватывать все операции;
public interface ChannelOutboundHandler extends ChannelHandler {
//当请求将 Channel 绑定到本地地址时被调用
void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;
//当请求将 Channel 连接到远程节点时被调用
void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,SocketAddress localAddress,
ChannelPromise promise) throws Exception;
//当请求将 Channel 从远程节点断开时被调用
void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
//当请求关闭 Channel 时被调用
void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
//当请求将 Channel 从它的 EventLoop 注销时被调用
void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
//当请求从 Channel 读取更多的数据时被调用
void read(ChannelHandlerContext ctx) throws Exception;
//当请求通过 Channel 将数据写到远程节点时被调用
void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;
//当请求通过 Channel 将入队数据冲刷到远程节点时被调用
void flush(ChannelHandlerContext ctx) throws Exception;
}
2. Трубопровод канала
Сейчас называется цепочкой ответственности образец, ему нужно было бы иметь «цепь» в Netty является ChannelPipeline.
Цепь ChannelPipeline ChannelHandler обеспечивает контейнер и определяет входящий и способы распространения цепочки в исходящем потоке событий, он также несменная ответственность за удаление функции интерфейса процессора.
public interface ChannelPipeline{
ChannelPipeline addFirst(String name, ChannelHandler handler);
ChannelPipeline addLast(String name, ChannelHandler handler);
ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler);
ChannelPipeline addAfter(String baseName, String name, ChannelHandler handler);
ChannelPipeline remove(ChannelHandler handler);
ChannelHandler replace(String oldName, String newName, ChannelHandler newHandler);
@Override
ChannelPipeline fireChannelRegistered();
@Override
ChannelPipeline fireChannelActive();
@Override
ChannelPipeline fireExceptionCaught(Throwable cause);
@Override
ChannelPipeline fireUserEventTriggered(Object event);
@Override
ChannelPipeline fireChannelRead(Object msg);
@Override
ChannelPipeline flush();
//省略部分方法.....
}
Затем мы смотрим на его реализацию с двумя узлами, головными узлами и хвостовыми точками по умолчанию. А в конструкторе они хвостатые. Это стандартная структура цепи.
public class DefaultChannelPipeline implements ChannelPipeline {
final AbstractChannelHandlerContext head;
final AbstractChannelHandlerContext tail;
private final Channel channel;
protected DefaultChannelPipeline(Channel channel) {
this.channel = ObjectUtil.checkNotNull(channel, "channel");
tail = new TailContext(this);
head = new HeadContext(this);
head.next = tail;
tail.prev = head;
}
}
При добавлении нового ChannelHandler он инкапсулируется как объект ChannelHandlerContext, а затем вставляется в связанный список.
private void addLast0(AbstractChannelHandlerContext newCtx) {
AbstractChannelHandlerContext prev = tail.prev;
newCtx.prev = prev;
newCtx.next = tail;
prev.next = newCtx;
tail.prev = newCtx;
}
3. Контекст обработчика канала
ChannelHandlerContext представляет связь между ChannelHandler и ChannelPipeline.Каждый раз, когда ChannelHandler добавляется в ChannelPipeline, создается ChannelHandlerContext.
Основная функция ChannelHandlerContext заключается в управлении взаимодействием между связанным с ним ChannelHandler и другими ChannelHandler в том же ChannelPipeline.
public interface ChannelHandlerContext{
Channel channel();
EventExecutor executor();
ChannelHandler handler();
ChannelPipeline pipeline();
@Override
ChannelHandlerContext fireChannelRegistered();
@Override
ChannelHandlerContext fireChannelUnregistered();
@Override
ChannelHandlerContext fireChannelActive();
@Override
ChannelHandlerContext fireChannelRead(Object msg);
@Override
ChannelHandlerContext read();
@Override
ChannelHandlerContext flush();
//省略部分方法...
}
ChannelHandlerContext отвечает за распространение событий ответственного интерфейса обработчика в цепочке.
Он имеет два важных метода, ищущих обработчики входящего типа и исходящего типа.
Стоит отметить, что если запускается входящее событие, оно будет распространяться от начала ChannelPipeline до конца ChannelPipeline; исходящее событие начнется в крайней правой части ChannelPipeline и будет распространяться влево.
abstract class AbstractChannelHandlerContext implements ChannelHandlerContext, ResourceLeakHint {
volatile AbstractChannelHandlerContext next;
volatile AbstractChannelHandlerContext prev;
//查找下一个Inbound类型的处理器,左 > 右
private AbstractChannelHandlerContext findContextInbound(int mask) {
AbstractChannelHandlerContext ctx = this;
EventExecutor currentExecutor = executor();
do {
ctx = ctx.next;
} while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_INBOUND));
return ctx;
}
//查找下一个Outbound类型的处理器,右 > 左
private AbstractChannelHandlerContext findContextOutbound(int mask) {
AbstractChannelHandlerContext ctx = this;
EventExecutor currentExecutor = executor();
do {
ctx = ctx.prev;
} while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_OUTBOUND));
return ctx;
}
}
4. Процесс обработки
Когда мы отправляем сообщение на сервер, срабатывает метод чтения.
public abstract class AbstractNioByteChannel extends AbstractNioChannel {
public final void read() {
//从Channel中获取对应的ChannelPipeline
final ChannelPipeline pipeline = pipeline();
//数据载体
ByteBuf byteBuf = allocHandle.allocate(allocator);
//传递数据
pipeline.fireChannelRead(byteBuf);
}
}
В приведенном выше коде будет вызываться ChannelPipeline, который будет начинаться с узла Head и вызывать процессор по очереди в соответствии с объектом контекста.
public class DefaultChannelPipeline implements ChannelPipeline {
public final ChannelPipeline fireChannelRead(Object msg) {
AbstractChannelHandlerContext.invokeChannelRead(head, msg);
return this;
}
}
Поскольку первый узел является головным узлом HeadContext по умолчанию, он начинается с ChannelHandlerContext.
abstract class AbstractChannelHandlerContext implements ChannelHandlerContext, ResourceLeakHint {
//找到下一个ChannelHandler并执行
public ChannelHandlerContext fireChannelRead(final Object msg) {
invokeChannelRead(findContextInbound(MASK_CHANNEL_READ), msg);
return this;
}
}
Затем в нашем пользовательском ChannelHandler он будет вызван.
public class ChannelHandler1 extends ChannelInboundHandlerAdapter {
public void channelRead(ChannelHandlerContext ctx, Object msg){
System.out.println("ChannelHandler1:"+msg);
ctx.fireChannelRead(msg);
}
}
Если сообщение имеет несколько ChannelHandler, вы можете свободно выбирать, следует ли продолжить запрос.
Например, если вы думаете, что сообщение было обработано и не должно продолжать обращаться к вышеуказанному CTX.FireChannelread (MSG); прокомментировал прекращение всей цепочки ответственности.
Семь, режим стратегии
Этот шаблон определяет серию алгоритмов и инкапсулирует каждый алгоритм, чтобы их можно было заменить друг другом, а изменения в алгоритме не повлияют на клиентов, использующих алгоритм.
Шаблон стратегии является очень распространенным и очень полезным шаблоном проектирования.Если в вашем бизнес-коде много if...else, вы можете подумать, можете ли вы использовать шаблон стратегии для его преобразования.
Мы все знакомы с RocketMQ, отличным промежуточным программным обеспечением для распределенных сообщений. Промежуточное ПО для сообщений, говоря простым языком, заключается в том, что клиент отправляет сообщение, а сервер сохраняет его и предоставляет потребителям для потребления.
Существуют различные типы сообщений-запросов, и процедуры обработки, безусловно, отличаются, каждый раз оценивать и обрабатывать их неполноценно. В RocketMQ он зарегистрирует все процессоры, а затем позволит соответствующему процессору обработать запрос в соответствии с кодом сообщения запроса, который является применением шаблона стратегии.
Во-первых, они должны реализовать тот же интерфейс, в этом случае интерфейс обработчика запроса.
public interface NettyRequestProcessor {
RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request)throws Exception;
boolean rejectRequest();
}
Этот интерфейс делает только одну вещь — обрабатывает запросы от клиентов. Различные типы запросов инкапсулируются в разные объекты RemotingCommand.
RocketMQ имеет около 90 типов запросов, все они различаются кодом в RequestCode.
Затем определите серию классов политик. Давайте посмотрим на несколько.
//默认的消息处理器
public class DefaultRequestProcessor implements NettyRequestProcessor {}
//发送消息的处理器
public class SendMessageProcessor extends AbstractSendMessageProcessor implements NettyRequestProcessor {}
//拉取消息的处理器
public class PullMessageProcessor implements NettyRequestProcessor {}
//查询消息的处理器
public class QueryMessageProcessor implements NettyRequestProcessor {}
//消费者端管理的处理器
public class ConsumerManageProcessor implements NettyRequestProcessor {}
Затем инкапсулируйте эти классы стратегий. В RocketMQ эти обработчики регистрируются при запуске сервера Broker.
public class BrokerController {
public void registerProcessor() {
SendMessageProcessor sendProcessor = new SendMessageProcessor(this);
this.remotingServer.registerProcessor(RequestCode.SEND_MESSAGE, sendProcessor, this.sendMessageExecutor);
this.remotingServer.registerProcessor(RequestCode.SEND_MESSAGE_V2, sendProcessor, this.sendMessageExecutor);
this.remotingServer.registerProcessor(RequestCode.PULL_MESSAGE,this.pullMessageProcessor,this.pullMessageExecutor);
this.remotingServer.registerProcessor(RequestCode.SEND_REPLY_MESSAGE, replyMessageProcessor, replyMessageExecutor);
this.remotingServer.registerProcessor(RequestCode.SEND_REPLY_MESSAGE_V2, replyMessageProcessor, replyMessageExecutor);
NettyRequestProcessor queryProcessor = new QueryMessageProcessor(this);
this.remotingServer.registerProcessor(RequestCode.QUERY_MESSAGE, queryProcessor, this.queryMessageExecutor);
this.remotingServer.registerProcessor(RequestCode.VIEW_MESSAGE_BY_ID, queryProcessor, this.queryMessageExecutor);
ClientManageProcessor clientProcessor = new ClientManageProcessor(this);
this.remotingServer.registerProcessor(RequestCode.HEART_BEAT, clientProcessor, this.heartbeatExecutor);
this.remotingServer.registerProcessor(RequestCode.UNREGISTER_CLIENT, clientProcessor, this.clientManageExecutor);
//省略部分注册过程.....
}
}
Наконец, после того как Netty получит запрос клиента, она найдет соответствующий класс политики в соответствии с типом сообщения для обработки сообщения.
public abstract class NettyRemotingAbstract {
public void processRequestCommand(final ChannelHandlerContext ctx, final RemotingCommand cmd) {
//根据请求类型找到对应的策略类
final Pair<NettyRequestProcessor, ExecutorService> matched = this.processorTable.get(cmd.getCode());
//如果没有找到就使用默认的
final Pair<NettyRequestProcessor, ExecutorService> pair =
null == matched ? this.defaultRequestProcessor : matched;
//执行策略
final RemotingCommand response = pair.getObject1().processRequest(ctx, cmd);
//省略大部分代码......
}
}
Если есть новый тип сообщения запроса, RocketMQ не нужно изменять бизнес-код, просто добавьте новый класс политики и зарегистрируйте его.
Восемь, прокси-режим
Режим прокси, чтобы предоставить прокси для управления доступом к объекту для других объектов.
В некоторых платформах с открытым исходным кодом или продуктах промежуточного программного обеспечения агентская модель будет очень распространена. Чем проще мы его используем, тем более сложной может оказаться структура, которая поможет нам в этом. В модели прокси-приложений часто отражается вкус прививки.
1. Даббо
Как инфраструктура RPC, Dubbo выполняет очень важную функцию:
Предоставляет высокопроизводительные возможности удаленного вызова на основе прокси-сервера.Служба использует интерфейс в качестве элемента детализации и скрывает основные детали удаленного вызова для разработчиков.
Здесь мы сосредоточимся на двух ключевых моментах:
- Ориентированный интерфейсный агент;
- Маскируйте основные детали звонка.
Например, у нас есть служба инвентаризации, которая предоставляет интерфейс для вычета инвентаря.
public interface StorageDubboService {
int decreaseStorage(StorageDTO storage);
}
В других сервисах, когда необходимо вычесть запасы, этот интерфейс будет использоваться через Dubbo, что относительно просто.
@Reference
StorageDubboService storageDubboService;
Для нас это очень просто использовать, но StorageDubboService — это просто обычный сервисный класс, который не имеет возможности удаленного вызова.
Dubbo создает прокси-классы для этих классов обслуживания. Создайте и верните прокси-объект через ReferenceBean.
public class ReferenceBean<T>{
@Override
public Object getObject() {
return get();
}
public synchronized T get() {
if (ref == null) {
init();
}
return ref;
}
}
Когда мы его используем, прокси-объект фактически вызывается, и прокси-объект выполняет сложные удаленные вызовы. Такие как подключение к реестру, балансировка нагрузки, отказоустойчивость кластера, подключение к серверу для отправки сообщений и другие функции.
2, Мибатис
Еще одно типичное применение — мы часто используем Mybatis. Когда мы используем, как правило, работаем только с интерфейсом Mapper, а затем Mybatis найдет соответствующий оператор SQL для выполнения.
public interface UserMapper {
List<User> getUserList();
}
Как и в приведенном выше коде, UserMapper — это обычный интерфейс, но как он наконец выполняет наш оператор SQL?
Ответ также является прокси. Когда Mybatis сканирует определенный нами интерфейс Mapper, он установит его в MapperFactoryBean, создаст и вернет прокси-объект.
protected T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
Прокси-объект находит объект MappedStatement по запрошенному имени метода, вызывает исполняющую программу, анализирует объект SqlSource для генерации SQL, выполняет и анализирует возвращенный результат и т. д.
Конкретный процесс реализации вышеуказанных случаев не будет подробно обсужден здесь. Если вы заинтересованы, вы можете прочитать другие статьи автор ~
Девять, режим декоратора
Паттерн декоратор — это паттерн, динамически добавляющий некоторые обязанности объекту (т. е. добавляющий его дополнительную функциональность) без изменения существующей структуры объекта, он относится к паттерну структуры объекта.
Дизайн кэша в Mybatis является типичным применением шаблона декоратора.
Прежде всего, мы знаем, что исполнитель MyBatis является ядром планирования MyBatis, который отвечает за генерацию и выполнение операторов SQL.
Этот исполнитель создается при создании SqlSession.Исполнитель по умолчанию — SimpleExecutor.
Однако для того, чтобы увеличить ответственность привода, он становится CachingeExecutor, чтобы добавить CachingeExuecuteExure на Simeexecutor.
Фактическая операция в CachingeExecutor делегирована для SimpleExecutor для выполнения, просто добавляя кэшированную операцию до и после выполнения.
Во-первых, давайте посмотрим на процесс его украшения.
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//默认的执行器
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
//使用缓存执行器来装饰
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
Когда SqlSession выполняет метод, он сначала вызывает CachingExecutor, давайте посмотрим на метод запроса.
public class CachingExecutor implements Executor {
@Override
public <E> List<E> query()throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
Код здесь, если кеш включен, сначала получит результат из кеша. Если кеш не включен или в кеше нет результата, вызывается SimpleExecutor для запроса к базе данных.
10. Режим наблюдателя
Шаблон наблюдателя определяет зависимость между объектами «один ко многим».При изменении состояния объекта все зависящие от него объекты уведомляются и автоматически обновляются.
В проектах Spring или SpringBoot иногда нам нужно выполнить некоторую инициализацию системы после запуска и загрузки контейнера Spring. В настоящее время мы можем настроить наблюдатель ApplicationListener для достижения этой цели. Это практика шаблона Observer.
@Component
public class ApplicationStartup implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("干一些系统初始化的事情....");
ApplicationContext context = event.getApplicationContext();
String[] names = context.getBeanDefinitionNames();
for (String beanName:names){
System.out.println("----------"+beanName+"---------");
}
}
}
Прежде всего, мы знаем, что ApplicationContext является основным контейнером в Spring.
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
//观察者容器
private final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();
//被观察者
private ApplicationEventMulticaster applicationEventMulticaster;
}
При обновлении контейнера ApplicationContext наблюдатель будет инициализирован и зарегистрирован в контейнере Spring.
Затем зарегистрируйте различных наблюдателей для наблюдаемых, сформировав зависимость «один ко многим».
public abstract class AbstractApplicationContext{
protected void registerListeners() {
for (ApplicationListener<?> listener : getApplicationListeners()) {
getApplicationEventMulticaster().addApplicationListener(listener);
}
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (String listenerBeanName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
}
Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
this.earlyApplicationEvents = null;
if (earlyEventsToProcess != null) {
for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
}
}
}
В это время наш пользовательский объект-наблюдатель также зарегистрирован в applicationEventMulticaster.
Наконец, когда ApplicationContext обновляется, публикуется событие ContextRefreshedEvent.
protected void finishRefresh() {
publishEvent(new ContextRefreshedEvent(this));
}
Чтобы уведомить наблюдателя, вызовите ApplicationListener.onApplicationEvent().
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
listener.onApplicationEvent(event);
}
Далее давайте посмотрим, как этот механизм применяется в Dubbo.
Процесс экспорта сервиса Dubbo начинается с того, что контейнер Spring публикует событие обновления. Dubbo немедленно выполнит логику экспорта сервиса после получения события.
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware,
ApplicationEventPublisherAware {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
}
Мы видим, что ServiceBean в Dubbo также реализует интерфейс ApplicationListener, и метод экспорта будет выполнен после того, как контейнер Spring опубликует событие обновления. Акцентируем внимание на том, что после того, как Dubbo выполнил экспорт, он также публикует событие.
public class ServiceBean<T>{
public void export() {
super.export();
publishExportEvent();
}
private void publishExportEvent() {
ServiceBeanExportedEvent exportEvent = new ServiceBeanExportedEvent(this);
applicationEventPublisher.publishEvent(exportEvent);
}
}
ServiceBeanExportedEvent, событие, экспортируемое службой, должно наследовать объект события ApplicationEvent в Spring.
public class ServiceBeanExportedEvent extends ApplicationEvent {
public ServiceBeanExportedEvent(ServiceBean serviceBean) {
super(serviceBean);
}
public ServiceBean getServiceBean() {
return (ServiceBean) super.getSource();
}
}
Затем мы настраиваем ApplicationListener, который является наблюдателем, для прослушивания событий экспорта интерфейса службы Dubbo.
@Component
public class ServiceBeanListener implements ApplicationListener<ServiceBeanExportedEvent> {
@Override
public void onApplicationEvent(ServiceBeanExportedEvent event) {
ServiceBean serviceBean = event.getServiceBean();
String beanName = serviceBean.getBeanName();
Service service = serviceBean.getService();
System.out.println(beanName+":"+service);
}
}
11. Командный режим
Шаблон Command — это поведенческий шаблон проектирования, который преобразует запрос в отдельный объект, содержащий всю информацию, связанную с запросом. Это преобразование позволяет параметризовать методы, откладывать выполнение запроса или ставить их в очередь в зависимости от запроса, а также реализовывать операции с отзывом.
Hystrix — это отказоустойчивая платформа с открытым исходным кодом от Netflix с возможностями самозащиты. Может остановить цепную реакцию сбоев, быстро выйти из строя и изящно деградировать.
Он оборачивает все вызовы внешних систем/зависимостей с помощью HystrixCommand или HystrixObservableCommand, каждый из которых выполняется в отдельном потоке/под авторизацией сигнала. Это типичное применение шаблона команды.
Давайте рассмотрим пример приложения Hystrix.
Во-первых, нам нужно создать конкретный класс команд, передав объект-получатель через конструктор.
public class OrderServiceHystrixCommand extends HystrixCommand<Object> {
//接收者,处理业务逻辑
private OrderService orderService;
public OrderServiceHystrixCommand(OrderService orderService) {
super(setter());
this.orderService = orderService;
}
//设置Hystrix相关参数
public static Setter setter() {
HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("orderGroup");
HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("orderService");
HystrixThreadPoolProperties.Setter threadPoolProperties = HystrixThreadPoolProperties.Setter().withCoreSize(1)
.withQueueSizeRejectionThreshold(1);
HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter();
return Setter.withGroupKey(groupKey)
.andCommandKey(commandKey)
.andThreadPoolPropertiesDefaults(threadPoolProperties)
.andCommandPropertiesDefaults(commandProperties);
}
@Override
protected Object run() throws InterruptedException {
Thread.sleep(500);
return orderService.orders();
}
@Override
protected Object getFallback() {
System.out.println("-------------------------------");
return new ArrayList();
}
}
Затем, когда клиент вызовет его, создайте этот класс команд и выполните его.
@RestController
public class OrderController {
@Autowired
OrderService orderService;
@RequestMapping("/orders")
public Object orders(){
OrderServiceHystrixCommand command = new OrderServiceHystrixCommand(orderService);
return command.execute();
}
}
Похоже, что командная модель и шаблон стратегии несколько похожи в том, что они могут оба параметризировать объекты с определенным поведением. Но их мышление очень иное.
Например, мы можем использовать команды для преобразования любой операции в объект, а параметры операции станут переменными элементов объекта. Точно так же мы также можем сделать что-либо с запросом, например, задержка выполнения, регистрацию, сохранение исторических команд и т. Д.
С другой стороны, шаблон стратегии фокусируется на описании различных способов выполнения чего-либо, что позволяет вам переключать алгоритмы в пределах одного класса контекста.
Суммировать
В этой статье основное внимание уделяется реализации шаблонов проектирования в различных средах, чтобы лучше понять идеи и сценарии применения, лежащие в основе шаблонов. Добро пожаловать, друзья с разными идеями, оставьте сообщение для обсуждения~
Нелегко быть оригинальным, это понравится приглашенным официальным лицам перед отъездом.Это будет движущей силой для автора продолжать писать~