⚠️Эта статья является первой подписанной статьей сообщества Nuggets, перепечатка без разрешения запрещена.
Причина инцидента
В середине июля расследование потенциальных рисков системы нашей компании было в самом разгаре, среди них я обнаружил, что в источнике вызова текущей системы отсутствует информация о токене, и его трудно идентифицировать и отслеживать конкретно, поэтому его необходимо оптимизировать. .
В ответ на две только что упомянутые проблемы мне нужно только реализовать определенный базовый класс фреймворка, а затем выполнить небольшую бизнес-обработку.Согласно документации фреймворка, выполните следующие действия, чтобы добиться следующего:
После того, как разработка и отладка были завершены за один раз, мне стало интересно узнать об этом методе реализации.Я был очень озадачен тем, как они создаются и воспроизводятся во фреймворке.Если вам интересно, позвольте мне узнать (😜)
Что такое СПИ
Сначала я даже не знал, что эта технология/решение поддерживается самой Java, и думал, что это хитрая операция, разработанная самим фреймворком. называетсяSPI, официальное объяснение следующее:
SPI: полное название — интерфейс поставщика услуг. Это набор интерфейсов, предоставляемых Java для реализации или расширения третьими лицами, в основном используемых дляРасширение фреймворка, разработка плагинови Т. Д.
Например, упомянутый выше фильтр параметров реализации относится к категории расширений фреймворка.После краткого понимания давайте сделаем небольшую демонстрацию.
Как работает СПИ
Возможности обнаружения SPI не должны зависеть от других библиотек классов.Существуют две основные реализации:
- sun.misc.Service Возможность загрузки предоставлена Sun
- Java.util.serviceLoader # load jdk сам предоставляет возможности загрузки
Поскольку второй метод — это внутренний код JDK, включая исходный код, этот метод будет использоваться по умолчанию для описания в будущем.
Основные этапы использования:
-
Определите интерфейс, который должен предоставлять внешние возможности
public interface SPIInterface { String handle(); }
-
Определите класс реализации, который реализует указанный интерфейс
public class SPIInterfaceImpl implements SPIInterface { @Override public String handle() { return "当前时间为: " + LocalDateTime.now(); } }
-
Настройте соответствующий класс реализации в указанном месте:resource/META-INF/services
УведомлениеresourceРесурсный файл
# 文件位置(resource/META-INF/services/com.mine.spi.SPIInterface) # 内容(实现类的全类名) com.mine.spi.impl.SPIInterfaceImpl
-
Используя возможности инициализации, предоставляемые JDK, вы можете напрямую вызывать
public class SpiApp { public static void main(String[] args) { ServiceLoader<SPIInterface> load = ServiceLoader.load(SPIInterface.class); for (SPIInterface ser : load) { System.out.println(ser.handle()); } } } // 响应 // 当前时间为: 2021-08-24T03:30:52.397
Просто взорвать, ключ в том, что JDK помог нам реализовать этот набор шагов обнаружения и инициализации Давайте подробно проанализируем его основной исходный код 😁
Из метода:java.util.ServiceLoader#load
Для записи передайте текущий тип класса интерфейса и его загрузчик классов в переменную Loader:
/**
* service:接口类型
* loader:类加载器
* acc:安全管理器
*/
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
После того, как переменная передана, инициализируйте класс:LazyIterator
, из названия видно, что это ленивый итератор, экземпляр инициализируется только при фактическом использовании триггера, основная логика инициализации находится в методе:java.util.ServiceLoader.LazyIterator#nextService
середина.
private S nextService() {
// 省略其他代码...
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
// 省略其他代码...
}
Поскольку интерфейс, чтобы получить имя полного класса и типа, он построен объектом отражения или экземпляром очень легко получить экземпляр объекта, а общий код без различий.
Давайте посмотрим на некоторые примеры фреймворков, реально использующих SPI, и посмотрим на код предшественников 😎
Анализ вариантов использования SPI
Log4j-Api
Возьмем в качестве примера структуру ведения журналов Log4j.log4j-api-2.13.3.jar
версия основана наSPI
ДостигнутоPropertySource
Интерфейсы для сбора информации о текущей конфигурации сервера, как показано ниже:
такой же,log4j-core-2.13.3.jar
на основеSPI
Привязка бревенчатого фасада реализована, а код ядра такой:
/**
* Binding for the Log4j API.
*/
public class Log4jProvider extends Provider {
public Log4jProvider() {
super(10, "2.6.0", Log4jContextFactory.class);
}
}
JDBC-драйвер
С нашим широко используемым драйвером JDBCmysql-connector-java-5.1.43.jar
Например, он также реализует интерфейс SPI, а классы драйверов:Driver
,FabricMySQLDriver
Его базовая реализация является зарегистрированной с классом управления привода, основной код выглядит следующим образом, он автоматически помогает нам сделатьClass.forName("com.mysql.jdbc.Driver")
Этот шаг загружает действие.
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
}
FabricMySQLDriver
То же самое относится и к классам.Конечно, мы также можем активно уничтожить этот механизм загрузки, например, внедрив MySQLDriver самостоятельно для реализации подключения к базе данных.Основной код выглядит следующим образом:
public class CustomDriver extends NonRegisteringDriver implements Driver {
static {
try {
java.sql.DriverManager.registerDriver(new CustomDriver());
} catch (SQLException ignored) {}
}
public CustomDriver() throws SQLException { }
@Override
public Connection connect(String url, Properties info) throws SQLException {
System.out.println("[Kerwin] 执行数据库连接...");
return super.connect(url, info);
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
потомCustomDriver
Просто введите его в SPI.
должен быть в курсеCustomDriver
Классы должны реализовать наследованиеNonRegisteringDriver
класс, в противном случае он будет зарегистрирован сначала драйвером по умолчанию.После завершения, используя древний код JDBC для вызова, вы можете смоделировать ситуацию разрушения SPI, как показано на рисунке:
public void customDriver() throws SQLException {
Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/db_file?characterEncoding=UTF-8&useSSL=false", "root", "");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM script_dir LIMIT 1");
while (rs.next()) {
System.out.println(rs.getString(1));
}
}
Видно, что мы использовали пользовательский класс драйвера для успешного подключения к базе данных, заменив исходный класс драйвера Driver.Конкретные детали необходимо снова отладить, потому что задействован тип интерфейса, и после получения соединения, Return и т.д. на.
Вывод консоли:
[Kerwin] 执行数据库连接...
Сценарии применения SPI
После понимания его основного использования и принципов,SPI
ТайнаМеханизм для выборочной настройки классов реализации интерфейса в указанных местах на основе соглашений, динамически инициализируемых и выполняемых JDK..
Хотите использовать SPI для ежедневного развития?
Мы можем непосредственно испытать из вышеперечисленногоSPI
Преимущества механизма в том, что он может играть роль выбора стратегии, динамической инициализации и развязки, так стоит ли нам использовать его в обычной разработке проекта? Я лично не рекомендую использовать SPI, основная причина в том, что мы можем использовать более элегантный способ заменыSPI
механизмы, такие как:
- Динамическая инициализация, выбор стратегии = "Мы можем использовать стратегию + заводской режим для достижения динамического выбора стратегии и сотрудничать с ZK для достижения динамической инициализации (включить/отключить)
- Развязка = «на основе хорошего дизайна, развязка может быть легко достигнуто
Основываясь на вышеприведенном варианте осуществления, в то время как код элемента может быть предоставлен, чтобы гарантировать, что преимущества SPI легче читать, понимать, снижать затраты.
Следует ли использовать SPI для разработки инструментов фреймворка/компонента?
Ответ не вызывает сомнений. Многие современные платформы и инструменты реализованы с использованием SPI. После введения механизма SPI интерфейс службы и реализация службы перейдут в раздельное состояние, что позволит использовать механизмы развязки и расширяемости.
НапримерSharding-jdbc
Интерфейс алгоритма шифрования изначально предоставляет только методы шифрования AES и MD5, проекты, которым нужны другие методы шифрования, могут использовать механизм SPI для записи нужных им методов шифрования в структуру, а затем вызывать их по мере необходимости, независимо от того, удобнее ли использование или обслуживание. .
Поскольку версия SPI, реализованная в Java, является относительно грубой и агрессивной, она будет создавать экземпляры всех классов реализации интерфейса, поэтому существуют фреймворки, которые инкапсулируют и оптимизируют SPI Java, напримерDubbo
, который изменяет полное имя класса в файле конфигурации на пару ключ-значение для удовлетворения потребностей загрузки по требованию и добавляет функции IOC и AOP, адаптивное расширение и другие механизмы.
Благодаря приведенным выше методам работы мы можем понять, что механизм SPI не является таинственным, и если людям нужна простая инкапсуляция, это все еще легко.
Изучить разум SPI
Механизм SPI имеет определенную неизбежность, как упоминалось выше.Sharding-jdbc
Например, алгоритм шифрования , только реальный пользователь знает, что ему нужно, поэтомуВозможность оставить часть решения (реализации) за пользователемЭто обязательно, иначе код обязательно сильно раздуется, чтобы соответствовать всем ситуациям, будь то фреймворк или инструмент. Наиболее важными принципами проектирования являются:
Принцип инверсии зависимостей (программа для абстрактных слоев, а не для конкретных классов)
В нашей повседневной разработке нам также необходимо думать о том, как проектировать интерфейсы, как полагаться на уровень абстракции для программирования и уменьшать связь с классами реализации. принципы и т.д. Знания, для понимания лучших практик различных шаблонов проектирования, для пошаговой оптимизации кода, рекомендую свои предыдущие статьи здесь:Шаблоны проектирования в целом: от того, зачем нужны принципы, до фактической реализации (с картой знаний).
Суммировать
До сих пор мы понимали, что такое SPI и как он работает, знакомы с его типичными случаями, а также со сценариями его применения, концепциями дизайна и т. д. Ниже приведены некоторые целевые предложения:
- Механизм SPI является одной из необходимых возможностей для проектов на уровне фреймворка/инструмента.Те, кто решил стать старшим инженером, должны понимать его концепции проектирования и принципы реализации.
- Основная идея SPI:Дать часть решения (внедрение) пользователю, то есть полагаться на инверсию
- Поняв преимущества и характеристики SPI, мы можем использовать другие решения для достижения лучших результатов в одном проекте. Не используйте его ради использования.
- В будущем, при разработке или использовании некоторых промежуточных программ/инструментов, вы можете уделять больше внимания тому, предоставляет ли они соответствующий интерфейс SPI, который может быть более эффективным.
Если вы считаете этот контент полезным:
- Конечно, ставьте лайки и поддерживайте~
- Кроме того, вы можете искать и следить за официальной учетной записью "это Кервин», давайте вместе пойдем по дороге технологий~ 😋