Adds Spring Session support (#1961)

* Adds Spring Session support

* Changes following the review
This commit is contained in:
Marcin Grzejszczak
2021-06-09 16:11:23 +02:00
committed by GitHub
parent 821bc3abd4
commit 4fcc7cc32e
30 changed files with 894 additions and 22 deletions

View File

@@ -507,6 +507,46 @@ Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.in
|method|Method name that got annotated with @Scheduled.
|===
=== Session Create Span
> Span created when a new session has to be created.
**Span name** `session.create`.
Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.instrument.session.SleuthSessionSpan`
=== Session Delete Span
> Span created when a session is deleted.
**Span name** `session.delete`.
Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.instrument.session.SleuthSessionSpan`
=== Session Find Span
> Span created when a new session is searched for.
**Span name** `session.find`.
Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.instrument.session.SleuthSessionSpan`
IMPORTANT: All tags and events must be prefixed with `session.` prefix!
.Tag Keys
|===
|Name | Description
|session.index.name|
|===
=== Session Save Span
> Span created when a new session is saved.
**Span name** `session.save`.
Fully qualified name of the enclosing class `org.springframework.cloud.sleuth.instrument.session.SleuthSessionSpan`
=== Task Execution Listener Span
> Span created within the lifecycle of a task.

View File

@@ -714,3 +714,11 @@ Please check the <<appendix.adoc#appendix,appendix>> page under `spring.sleuth.j
You can configure P6Spy manually using one of available configuration methods. For more information please refer to the http://p6spy.readthedocs.io/en/latest/configandusage.html[P6Spy Configuration Guide].
In order to disable this instrumentation set `spring.sleuth.jdbc.enabled` to `false`.
[[sleuth-session-integration]]
== Spring Session
This feature is available for all tracer implementations.
We're instrumenting the `Session` repositories that wraps all operations in a span.
In order to disable this instrumentation set `spring.sleuth.session.enabled` to `false`.

View File

@@ -213,6 +213,11 @@
<artifactId>HikariCP</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<optional>true</optional>
</dependency>
<!-- BRAVE -->
<dependency>
<groupId>org.springframework.cloud</groupId>

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2013-2021 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.autoconfig.instrument.session;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.sleuth.CurrentTraceContext;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.autoconfig.brave.BraveAutoConfiguration;
import org.springframework.cloud.sleuth.instrument.session.TraceSessionRepositoryAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.SessionRepository;
/**
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration
* Auto-configuration} that registers instrumentation for Spring Session.
*
* @author Marcin Grzejszczak
* @since 3.1.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SessionRepository.class)
@ConditionalOnProperty(value = "spring.sleuth.session.enabled", matchIfMissing = true)
@ConditionalOnBean(Tracer.class)
@AutoConfigureAfter(BraveAutoConfiguration.class)
public class TraceSessionAutoConfiguration {
// Bean post processors don't work cause session-redis requires concrete classes
@Bean
TraceSessionRepositoryAspect traceSessionRepositoryAspect(Tracer tracer, CurrentTraceContext currentTraceContext) {
return new TraceSessionRepositoryAspect(tracer, currentTraceContext);
}
}

View File

@@ -185,6 +185,12 @@
"description": "Enable R2dbc instrumentation.",
"defaultValue": true
},
{
"name": "spring.sleuth.session.enabled",
"type": "java.lang.Boolean",
"description": "Enable Spring Session instrumentation.",
"defaultValue": true
},
{
"name": "spring.sleuth.vault.enabled",
"type": "java.lang.Boolean",

View File

@@ -18,6 +18,7 @@ org.springframework.cloud.sleuth.autoconfig.instrument.web.client.TraceWebClient
org.springframework.cloud.sleuth.autoconfig.instrument.web.client.feign.TraceFeignClientAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.web.client.TraceWebAsyncClientAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.scheduling.TraceSchedulingAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.session.TraceSessionAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.reactor.TraceReactorAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.messaging.TraceFunctionAutoConfiguration,\
org.springframework.cloud.sleuth.autoconfig.instrument.messaging.TraceSpringIntegrationAutoConfiguration,\

View File

@@ -30,6 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
@@ -62,7 +63,7 @@ public class BraveRpcAutoConfigurationIntegrationTests {
@EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class,
R2dbcDataAutoConfiguration.class })
R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class })
@Configuration(proxyBeanMethods = false)
public static class Config {

View File

@@ -43,6 +43,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
@@ -150,8 +151,8 @@ public class WebClientTests {
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class, MongoAutoConfiguration.class,
MongoDataAutoConfiguration.class })
R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class,
MongoAutoConfiguration.class, MongoDataAutoConfiguration.class })
@DisableSecurity
public static class TestConfiguration {

View File

@@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
@@ -53,7 +54,7 @@ public class TraceAsyncDefaultAutoConfigurationTests {
@EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class,
R2dbcDataAutoConfiguration.class })
R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class })
static class Config {
@Bean

View File

@@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
@@ -115,10 +116,12 @@ public class TraceQuartzAutoConfigurationTest {
}
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class,
R2dbcDataAutoConfiguration.class })
@EnableAutoConfiguration(
exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class,
R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class },
excludeName = "org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration")
public static class EnableAutoConfig {
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2013-2021 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.autoconfig.instrument.session;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.sleuth.autoconfig.TraceNoOpAutoConfiguration;
import org.springframework.cloud.sleuth.instrument.session.TraceSessionRepositoryAspect;
import org.springframework.session.SessionRepository;
class TraceSessionAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withPropertyValues("spring.sleuth.noop.enabled=true").withConfiguration(
AutoConfigurations.of(TraceNoOpAutoConfiguration.class, TraceSessionAutoConfiguration.class));
@Test
void should_register_session_aspect() {
this.contextRunner
.run(context -> Assertions.assertThat(context).hasSingleBean(TraceSessionRepositoryAspect.class));
}
@Test
void should_not_register_session_aspect_when_session_repository_not_on_classpath() {
this.contextRunner.withClassLoader(new FilteredClassLoader(SessionRepository.class))
.run(context -> Assertions.assertThat(context).doesNotHaveBean(TraceSessionRepositoryAspect.class));
}
@Test
void should_not_register_session_aspect_when_session_instrumentation_is_disabled() {
this.contextRunner.withPropertyValues("spring.sleuth.session.enabled=false")
.run(context -> Assertions.assertThat(context).doesNotHaveBean(TraceSessionRepositoryAspect.class));
}
}

View File

@@ -30,6 +30,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
@@ -123,7 +124,7 @@ public class BraveWebClientAutoConfigurationTests {
@EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class,
R2dbcDataAutoConfiguration.class })
R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class })
static class Config {
// custom builder

View File

@@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
@@ -57,7 +58,7 @@ public class GH846Tests {
@EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class,
R2dbcDataAutoConfiguration.class })
R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class })
static class App {
@Bean

View File

@@ -21,6 +21,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
@@ -46,7 +47,7 @@ public class TraceWebClientDisabledTests {
@EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class,
R2dbcDataAutoConfiguration.class })
R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class })
public static class Config {
}

View File

@@ -23,6 +23,7 @@ import org.springframework.boot.actuate.autoconfigure.security.servlet.Managemen
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
@@ -53,10 +54,12 @@ public class ZipkinSamplerTests {
}
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration(exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class,
R2dbcDataAutoConfiguration.class })
@EnableAutoConfiguration(
exclude = { GatewayClassPathWarningAutoConfiguration.class, GatewayAutoConfiguration.class,
GatewayMetricsAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class,
MongoAutoConfiguration.class, QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class,
R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class },
excludeName = "org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration")
static class TestConfig {
}

View File

@@ -1,3 +1,3 @@
logging.level.org.springframework.cloud: DEBUG
spring.autoconfigure.exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration, org.springframework.cloud.gateway.config.GatewayAutoConfiguration, org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration, org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration, org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration, org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration, org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration, org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration
spring.autoconfigure.exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration, org.springframework.cloud.gateway.config.GatewayAutoConfiguration, org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration, org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration, org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration, org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration, org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration, org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration, org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration, org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

View File

@@ -197,6 +197,11 @@
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>p6spy</groupId>

View File

@@ -24,7 +24,7 @@ import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
import org.springframework.cloud.sleuth.Tracer;
/**
* Aspec around {@link CircuitBreaker} creation.
* Aspect around {@link CircuitBreaker} creation.
*
* @author Marcin Grzejszczak
* @since 3.0.0

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2013-2021 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.session;
import org.springframework.cloud.sleuth.docs.DocumentedSpan;
import org.springframework.cloud.sleuth.docs.TagKey;
enum SleuthSessionSpan implements DocumentedSpan {
/**
* Span created when a new session has to be created.
*/
SESSION_CREATE_SPAN {
@Override
public String getName() {
return "session.create";
}
},
/**
* Span created when a new session is searched for.
*/
SESSION_FIND_SPAN {
@Override
public String getName() {
return "session.find";
}
@Override
public TagKey[] getTagKeys() {
return Tags.values();
}
@Override
public String prefix() {
return "session.";
}
},
/**
* Span created when a new session is saved.
*/
SESSION_SAVE_SPAN {
@Override
public String getName() {
return "session.save";
}
},
/**
* Span created when a session is deleted.
*/
SESSION_DELETE_SPAN {
@Override
public String getName() {
return "session.delete";
}
};
enum Tags implements TagKey {
INDEX_NAME {
@Override
public String getKey() {
return "session.index.name";
}
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2018-2021 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.session;
import java.util.Map;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.docs.AssertingSpan;
import org.springframework.session.FindByIndexNameSessionRepository;
class TraceFindByIndexNameSessionRepository extends TraceSessionRepository implements FindByIndexNameSessionRepository {
private final FindByIndexNameSessionRepository delegate;
TraceFindByIndexNameSessionRepository(Tracer tracer, FindByIndexNameSessionRepository delegate) {
super(tracer, delegate);
this.delegate = delegate;
}
@Override
public Map findByPrincipalName(String principalName) {
AssertingSpan span = newSessionFindSpan();
try (Tracer.SpanInScope ws = this.tracer.withSpan(span.start())) {
return this.delegate.findByPrincipalName(principalName);
}
finally {
span.end();
}
}
private AssertingSpan newSessionFindSpan() {
return AssertingSpan.of(SleuthSessionSpan.SESSION_FIND_SPAN, this.tracer.nextSpan())
.name(SleuthSessionSpan.SESSION_FIND_SPAN.getName());
}
@Override
public Map findByIndexNameAndIndexValue(String indexName, String indexValue) {
AssertingSpan span = newSessionFindSpan();
try (Tracer.SpanInScope ws = this.tracer.withSpan(span.start())) {
span.tag(SleuthSessionSpan.Tags.INDEX_NAME, indexName);
return this.delegate.findByIndexNameAndIndexValue(indexName, indexValue);
}
finally {
span.end();
}
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2018-2021 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.session;
import reactor.core.publisher.Mono;
import org.springframework.cloud.sleuth.CurrentTraceContext;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.instrument.reactor.ReactorSleuth;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.Session;
class TraceReactiveSessionRepository implements ReactiveSessionRepository {
private final ReactiveSessionRepository delegate;
private final Tracer tracer;
private final CurrentTraceContext currentTraceContext;
TraceReactiveSessionRepository(Tracer tracer, CurrentTraceContext currentTraceContext,
ReactiveSessionRepository delegate) {
this.delegate = delegate;
this.tracer = tracer;
this.currentTraceContext = currentTraceContext;
}
@Override
public Mono createSession() {
return ReactorSleuth.tracedMono(this.tracer, this.currentTraceContext,
SleuthSessionSpan.SESSION_CREATE_SPAN.getName(), () -> this.delegate.createSession());
}
@Override
public Mono<Void> save(Session session) {
return ReactorSleuth.tracedMono(this.tracer, this.currentTraceContext,
SleuthSessionSpan.SESSION_SAVE_SPAN.getName(), () -> this.delegate.save(session));
}
@Override
public Mono findById(String id) {
return ReactorSleuth.tracedMono(this.tracer, this.currentTraceContext,
SleuthSessionSpan.SESSION_FIND_SPAN.getName(), () -> this.delegate.findById(id));
}
@Override
public Mono<Void> deleteById(String id) {
return ReactorSleuth.tracedMono(this.tracer, this.currentTraceContext,
SleuthSessionSpan.SESSION_DELETE_SPAN.getName(), () -> this.delegate.deleteById(id));
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2018-2021 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.session;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.cloud.sleuth.docs.AssertingSpan;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
class TraceSessionRepository implements SessionRepository {
private static final Log log = LogFactory.getLog(TraceSessionRepository.class);
private final SessionRepository delegate;
protected final Tracer tracer;
TraceSessionRepository(Tracer tracer, SessionRepository delegate) {
this.delegate = delegate;
this.tracer = tracer;
}
@Override
public Session createSession() {
return wrap(SleuthSessionSpan.SESSION_CREATE_SPAN, (Supplier<Session>) this.delegate::createSession);
}
private <T> T wrap(SleuthSessionSpan sessionSpan, Supplier<T> supplier) {
AssertingSpan span = newSpan(sessionSpan);
if (log.isDebugEnabled()) {
log.debug(
"Wrapping call in a span with name [" + span.getDocumentedSpan().getName() + "] - [" + span + "]");
}
try (Tracer.SpanInScope ws = this.tracer.withSpan(span.start())) {
return supplier.get();
}
finally {
span.end();
}
}
private void wrap(SleuthSessionSpan sessionSpan, Runnable runnable) {
wrap(sessionSpan, () -> {
runnable.run();
return null;
});
}
private AssertingSpan newSpan(SleuthSessionSpan sessionCreateSpan) {
return AssertingSpan.of(sessionCreateSpan, this.tracer.nextSpan()).name(sessionCreateSpan.getName());
}
@Override
public void save(Session session) {
wrap(SleuthSessionSpan.SESSION_SAVE_SPAN, () -> this.delegate.save(session));
}
@Override
public Session findById(String id) {
return wrap(SleuthSessionSpan.SESSION_FIND_SPAN, () -> this.delegate.findById(id));
}
@Override
public void deleteById(String id) {
wrap(SleuthSessionSpan.SESSION_DELETE_SPAN, () -> this.delegate.deleteById(id));
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright 2018-2021 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.session;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cloud.sleuth.CurrentTraceContext;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.ReactiveSessionRepository;
import org.springframework.session.SessionRepository;
import org.springframework.util.ReflectionUtils;
/**
* Aspect around {@link SessionRepository} and {@link ReactiveSessionRepository} method
* execution.
*
* @author Marcin Grzejszczak
* @since 3.1.0
*/
@Aspect
public class TraceSessionRepositoryAspect {
private static final Log log = LogFactory.getLog(TraceSessionRepositoryAspect.class);
private final Tracer tracer;
private final CurrentTraceContext currentTraceContext;
public TraceSessionRepositoryAspect(Tracer tracer, CurrentTraceContext currentTraceContext) {
this.tracer = tracer;
this.currentTraceContext = currentTraceContext;
}
// RedisIndexedSessionRepository
@Around("execution(public * org.springframework.session.SessionRepository.*(..))")
public Object wrapSessionRepository(ProceedingJoinPoint pjp) throws Throwable {
SessionRepository target = (SessionRepository) pjp.getTarget();
if (target instanceof TraceSessionRepository) {
return pjp.proceed();
}
target = wrapSessionRepository(target);
return callMethodOnWrappedObject(pjp, target);
}
private SessionRepository wrapSessionRepository(SessionRepository target) {
if (target instanceof FindByIndexNameSessionRepository) {
return new TraceFindByIndexNameSessionRepository(this.tracer, (FindByIndexNameSessionRepository) target);
}
return new TraceSessionRepository(this.tracer, target);
}
private <T> Object callMethodOnWrappedObject(ProceedingJoinPoint pjp, T target) throws Throwable {
Method method = getMethod(pjp, target);
if (method != null) {
if (log.isDebugEnabled()) {
log.debug("Found a corresponding method on the trace representation [" + method + "]");
}
return method.invoke(target, pjp.getArgs());
}
if (log.isDebugEnabled()) {
log.debug("Method [" + pjp.getSignature().getName()
+ "] not found on the trace representation. Will run the original one.");
}
return pjp.proceed();
}
@Around("execution(public * org.springframework.session.ReactiveSessionRepository.*(..))")
public Object wrapReactiveSessionRepository(ProceedingJoinPoint pjp) throws Throwable {
ReactiveSessionRepository target = (ReactiveSessionRepository) pjp.getTarget();
if (target instanceof TraceReactiveSessionRepository) {
return pjp.proceed();
}
target = new TraceReactiveSessionRepository(this.tracer, this.currentTraceContext, target);
return callMethodOnWrappedObject(pjp, target);
}
private Method getMethod(ProceedingJoinPoint pjp, Object tracingWrapper) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
Method foundMethodOnTracingWrapper = ReflectionUtils.findMethod(tracingWrapper.getClass(), method.getName(),
method.getParameterTypes());
if (foundMethodOnTracingWrapper != null) {
if (log.isDebugEnabled()) {
log.debug("Found an exact match for method execution [" + foundMethodOnTracingWrapper + "]");
}
return foundMethodOnTracingWrapper;
}
Method[] uniquePublicDeclaredMethodsOnTracingWrapper = ReflectionUtils
.getUniqueDeclaredMethods(tracingWrapper.getClass(), m -> Modifier.isPublic(m.getModifiers()));
if (uniquePublicDeclaredMethodsOnTracingWrapper.length == 0) {
return null;
}
if (log.isTraceEnabled()) {
log.trace("Will pick one of the unique declared methods ["
+ Arrays.toString(uniquePublicDeclaredMethodsOnTracingWrapper) + "] that has a name ["
+ method.getName() + "]");
}
Object[] argsOnOriginalObject = pjp.getArgs();
return Arrays.stream(uniquePublicDeclaredMethodsOnTracingWrapper)
.filter(m -> m.getName().equals(method.getName())
&& paramsAreOfSameTyperInherited(argsOnOriginalObject, m.getParameterTypes()))
.findFirst().orElse(null);
}
private boolean paramsAreOfSameTyperInherited(Object[] argsOnOriginalObject, Class<?>[] typeOnTracingWrapper) {
if (argsOnOriginalObject.length != typeOnTracingWrapper.length) {
return false;
}
for (int i = 0; i < argsOnOriginalObject.length; i++) {
Class<?> argType = argsOnOriginalObject[i].getClass();
Class<?> typeOnWrapper = typeOnTracingWrapper[i];
if (!typeOnWrapper.isAssignableFrom(argType)) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2013-2021 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.session;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.sleuth.tracer.SimpleSpan;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.SessionRepository;
import static org.assertj.core.api.BDDAssertions.then;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
class TraceFindByIndexNameSessionRepositoryTests extends TraceSessionRepositoryTests {
FindByIndexNameSessionRepository delegate = mock(FindByIndexNameSessionRepository.class);
TraceFindByIndexNameSessionRepository traceSessionRepository = new TraceFindByIndexNameSessionRepository(
this.simpleTracer, this.delegate);
@Test
void should_trace_session_find_by_principle_name() {
this.traceSessionRepository.findByPrincipalName("foo");
verify(this.delegate).findByPrincipalName(any());
SimpleSpan lastSpan = this.simpleTracer.getLastSpan();
then(lastSpan.name).isEqualTo(SleuthSessionSpan.SESSION_FIND_SPAN.getName());
}
@Test
void should_trace_session_find_by_index_name_value() {
this.traceSessionRepository.findByIndexNameAndIndexValue("indexName1", "indexValue1");
verify(this.delegate).findByIndexNameAndIndexValue(any(), any());
SimpleSpan lastSpan = this.simpleTracer.getLastSpan();
then(lastSpan.name).isEqualTo(SleuthSessionSpan.SESSION_FIND_SPAN.getName());
then(lastSpan.tags).containsEntry(SleuthSessionSpan.Tags.INDEX_NAME.getKey(), "indexName1");
}
@Override
SessionRepository sessionRepositoryDelegate() {
return this.delegate;
}
@Override
SessionRepository sessionRepository() {
return this.traceSessionRepository;
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2013-2021 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.session;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.cloud.sleuth.tracer.SimpleCurrentTraceContext;
import org.springframework.cloud.sleuth.tracer.SimpleSpan;
import org.springframework.cloud.sleuth.tracer.SimpleTracer;
import org.springframework.session.ReactiveSessionRepository;
import static org.assertj.core.api.BDDAssertions.then;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
class TraceReactiveSessionRepositoryTests {
SimpleCurrentTraceContext simpleCurrentTraceContext = new SimpleCurrentTraceContext();
SimpleTracer simpleTracer = new SimpleTracer() {
@Override
public SimpleSpan nextSpan() {
SimpleSpan span = super.nextSpan();
simpleCurrentTraceContext.traceContext = span.context();
return span;
}
};
ReactiveSessionRepository delegate = mock(ReactiveSessionRepository.class);
ReactiveSessionRepository traceSessionRepository = new TraceReactiveSessionRepository(this.simpleTracer,
this.simpleCurrentTraceContext, delegate);
@Test
void should_trace_session_creation() {
given(this.delegate.createSession()).willReturn(Mono.empty());
this.traceSessionRepository.createSession().block(Duration.ofMillis(10));
verify(this.delegate).createSession();
then(this.simpleTracer.getLastSpan().name).isEqualTo(SleuthSessionSpan.SESSION_CREATE_SPAN.getName());
}
@Test
void should_trace_session_save() {
given(this.delegate.save(any())).willReturn(Mono.empty());
this.traceSessionRepository.save(null).block(Duration.ofMillis(10));
verify(this.delegate).save(any());
then(this.simpleTracer.getLastSpan().name).isEqualTo(SleuthSessionSpan.SESSION_SAVE_SPAN.getName());
}
@Test
void should_trace_session_find_by_id() {
given(this.delegate.findById(any())).willReturn(Mono.empty());
this.traceSessionRepository.findById("").block(Duration.ofMillis(10));
verify(this.delegate).findById(any());
then(this.simpleTracer.getLastSpan().name).isEqualTo(SleuthSessionSpan.SESSION_FIND_SPAN.getName());
}
@Test
void should_trace_session_delete_by_id() {
given(this.delegate.deleteById(any())).willReturn(Mono.empty());
this.traceSessionRepository.deleteById("").block(Duration.ofMillis(10));
verify(this.delegate).deleteById(any());
then(this.simpleTracer.getLastSpan().name).isEqualTo(SleuthSessionSpan.SESSION_DELETE_SPAN.getName());
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2013-2021 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.session;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.sleuth.tracer.SimpleTracer;
import org.springframework.session.SessionRepository;
import static org.assertj.core.api.BDDAssertions.then;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
class TraceSessionRepositoryTests {
SimpleTracer simpleTracer = new SimpleTracer();
SessionRepository delegate = mock(SessionRepository.class);
SessionRepository traceSessionRepository = new TraceSessionRepository(this.simpleTracer, delegate);
@Test
void should_trace_session_creation() {
sessionRepository().createSession();
verify(sessionRepositoryDelegate()).createSession();
then(this.simpleTracer.getLastSpan().name).isEqualTo(SleuthSessionSpan.SESSION_CREATE_SPAN.getName());
}
@Test
void should_trace_session_save() {
sessionRepository().save(null);
verify(sessionRepositoryDelegate()).save(any());
then(this.simpleTracer.getLastSpan().name).isEqualTo(SleuthSessionSpan.SESSION_SAVE_SPAN.getName());
}
@Test
void should_trace_session_find_by_id() {
sessionRepository().findById("");
verify(sessionRepositoryDelegate()).findById(any());
then(this.simpleTracer.getLastSpan().name).isEqualTo(SleuthSessionSpan.SESSION_FIND_SPAN.getName());
}
@Test
void should_trace_session_delete_by_id() {
sessionRepository().deleteById("");
verify(sessionRepositoryDelegate()).deleteById(any());
then(this.simpleTracer.getLastSpan().name).isEqualTo(SleuthSessionSpan.SESSION_DELETE_SPAN.getName());
}
SessionRepository sessionRepository() {
return this.traceSessionRepository;
}
SessionRepository sessionRepositoryDelegate() {
return this.delegate;
}
}

View File

@@ -104,4 +104,11 @@ public class SimpleSpan implements Span {
return this;
}
@Override
public String toString() {
return "SimpleSpan{" + "tags=" + tags + ", started=" + started + ", ended=" + ended + ", throwable=" + throwable
+ ", remoteServiceName='" + remoteServiceName + '\'' + ", spanKind=" + spanKind + ", events=" + events
+ ", name='" + name + '\'' + '}';
}
}

View File

@@ -22,6 +22,7 @@ import brave.sampler.Sampler;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
@@ -74,8 +75,9 @@ public class MultipleHopsIntegrationTests
}
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration(exclude = { MongoAutoConfiguration.class, QuartzAutoConfiguration.class,
JmxAutoConfiguration.class, R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class })
@EnableAutoConfiguration(
exclude = { MongoAutoConfiguration.class, QuartzAutoConfiguration.class, JmxAutoConfiguration.class,
R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class })
static class Config {
@Bean

View File

@@ -36,6 +36,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration;
import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
@@ -168,8 +169,9 @@ public class JmsTracingConfigurationTest {
}
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration(exclude = { KafkaAutoConfiguration.class, MongoAutoConfiguration.class,
QuartzAutoConfiguration.class, R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class })
@EnableAutoConfiguration(
exclude = { KafkaAutoConfiguration.class, MongoAutoConfiguration.class, QuartzAutoConfiguration.class,
R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class })
class JmsTestTracingConfiguration {
}

View File

@@ -23,6 +23,7 @@ import brave.test.TestSpanHandler;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
@@ -33,7 +34,7 @@ import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class })
R2dbcAutoConfiguration.class, R2dbcDataAutoConfiguration.class, RedisAutoConfiguration.class })
@ImportResource("classpath:beans/applicationContext.xml")
@EnableIntegration
@EnableAsync