Каково это — вносить код в лучший проект с открытым исходным кодом Spring Boot?

Java
Каково это — вносить код в лучший проект с открытым исходным кодом Spring Boot?

Ставь лайк и потом смотри, вырабатывай полезную привычку

задний план

Средой ведения журналов Spring Boot по умолчанию всегда был Logback, который хорошо поддерживается. А для Logback Spring Boot также предоставляет функцию расширения —<springProfile>, этот тег можно использовать в XML-файле конфигурации Logback, который очень удобно использовать с профилем Spring для различения среды.

Например, вы можете настроить только одинlogback-spring.xmlфайл конфигурации, затем используйте<springProfile>Чтобы отличить среду, среда разработки выводит только на консоль, а другие среды выводят в файл.

<Root level="INFO">
  <!-- 开发环境使用Console Appender,生产环境使用File Appender -->
  <springProfile name="dev">
    <AppenderRef ref="Console"/>
  </springProfile>
  <SpringProfile name="!dev">
    <AppenderRef ref="File"/>
  </SpringProfile>
</Root>

Преимущество этого в том, что мне нужен только один файл конфигурации logback.xml для решения проблемы нескольких сред вместо одного logback-spring.xml для каждой среды, что действительно ароматно.(Синтаксис этого профиля также может иметь более гибкий синтаксис, см.Официальная документация Spring Boot)

Но иногда из-за производительности или по другим причинам мы выбираем log4j2 в качестве среды ведения журналов Spring Boot. Spirng Boot также поддерживает log4j2.

Переключиться на log4j2 легко, но Spring Boot не расширяет log4j2! Метод конфигурации xml log4j2 не поддерживает теги, поэтому невозможно настроить несколько сред с удовольствием! После поиска у кого-то на StackOverflow такая же путаница, и эта функция на данный момент никем не предоставляется

Итак, у меня возникла смелая идея:Разработайте расширение Spring Boot — Log4j2 XML самостоятельно, чтобы log4j2 XML также поддерживал<SpringProfile>Этикетка, а затем внести свой вклад в Spring Boot, если он будет принят.

И это не пиар для изменения комментария, изменения знака препинания, изменения имени переменной и т. д., это новая функция, и как только она будет принята, я внесу свой вклад в документацию Spring Boot!

image.png

развитие функции

Просто делайте то, что вы говорите, сначала проанализируйте исходный код синтаксического анализа XML Log4j2 и посмотрите, сможете ли вы начать

Анализ исходного кода синтаксического анализа XML Log4j2

После некоторого анализа я нашел код разбора XML-файла Log4j2 вorg.apache.logging.log4j.core.config.xml.XmlConfiguration, Внимательно прочитав класс +DEBUG, я обнаружил, что различные методы синтаксического анализа этого класса синтаксического анализа XML являются либо статическими, либо частными.В начале проектирования функция предоставления расширений и пользовательских тегов не рассматривалась. Например, этот метод рекурсивного разбора тегов является напрямую закрытым:

private void constructHierarchy(final Node node, final Element element) {
        processAttributes(node, element);
        final StringBuilder buffer = new StringBuilder();
        final NodeList list = element.getChildNodes();
        final List<Node> children = node.getChildren();
        for (int i = 0; i < list.getLength(); i++) {
            final org.w3c.dom.Node w3cNode = list.item(i);
            if (w3cNode instanceof Element) {
                final Element child = (Element) w3cNode;
                final String name = getType(child);
                final PluginType<?> type = pluginManager.getPluginType(name);
                final Node childNode = new Node(node, name, type);
                constructHierarchy(childNode, child);
                if (type == null) {
                    final String value = childNode.getValue();
                    if (!childNode.hasChildren() && value != null) {
                        node.getAttributes().put(name, value);
                    } else {
                        status.add(new Status(name, element, ErrorType.CLASS_NOT_FOUND));
                    }
                } else {
                    children.add(childNode);
                }
            } else if (w3cNode instanceof Text) {
                final Text data = (Text) w3cNode;
                buffer.append(data.getData());
            }
        }

        final String text = buffer.toString().trim();
        if (text.length() > 0 || (!node.hasChildren() && !node.isRoot())) {
            node.setValue(text);
        }
    }

Даже проанализированные данные являются конфиденциальными

private Element rootElement;

Это невозможно реализовать, только переписав некоторые методы через наследование, если только весь класс не может быть расширен для расширения пользовательских тегов...

Вопросы риска и совместимости

Это смущает.Хотя можно переписать весь класс, совместимость не гарантируется. Потому что, как только XML-конфигурация Log4j2 будет обновлена, мое расширение будет удалено, будь то крупное обновление или небольшое обновление, но если этот класс изменится, мне придется переписать это расширение, что действительно небезопасно.

но я искалXmlConfigurationПосле истории фиксации этого класса было обнаружено, что последний раз он обновлялся в июне 2019 года.image.pngИ вся структура Log4j2 в период с июня 2019 года по март 2021 года выпустила 9 выпусков.image.pngВесь проект обновлялся два года, почти в десяти версиях XmlConfiguration обновлялся только один раз, что указывает на очень низкую частоту обновлений. И сравнивая записи об изменениях, обнаруживается, что обновлений этого класса в последнее время очень мало.

Если подумать, что, если я перепишу XmlConfiguration? При такой низкой частоте обновлений и небольшом количестве обновлений риск перезаписи очень низок. И я не стал все это переписывать, я просто скопировал исходный код и немного добавил поддержку пользовательских тегов, и количество изменений было не большим. Даже если вам нужно обновиться с помощью Log4j2, нетрудно сравнить код и снова настроить его.

Вот как я убедил себя и начал тянуть код...

разветвление/клонирование кода, сборка в локальной среде

адрес склада spring-boot:GitHub.com/spring-pro — это…

  1. Разветвить код загрузки Spring
  2. клонировать репозиторий этого форка
  3. На основе мастера создайте новую ветку log4j2_enhancement для разработки.

Вы также можете напрямую передать здесь клон IDEA, но только если выИметь «надежную и стабильную» сеть.

Поскольку Spring/Spring Boot перенес инструмент сборки с Maven на Gradle, версия IDEA не должна быть слишком старой, а старая версия может недостаточно хорошо поддерживать Gradle.

Если ваша сеть достаточно "надежна и стабильна", то вам нужно только открыть исходный код Spring Boot в IDEA, вы можете настроить среду разработки и запустить тест напрямую. В противном случае вы можете столкнуться с различными проблемами, такими как сбой загрузки Gradle и связанных пакетов, а также сбой загрузки пакетов хранилища Maven...

Spring Boot поддерживает расширение Logback

Теперь, когда в Spring Boot есть расширенный Logback (XML), давайте посмотрим, как он будет улучшен.

После некоторого анализа я нашел точку расширения этого Logback:

class SpringBootJoranConfigurator extends JoranConfigurator {

	private LoggingInitializationContext initializationContext;

	SpringBootJoranConfigurator(LoggingInitializationContext initializationContext) {
		this.initializationContext = initializationContext;
	}

	@Override
	public void addInstanceRules(RuleStore rs) {
		super.addInstanceRules(rs);
		Environment environment = this.initializationContext.getEnvironment();
		rs.addRule(new ElementSelector("configuration/springProperty"), new SpringPropertyAction(environment));
		rs.addRule(new ElementSelector("*/springProfile"), new SpringProfileAction(environment));
		rs.addRule(new ElementSelector("*/springProfile/*"), new NOPAction());
	}
}

Это так просто? Еще раз проанализировав JoranConfigurator и связанные с ним классы по этому классу, я обнаружил, что это все из-за Logback.

Как упоминалось в документации Logback, этоJoranНа самом деле общая система конфигурации, которую можно использовать независимо от системы регистрации. Но я искал и не смог найти источник этого Джорана, кроме документации Logback.

Но это не имеет значения, я просто использую его как анализатор общей конфигурации, на который ссылается logback.

Этот синтаксический анализатор является более гибким и может настраивать поведение парсинга тега/тега. Вам нужно только переопределить метод addInstanceRules и добавить собственное имя тега и класс поведения:

@Override
public void addInstanceRules(RuleStore rs) {
    super.addInstanceRules(rs);
    Environment environment = this.initializationContext.getEnvironment();
    rs.addRule(new ElementSelector("configuration/springProperty"), new SpringPropertyAction(environment));
    //就是这么简单……
    rs.addRule(new ElementSelector("*/springProfile"), new SpringProfileAction(environment));
    rs.addRule(new ElementSelector("*/springProfile/*"), new NOPAction());
}

Затем в SpringProfileAction через объект Spring Environment вы можете получить активные в данный момент профили для соответствия.

Сделайте то же самое и добавьте пользовательское расширение в Log4j2.

Хотя синтаксический анализ XML в Log4j2 не такой гибкий, как вставные расширения Logback. Но, основываясь на моем предыдущем анализе рисков и совместимости, переписывание XmlConfiguration также может реализовать синтаксический анализ пользовательских тегов:

Сначала создайте SpringBootXmlConfiguration

Код этого класса полностью скопированorg.apache.logging.log4j.core.config.xml.XmlConfiguration, а затем добавьте два параметра, связанных со средой:

private final LoggingInitializationContext initializationContext;

private final Environment environment;

Затем добавьте в конструктор InitializationContext и введите:

public SpringBootXmlConfiguration(final LoggingInitializationContext initializationContext,
			final LoggerContext loggerContext, final ConfigurationSource configSource) {
		super(loggerContext, configSource);
		this.initializationContext = initializationContext;
		this.environment = initializationContext.getEnvironment();
    	...
}

Наконец, вам нужно только настроить метод рекурсивного синтаксического анализа, упомянутый выше, и добавить поддержку тега SpringProfile:

private void constructHierarchy(final Node node, final Element element, boolean profileNode) {
    //SpringProfile节点不需要处理属性
    if (!profileNode) {
        processAttributes(node, element);
    }
    final StringBuilder buffer = new StringBuilder();
    final NodeList list = element.getChildNodes();
    final List<Node> children = node.getChildren();
    for (int i = 0; i < list.getLength(); i++) {
        final org.w3c.dom.Node w3cNode = list.item(i);
        if (w3cNode instanceof Element) {
            final Element child = (Element) w3cNode;

            final String name = getType(child);
            //如果是<SpringProfile>标签,就跳过plugin的查找和解析
            // Enhance log4j2.xml configuration
            if (SPRING_PROFILE_TAG_NAME.equalsIgnoreCase(name)) {
                //如果定义的Profile匹配当前激活的Profiles,就递归解析子节点,否则就跳过当前节点(和子节点)
                if (acceptsProfiles(child.getAttribute("name"))) {
                    constructHierarchy(node, child, true);
                }
                // Break <SpringProfile> node
                continue;
            }
            //查找节点对应插件,解析节点,添加到node,构建rootElement树
            //......
    }
}
//判断profile是否符合规则,从Spring Boot - Logback里复制的……
private boolean acceptsProfiles(String profile) {
    if (this.environment == null) {
        return false;
    }
    String[] profileNames = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(profile));
    if (profileNames.length == 0) {
        return false;
    }
    return this.environment.acceptsProfiles(Profiles.of(profileNames));
}

Настройте запись SpringBootXmlConfiguration

Что ж, все готово, это так просто, этот небольшой код завершает усовершенствование XML для Log4j2. Теперь мне просто нужно заменить XmlConfiguration по умолчанию на мою SpringBootXmlConfiguration при сборке Log4j2:

//org.springframework.boot.logging.log4j2.Log4J2LoggingSystem
......
LoggerContext ctx = getLoggerContext();
URL url = ResourceUtils.getURL(location);
ConfigurationSource source = getConfigurationSource(url);
Configuration configuration;
if (url.toString().endsWith("xml") && initializationContext != null) {
    //XML文件并且initializationContext不为空时,就使用增强的SpringBootXmlConfiguration进行解析
    configuration = new SpringBootXmlConfiguration(initializationContext, ctx, source);
}
else {
    configuration = ConfigurationFactory.getInstance().getConfiguration(ctx, source);
}
......

Подготовьтесь к модульным тестам

Функциональность завершена, теперь пришло время подготовиться к модульному тестированию. Здесь вы можете обратиться к классу модульного теста, связанному с Logback, скопировать его напрямую и изменить его до версии Log4j2.

Текущая версия Spring Boot использует Junit 5. Теперь создайте новый класс SpringBootXmlConfigurationTests, а затем имитируйте класс модульного теста Logback, чтобы написать набор тестовых методов и тестовых файлов конфигурации:

<!--profile-expression.xml-->
<springProfile name="production | test">
  <logger name="org.springframework.boot.logging.log4j2" level="TRACE" />
</springProfile>

<!--production-file.xml-->
<springProfile name="production">
  <logger name="org.springframework.boot.logging.log4j2" level="TRACE" />
</springProfile>

<!--multi-profile-names.xml-->
<springProfile name="production, test">
  <logger name="org.springframework.boot.logging.log4j2" level="TRACE" />
</springProfile>

<!--nested.xml-->
<springProfile name="outer">
  <springProfile name="inner">
    <logger name="org.springframework.boot.logging.log4j2" level="TRACE" />
  </springProfile>
</springProfile>

...
void profileActive();
void multipleNamesFirstProfileActive();
void multipleNamesSecondProfileActive();
void profileNotActive();
void profileExpressionMatchFirst();
void profileExpressionMatchSecond();
void profileExpressionNoMatch();
void profileNestedActiveActive();
void profileNestedActiveNotActive();
......

Поколебавшись какое-то время, я наконец закончил писать модульные тесты, и все тесты прошли. Далее вы можете подготовиться к отправке PR

Отправить PR

Сначала в проекте после форка делаем Pull requestimage.pngЗатем выберите ветку, которую вы хотите pr, и создайте primage.pngЗатем вам необходимо подробно заполнить описание вашего PRimage.pngЯ подробно описываю функции, которые я представил, а также проблемы совместимости и риска, которые я проанализировал выше:

Enhance the configuration of log4j2 (xml), support Profile-specific Configuration (<SpringProfile>), consistent with logback extension. Spring Boot currently only enhances the Logback (XML) configuration to support the tag. This feature is very useful, but is not supported by Log4j2. I copied the code in Log4j2 XML to parse the XML configuration and created a new SpringBootXmlConfiguration to support the tag, which is as simple and easy to use as Logback Extension. Compatibility issues with rewriting the Log4j2 parsing code:

  1. I just copied the XmlConfiguration code directly from Log4j2, adding very little code and making no big changes like formatting. If there is an update to Log4j2, it is easy to rewrite the parsing class and update it accordingly.
  2. The XmlConfiguration class in Log4j2 was last updated in June 2019, with no updates between [2.12.0,2.14.1] and the default dependent version of Log4j2 in Springboot (master) is 2.14.1

To sum up, there is no risk in this kind of enhancement

Застрял с бессердечными проверками CI

После подачи PR я думал, что на этом все и закончится...

Оказывается, Github Action Spring Boot имеет проверку CI и после долгого ожидания сообщает мне, что сборка не удалась...image.pngЗдесь можно ввести данные для просмотра конкретного журнала сборки.image.pngcheckFormat/checkStyle не удалось...

Я был неосторожен, я забыл про checkStyle. Этот проект с открытым исходным кодом должен иметь строгие требования к стилю кода. Мой код был скопирован из Log4j2. Стандарты стиля кода двух проектов определенно разные!

Настроить стиль кода

Я вернулся и просмотрел рекомендации по внесению вклада в Spring Boot и обнаружил, что в них упоминаетсяплагин spring-javaformat, для проверки/форматирования кода, как плагинов Eclipse/Idea, так и плагинов gradle/maven.

Я наивно думал, что этот плагин IDEA может легко отформатировать мой код в спецификации Spring.После его установки Reformat Code обнаружил, что он бесполезен, и все еще не может пройти проверку стиля...... Если вы знаете, как его использовать , вы можете поделиться в комментариях

Затем я начал локально выполнять его задачу checkstyle, постоянно корректируя стиль кода...

Выполнение этого стиля проверки/формата проверки выполняется через Gradle, поэтому его также можно выполнить на панели Gradle IDEA:image.pngСтиль кода Spring Boot очень строгий, например, комментарии должны добавляться с точками, конец файла должен заканчиваться пустой строкой, порядок импорта пакетов, длина каждой строки кода и т.д.

После выполнения плагина checkstyle/checkformat плагин подскажет, в каком файле и в какой строке возникла проблема, просто следуйте модификациям.

После более чем часа настройки я, наконец, прошел проверку кода... очки стоят

Отправьте код еще раз

После того, как настройка стиля/формата кода была завершена, я снова отправил код, все еще в исходной ветке. Если отправить сюда, проверка CI в этом PR будет активирована автоматически.

Примерно через 20 минут строительство было окончательно завершено, и прошлоimage.png

Ответ от официального

Через три-четыре дня я получил ответ от официальных лиц, а вместе с ним и поданный мной PR был закрыт...image.pngОтношение официального ответа по-прежнему очень дружелюбное. Это, вероятно, означает, что каким бы стабильным ни был код, который я присылаю, этот метод насильственной перезаписи все еще не очень хорош. Они надеются, что Log4j2 предоставит расширение, а затем Spring Boot расширит его. Внедрить улучшения в Log4j2.

И прикрепил вопрос, тема - поддержка Spring Boot для Log4j2, и на этот раз добавил мой PR:GitHub.com/spring-pro — это… image.png

Суммировать

Хотя Spring Boot не принял мой код, это было не потому, что мой код был написан дерьмом 😂, а этот метод был слишком навязчивым, рискованным и недостаточно дружелюбным, лучше было бы реализовать его расширенным образом.

Это также отражает важность масштабируемости программ.При разработке программ или фреймворков мы должны учитывать масштабируемость и следовать принципу открытости-закрытости.

Неважно, если я отказался от своего вклада на этот раз, по крайней мере, Spring Boot официально понимает, что в этом есть необходимость, и есть готовые коды реализации, если в будущем будут возможности, я продолжу вносить свой вклад. коды.

приложение

Код, отправленный на этот раз, и соответствующий PR-адрес находятся здесь, и заинтересованные студенты могут сослаться на них.

Оригинальность не так проста, пожалуйста, свяжитесь с автором для перепечатки. Если моя статья полезна для вас, пожалуйста, поставьте лайк/добавьте в избранное/подпишитесь, чтобы поддержать и поддержать ее ❤❤❤❤❤❤