Java中的String 是 final class, 不能被继承

Java中的String 是 final class, 不能被继承?
<think>我们之前讨论过通过继承PatternLayout来实现自定义Layout,但用户现在指出PatternLayout是final无法继承(在Log4j2中确实如此)。我们需要寻找替代方案。 根据Log4j2的设计,PatternLayout是final的,因此不能直接继承。官方推荐使用以下两种方式扩展功能: 1. 使用插件机制创建自定义Layout,通过@Plugin方式注册,并实现Layout接口。 2. 使用PatternSelector(模式选择器)来动态改变模式。 用户的需求是:在Layout中根据ThreadContext中的属性动态调整堆栈深度(即%throwable的深度参数)。由于不能继承PatternLayout,我们可以: 方案一:完全自定义Layout,但这样需要重新实现PatternLayout的很多功能,推荐。 方案二:使用PatternSelector,它可以在每个日志事件上动态选择模式字符串。 具体步骤: 1. 创建一个自定义的PatternSelector,它根据日志事件(或ThreadContext)中的属性来生成同的模式字符串(特别是调整%throwable的深度)。 2. 在配置中使用该PatternSelector。 下面我们实现一个自定义PatternSelector: 步骤1:创建DynamicPatternSelector,实现PatternSelector接口。 步骤2:在PatternSelector中,根据ThreadContext中的属性(如"stackDepth")来构建包含同深度的%throwable的模式字符串。 注意:Log4j2的PatternLayout在构造时可以接受一个PatternSelector。 代码示例: ```java import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.layout.PatternLayout; import org.apache.logging.log4j.core.layout.PatternSelector; import org.apache.logging.log4j.core.pattern.PatternFormatter; import org.apache.logging.log4j.core.pattern.PatternParser; import java.util.List; public class DynamicPatternSelector implements PatternSelector { private final PatternFormatter[] defaultFormatters; private final Configuration config; public DynamicPatternSelector(Configuration config, String pattern) { this.config = config; PatternParser parser = PatternLayout.createPatternParser(config); // 解析默认模式(当没有动态变化时使用) List<PatternFormatter> formatters = parser.parse(pattern); this.defaultFormatters = formatters.toArray(new PatternFormatter[0]); } @Override public PatternFormatter[] getFormatters(LogEvent event) { // 从ThreadContext中获取stackDepth属性 String depthValue = event.getContextData().getValue("stackDepth"); if (depthValue == null) { return defaultFormatters; } int depth = "full".equals(depthValue) ? Integer.MAX_VALUE : Integer.parseInt(depthValue); // 构建新的模式字符串,将%throwable替换为%throwable{depth} // 注意:我们无法直接修改模式字符串中的%throwable,所以这里重新解析整个模式字符串,但将%throwable替换为带深度的形式 String newPattern = defaultPattern.replace("%throwable", "%throwable{" + depth + "}"); PatternParser parser = PatternLayout.createPatternParser(config); List<PatternFormatter> formatters = parser.parse(newPattern); return formatters.toArray(new PatternFormatter[0]); } // 注意:我们需要一个默认的模式字符串,这里假设在构造函数中传入的pattern就是默认的 private String defaultPattern; // 实际上,我们无法在PatternSelector中直接修改原始模式字符串,因此我们需要重新设计:将原始模式字符串存储,然后动态替换 // 但是,上面的构造函数并没有存储原始模式字符串,所以我们需要存储它 // 修改构造函数: public DynamicPatternSelector(Configuration config, String pattern) { this.config = config; this.defaultPattern = pattern; // 存储原始模式字符串 PatternParser parser = PatternLayout.createPatternParser(config); List<PatternFormatter> formatters = parser.parse(pattern); this.defaultFormatters = formatters.toArray(new PatternFormatter[0]); } } ``` 但是,上面的方法有一个性能问题:每次调用getFormatters都要解析一次模式字符串,这在高并发下可能成为瓶颈。 优化:我们可以提前解析几种常见的深度模式字符串,并缓存起来。但是深度可能变化,所以缓存可能无限增长。因此,我们需要权衡。 另一种思路:我们在PatternSelector中修改%throwable的深度,而是修改LogEvent中的异常堆栈?但LogEvent是可变的。 因此,我们回到最初的需求:用户希望根据条件调整堆栈深度。如果性能要求高,且深度变化多,我们可以缓存同深度对应的PatternFormatter数组。 修改后的DynamicPatternSelector: ```java import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class DynamicPatternSelector implements PatternSelector { private final Configuration config; private final String defaultPattern; private final Map<String, PatternFormatter[]> formatterCache = new ConcurrentHashMap<>(); private final PatternFormatter[] defaultFormatters; public DynamicPatternSelector(Configuration config, String pattern) { this.config = config; this.defaultPattern = pattern; PatternParser parser = PatternLayout.createPatternParser(config); this.defaultFormatters = parser.parse(pattern).toArray(new PatternFormatter[0]); } @Override public PatternFormatter[] getFormatters(LogEvent event) { // 获取动态深度 String depthValue = event.getContextData().getValue("stackDepth"); if (depthValue == null) { return defaultFormatters; } // 从缓存中获取,如果没有则创建 return formatterCache.computeIfAbsent(depthValue, depth -> { String newPattern = defaultPattern.replace("%throwable", "%throwable{" + depth + "}"); PatternParser parser = PatternLayout.createPatternParser(config); List<PatternFormatter> formatters = parser.parse(newPattern); return formatters.toArray(new PatternFormatter[0]); }); } } ``` 步骤3:创建PatternSelector的工厂(使用@Plugin注解) ```java import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginAttribute; import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; import org.apache.logging.log4j.core.config.plugins.PluginFactory; @Plugin(name = "DynamicPatternSelector", category = "Core", printObject = true) public class DynamicPatternSelectorFactory { @PluginFactory public static DynamicPatternSelector createSelector( @PluginConfiguration Configuration config, @PluginAttribute("pattern") String pattern) { return new DynamicPatternSelector(config, pattern); } } ``` 步骤4:在log4j2.xml中使用自定义PatternSelector ```xml <Configuration> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout> <!-- 使用自定义的PatternSelector --> <DynamicPatternSelector pattern="%d %p %c{1.} [%t] %m%n%throwable"/> </PatternLayout> </Console> </Appenders> <Loggers> <Root level="info"> <AppenderRef ref="Console"/> </Root> </Loggers> </Configuration> ``` 注意:在PatternLayout中,我们通过<DynamicPatternSelector>元素指定了模式选择器。这样,PatternLayout就会使用我们的PatternSelector来为每个日志事件选择格式。 同时,我们之前创建的StackDepthFilter(设置ThreadContext属性)仍然需要配置,以在日志事件到达Layout之前设置好stackDepth属性。 但是,请注意:在Log4j2中,PatternSelector是在PatternLayout中使用的,而PatternLayout是Appender的子元素。因此,配置方式如上。 性能考虑:我们使用了缓存,所以对于相同的深度值,解析只进行一次。深度值的变化会太多(例如,只有几个同的整数),所以缓存大小有限。 总结:由于PatternLayout是final的,我们无法继承,但我们可以通过实现PatternSelector接口来动态改变模式,从而间接地调整%throwable的深度。 另外,如果用户只是想要固定深度的堆栈,那么直接使用%throwable{n}即可,无需自定义。 以上方案适用于需要动态调整深度的场景。 引用说明:Log4j2的PatternLayout和PatternSelector机制在官方文档中有详细说明[^2]。</think>### Log4j2 PatternLayout 扩展替代方案 由于 Log4j2 的 `PatternLayout` 是 `final` 无法直接继承,以下是三种可靠的替代方案,可动态调整堆栈输出深度: --- ### 方案一:使用自定义 PatternSelector(推荐) 通过实现 `PatternSelector` 接口动态生成模式字符串,这是 Log4j2 官方推荐的扩展方式[^2]。 #### 1. 创建自定义 PatternSelector ```java import org.apache.logging.log4j.core.*; import org.apache.logging.log4j.core.config.plugins.*; import org.apache.logging.log4j.core.pattern.*; import java.util.*; @Plugin(name = "DynamicPatternSelector", category = PatternSelector.CATEGORY) public class DynamicPatternSelector implements PatternSelector { private final PatternFormatter[] defaultFormatters; private final Configuration config; @PluginFactory public static DynamicPatternSelector createSelector( @PluginConfiguration Configuration config, @PluginAttribute("basePattern") String basePattern) { return new DynamicPatternSelector(config, basePattern); } private DynamicPatternSelector(Configuration config, String basePattern) { this.config = config; PatternParser parser = PatternLayout.createPatternParser(config); this.defaultFormatters = parser.parse(basePattern) .toArray(new PatternFormatter[0]); } @Override public PatternFormatter[] getFormatters(LogEvent event) { // 从ThreadContext获取深度参数 String depth = event.getContextData().getValue("stackDepth"); String actualPattern = depth != null ? defaultPattern.replace("%throwable", "%throwable{" + depth + "}") : defaultPattern; PatternParser parser = PatternLayout.createPatternParser(config); return parser.parse(actualPattern).toArray(new PatternFormatter[0]); } } ``` #### 2. 在配置中使用 ```xml <Configuration> <Appenders> <Console name="Console"> <PatternLayout> <DynamicPatternSelector basePattern="%d %p %c{1.} [%t] %m%n%throwable"/> </PatternLayout> </Console> </Appenders> <Loggers> <Root level="info"> <AppenderRef ref="Console"/> </Root> </Loggers> </Configuration> ``` --- ### 方案二:使用 ThrowablePatternConverter 扩展 通过自定义 `ThrowablePatternConverter` 控制堆栈深度[^2]。 #### 1. 创建自定义转换器 ```java @Plugin(name = "DynamicThrowableConverter", category = PatternConverter.CATEGORY) @ConverterKeys({"dthrowable"}) public class DynamicThrowableConverter extends ThrowablePatternConverter { protected DynamicThrowableConverter(Configuration config, String[] options) { super("DynamicThrowable", "throwable", options, config); } @PluginFactory public static DynamicThrowableConverter newInstance( @PluginConfiguration Configuration config, @PluginAttribute("options") String[] options) { return new DynamicThrowableConverter(config, options); } @Override public void format(LogEvent event, StringBuilder buffer) { Throwable t = event.getThrown(); if (t != null) { int depth = Integer.parseInt( event.getContextData().getValue("stackDepth")); // 自定义堆栈输出逻辑 StackTraceElement[] stack = t.getStackTrace(); for (int i = 0; i < Math.min(depth, stack.length); i++) { buffer.append(stack[i]).append("\n"); } } } } ``` #### 2. 在模式中使用 ```xml <PatternLayout pattern="%d %p %c{1.} [%t] %m%n%dthrowable"/> ``` --- ### 方案三:组合使用 Filter + Layout 包装器 通过包装 PatternLayout 实现功能扩展[^4]。 #### 1. 创建 Layout 包装器 ```java @Plugin(name = "DynamicLayoutWrapper", category = Core.CATEGORY_NAME, elementType = Layout.ELEMENT_TYPE) public class DynamicLayoutWrapper implements Layout<String> { private final PatternLayout delegate; @PluginFactory public static DynamicLayoutWrapper createWrapper( @PluginConfiguration Configuration config, @PluginAttribute(value = "pattern") String pattern) { return new DynamicLayoutWrapper(PatternLayout.newBuilder() .withPattern(pattern) .withConfiguration(config) .build()); } private DynamicLayoutWrapper(PatternLayout delegate) { this.delegate = delegate; } @Override public String toSerializable(LogEvent event) { String depth = event.getContextData().getValue("stackDepth"); if (depth != null) { String modifiedPattern = delegate.getPattern() .replace("%throwable", "%throwable{" + depth + "}"); return PatternLayout.newBuilder() .withPattern(modifiedPattern) .build() .toSerializable(event); } return delegate.toSerializable(event); } } ``` #### 2. 配置中使用 ```xml <Console name="Console" target="SYSTEM_OUT"> <DynamicLayoutWrapper pattern="%d %p %c{1.} [%t] %m%n%throwable"/> </Console> ``` --- ### 关键注意事项 1. **性能优化**:方案一(PatternSelector)性能最佳,推荐生产环境使用 2. **ThreadContext 管理**:使用后及时清理线程变量 ```java ThreadContext.remove("stackDepth"); ``` 3. **默认值处理**:在自定义组件中始终提供默认深度值 ```java int depth = NumberUtils.toInt(ctxValue, 10); // 默认10行 ``` 4. **异常安全**:处理 `NumberFormatException` 等边界情况 5. **配置简化**:静态深度需求优先使用原生功能[^2]: ```xml <PatternLayout pattern="%d %p %c %m%n%throwable{5}"/> ``` --- ### 日志框架选型建议 | 特性 | Log4j2 | Logback | java.util.logging | |------|--------|---------|-------------------| | 性能 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | | 扩展性 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐ | | 配置灵活性 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | | 云原生支持 | ⭐⭐⭐⭐ | ⭐⭐ | ⭐ | 对于需要深度定制的场景,Log4j2 的插件体系仍是最佳选择[^1][^3]。对于新项目,可考虑 Logback 作为替代方案[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值