Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.0] dubbo-spring-boot-actuator compatible with Spring Boot Actuator 2.6.x #9426

Merged
merged 1 commit into from Dec 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion dubbo-spring-boot/dubbo-spring-boot-actuator/README.md
Expand Up @@ -150,7 +150,7 @@ Actuator endpoint `dubbo` supports Actuator Endpoints :
| ------------------- | ----------- | ----------------------------------- | ------------------ | ------------------ | ------------------ |
| `dubbo` | `true` | `/actuator/dubbo` | `GET` | Exposes Dubbo's meta data | `application/json` |
| `dubboproperties` | `true` | `/actuator/dubbo/properties` | `GET` | Exposes all Dubbo's Properties | `application/json` |
| `dubboservices` | `false` | `/dubbo/services` | `GET` | Exposes all Dubbo's `ServiceBean` | `application/json` |
| `dubboservices` | `false` | `/actuator/dubbo/services` | `GET` | Exposes all Dubbo's `ServiceBean` | `application/json` |
| `dubboreferences` | `false` | `/actuator/dubbo/references` | `GET` | Exposes all Dubbo's `ReferenceBean` | `application/json` |
| `dubboconfigs` | `true` | `/actuator/dubbo/configs` | `GET` | Exposes all Dubbo's `*Config` | `application/json` |
| `dubboshutdown` | `false` | `/actuator/dubbo/shutdown` | `POST` | Shutdown Dubbo services | `application/json` |
Expand Down
Expand Up @@ -24,6 +24,7 @@
import org.apache.dubbo.spring.boot.actuate.endpoint.DubboShutdownEndpoint;
import org.apache.dubbo.spring.boot.actuate.endpoint.condition.CompatibleConditionalOnEnabledEndpoint;

import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
Expand All @@ -45,41 +46,47 @@ public class DubboEndpointAnnotationAutoConfiguration {

@Bean
@ConditionalOnMissingBean
@ConditionalOnAvailableEndpoint
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ConditionalOnAvailableEndpoint is available sine Spring Boot 2.2.0, see https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/actuate/autoconfigure/endpoint/condition/ConditionalOnAvailableEndpoint.html
If we reference @ConditionalOnAvailableEndpoint directly, it may cause loading failed on Spring Boot version < 2.2.0.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems missing annotation class will not cause ClassNotFoundException, please check it on Spring Boot version < 2.2.0.

@CompatibleConditionalOnEnabledEndpoint
public DubboMetadataEndpoint dubboEndpoint() {
return new DubboMetadataEndpoint();
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnAvailableEndpoint
@CompatibleConditionalOnEnabledEndpoint
public DubboConfigsMetadataEndpoint dubboConfigsMetadataEndpoint() {
return new DubboConfigsMetadataEndpoint();
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnAvailableEndpoint
@CompatibleConditionalOnEnabledEndpoint
public DubboPropertiesMetadataEndpoint dubboPropertiesEndpoint() {
return new DubboPropertiesMetadataEndpoint();
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnAvailableEndpoint
@CompatibleConditionalOnEnabledEndpoint
public DubboReferencesMetadataEndpoint dubboReferencesMetadataEndpoint() {
return new DubboReferencesMetadataEndpoint();
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnAvailableEndpoint
@CompatibleConditionalOnEnabledEndpoint
public DubboServicesMetadataEndpoint dubboServicesMetadataEndpoint() {
return new DubboServicesMetadataEndpoint();
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnAvailableEndpoint
@CompatibleConditionalOnEnabledEndpoint
public DubboShutdownEndpoint dubboShutdownEndpoint() {
return new DubboShutdownEndpoint();
Expand Down
Expand Up @@ -16,15 +16,16 @@
*/
package org.apache.dubbo.spring.boot.actuate.endpoint.condition;

import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;

import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;

import java.util.stream.Stream;

/**
* {@link Conditional} that checks whether or not an endpoint is enabled, which is compatible with
* org.springframework.boot.actuate.autoconfigure.endpoint.condition.OnEnabledEndpointCondition
Expand All @@ -35,35 +36,33 @@
*/
class CompatibleOnEnabledEndpointCondition implements Condition {

static String[] CONDITION_CLASS_NAMES = {
"org.springframework.boot.actuate.autoconfigure.endpoint.condition.OnAvailableEndpointCondition", // 2.2.0+
"org.springframework.boot.actuate.autoconfigure.endpoint.condition.OnEnabledEndpointCondition" // [2.0.0 , 2.2.x]
};
private static final Logger LOGGER = LoggerFactory.getLogger(CompatibleOnEnabledEndpointCondition.class);

// Spring Boot [2.0.0 , 2.2.x]
static String CONDITION_CLASS_NAME_OLD =
"org.springframework.boot.actuate.autoconfigure.endpoint.condition.OnEnabledEndpointCondition";

// Spring Boot 2.2.0 +
static String CONDITION_CLASS_NAME_NEW =
"org.springframework.boot.actuate.autoconfigure.endpoint.condition.OnAvailableEndpointCondition";


@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
ClassLoader classLoader = context.getClassLoader();

Condition condition = Stream.of(CONDITION_CLASS_NAMES) // Iterate class names
.filter(className -> ClassUtils.isPresent(className, classLoader)) // Search class existing or not by name
.findFirst() // Find the first candidate
.map(className -> ClassUtils.resolveClassName(className, classLoader)) // Resolve class name to Class
.filter(Condition.class::isAssignableFrom) // Accept the Condition implementation
.map(BeanUtils::instantiateClass) // Instantiate Class to be instance
.map(Condition.class::cast) // Cast the instance to be Condition one
.orElse(NegativeCondition.INSTANCE); // Or else get a negative condition

return condition.matches(context, metadata);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please try fix error of condition.matches(context, metadata) of OnAvailableEndpointCondition by add required attributes or re-construct the metadata, to compatible with spring boot 2.2.0+ and 2.6.1+.

//OnAvailableEndpointCondition of Spring Boot 2.6.1

	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
		Environment environment = context.getEnvironment();
		MergedAnnotation<ConditionalOnAvailableEndpoint> conditionAnnotation = metadata.getAnnotations()
				.get(ConditionalOnAvailableEndpoint.class);
		Class<?> target = getTarget(context, metadata, conditionAnnotation);
		MergedAnnotation<Endpoint> endpointAnnotation = getEndpointAnnotation(target);
		return getMatchOutcome(environment, conditionAnnotation, endpointAnnotation);
	}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gitchenjh If it can work in Spring Boot <2.2.0, it is more compatible to use ConditionalOnAvailableEndpoint directly. The only problem is that the code is a bit difficult to understand.

}

private static class NegativeCondition implements Condition {

static final NegativeCondition INSTANCE = new NegativeCondition();

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
if (ClassUtils.isPresent(CONDITION_CLASS_NAME_OLD, classLoader)) {
Class<?> cls = ClassUtils.resolveClassName(CONDITION_CLASS_NAME_OLD, classLoader);
if (Condition.class.isAssignableFrom(cls)) {
Condition condition = Condition.class.cast(BeanUtils.instantiateClass(cls));
return condition.matches(context, metadata);
}
}
// Check by org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint
if (ClassUtils.isPresent(CONDITION_CLASS_NAME_NEW, classLoader)) {
return true;
Comment on lines 51 to +62
Copy link
Member

@kylixs kylixs Dec 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good. The CompatibleOnEnabledEndpointCondition only delegate OnEnabledEndpointCondition, and do nothing with OnAvailableEndpointCondition. In this case, it looks clearer.

Copy link
Contributor Author

@gitchenjh gitchenjh Dec 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is CompatibleOnEnabledEndpointCondition#matches(context, metadata) return true if OnEnabledEndpointCondition class were not found.
But I'm not sure is there a possibility that both OnEnabledEndpointCondition and OnAvailableEndpointCondition class were not found(eg. Spring Boot 1) . So I let this method return false by default, if OnAvailableEndpointCondition class were found, then return true, and @ConditionalOnAvailableEndpoint should work

}
// No condition class found
LOGGER.warn(String.format("No condition class found, Dubbo Health Endpoint [%s] will not expose", metadata));
return false;
}
}