Ensures that all Lazy components are resolving any beans when context is running

fixes gh-1750
This commit is contained in:
Marcin Grzejszczak
2020-12-01 15:13:58 +01:00
parent cb03ca0f86
commit 55440c641e
17 changed files with 130 additions and 17 deletions

View File

@@ -52,6 +52,7 @@ import org.springframework.cloud.sleuth.DefaultSpanNamer;
import org.springframework.cloud.sleuth.LocalServiceName;
import org.springframework.cloud.sleuth.SpanAdjuster;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.internal.SleuthContextListener;
import org.springframework.cloud.sleuth.log.SleuthLogAutoConfiguration;
import org.springframework.cloud.sleuth.sampler.SamplerAutoConfiguration;
import org.springframework.context.annotation.Bean;
@@ -104,6 +105,11 @@ public class TraceAutoConfiguration {
return 0;
};
@Bean
SleuthContextListener traceContextClosedListener() {
return new SleuthContextListener();
}
@Bean
@ConditionalOnMissingBean
// NOTE: stable bean name as might be used outside sleuth

View File

@@ -19,7 +19,6 @@ package org.springframework.cloud.sleuth.instrument.async;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.sleuth.instrument.scheduling.SleuthSchedulingProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
@@ -38,9 +37,4 @@ import org.springframework.context.annotation.Configuration;
SleuthSchedulingProperties.class })
public class AsyncAutoConfiguration {
@Bean
SleuthContextListener traceContextClosedListener() {
return new SleuthContextListener();
}
}

View File

@@ -27,6 +27,7 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.cloud.sleuth.DefaultSpanNamer;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.internal.ContextUtil;
import org.springframework.core.task.AsyncTaskExecutor;
/**

View File

@@ -26,6 +26,7 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.cloud.sleuth.DefaultSpanNamer;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.internal.ContextUtil;
/**
* {@link Executor} that wraps {@link Runnable} in a trace representation.

View File

@@ -41,6 +41,7 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.cloud.sleuth.DefaultSpanNamer;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.internal.ContextUtil;
import org.springframework.util.ReflectionUtils;
/**

View File

@@ -30,6 +30,7 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.cloud.sleuth.DefaultSpanNamer;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.internal.ContextUtil;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.concurrent.ListenableFuture;

View File

@@ -37,6 +37,7 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.cloud.sleuth.DefaultSpanNamer;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.internal.ContextUtil;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

View File

@@ -30,6 +30,7 @@ import brave.Tracing;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.internal.ContextUtil;
/**
* A decorator class for {@link ExecutorService} to support tracing in Executors.

View File

@@ -23,6 +23,7 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.sleuth.internal.ContextUtil;
/**
* A decorator class for {@link ScheduledExecutorService} to support tracing in Executors.

View File

@@ -43,6 +43,7 @@ import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.cloud.commons.httpclient.HttpClientConfiguration;
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter;
import org.springframework.cloud.sleuth.instrument.web.TraceHttpAutoConfiguration;
import org.springframework.cloud.sleuth.internal.ContextUtil;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -309,10 +310,17 @@ class LazyTracingClientHttpRequestInterceptor implements ClientHttpRequestInterc
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
if (isContextUnusable()) {
return execution.execute(request, body);
}
return interceptor().intercept(request, body, execution);
}
private TracingClientHttpRequestInterceptor interceptor() {
boolean isContextUnusable() {
return ContextUtil.isContextUnusable(this.beanFactory);
}
ClientHttpRequestInterceptor interceptor() {
if (this.interceptor == null) {
this.interceptor = this.beanFactory
.getBean(TracingClientHttpRequestInterceptor.class);

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.async;
package org.springframework.cloud.sleuth.internal;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -22,12 +22,12 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
/**
* Utility class that verifies that context is in creation.
* Utility class that verifies that context is in creation. Do not use.
*
* @author Marcin Grzejszczak
* @since 2.1.0
*/
final class ContextUtil {
public final class ContextUtil {
private ContextUtil() {
throw new IllegalStateException("Can't instantiate a utility class");
@@ -35,7 +35,11 @@ final class ContextUtil {
private static final Log log = LogFactory.getLog(ContextUtil.class);
static boolean isContextUnusable(BeanFactory beanFactory) {
/**
* @param beanFactory bean facotry
* @return {@code true} when context is not ready to be used
*/
public static boolean isContextUnusable(BeanFactory beanFactory) {
SleuthContextListener listener = SleuthContextListener.getBean(beanFactory);
boolean contextUnusable = listener.isUnusable();
if (contextUnusable && log.isDebugEnabled()) {

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.async;
package org.springframework.cloud.sleuth.internal;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -32,7 +32,13 @@ import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.SmartApplicationListener;
class SleuthContextListener implements SmartApplicationListener {
/**
* Internal tool used by Sleuth. Do not use.
*
* @author Marcin Grzejszczak
* @since 2.2.5
*/
public class SleuthContextListener implements SmartApplicationListener {
static final Map<BeanFactory, SleuthContextListener> CACHE = new ConcurrentHashMap<>();
@@ -42,7 +48,7 @@ class SleuthContextListener implements SmartApplicationListener {
final AtomicBoolean closed;
SleuthContextListener() {
public SleuthContextListener() {
this.refreshed = new AtomicBoolean();
this.closed = new AtomicBoolean();
}
@@ -52,7 +58,13 @@ class SleuthContextListener implements SmartApplicationListener {
this.closed = closed;
}
static SleuthContextListener getBean(BeanFactory beanFactory) {
/**
* Returns an instance of the {@link SleuthContextListener} that might have already
* been initialized.
* @param beanFactory bean factory
* @return instance of {@link SleuthContextListener}
*/
public static SleuthContextListener getBean(BeanFactory beanFactory) {
return CACHE.getOrDefault(beanFactory, new SleuthContextListener());
}
@@ -83,7 +95,10 @@ class SleuthContextListener implements SmartApplicationListener {
}
}
boolean isUnusable() {
/**
* @return @{code true} when Spring Context has NOT yet been started
*/
public boolean isUnusable() {
return !this.refreshed.get() || this.closed.get();
}

View File

@@ -36,6 +36,7 @@ import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.sleuth.DefaultSpanNamer;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.internal.SleuthContextListenerAccessor;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.ErrorHandler;

View File

@@ -48,6 +48,7 @@ import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.sleuth.DefaultSpanNamer;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.internal.SleuthContextListenerAccessor;
import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.BDDAssertions.then;

View File

@@ -36,6 +36,7 @@ import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cloud.sleuth.DefaultSpanNamer;
import org.springframework.cloud.sleuth.SpanNamer;
import org.springframework.cloud.sleuth.internal.SleuthContextListenerAccessor;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.web.client;
import java.io.IOException;
import brave.spring.web.TracingClientHttpRequestInterceptor;
import org.junit.jupiter.api.Test;
import org.mockito.BDDMockito;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
class LazyTracingClientHttpRequestInterceptorTests {
@Test
void should_not_retrieve_bean_from_context_when_context_is_not_ready()
throws IOException {
BeanFactory beanFactory = mock(BeanFactory.class);
LazyTracingClientHttpRequestInterceptor interceptor = new LazyTracingClientHttpRequestInterceptor(
beanFactory);
interceptor.intercept(mock(HttpRequest.class), new byte[0],
mock(ClientHttpRequestExecution.class));
then(beanFactory).should(never())
.getBean(TracingClientHttpRequestInterceptor.class);
}
@Test
void should_retrieve_bean_from_context_when_context_is_ready() throws IOException {
BeanFactory beanFactory = mock(BeanFactory.class);
ClientHttpRequestInterceptor requestInterceptor = mock(
ClientHttpRequestInterceptor.class);
LazyTracingClientHttpRequestInterceptor interceptor = new LazyTracingClientHttpRequestInterceptor(
beanFactory) {
@Override
ClientHttpRequestInterceptor interceptor() {
return requestInterceptor;
}
@Override
boolean isContextUnusable() {
return false;
}
};
interceptor.intercept(mock(HttpRequest.class), new byte[0],
mock(ClientHttpRequestExecution.class));
then(requestInterceptor).should().intercept(BDDMockito.any(HttpRequest.class),
BDDMockito.any(byte[].class),
BDDMockito.any(ClientHttpRequestExecution.class));
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.cloud.sleuth.instrument.async;
package org.springframework.cloud.sleuth.internal;
import java.util.concurrent.atomic.AtomicBoolean;