在单元测试中 Lombok 实例化之前加载 Spring 引导组件
Load Spring Boot component before Lombok instantation in unit test
我开发了一种 wrapper 以使其用作自定义记录器。我正在使用 @CustomLog
Lombok 注释实例化此 class 只是为了使其更容易和更清晰。接下来是棘手的事情:这个包装器背后的想法是使用一个公共记录器(如 org.slf4j.Logger
)和一个自定义监视器 class,每次我调用 log.error()
时,都会得到正确的消息登录终端并将事件发送到我的监控工具(在本例中为 Prometheus)。
为了实现这一点,我做了以下 classes:
CustomLoggerFactory
Lombok 调用的工厂来实例化我的自定义记录器。
public final class CustomLoggerFactory {
public static CustomLogger getLogger(String className) {
return new CustomLogger(className);
}
}
CustomLogger
将收到 class 名称,然后调用 org.slf4j.LoggerFactory
.
public class CustomLogger {
private org.slf4j.Logger logger;
private PrometheusMonitor prometheusMonitor;
private String className;
public CustomLogger(String className) {
this.logger = org.slf4j.LoggerFactory.getLogger(className);
this.className = className;
this.monitor = SpringContext.getBean(PrometheusMonitor.class);
}
}
PrometheusMonitor
class 是负责创建指标之类的人。这里最重要的是 它由 Spring Boot. 管理
@Component
public class PrometheusMonitor {
private MeterRegistry meterRegistry;
public PrometheusMonitor(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
}
- 您可能已经注意到,要从
CustomLogger
访问 PrometheusMonitor
我需要一个额外的 class 以便从非 Spring 获取 Bean/访问上下文管理 class。这是 SpringContext
class,它有一个 static
方法来通过提供的 class 获取 bean。
@Component
public class SpringContext implements ApplicationContextAware {
private static ApplicationContext context;
public static <T extends Object> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
SpringContext.context = context;
}
}
因此,当 运行 应用程序时,所有这些工作正常。我确保先加载 SpringContext
class,所以一旦每个 CustomLogger
被实例化,它就可以正常工作。
但是 大 问题出现了:这在对我的应用程序进行单元测试时不起作用。我尝试了很多事情,我看到了一些可能对我有帮助但我试图避免的解决方案(例如使用 PowerMockito)。在我添加到我的测试 class 的任何 @Before
方法之前,Lombok 正在处理 @CustomLog
注释。一旦 getBean()
方法被调用,我得到一个异常原因 context
是 null
.
我的猜测是,如果我可以 强制 在 Lombok 发挥它的魔力之前加载 SpringContext
,我可以解决它,但我不确定这是否均匀可能的。非常感谢您花时间阅读本文。如果我能提供更多信息,请告诉我。
注意:听起来您的自定义日志记录需求可以通过正常登录到 slf4j 并向 slf4j 框架注册一个额外的处理程序来更好地满足您的自定义日志记录需求,以便 slf4j 将任何日志转发给您(除了其他处理程序之外,例如制作日志文件的那个)。
Lombok is processing @CustomLog
生成的日志字段是静态的。如果注释有任何帮助,您需要 @BeforeClass
,但这可能也不是时候。龙目岛的魔法在这里似乎并不相关。查看 delombok 告诉您 lombok 正在做什么:它只是.. 一个静态字段,在声明时被初始化。
好吧,我设法通过稍微改变 CustomLogger
的工作方式来解决这个问题。这意味着您可以在第一次使用它时,而不是将 monitor
字段与记录器一起实例化。例如:
public class CustomLogger {
private org.slf4j.Logger logger;
private Monitor monitor;
public CustomLogger(String className) {
this.logger = org.slf4j.LoggerFactory.getLogger(className);
}
public void info(String message) {
this.logger.info(message);
}
public void error(String message) {
this.logger.error(message);
if (this.monitor == null) {
this.monitor = SpringContext.getBean(PrometheusMonitor.class);
}
this.monitor.send(message);
}
}
但毕竟我决定不遵循这种方法,因为我认为它不是最好的方法,也不值得。
我开发了一种 wrapper 以使其用作自定义记录器。我正在使用 @CustomLog
Lombok 注释实例化此 class 只是为了使其更容易和更清晰。接下来是棘手的事情:这个包装器背后的想法是使用一个公共记录器(如 org.slf4j.Logger
)和一个自定义监视器 class,每次我调用 log.error()
时,都会得到正确的消息登录终端并将事件发送到我的监控工具(在本例中为 Prometheus)。
为了实现这一点,我做了以下 classes:
CustomLoggerFactory
Lombok 调用的工厂来实例化我的自定义记录器。
public final class CustomLoggerFactory {
public static CustomLogger getLogger(String className) {
return new CustomLogger(className);
}
}
CustomLogger
将收到 class 名称,然后调用org.slf4j.LoggerFactory
.
public class CustomLogger {
private org.slf4j.Logger logger;
private PrometheusMonitor prometheusMonitor;
private String className;
public CustomLogger(String className) {
this.logger = org.slf4j.LoggerFactory.getLogger(className);
this.className = className;
this.monitor = SpringContext.getBean(PrometheusMonitor.class);
}
}
PrometheusMonitor
class 是负责创建指标之类的人。这里最重要的是 它由 Spring Boot. 管理
@Component
public class PrometheusMonitor {
private MeterRegistry meterRegistry;
public PrometheusMonitor(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
}
- 您可能已经注意到,要从
CustomLogger
访问PrometheusMonitor
我需要一个额外的 class 以便从非 Spring 获取 Bean/访问上下文管理 class。这是SpringContext
class,它有一个static
方法来通过提供的 class 获取 bean。
@Component
public class SpringContext implements ApplicationContextAware {
private static ApplicationContext context;
public static <T extends Object> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
SpringContext.context = context;
}
}
因此,当 运行 应用程序时,所有这些工作正常。我确保先加载 SpringContext
class,所以一旦每个 CustomLogger
被实例化,它就可以正常工作。
但是 大 问题出现了:这在对我的应用程序进行单元测试时不起作用。我尝试了很多事情,我看到了一些可能对我有帮助但我试图避免的解决方案(例如使用 PowerMockito)。在我添加到我的测试 class 的任何 @Before
方法之前,Lombok 正在处理 @CustomLog
注释。一旦 getBean()
方法被调用,我得到一个异常原因 context
是 null
.
我的猜测是,如果我可以 强制 在 Lombok 发挥它的魔力之前加载 SpringContext
,我可以解决它,但我不确定这是否均匀可能的。非常感谢您花时间阅读本文。如果我能提供更多信息,请告诉我。
注意:听起来您的自定义日志记录需求可以通过正常登录到 slf4j 并向 slf4j 框架注册一个额外的处理程序来更好地满足您的自定义日志记录需求,以便 slf4j 将任何日志转发给您(除了其他处理程序之外,例如制作日志文件的那个)。
Lombok is processing @CustomLog
生成的日志字段是静态的。如果注释有任何帮助,您需要 @BeforeClass
,但这可能也不是时候。龙目岛的魔法在这里似乎并不相关。查看 delombok 告诉您 lombok 正在做什么:它只是.. 一个静态字段,在声明时被初始化。
好吧,我设法通过稍微改变 CustomLogger
的工作方式来解决这个问题。这意味着您可以在第一次使用它时,而不是将 monitor
字段与记录器一起实例化。例如:
public class CustomLogger {
private org.slf4j.Logger logger;
private Monitor monitor;
public CustomLogger(String className) {
this.logger = org.slf4j.LoggerFactory.getLogger(className);
}
public void info(String message) {
this.logger.info(message);
}
public void error(String message) {
this.logger.error(message);
if (this.monitor == null) {
this.monitor = SpringContext.getBean(PrometheusMonitor.class);
}
this.monitor.send(message);
}
}
但毕竟我决定不遵循这种方法,因为我认为它不是最好的方法,也不值得。