blob: f11c575465332162172b162578fe8024b8212e8a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
*
* http://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 com.alibaba.boot.dubbo.actuate.endpoint.mvc;
import com.alibaba.boot.dubbo.actuate.endpoint.DubboEndpoint;
import com.alibaba.dubbo.config.*;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.spring.ReferenceBean;
import com.alibaba.dubbo.config.spring.ServiceBean;
import com.alibaba.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.InjectionMetadata;
import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter;
import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.http.MediaType;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
import static com.alibaba.boot.dubbo.actuate.endpoint.DubboEndpoint.*;
import static com.alibaba.boot.dubbo.util.DubboUtils.filterDubboProperties;
import static com.alibaba.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor.BEAN_NAME;
import static com.alibaba.dubbo.registry.support.AbstractRegistryFactory.getRegistries;
import static org.springframework.beans.factory.BeanFactoryUtils.beansOfTypeIncludingAncestors;
import static org.springframework.util.ClassUtils.isPrimitiveOrWrapper;
/**
* {@link MvcEndpoint} to expose Dubbo Metadata
*
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
* @see MvcEndpoint
* @since 1.0.0
*/
public class DubboMvcEndpoint extends EndpointMvcAdapter implements ApplicationContextAware, EnvironmentAware {
private final Logger logger = LoggerFactory.getLogger(getClass());
private ApplicationContext applicationContext;
private ConfigurableEnvironment environment;
public DubboMvcEndpoint(DubboEndpoint dubboEndpoint) {
super(dubboEndpoint);
}
@RequestMapping(value = DUBBO_SHUTDOWN_ENDPOINT_URI, method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public DeferredResult shutdown() throws Exception {
DeferredResult result = new DeferredResult();
Map<String, Object> shutdownCountData = new LinkedHashMap<>();
// registries
int registriesCount = getRegistries().size();
// protocols
int protocolsCount = getProtocolConfigsBeanMap().size();
ProtocolConfig.destroyAll();
shutdownCountData.put("registries", registriesCount);
shutdownCountData.put("protocols", protocolsCount);
// Service Beans
Map<String, ServiceBean> serviceBeansMap = getServiceBeansMap();
if (!serviceBeansMap.isEmpty()) {
for (ServiceBean serviceBean : serviceBeansMap.values()) {
serviceBean.destroy();
}
}
shutdownCountData.put("services", serviceBeansMap.size());
// Reference Beans
ReferenceAnnotationBeanPostProcessor beanPostProcessor = getReferenceAnnotationBeanPostProcessor();
int referencesCount = beanPostProcessor.getReferenceBeans().size();
beanPostProcessor.destroy();
shutdownCountData.put("references", referencesCount);
// Set Result to complete
Map<String, Object> shutdownData = new TreeMap<>();
shutdownData.put("shutdown.count", shutdownCountData);
result.setResult(shutdownData);
return result;
}
@RequestMapping(value = DUBBO_CONFIGS_ENDPOINT_URI, method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Map<String, Map<String, Map<String, Object>>> configs() {
Map<String, Map<String, Map<String, Object>>> configsMap = new LinkedHashMap<>();
addDubboConfigBeans(ApplicationConfig.class, configsMap);
addDubboConfigBeans(ConsumerConfig.class, configsMap);
addDubboConfigBeans(MethodConfig.class, configsMap);
addDubboConfigBeans(ModuleConfig.class, configsMap);
addDubboConfigBeans(MonitorConfig.class, configsMap);
addDubboConfigBeans(ProtocolConfig.class, configsMap);
addDubboConfigBeans(ProviderConfig.class, configsMap);
addDubboConfigBeans(ReferenceConfig.class, configsMap);
addDubboConfigBeans(RegistryConfig.class, configsMap);
addDubboConfigBeans(ServiceConfig.class, configsMap);
return configsMap;
}
@RequestMapping(value = DUBBO_SERVICES_ENDPOINT_URI, method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Map<String, Map<String, Object>> services() {
Map<String, ServiceBean> serviceBeansMap = getServiceBeansMap();
Map<String, Map<String, Object>> servicesMetadata = new LinkedHashMap<>(serviceBeansMap.size());
for (Map.Entry<String, ServiceBean> entry : serviceBeansMap.entrySet()) {
String serviceBeanName = entry.getKey();
ServiceBean serviceBean = entry.getValue();
Map<String, Object> serviceBeanMetadata = resolveBeanMetadata(serviceBean);
Object service = resolveServiceBean(serviceBeanName, serviceBean);
if (service != null) {
// Add Service implementation class
serviceBeanMetadata.put("serviceClass", service.getClass().getName());
}
servicesMetadata.put(serviceBeanName, serviceBeanMetadata);
}
return servicesMetadata;
}
@RequestMapping(value = DUBBO_REFERENCES_ENDPOINT_URI, method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Map<String, Map<String, Object>> references() {
Map<String, Map<String, Object>> referencesMetadata = new LinkedHashMap<>();
Map<InjectionMetadata.InjectedElement, ReferenceBean<?>> injectedElementReferenceBeanMap
= resolveInjectedElementReferenceBeanMap();
for (Map.Entry<InjectionMetadata.InjectedElement, ReferenceBean<?>> entry :
injectedElementReferenceBeanMap.entrySet()) {
InjectionMetadata.InjectedElement injectedElement = entry.getKey();
ReferenceBean<?> referenceBean = entry.getValue();
Map<String, Object> beanMetadata = resolveBeanMetadata(referenceBean);
beanMetadata.put("invoker", resolveBeanMetadata(referenceBean.get()));
referencesMetadata.put(String.valueOf(injectedElement.getMember()), beanMetadata);
}
return referencesMetadata;
}
@RequestMapping(value = DUBBO_PROPERTIES_ENDPOINT_URI, method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public SortedMap<String, Object> properties() {
return filterDubboProperties(environment);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void setEnvironment(Environment environment) {
if (environment instanceof ConfigurableEnvironment) {
this.environment = (ConfigurableEnvironment) environment;
}
}
private Map<String, ServiceBean> getServiceBeansMap() {
return beansOfTypeIncludingAncestors(applicationContext, ServiceBean.class);
}
private void addDubboConfigBeans(Class<? extends AbstractConfig> dubboConfigClass,
Map<String, Map<String, Map<String, Object>>> configsMap) {
Map<String, ? extends AbstractConfig> dubboConfigBeans = beansOfTypeIncludingAncestors(applicationContext, dubboConfigClass);
String name = dubboConfigClass.getSimpleName();
Map<String, Map<String, Object>> beansMetadata = new TreeMap<>();
for (Map.Entry<String, ? extends AbstractConfig> entry : dubboConfigBeans.entrySet()) {
String beanName = entry.getKey();
AbstractConfig configBean = entry.getValue();
Map<String, Object> configBeanMeta = resolveBeanMetadata(configBean);
beansMetadata.put(beanName, configBeanMeta);
}
configsMap.put(name, beansMetadata);
}
private ReferenceAnnotationBeanPostProcessor getReferenceAnnotationBeanPostProcessor() {
return applicationContext.getBean(BEAN_NAME, ReferenceAnnotationBeanPostProcessor.class);
}
/**
* Resolves the {@link Collection} of {@link InjectionMetadata.InjectedElement} that were annotated by {@link Reference}
* from all Spring Beans.
*
* @return non-null {@link Collection}
* TODO Reactors ReferenceAnnotationBeanPostProcessor to expose those info
*/
private Map<InjectionMetadata.InjectedElement, ReferenceBean<?>> resolveInjectedElementReferenceBeanMap() {
Map<InjectionMetadata.InjectedElement, ReferenceBean<?>> injectedElementReferenceBeanMap = new LinkedHashMap<>();
final ReferenceAnnotationBeanPostProcessor processor = getReferenceAnnotationBeanPostProcessor();
ConcurrentMap<String, InjectionMetadata> injectionMetadataCache =
getFieldValue(processor, "injectionMetadataCache", ConcurrentMap.class);
ConcurrentMap<String, ReferenceBean<?>> referenceBeansCache =
getFieldValue(processor, "referenceBeansCache", ConcurrentMap.class);
for (InjectionMetadata metadata : injectionMetadataCache.values()) {
Set<InjectionMetadata.InjectedElement> checkedElements =
getFieldValue(metadata, "checkedElements", Set.class);
Collection<InjectionMetadata.InjectedElement> injectedElements =
getFieldValue(metadata, "injectedElements", Collection.class);
Collection<InjectionMetadata.InjectedElement> actualInjectedElements =
checkedElements != null ? checkedElements : injectedElements;
for (InjectionMetadata.InjectedElement injectedElement : actualInjectedElements) {
ReferenceBean<?> referenceBean = resolveReferenceBean(injectedElement, referenceBeansCache);
injectedElementReferenceBeanMap.put(injectedElement, referenceBean);
}
}
return injectedElementReferenceBeanMap;
}
private ReferenceBean<?> resolveReferenceBean(InjectionMetadata.InjectedElement injectedElement,
ConcurrentMap<String, ReferenceBean<?>> referenceBeansCache) {
// Member is Field or Method annotated @Reference
Member member = injectedElement.getMember();
Class<?> beanClass = null;
Reference reference = getFieldValue(injectedElement, "reference", Reference.class);
if (member instanceof Field) {
Field field = (Field) member;
beanClass = field.getType();
} else if (member instanceof Method) {
Method method = (Method) member;
beanClass = ((Method) member).getReturnType();
} else {
if (logger.isWarnEnabled()) {
logger.warn("What's wrong with Member? Member should not be Field or Method");
}
throw new IllegalStateException("What's wrong with Member? Member should not be Field or Method");
}
String referenceBeanCacheKey = generateReferenceBeanCacheKey(reference, beanClass);
return referenceBeansCache.get(referenceBeanCacheKey);
}
/**
* Original implementation :
*
* @see ReferenceAnnotationBeanPostProcessor#generateReferenceBeanCacheKey(Reference, java.lang.Class)
*/
private static String generateReferenceBeanCacheKey(Reference reference, Class<?> beanClass) {
String interfaceName = resolveInterfaceName(reference, beanClass);
String key = reference.group() + "/" + interfaceName + ":" + reference.version();
return key;
}
/**
* Original implementation:
*
* @see ReferenceAnnotationBeanPostProcessor#resolveInterfaceName(Reference, java.lang.Class)
*/
private static String resolveInterfaceName(Reference reference, Class<?> beanClass)
throws IllegalStateException {
String interfaceName;
if (!"".equals(reference.interfaceName())) {
interfaceName = reference.interfaceName();
} else if (!void.class.equals(reference.interfaceClass())) {
interfaceName = reference.interfaceClass().getName();
} else if (beanClass.isInterface()) {
interfaceName = beanClass.getName();
} else {
throw new IllegalStateException(
"The @Reference undefined interfaceClass or interfaceName, and the property type "
+ beanClass.getName() + " is not a interface.");
}
return interfaceName;
}
private <T> T getFieldValue(Object object, String fieldName, Class<T> fieldType) {
Field field = ReflectionUtils.findField(object.getClass(), fieldName, fieldType);
ReflectionUtils.makeAccessible(field);
return (T) ReflectionUtils.getField(field, object);
}
private Map<String, Object> resolveBeanMetadata(final Object bean) {
final Map<String, Object> beanMetadata = new LinkedHashMap<>();
try {
BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
Method readMethod = propertyDescriptor.getReadMethod();
if (readMethod != null && isSimpleType(propertyDescriptor.getPropertyType())) {
String name = Introspector.decapitalize(propertyDescriptor.getName());
Object value = readMethod.invoke(bean);
beanMetadata.put(name, value);
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return beanMetadata;
}
private Object resolveServiceBean(String serviceBeanName, ServiceBean serviceBean) {
int index = serviceBeanName.indexOf("#");
if (index > -1) {
Class<?> interfaceClass = serviceBean.getInterfaceClass();
String serviceName = serviceBeanName.substring(index + 1);
if (applicationContext.containsBean(serviceName)) {
return applicationContext.getBean(serviceName, interfaceClass);
}
}
return null;
}
private static boolean isSimpleType(Class<?> type) {
return isPrimitiveOrWrapper(type)
|| type == String.class
|| type == BigDecimal.class
|| type == BigInteger.class
|| type == Date.class
|| type == URL.class
|| type == Class.class
;
}
private Map<String, ProtocolConfig> getProtocolConfigsBeanMap() {
return beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class);
}
}