support ArthasEnvironment. #986

This commit is contained in:
hengyunabc
2020-02-14 02:40:08 +08:00
parent 6e39e74ce6
commit 4937b99d12
31 changed files with 1037 additions and 11 deletions
@@ -0,0 +1,110 @@
package com.taobao.arthas.core.config;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import com.taobao.arthas.core.env.Environment;
/**
*
* @author hengyunabc 2020-01-10
*
*/
public class BinderUtils {
public static void inject(Environment environment, Object instance) {
inject(environment, null, null, instance);
}
public static void inject(Environment environment, String prefix, Object instance) {
inject(environment, null, prefix, instance);
}
public static void inject(Environment environment, String parentPrefix, String prefix, Object instance) {
Class<? extends Object> type = instance.getClass();
try {
Config annotation = type.getAnnotation(Config.class);
if (prefix == null) {
prefix = "";
}
if (annotation == null) {
prefix = parentPrefix + '.' + prefix;
} else {
prefix = annotation.prefix();
if (prefix != null) {
if (parentPrefix != null && parentPrefix.length() > 0) {
prefix = parentPrefix + '.' + prefix;
}
}
}
Method[] declaredMethods = type.getDeclaredMethods();
if (declaredMethods != null) {
// 获取到所有setter方法,再提取出field。根据前缀从 properties里取出值,再尝试用setter方法注入到对象里
for (Method method : declaredMethods) {
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes != null && parameterTypes.length == 1 && methodName.startsWith("set")
&& methodName.length() > "set".length()) {
String field = getFieldNameFromSetterMethod(methodName);
Object reslovedValue = environment.getProperty(prefix + '.' + field, parameterTypes[0]);
if (reslovedValue != null) {
method.invoke(instance, new Object[] { reslovedValue });
}
}
}
}
} catch (Exception e) {
throw new RuntimeException("inject error. prefix: " + prefix + ", instance: " + instance, e);
}
// process @NestedConfig
Field[] fields = type.getDeclaredFields();
if (fields != null) {
for (Field field : fields) {
NestedConfig nestedConfig = field.getAnnotation(NestedConfig.class);
if (nestedConfig != null) {
String prefixForField = field.getName();
if (parentPrefix != null && prefix.length() > 0) {
prefixForField = prefix + '.' + prefixForField;
}
field.setAccessible(true);
try {
Object fieldValue = field.get(instance);
if (fieldValue == null) {
fieldValue = field.getType().newInstance();
}
inject(environment, prefix, prefixForField, fieldValue);
field.set(instance, fieldValue);
} catch (Exception e) {
throw new RuntimeException("process @NestedConfig error, field: " + field + ", prefix: "
+ prefix + ", instance: " + instance, e);
}
}
}
}
}
/**
* 从setter方法获取到field的String。比如 setHost 则获取到的是host。
*
* @param methodName
* @return
*/
private static String getFieldNameFromSetterMethod(String methodName) {
String field = methodName.substring("set".length());
String startPart = field.substring(0, 1).toLowerCase();
String endPart = field.substring(1);
field = startPart + endPart;
return field;
}
}
@@ -0,0 +1,19 @@
package com.taobao.arthas.core.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author hengyunabc 2019-08-05
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Config {
String prefix() default "";
}
@@ -15,6 +15,7 @@ import static java.lang.reflect.Modifier.isStatic;
* @author vlinux
* @author hengyunabc 2018-11-12
*/
@Config(prefix = "arthas")
public class Configure {
public static final long DEFAULT_SESSION_TIMEOUT_SECONDS = ShellServerOptions.DEFAULT_SESSION_TIMEOUT/1000;
private String ip;
@@ -117,9 +118,6 @@ public class Configure {
this.statUrl = statUrl;
}
// 对象的编码解码器
private final static FeatureCodec codec = new FeatureCodec(';', '=');
/**
* 序列化成字符串
*
@@ -148,7 +146,7 @@ public class Configure {
}
return codec.toString(map);
return FeatureCodec.DEFAULT_COMMANDLINE_CODEC.toString(map);
}
/**
@@ -159,7 +157,7 @@ public class Configure {
*/
public static Configure toConfigure(String toString) throws IllegalAccessException {
final Configure configure = new Configure();
final Map<String, String> map = codec.toMap(toString);
final Map<String, String> map = FeatureCodec.DEFAULT_COMMANDLINE_CODEC.toMap(toString);
for (Map.Entry<String, String> entry : map.entrySet()) {
final Field field = ArthasReflectUtils.getField(Configure.class, entry.getKey());
@@ -16,6 +16,8 @@ import static com.taobao.arthas.core.util.StringUtils.isBlank;
* Created by dukun on 15/3/31.
*/
public class FeatureCodec {
// 对象的编码解码器
public final static FeatureCodec DEFAULT_COMMANDLINE_CODEC = new FeatureCodec(';', '=');
/**
* KV片段分割符<br/>
@@ -0,0 +1,17 @@
package com.taobao.arthas.core.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author hengyunabc 2019-08-05
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface NestedConfig {
}
@@ -0,0 +1,5 @@
package com.taobao.arthas.core.config;
public class SecondConfig {
}
@@ -0,0 +1,9 @@
package com.taobao.arthas.core.config;
@Config
public class TestConfig {
@NestedConfig
SecondConfig secondConfig;
}
@@ -19,6 +19,9 @@ package com.taobao.arthas.core.env;
import java.util.LinkedHashSet;
import java.util.Set;
import com.taobao.arthas.core.env.convert.ConfigurableConversionService;
import com.taobao.arthas.core.env.convert.DefaultConversionService;
/**
* Abstract base class for resolving properties against any underlying source.
*
@@ -27,6 +27,13 @@ public class ArthasEnvironment implements Environment {
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
/**
* Add the given property source object with highest precedence.
*/
public void addFirst(PropertySource<?> propertySource) {
this.propertySources.addFirst(propertySource);
}
/**
* Add the given property source object with lowest precedence.
*/
@@ -16,6 +16,8 @@
package com.taobao.arthas.core.env;
import com.taobao.arthas.core.env.convert.ConfigurableConversionService;
/**
* Configuration interface to be implemented by most if not all
* {@link PropertyResolver} types. Provides facilities for accessing and
@@ -14,7 +14,9 @@
* limitations under the License.
*/
package com.taobao.arthas.core.env;
package com.taobao.arthas.core.env.convert;
import com.taobao.arthas.core.env.ConversionService;
/**
* Configuration interface to be implemented by most if not all
@@ -28,7 +30,7 @@ package com.taobao.arthas.core.env;
*
* @author Chris Beams
* @since 3.1
* @see org.springframework.core.env.ConfigurablePropertyResolver#getConversionService()
* @see com.taobao.arthas.core.env.springframework.core.env.ConfigurablePropertyResolver#getConversionService()
* @see org.springframework.core.env.ConfigurableEnvironment
* @see org.springframework.context.ConfigurableApplicationContext#getEnvironment()
*/
@@ -0,0 +1,40 @@
/*
* Copyright 2002-2015 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
*
* 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.taobao.arthas.core.env.convert;
/**
* A converter converts a source object of type S to a target of type T.
* Implementations of this interface are thread-safe and can be shared.
*
* <p>Implementations may additionally implement {@link ConditionalConverter}.
*
* @author Keith Donald
* @since 3.0
* @param <S> The source type
* @param <T> The target type
*/
public interface Converter<S, T> {
/**
* Convert the source of type S to target type T.
* @param source the source object to convert, which must be an instance of S (never {@code null})
* @return the converted object, which must be an instance of T (potentially {@code null})
* @throws IllegalArgumentException if the source could not be converted to the desired target type
*/
T convert(S source, Class<T> targetType);
}
@@ -0,0 +1,52 @@
package com.taobao.arthas.core.env.convert;
/**
* Holder for a source-to-target class pair.
*/
public final class ConvertiblePair {
private final Class<?> sourceType;
private final Class<?> targetType;
/**
* Create a new source-to-target pair.
*
* @param sourceType the source type
* @param targetType the target type
*/
public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {
this.sourceType = sourceType;
this.targetType = targetType;
}
public Class<?> getSourceType() {
return this.sourceType;
}
public Class<?> getTargetType() {
return this.targetType;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || obj.getClass() != ConvertiblePair.class) {
return false;
}
ConvertiblePair other = (ConvertiblePair) obj;
return this.sourceType.equals(other.sourceType) && this.targetType.equals(other.targetType);
}
@Override
public int hashCode() {
return this.sourceType.hashCode() * 31 + this.targetType.hashCode();
}
@Override
public String toString() {
return this.sourceType.getName() + " -> " + this.targetType.getName();
}
}
@@ -1,17 +1,123 @@
package com.taobao.arthas.core.env;
package com.taobao.arthas.core.env.convert;
import java.lang.reflect.Array;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;
public class DefaultConversionService implements ConfigurableConversionService {
private static ConcurrentHashMap<ConvertiblePair, Converter> converters = new ConcurrentHashMap<ConvertiblePair, Converter>();
public DefaultConversionService() {
addDefaultConverter();
}
private void addDefaultConverter() {
converters.put(new ConvertiblePair(String.class, Integer.class), new StringToIntegerConverter());
converters.put(new ConvertiblePair(String.class, Long.class), new StringToLongConverter());
converters.put(new ConvertiblePair(String.class, Boolean.class), new StringToBooleanConverter());
converters.put(new ConvertiblePair(String.class, InetAddress.class), new StringToInetAddressConverter());
converters.put(new ConvertiblePair(String.class, Enum.class), new StringToEnumConverter());
converters.put(new ConvertiblePair(String.class, Arrays.class), new StringToArrayConverter(this));
}
@Override
public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
// TODO Auto-generated method stub
return true;
if (sourceType == targetType) {
return true;
}
if (targetType.isPrimitive()) {
targetType = objectiveClass(targetType);
}
if (converters.containsKey(new ConvertiblePair(sourceType, targetType))) {
return true;
}
if (targetType.isEnum()) {
if (converters.containsKey(new ConvertiblePair(sourceType, Enum.class))) {
return true;
}
}
if (targetType.isArray()) {
return true;
}
return false;
}
@Override
public <T> T convert(Object source, Class<T> targetType) {
// TODO Auto-generated method stub
if (targetType.isPrimitive()) {
targetType = (Class<T>) objectiveClass(targetType);
}
Converter converter = converters.get(new ConvertiblePair(source.getClass(), targetType));
if (converter == null && targetType.isArray()) {
converter = converters.get(new ConvertiblePair(source.getClass(), Arrays.class));
}
if (converter == null && targetType.isEnum()) {
converter = converters.get(new ConvertiblePair(source.getClass(), Enum.class));
}
if (converter != null) {
return (T) converter.convert(source, targetType);
}
return (T) source;
}
/**
* Get an array class of the given class.
*
* @param klass to get an array class of
* @param <C> the targeted class
* @return an array class of the given class
*/
public static <C> Class<C[]> arrayClass(Class<C> klass) {
return (Class<C[]>) Array.newInstance(klass, 0).getClass();
}
/**
* Get the class that extends {@link Object} that represent the given class.
*
* @param klass to get the object class of
* @return the class that extends Object class and represent the given class
*/
public static Class<?> objectiveClass(Class<?> klass) {
Class<?> component = klass.getComponentType();
if (component != null) {
if (component.isPrimitive() || component.isArray())
return arrayClass(objectiveClass(component));
} else if (klass.isPrimitive()) {
if (klass == char.class)
return Character.class;
if (klass == int.class)
return Integer.class;
if (klass == boolean.class)
return Boolean.class;
if (klass == byte.class)
return Byte.class;
if (klass == double.class)
return Double.class;
if (klass == float.class)
return Float.class;
if (klass == long.class)
return Long.class;
if (klass == short.class)
return Short.class;
}
return klass;
}
}
@@ -0,0 +1,31 @@
/*
* Copyright 2002-2014 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
*
* 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.taobao.arthas.core.env.convert;
/**
* Simply calls {@link Object#toString()} to convert a source Object to a String.
*
* @author Keith Donald
* @since 3.0
*/
final class ObjectToStringConverter implements Converter<Object, String> {
public String convert(Object source, Class<String> targetType) {
return source.toString();
}
}
@@ -0,0 +1,33 @@
package com.taobao.arthas.core.env.convert;
import java.lang.reflect.Array;
import com.taobao.arthas.core.env.ConversionService;
import com.taobao.arthas.core.util.StringUtils;
final class StringToArrayConverter<T> implements Converter<String, T[]> {
private ConversionService conversionService;
public StringToArrayConverter(ConversionService conversionService) {
this.conversionService = conversionService;
}
@Override
public T[] convert(String source, Class<T[]> targetType) {
String[] strings = StringUtils.tokenizeToStringArray(source, ",");
@SuppressWarnings("unchecked")
T[] values = (T[]) Array.newInstance(targetType.getComponentType(), strings.length);
for (int i = 0; i < strings.length; ++i) {
@SuppressWarnings("unchecked")
T value = (T) conversionService.convert(strings[i], targetType.getComponentType());
values[i] = value;
}
return values;
}
}
@@ -0,0 +1,64 @@
/*
* Copyright 2002-2011 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
*
* 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.taobao.arthas.core.env.convert;
import java.util.HashSet;
import java.util.Set;
/**
* Converts String to a Boolean.
*
* @author Keith Donald
* @author Juergen Hoeller
* @since 3.0
*/
final class StringToBooleanConverter implements Converter<String, Boolean> {
private static final Set<String> trueValues = new HashSet<String>(4);
private static final Set<String> falseValues = new HashSet<String>(4);
static {
trueValues.add("true");
trueValues.add("on");
trueValues.add("yes");
trueValues.add("1");
falseValues.add("false");
falseValues.add("off");
falseValues.add("no");
falseValues.add("0");
}
public Boolean convert(String source, Class<Boolean> targetType) {
String value = source.trim();
if ("".equals(value)) {
return null;
}
value = value.toLowerCase();
if (trueValues.contains(value)) {
return Boolean.TRUE;
}
else if (falseValues.contains(value)) {
return Boolean.FALSE;
}
else {
throw new IllegalArgumentException("Invalid boolean value '" + source + "'");
}
}
}
@@ -0,0 +1,12 @@
package com.taobao.arthas.core.env.convert;
@SuppressWarnings("rawtypes")
final class StringToEnumConverter<T extends Enum> implements Converter<String, T> {
@SuppressWarnings("unchecked")
@Override
public T convert(String source, Class<T> targetType) {
return (T) Enum.valueOf(targetType, source);
}
}
@@ -0,0 +1,17 @@
package com.taobao.arthas.core.env.convert;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class StringToInetAddressConverter implements Converter<String, InetAddress> {
@Override
public InetAddress convert(String source, Class<InetAddress> targetType) {
try {
return InetAddress.getByName(source);
} catch (UnknownHostException e) {
throw new IllegalArgumentException("Invalid InetAddress value '" + source + "'", e);
}
}
}
@@ -0,0 +1,9 @@
package com.taobao.arthas.core.env.convert;
final class StringToIntegerConverter implements Converter<String, Integer> {
@Override
public Integer convert(String source, Class<Integer> targetType) {
return Integer.parseInt(source);
}
}
@@ -0,0 +1,9 @@
package com.taobao.arthas.core.env.convert;
final class StringToLongConverter implements Converter<String, Long> {
@Override
public Long convert(String source, Class<Long> targetType) {
return Long.parseLong(source);
}
}
@@ -1,6 +1,9 @@
package com.taobao.arthas.core.server;
import com.taobao.arthas.core.config.Configure;
import com.taobao.arthas.core.config.FeatureCodec;
import com.taobao.arthas.core.env.ArthasEnvironment;
import com.taobao.arthas.core.env.MapPropertySource;
import com.alibaba.arthas.tunnel.client.TunnelClient;
import com.taobao.arthas.common.PidUtils;
import com.taobao.arthas.core.advisor.AdviceWeaver;
@@ -30,6 +33,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
@@ -45,6 +49,8 @@ public class ArthasBootstrap {
private static Logger logger = LogUtil.getArthasLogger();
private static ArthasBootstrap arthasBootstrap;
private ArthasEnvironment arthasEnvironment;
private AtomicBoolean isBindRef = new AtomicBoolean(false);
private Instrumentation instrumentation;
private Thread shutdown;
@@ -95,7 +101,21 @@ public class ArthasBootstrap {
public void bind(String args) throws Throwable {
initSpy();
if( arthasEnvironment == null) {
arthasEnvironment = new ArthasEnvironment();
}
Configure configure = Configure.toConfigure(args);
Map<String, String> argsMap = FeatureCodec.DEFAULT_COMMANDLINE_CODEC.toMap(args);
// 给配置全加上前缀
Map<String, Object> mapWithPrefix = new HashMap<String, Object>(argsMap.size());
for(Entry<String, String> entry : argsMap.entrySet()) {
mapWithPrefix.put("arthas." + entry.getKey(), entry.getValue());
}
MapPropertySource mapPropertySource = new MapPropertySource("args", mapWithPrefix);
arthasEnvironment.addFirst(mapPropertySource);
bind(configure);
}