diff --git a/polaris-agent-build/bin/start.sh b/polaris-agent-build/bin/start.sh index a53b4647..c07cdfc4 100644 --- a/polaris-agent-build/bin/start.sh +++ b/polaris-agent-build/bin/start.sh @@ -1,7 +1,8 @@ #!/bin/bash version=$(cat /app/version.txt) -polaris_agent_dir_name="polaris-java-agent-${version}" +polaris_agent_zip_name="polaris-java-agent-${version}" +polaris_agent_dir_name="polaris-java-agent" echo "polaris agent dir name ${polaris_agent_dir_name}" java_agent_dir=${JAVA_AGENT_DIR} @@ -12,13 +13,16 @@ mkdir -p ${java_agent_dir} # 将 /app/lib 下的文件 copy 一份出去 -cp -f /app/${polaris_agent_dir_name}.zip ${java_agent_dir}/ +cp -f /app/${polaris_agent_zip_name}.zip ${java_agent_dir}/ cp -f /app/version.txt ${java_agent_dir}/ # 这里需要将 agent 起到需要的相关信息全部注入到对应 plugin 中 cd ${java_agent_dir} -unzip ${polaris_agent_dir_name}.zip +unzip ${polaris_agent_zip_name}.zip +# 创建 polaris-java-agent 目录 +mkdir -p ${polaris_agent_dir_name} +cp -rf ${polaris_agent_zip_name}/* ${polaris_agent_dir_name}/ check_string_not_empty() { local string_to_check="$1" @@ -46,9 +50,22 @@ echo "plugins.enable=${custom_plugin_id}" > ${polaris_agent_dir_name}/conf/polar # 第二步,将 plugin 所需要的配置注入到 plugin 对应的目录中去 echo "inject with default config ${JAVA_AGENT_PLUGIN_CONF}" -custom_plugin_properties=${JAVA_AGENT_PLUGIN_CONF} target_config_file=${polaris_agent_dir_name}/conf/plugin/spring-cloud/application.properties -echo "${custom_plugin_properties}" > "${target_config_file}" +if check_string_not_empty "${JAVA_AGENT_PLUGIN_CONF}"; then + echo "${JAVA_AGENT_PLUGIN_CONF}" > "${target_config_file}" +else + echo "JAVA_AGENT_PLUGIN_CONF is empty" + echo "read polaris server ip: ${POLARIS_SERVER_IP}" + echo "read polaris discovery port: ${POLARIS_DISCOVER_PORT}" + echo "read polaris config ip: ${POLARIS_CONFIG_IP}" + echo "read polaris config port: ${POLARIS_CONFIG_PORT}" + polaris_address="grpc\\\:\/\/${POLARIS_SERVER_IP}\\\:${POLARIS_DISCOVER_PORT}" + polaris_config_address="grpc\\\:\/\/${POLARIS_CONFIG_IP}\\\:${POLARIS_CONFIG_PORT}" + echo "read polaris address: ${polaris_address}" + echo "read polaris config address: ${polaris_config_address}" + sed -i "s/spring.cloud.polaris.address=grpc\\\:\/\/127.0.0.1\\\:8091/spring.cloud.polaris.address=${polaris_address}/g" ${target_config_file} + sed -i "s/spring.cloud.polaris.config.address=grpc\\\:\/\/127.0.0.1\\\:8093/spring.cloud.polaris.config.address=${polaris_config_address}/g" ${target_config_file} +fi # 第三步,将地域信息拉取并设置进配置文件 # 腾讯云不能拿到大区,因此腾讯云上的region对应的是北极星的zone,zone对应北极星的campus @@ -66,4 +83,6 @@ zone_code=$? echo "zone is ${zone}, return code is ${zone_code}" if [ ${zone_code} -eq 0 ] && [ -n ${zone} ]; then sed -i "s/spring.cloud.tencent.metadata.content.campus=\"\"/spring.cloud.tencent.metadata.content.campus=${zone}/g" ${target_config_file} -fi \ No newline at end of file +fi + +cat ${target_config_file} diff --git a/polaris-agent-core/polaris-agent-core-asm/src/main/java/cn/polarismesh/agent/core/asm/instrument/plugin/PluginNameFilter.java b/polaris-agent-core/polaris-agent-core-asm/src/main/java/cn/polarismesh/agent/core/asm/instrument/plugin/PluginNameFilter.java index aa89d7d7..225b1285 100644 --- a/polaris-agent-core/polaris-agent-core-asm/src/main/java/cn/polarismesh/agent/core/asm/instrument/plugin/PluginNameFilter.java +++ b/polaris-agent-core/polaris-agent-core-asm/src/main/java/cn/polarismesh/agent/core/asm/instrument/plugin/PluginNameFilter.java @@ -18,13 +18,22 @@ package cn.polarismesh.agent.core.asm.instrument.plugin; import cn.polarismesh.agent.core.common.conf.ConfigManager; +import cn.polarismesh.agent.core.common.logger.CommonLogger; +import cn.polarismesh.agent.core.common.logger.StdoutCommonLoggerFactory; import cn.polarismesh.agent.core.common.utils.StringUtils; + +import java.io.IOException; import java.util.Collections; import java.util.HashSet; import java.util.Set; +import java.util.jar.JarFile; +import java.util.jar.Manifest; public class PluginNameFilter implements PluginFilter { + private static final CommonLogger logger = StdoutCommonLoggerFactory.INSTANCE + .getLogger(PluginNameFilter.class.getCanonicalName()); + private final Set pluginNames; public PluginNameFilter() { @@ -38,6 +47,9 @@ public boolean accept(PluginJar pluginJar) { private static Set getLoadablePluginNames() { String enablePlugins = ConfigManager.INSTANCE.getConfigValue(ConfigManager.KEY_PLUGIN_ENABLE); + logger.info("Enable plugins: " + enablePlugins); + enablePlugins = appendSpringCloudPluginNameIfNeeded(enablePlugins); + logger.info("Enable plugins after appendSpringCloudPluginNameIfNeeded: " + enablePlugins); if (StringUtils.isEmpty(enablePlugins)) { return Collections.emptySet(); } @@ -51,4 +63,63 @@ private static Set getLoadablePluginNames() { } return values; } + + private static String appendSpringCloudPluginNameIfNeeded(String enablePlugins) { + if (StringUtils.hasText(enablePlugins)) { + String[] names = enablePlugins.split(","); + for (String name : names) { + if (StringUtils.hasText(name) && name.contains("spring-cloud-")) { + return enablePlugins; + } + } + } + + JarFile jarFile = null; + try { + String classPath = System.getProperty("java.class.path"); + logger.info("Class path: " + classPath); + String[] paths = classPath.split(":"); + String mainJarPath = paths[0]; + logger.info("Main jar: " + mainJarPath); + jarFile = new JarFile(mainJarPath); + Manifest manifest = (jarFile).getManifest(); + String versionStr = manifest.getMainAttributes().getValue("Spring-Boot-Version"); + logger.info("Spring Boot Version: " + versionStr); + String springCloudPluginNamePattern = "spring-cloud-%s-plugin"; + String springCloudVersion = ""; + if (versionStr.startsWith("2.2") || versionStr.startsWith("2.3")) { + springCloudVersion = "hoxton"; + } else if (versionStr.startsWith("2.4") || versionStr.startsWith("2.5")) { + springCloudVersion = "2020"; + } else if (versionStr.startsWith("2.6") || versionStr.startsWith("2.7")) { + springCloudVersion = "2021"; + } else if (versionStr.startsWith("3.0") || versionStr.startsWith("3.1")) { + springCloudVersion = "2022"; + } else if (versionStr.startsWith("3.2") || versionStr.startsWith("3.3")) { + springCloudVersion = "2023"; + } + if (StringUtils.hasText(springCloudVersion)) { + String springCloudPluginName = String.format(springCloudPluginNamePattern, springCloudVersion); + logger.info("Spring Cloud Version: " + springCloudVersion); + if (StringUtils.hasText(enablePlugins)) { + enablePlugins = enablePlugins + "," + springCloudPluginName; + } else { + enablePlugins = springCloudPluginName; + } + } else { + logger.warn("No compatible Spring Cloud version found for Spring Boot version: " + versionStr); + } + } catch (IOException ioException) { + logger.warn("Cannot get Spring Boot Version from MANIFEST.", ioException); + } finally { + if (jarFile != null) { + try { + jarFile.close(); + } catch (IOException ioException) { + logger.warn("Cannot close jarFile", ioException); + } + } + } + return enablePlugins; + } } diff --git a/polaris-agent-core/polaris-agent-core-common/src/main/java/cn/polarismesh/agent/core/common/utils/ReflectionUtils.java b/polaris-agent-core/polaris-agent-core-common/src/main/java/cn/polarismesh/agent/core/common/utils/ReflectionUtils.java index 0a1a2d7e..c7f0583f 100644 --- a/polaris-agent-core/polaris-agent-core-common/src/main/java/cn/polarismesh/agent/core/common/utils/ReflectionUtils.java +++ b/polaris-agent-core/polaris-agent-core-common/src/main/java/cn/polarismesh/agent/core/common/utils/ReflectionUtils.java @@ -17,530 +17,540 @@ package cn.polarismesh.agent.core.common.utils; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.UndeclaredThrowableException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; - import cn.polarismesh.agent.core.common.exception.PolarisAgentException; +import java.lang.reflect.*; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + // copy from https://github.com/spring-projects/spring-framework/blob/main/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java public final class ReflectionUtils { - public static final String ARRAY_POSTFIX = "[]"; - - private ReflectionUtils() { - } - - public static String getParameterTypeName(Class parameterType) { - Objects.requireNonNull(parameterType, "parameterType"); - - if (!parameterType.isArray()) { - return parameterType.getName(); - } - - int arrayDepth = 0; - while (parameterType.isArray()) { - parameterType = parameterType.getComponentType(); - arrayDepth++; - } - - final int bufferSize = getBufferSize(parameterType.getName(), arrayDepth); - final StringBuilder buffer = new StringBuilder(bufferSize); - - buffer.append(parameterType.getName()); - for (int i = 0; i < arrayDepth; i++) { - buffer.append(ARRAY_POSTFIX); - } - return buffer.toString(); - } - - private static int getBufferSize(String paramTypeName, int arrayDepth) { - return paramTypeName.length() + (ARRAY_POSTFIX.length() * arrayDepth); - } - - private static final Method[] EMPTY_METHOD_ARRAY = new Method[0]; - - private static final Field[] EMPTY_FIELD_ARRAY = new Field[0]; - - /** - * Cache for {@link Class#getDeclaredMethods()} plus equivalent default methods - * from Java 8 based interfaces, allowing for fast iteration. - */ - private static final Map, Method[]> declaredMethodsCache = new ConcurrentHashMap<>(256); - - /** - * Cache for {@link Class#getDeclaredFields()}, allowing for fast iteration. - */ - private static final Map, Field[]> declaredFieldsCache = new ConcurrentHashMap<>(256); - - // Exception handling - - /** - * Handle the given reflection exception. - *

Should only be called if no checked exception is expected to be thrown - * by a target method, or if an error occurs while accessing a method or field. - *

Throws the underlying RuntimeException or Error in case of an - * InvocationTargetException with such a root cause. Throws an - * IllegalStateException with an appropriate message or - * UndeclaredThrowableException otherwise. - * - * @param ex the reflection exception to handle - */ - public static void handleReflectionException(Exception ex) { - if (ex instanceof NoSuchMethodException) { - throw new IllegalStateException("Method not found: " + ex.getMessage()); - } - if (ex instanceof IllegalAccessException) { - throw new IllegalStateException("Could not access method or field: " + ex.getMessage()); - } - if (ex instanceof InvocationTargetException) { - handleInvocationTargetException((InvocationTargetException) ex); - } - if (ex instanceof RuntimeException) { - throw (RuntimeException) ex; - } - throw new UndeclaredThrowableException(ex); - } - - /** - * Handle the given invocation target exception. Should only be called if no - * checked exception is expected to be thrown by the target method. - *

Throws the underlying RuntimeException or Error in case of such a root - * cause. Throws an UndeclaredThrowableException otherwise. - * - * @param ex the invocation target exception to handle - */ - public static void handleInvocationTargetException(InvocationTargetException ex) { - rethrowRuntimeException(ex.getTargetException()); - } - - /** - * Rethrow the given {@link Throwable exception}, which is presumably the - * target exception of an {@link InvocationTargetException}. - * Should only be called if no checked exception is expected to be thrown - * by the target method. - *

Rethrows the underlying exception cast to a {@link RuntimeException} or - * {@link Error} if appropriate; otherwise, throws an - * {@link UndeclaredThrowableException}. - * - * @param ex the exception to rethrow - * @throws RuntimeException the rethrown exception - */ - public static void rethrowRuntimeException(Throwable ex) { - if (ex instanceof RuntimeException) { - throw (RuntimeException) ex; - } - if (ex instanceof Error) { - throw (Error) ex; - } - throw new UndeclaredThrowableException(ex); - } - - /** - * Attempt to find a {@link Method} on the supplied class with the supplied name - * and parameter types. Searches all superclasses up to {@code Object}. - *

Returns {@code null} if no {@link Method} can be found. - * - * @param clazz the class to introspect - * @param name the name of the method - * @param paramTypes the parameter types of the method - * (may be {@code null} to indicate any signature) - * @return the Method object, or {@code null} if none found - */ - public static Method findMethod(Class clazz, String name, Class... paramTypes) { - Class searchType = clazz; - while (searchType != null) { - Method[] methods = (searchType.isInterface() ? searchType.getMethods() : - getDeclaredMethods(searchType, false)); - for (Method method : methods) { - if (name.equals(method.getName()) && (paramTypes == null || hasSameParams(method, paramTypes))) { - return method; - } - } - searchType = searchType.getSuperclass(); - } - return null; - } - - private static boolean hasSameParams(Method method, Class[] paramTypes) { - return (paramTypes.length == method.getParameterCount() && - Arrays.equals(paramTypes, method.getParameterTypes())); - } - - private static Method[] getDeclaredMethods(Class clazz, boolean defensive) { - Method[] result = declaredMethodsCache.get(clazz); - if (result == null) { - try { - Method[] declaredMethods = clazz.getDeclaredMethods(); - List defaultMethods = findConcreteMethodsOnInterfaces(clazz); - if (defaultMethods != null) { - result = new Method[declaredMethods.length + defaultMethods.size()]; - System.arraycopy(declaredMethods, 0, result, 0, declaredMethods.length); - int index = declaredMethods.length; - for (Method defaultMethod : defaultMethods) { - result[index] = defaultMethod; - index++; - } - } - else { - result = declaredMethods; - } - declaredMethodsCache.put(clazz, (result.length == 0 ? EMPTY_METHOD_ARRAY : result)); - } - catch (Throwable ex) { - throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() + - "] from ClassLoader [" + clazz.getClassLoader() + "]", ex); - } - } - return (result.length == 0 || !defensive) ? result : result.clone(); - } - - private static List findConcreteMethodsOnInterfaces(Class clazz) { - List result = null; - for (Class ifc : clazz.getInterfaces()) { - for (Method ifcMethod : ifc.getMethods()) { - if (!Modifier.isAbstract(ifcMethod.getModifiers())) { - if (result == null) { - result = new ArrayList<>(); - } - result.add(ifcMethod); - } - } - } - return result; - } - - /** - * Set the field represented by the supplied {@linkplain Field field object} on - * the specified {@linkplain Object target object} to the specified {@code value}. - *

In accordance with {@link Field#set(Object, Object)} semantics, the new value - * is automatically unwrapped if the underlying field has a primitive type. - *

This method does not support setting {@code static final} fields. - *

Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}. - * - * @param field the field to set - * @param target the target object on which to set the field - * (or {@code null} for a static field) - * @param value the value to set (may be {@code null}) - */ - public static void setField(Field field, Object target, Object value) { - try { - field.set(target, value); - } - catch (IllegalAccessException ex) { - handleReflectionException(ex); - } - } - - /** - * Get the field represented by the supplied {@link Field field object} on the - * specified {@link Object target object}. In accordance with {@link Field#get(Object)} - * semantics, the returned value is automatically wrapped if the underlying field - * has a primitive type. - *

Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}. - * - * @param field the field to get - * @param target the target object from which to get the field - * (or {@code null} for a static field) - * @return the field's current value - */ - public static Object getField(Field field, Object target) { - try { - return field.get(target); - } - catch (IllegalAccessException ex) { - handleReflectionException(ex); - } - throw new IllegalStateException("Should never get here"); - } - - /** - * Invoke the given callback on all fields in the target class, going up the - * class hierarchy to get all declared fields. - * - * @param clazz the target class to analyze - * @param fc the callback to invoke for each field - * @param ff the filter that determines the fields to apply the callback to - * @throws IllegalStateException if introspection fails - */ - public static void doWithFields(Class clazz, FieldCallback fc, FieldFilter ff) { - // Keep backing up the inheritance hierarchy. - Class targetClass = clazz; - do { - Field[] fields = getDeclaredFields(targetClass); - for (Field field : fields) { - if (ff != null && !ff.matches(field)) { - continue; - } - try { - fc.doWith(field); - } - catch (IllegalAccessException ex) { - throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex); - } - } - targetClass = targetClass.getSuperclass(); - } - while (targetClass != null && targetClass != Object.class); - } - - /** - * This variant retrieves {@link Class#getDeclaredFields()} from a local cache - * in order to avoid defensive array copying. - * - * @param clazz the class to introspect - * @return the cached array of fields - * @throws IllegalStateException if introspection fails - * @see Class#getDeclaredFields() - */ - private static Field[] getDeclaredFields(Class clazz) { - Field[] result = declaredFieldsCache.get(clazz); - if (result == null) { - try { - result = clazz.getDeclaredFields(); - declaredFieldsCache.put(clazz, (result.length == 0 ? EMPTY_FIELD_ARRAY : result)); - } - catch (Throwable ex) { - throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() + - "] from ClassLoader [" + clazz.getClassLoader() + "]", ex); - } - } - return result; - } - - /** - * Make the given field accessible, explicitly setting it accessible if - * necessary. The {@code setAccessible(true)} method is only called - * when actually necessary, to avoid unnecessary conflicts. - * - * @param field the field to make accessible - * @see java.lang.reflect.Field#setAccessible - */ - public static void makeAccessible(Field field) { - if ((!Modifier.isPublic(field.getModifiers()) || - !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || - Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) { - field.setAccessible(true); - } - } - - /** - * Callback interface invoked on each field in the hierarchy. - */ - @FunctionalInterface - public interface FieldCallback { - - /** - * Perform an operation using the given field. - * - * @param field the field to operate on - */ - void doWith(Field field) throws IllegalArgumentException, IllegalAccessException; - } - - - /** - * Callback optionally used to filter fields to be operated on by a field callback. - */ - @FunctionalInterface - public interface FieldFilter { - - /** - * Determine whether the given field matches. - * - * @param field the field to check - */ - boolean matches(Field field); - - /** - * Create a composite filter based on this filter and the provided filter. - *

If this filter does not match, the next filter will not be applied. - * - * @param next the next {@code FieldFilter} - * @return a composite {@code FieldFilter} - * @throws IllegalArgumentException if the FieldFilter argument is {@code null} - * @since 5.3.2 - */ - default FieldFilter and(FieldFilter next) { - return field -> matches(field) && next.matches(field); - } - } - - - /** - * 根据属性名返回对象的属性 - * - * @param target 对象 - * @param fieldName 对象的属性名 - * @return 获取到的对象属性 - */ - public static Object getObjectByFieldName(Object target, String fieldName) { - try { - Field field = target.getClass().getDeclaredField(fieldName); - field.setAccessible(true); - return field.get(target); - } - catch (Exception e) { - throw new PolarisAgentException("getObjectByFieldName", e); - } - } - - /** - * 根据属性名返回对象父类的属性 - * - * @param target 对象 - * @param fieldName 对象父类的属性名 - * @return 获取到的对象父类的属性 - */ - public static Object getSuperObjectByFieldName(Object target, String fieldName) { - try { - Field field = target.getClass().getSuperclass().getDeclaredField(fieldName); - field.setAccessible(true); - return field.get(target); - } - catch (Exception e) { - throw new PolarisAgentException("getSuperObjectByFieldName", e); - } - } - - /** - * 根据属性名重新设置对象的属性 - * - * @param target 对象 - * @param fieldName 对象的属性名 - * @param value 新值 - */ - public static void setValueByFieldName(Object target, String fieldName, Object value) { - try { - Field field = target.getClass().getDeclaredField(fieldName); - setValue(target, field, value); - } - catch (Exception e) { - throw new PolarisAgentException("setValueByFieldName", e); - } - } - - /** - * 根据属性名重新设置对象父类的属性 - * - * @param target 对象 - * @param fieldName 对象父类的属性名 - * @param value 新值 - */ - public static void setSuperValueByFieldName(Object target, String fieldName, Object value) { - try { - Field field = target.getClass().getSuperclass().getDeclaredField(fieldName); - setValue(target, field, value); - } - catch (Exception e) { - throw new PolarisAgentException("setSuperValueByFieldName", e); - } - } - - private static void setValue(Object target, Field field, Object value) { - try { - //Field modifiers = Field.class.getDeclaredField("modifiers"); - Field modifiers = getModifiersField(); - modifiers.setAccessible(true); - modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL); - field.setAccessible(true); - field.set(target, value); - } - catch (Exception e) { - throw new PolarisAgentException("setValue", e); - } - } - - private static Field getModifiersField() throws Exception { - Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); - getDeclaredFields0.setAccessible(true); - Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false); - Field modifierField = null; - for (Field f : fields) { - if ("modifiers".equals(f.getName())) { - modifierField = f; - break; - } - } - return modifierField; - } - - /** - * 根据方法名和参数执行方法 - * - * @param target 拥有该方法的对象 - * @param methodName 方法名 - * @param arg 方法入参 - * @return 方法返回值 - */ - public static Object invokeMethodByName(Object target, String methodName, Object arg) { - try { - Method m; - if (arg == null) { - m = target.getClass().getDeclaredMethod(methodName); - } - else { - m = target.getClass().getDeclaredMethod(methodName, arg.getClass()); - } - m.setAccessible(true); - return arg == null ? m.invoke(target) : m.invoke(target, arg); - } - catch (Exception e) { - throw new PolarisAgentException("invokeMethodByName", e); - } - } - - /** - * 根据类型获取构造器 - * @param clazz - * @param parameterTypes - * @return - * @param - */ - public static Constructor accessibleConstructor(Class clazz, Class... parameterTypes) { - try { - Constructor ctor = clazz.getDeclaredConstructor(parameterTypes); - makeAccessible(ctor); - return ctor; - } catch (NoSuchMethodException e) { - throw new PolarisAgentException("accessibleConstructor failed: ", e); - } - } - - public static void makeAccessible(Constructor ctor) { - if ((!Modifier.isPublic(ctor.getModifiers()) || !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) && !ctor.isAccessible()) { - ctor.setAccessible(true); - } - } - - public static void makeAccessible(Method ctor) { - if ((!Modifier.isPublic(ctor.getModifiers()) || !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) && !ctor.isAccessible()) { - ctor.setAccessible(true); - } - } - - public static T invokeConstructor(Constructor constructor, Object... params) { - try { - return constructor.newInstance(params); - } catch (Exception e) { - throw new PolarisAgentException("invokeConstructor failed: ", e); - } - } - - public static Object invokeMethod(Method method, Object target, Object... params) { - try { - return method.invoke(target, params); - } catch (Exception e) { - throw new PolarisAgentException("invokeMethod failed: ", e); - } - } + public static final String ARRAY_POSTFIX = "[]"; + + private ReflectionUtils() { + } + + public static String getParameterTypeName(Class parameterType) { + Objects.requireNonNull(parameterType, "parameterType"); + + if (!parameterType.isArray()) { + return parameterType.getName(); + } + + int arrayDepth = 0; + while (parameterType.isArray()) { + parameterType = parameterType.getComponentType(); + arrayDepth++; + } + + final int bufferSize = getBufferSize(parameterType.getName(), arrayDepth); + final StringBuilder buffer = new StringBuilder(bufferSize); + + buffer.append(parameterType.getName()); + for (int i = 0; i < arrayDepth; i++) { + buffer.append(ARRAY_POSTFIX); + } + return buffer.toString(); + } + + private static int getBufferSize(String paramTypeName, int arrayDepth) { + return paramTypeName.length() + (ARRAY_POSTFIX.length() * arrayDepth); + } + + private static final Method[] EMPTY_METHOD_ARRAY = new Method[0]; + + private static final Field[] EMPTY_FIELD_ARRAY = new Field[0]; + + /** + * Cache for {@link Class#getDeclaredMethods()} plus equivalent default methods + * from Java 8 based interfaces, allowing for fast iteration. + */ + private static final Map, Method[]> declaredMethodsCache = new ConcurrentHashMap<>(256); + + /** + * Cache for {@link Class#getDeclaredFields()}, allowing for fast iteration. + */ + private static final Map, Field[]> declaredFieldsCache = new ConcurrentHashMap<>(256); + + // Exception handling + + /** + * Handle the given reflection exception. + *

Should only be called if no checked exception is expected to be thrown + * by a target method, or if an error occurs while accessing a method or field. + *

Throws the underlying RuntimeException or Error in case of an + * InvocationTargetException with such a root cause. Throws an + * IllegalStateException with an appropriate message or + * UndeclaredThrowableException otherwise. + * + * @param ex the reflection exception to handle + */ + public static void handleReflectionException(Exception ex) { + if (ex instanceof NoSuchMethodException) { + throw new IllegalStateException("Method not found: " + ex.getMessage()); + } + if (ex instanceof IllegalAccessException) { + throw new IllegalStateException("Could not access method or field: " + ex.getMessage()); + } + if (ex instanceof InvocationTargetException) { + handleInvocationTargetException((InvocationTargetException) ex); + } + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + throw new UndeclaredThrowableException(ex); + } + + /** + * Handle the given invocation target exception. Should only be called if no + * checked exception is expected to be thrown by the target method. + *

Throws the underlying RuntimeException or Error in case of such a root + * cause. Throws an UndeclaredThrowableException otherwise. + * + * @param ex the invocation target exception to handle + */ + public static void handleInvocationTargetException(InvocationTargetException ex) { + rethrowRuntimeException(ex.getTargetException()); + } + + /** + * Rethrow the given {@link Throwable exception}, which is presumably the + * target exception of an {@link InvocationTargetException}. + * Should only be called if no checked exception is expected to be thrown + * by the target method. + *

Rethrows the underlying exception cast to a {@link RuntimeException} or + * {@link Error} if appropriate; otherwise, throws an + * {@link UndeclaredThrowableException}. + * + * @param ex the exception to rethrow + * @throws RuntimeException the rethrown exception + */ + public static void rethrowRuntimeException(Throwable ex) { + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + if (ex instanceof Error) { + throw (Error) ex; + } + throw new UndeclaredThrowableException(ex); + } + + /** + * Attempt to find a {@link Method} on the supplied class with the supplied name + * and parameter types. Searches all superclasses up to {@code Object}. + *

Returns {@code null} if no {@link Method} can be found. + * + * @param clazz the class to introspect + * @param name the name of the method + * @param paramTypes the parameter types of the method + * (may be {@code null} to indicate any signature) + * @return the Method object, or {@code null} if none found + */ + public static Method findMethod(Class clazz, String name, Class... paramTypes) { + Class searchType = clazz; + while (searchType != null) { + Method[] methods = (searchType.isInterface() ? searchType.getMethods() : + getDeclaredMethods(searchType, false)); + for (Method method : methods) { + if (name.equals(method.getName()) && (paramTypes == null || hasSameParams(method, paramTypes))) { + return method; + } + } + searchType = searchType.getSuperclass(); + } + return null; + } + + private static boolean hasSameParams(Method method, Class[] paramTypes) { + return (paramTypes.length == method.getParameterCount() && + Arrays.equals(paramTypes, method.getParameterTypes())); + } + + private static Method[] getDeclaredMethods(Class clazz, boolean defensive) { + Method[] result = declaredMethodsCache.get(clazz); + if (result == null) { + try { + Method[] declaredMethods = clazz.getDeclaredMethods(); + List defaultMethods = findConcreteMethodsOnInterfaces(clazz); + if (defaultMethods != null) { + result = new Method[declaredMethods.length + defaultMethods.size()]; + System.arraycopy(declaredMethods, 0, result, 0, declaredMethods.length); + int index = declaredMethods.length; + for (Method defaultMethod : defaultMethods) { + result[index] = defaultMethod; + index++; + } + } else { + result = declaredMethods; + } + declaredMethodsCache.put(clazz, (result.length == 0 ? EMPTY_METHOD_ARRAY : result)); + } catch (Throwable ex) { + throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() + + "] from ClassLoader [" + clazz.getClassLoader() + "]", ex); + } + } + return (result.length == 0 || !defensive) ? result : result.clone(); + } + + private static List findConcreteMethodsOnInterfaces(Class clazz) { + List result = null; + for (Class ifc : clazz.getInterfaces()) { + for (Method ifcMethod : ifc.getMethods()) { + if (!Modifier.isAbstract(ifcMethod.getModifiers())) { + if (result == null) { + result = new ArrayList<>(); + } + result.add(ifcMethod); + } + } + } + return result; + } + + /** + * Set the field represented by the supplied {@linkplain Field field object} on + * the specified {@linkplain Object target object} to the specified {@code value}. + *

In accordance with {@link Field#set(Object, Object)} semantics, the new value + * is automatically unwrapped if the underlying field has a primitive type. + *

This method does not support setting {@code static final} fields. + *

Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}. + * + * @param field the field to set + * @param target the target object on which to set the field + * (or {@code null} for a static field) + * @param value the value to set (may be {@code null}) + */ + public static void setField(Field field, Object target, Object value) { + try { + field.set(target, value); + } catch (IllegalAccessException ex) { + handleReflectionException(ex); + } + } + + /** + * Get the field represented by the supplied {@link Field field object} on the + * specified {@link Object target object}. In accordance with {@link Field#get(Object)} + * semantics, the returned value is automatically wrapped if the underlying field + * has a primitive type. + *

Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}. + * + * @param field the field to get + * @param target the target object from which to get the field + * (or {@code null} for a static field) + * @return the field's current value + */ + public static Object getField(Field field, Object target) { + try { + return field.get(target); + } catch (IllegalAccessException ex) { + handleReflectionException(ex); + } + throw new IllegalStateException("Should never get here"); + } + + /** + * Invoke the given callback on all fields in the target class, going up the + * class hierarchy to get all declared fields. + * + * @param clazz the target class to analyze + * @param fc the callback to invoke for each field + * @param ff the filter that determines the fields to apply the callback to + * @throws IllegalStateException if introspection fails + */ + public static void doWithFields(Class clazz, FieldCallback fc, FieldFilter ff) { + // Keep backing up the inheritance hierarchy. + Class targetClass = clazz; + do { + Field[] fields = getDeclaredFields(targetClass); + for (Field field : fields) { + if (ff != null && !ff.matches(field)) { + continue; + } + try { + fc.doWith(field); + } catch (IllegalAccessException ex) { + throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex); + } + } + targetClass = targetClass.getSuperclass(); + } + while (targetClass != null && targetClass != Object.class); + } + + /** + * This variant retrieves {@link Class#getDeclaredFields()} from a local cache + * in order to avoid defensive array copying. + * + * @param clazz the class to introspect + * @return the cached array of fields + * @throws IllegalStateException if introspection fails + * @see Class#getDeclaredFields() + */ + private static Field[] getDeclaredFields(Class clazz) { + Field[] result = declaredFieldsCache.get(clazz); + if (result == null) { + try { + result = clazz.getDeclaredFields(); + declaredFieldsCache.put(clazz, (result.length == 0 ? EMPTY_FIELD_ARRAY : result)); + } catch (Throwable ex) { + throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() + + "] from ClassLoader [" + clazz.getClassLoader() + "]", ex); + } + } + return result; + } + + /** + * Make the given field accessible, explicitly setting it accessible if + * necessary. The {@code setAccessible(true)} method is only called + * when actually necessary, to avoid unnecessary conflicts. + * + * @param field the field to make accessible + * @see java.lang.reflect.Field#setAccessible + */ + public static void makeAccessible(Field field) { + if ((!Modifier.isPublic(field.getModifiers()) || + !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || + Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) { + field.setAccessible(true); + } + } + + /** + * Callback interface invoked on each field in the hierarchy. + */ + @FunctionalInterface + public interface FieldCallback { + + /** + * Perform an operation using the given field. + * + * @param field the field to operate on + */ + void doWith(Field field) throws IllegalArgumentException, IllegalAccessException; + } + + + /** + * Callback optionally used to filter fields to be operated on by a field callback. + */ + @FunctionalInterface + public interface FieldFilter { + + /** + * Determine whether the given field matches. + * + * @param field the field to check + */ + boolean matches(Field field); + + /** + * Create a composite filter based on this filter and the provided filter. + *

If this filter does not match, the next filter will not be applied. + * + * @param next the next {@code FieldFilter} + * @return a composite {@code FieldFilter} + * @throws IllegalArgumentException if the FieldFilter argument is {@code null} + * @since 5.3.2 + */ + default FieldFilter and(FieldFilter next) { + return field -> matches(field) && next.matches(field); + } + } + + + /** + * 根据属性名返回对象的属性 + * + * @param target 对象 + * @param fieldName 对象的属性名 + * @return 获取到的对象属性 + */ + public static Object getObjectByFieldName(Object target, String fieldName) { + try { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(target); + } catch (Exception e) { + throw new PolarisAgentException("getObjectByFieldName", e); + } + } + + /** + * 根据属性名返回对象父类的属性 + * + * @param target 对象 + * @param fieldName 对象父类的属性名 + * @return 获取到的对象父类的属性 + */ + public static Object getSuperObjectByFieldName(Object target, String fieldName) { + try { + Field field = target.getClass().getSuperclass().getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(target); + } catch (Exception e) { + throw new PolarisAgentException("getSuperObjectByFieldName", e); + } + } + + /** + * 根据属性名重新设置对象的属性 + * + * @param target 对象 + * @param fieldName 对象的属性名 + * @param value 新值 + */ + public static void setValueByFieldName(Object target, String fieldName, Object value) { + try { + Field field = target.getClass().getDeclaredField(fieldName); + setValue(target, field, value); + } catch (Exception e) { + throw new PolarisAgentException("setValueByFieldName", e); + } + } + + /** + * 根据属性名重新设置对象父类的属性 + * + * @param target 对象 + * @param fieldName 对象父类的属性名 + * @param value 新值 + */ + public static void setSuperValueByFieldName(Object target, String fieldName, Object value) { + try { + Field field = target.getClass().getSuperclass().getDeclaredField(fieldName); + setValue(target, field, value); + } catch (Exception e) { + throw new PolarisAgentException("setSuperValueByFieldName", e); + } + } + + private static void setValue(Object target, Field field, Object value) { + try { + //Field modifiers = Field.class.getDeclaredField("modifiers"); + Field modifiers = getModifiersField(); + modifiers.setAccessible(true); + modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL); + field.setAccessible(true); + field.set(target, value); + } catch (Exception e) { + throw new PolarisAgentException("setValue", e); + } + } + + private static Field getModifiersField() throws Exception { + Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); + getDeclaredFields0.setAccessible(true); + Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false); + Field modifierField = null; + for (Field f : fields) { + if ("modifiers".equals(f.getName())) { + modifierField = f; + break; + } + } + return modifierField; + } + + /** + * 根据方法名和参数执行方法 + * + * @param target 拥有该方法的对象 + * @param methodName 方法名 + * @param arg 方法入参 + * @return 方法返回值 + */ + public static Object invokeMethodByName(Object target, String methodName, Object arg) { + try { + Method m; + if (arg == null) { + m = target.getClass().getDeclaredMethod(methodName); + } else { + m = target.getClass().getDeclaredMethod(methodName, arg.getClass()); + } + m.setAccessible(true); + return arg == null ? m.invoke(target) : m.invoke(target, arg); + } catch (Exception e) { + throw new PolarisAgentException("invokeMethodByName", e); + } + } + + /** + * 根据类型获取构造器 + * + * @param clazz + * @param parameterTypes + * @param + * @return + */ + public static Constructor accessibleConstructor(Class clazz, Class... parameterTypes) { + try { + Constructor ctor = clazz.getDeclaredConstructor(parameterTypes); + makeAccessible(ctor); + return ctor; + } catch (NoSuchMethodException e) { + throw new PolarisAgentException("accessibleConstructor failed: ", e); + } + } + + public static void makeAccessible(Constructor ctor) { + if ((!Modifier.isPublic(ctor.getModifiers()) || !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) && !ctor.isAccessible()) { + ctor.setAccessible(true); + } + } + + public static void makeAccessible(Method ctor) { + if ((!Modifier.isPublic(ctor.getModifiers()) || !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) && !ctor.isAccessible()) { + ctor.setAccessible(true); + } + } + + public static T invokeConstructor(Constructor constructor, Object... params) { + try { + return constructor.newInstance(params); + } catch (Exception e) { + throw new PolarisAgentException("invokeConstructor failed: ", e); + } + } + + public static Object invokeMethod(Method method, Object target, Object... params) { + try { + return method.invoke(target, params); + } catch (Exception e) { + throw new PolarisAgentException("invokeMethod failed: ", e); + } + } + + /** + * 使用主进程的类加载器判断主进程中的类 + * + * @param className + * @return + */ + public static boolean checkClassExists(String className) { + try { + ClassLoader mainClassLoader = ClassLoader.getSystemClassLoader(); + Class mainClass = mainClassLoader.loadClass(className); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + + /** + * 使用主进程的类加载器加载主进程中的类 + * + * @param className + * @return + */ + public static Class findClass(String className) { + try { + ClassLoader mainClassLoader = ClassLoader.getSystemClassLoader(); + return mainClassLoader.loadClass(className); + } catch (ClassNotFoundException e) { + return null; + } + } } diff --git a/polaris-agent-examples/spring-cloud-plugins-examples/spring-cloud-2023-examples/quickstart-examples/provider/pom.xml b/polaris-agent-examples/spring-cloud-plugins-examples/spring-cloud-2023-examples/quickstart-examples/provider/pom.xml index 76dda92c..b4336068 100644 --- a/polaris-agent-examples/spring-cloud-plugins-examples/spring-cloud-2023-examples/quickstart-examples/provider/pom.xml +++ b/polaris-agent-examples/spring-cloud-plugins-examples/spring-cloud-2023-examples/quickstart-examples/provider/pom.xml @@ -1,85 +1,85 @@ - - org.springframework.boot - spring-boot-starter-parent - 3.2.3 - - - 4.0.0 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + org.springframework.boot + spring-boot-starter-parent + 3.2.3 + + + 4.0.0 - 2023-provider - Demo Provider Project For Spring Cloud Alibaba 2023 - polaris-javaagent-demo-sc-quickstart-2023-provider - jar - 1.7.0 + 2023-provider + Demo Provider Project For Spring Cloud Alibaba 2023 + polaris-javaagent-demo-sc-quickstart-2023-provider + jar + 1.7.0 - - - - com.alibaba.cloud - spring-cloud-alibaba-dependencies - 2023.0.0.0-RC1 - pom - import - + + + + com.alibaba.cloud + spring-cloud-alibaba-dependencies + 2023.0.0.0-RC1 + pom + import + - - org.springframework.cloud - spring-cloud-dependencies - 2023.0.0 - pom - import - - - + + org.springframework.cloud + spring-cloud-dependencies + 2023.0.0 + pom + import + + + - - - - - - - - org.springframework.boot - spring-boot-starter-web - + + + + org.springframework.cloud + spring-cloud-starter-bootstrap + + + org.springframework.boot + spring-boot-starter-web + - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-discovery - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-config - + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + - - org.springframework.boot - spring-boot-starter-actuator - - + + org.springframework.boot + spring-boot-starter-actuator + + - - - - org.springframework.boot - spring-boot-maven-plugin - - true - - - - - repackage - - - - - - + + + + org.springframework.boot + spring-boot-maven-plugin + + true + + + + + repackage + + + + + + \ No newline at end of file diff --git a/polaris-agent-examples/spring-cloud-plugins-examples/spring-cloud-2023-examples/quickstart-examples/provider/src/main/resources/bootstrap.properties b/polaris-agent-examples/spring-cloud-plugins-examples/spring-cloud-2023-examples/quickstart-examples/provider/src/main/resources/bootstrap.properties index b45f00e2..743bd44e 100644 --- a/polaris-agent-examples/spring-cloud-plugins-examples/spring-cloud-2023-examples/quickstart-examples/provider/src/main/resources/bootstrap.properties +++ b/polaris-agent-examples/spring-cloud-plugins-examples/spring-cloud-2023-examples/quickstart-examples/provider/src/main/resources/bootstrap.properties @@ -1,18 +1,16 @@ -#server.port=65001 -#spring.application.name=service-provider-2022 -#spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 -#spring.cloud.nacos.config.server-addr=127.0.0.1:8848 -#spring.cloud.nacos.config.enabled=true -#spring.cloud.nacos.discovery.enabled=true -##spring.cloud.nacos.discovery.instance-enabled=true -##only register IPv4 instance -##spring.cloud.nacos.discovery.ip-type=IPv4 -##only register IPv6 instance -##spring.cloud.nacos.discovery.ip-type=IPv6 -# -#spring.cloud.nacos.username=nacos -#spring.cloud.nacos.password=nacos -# -#management.endpoints.web.exposure.include=* -#management.endpoint.health.show-details=always -#spring.main.allow-circular-references=true \ No newline at end of file +server.port=65001 +spring.application.name=service-provider-2022 +spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 +spring.cloud.nacos.config.server-addr=127.0.0.1:8848 +spring.cloud.nacos.config.enabled=true +spring.cloud.nacos.discovery.enabled=true +#spring.cloud.nacos.discovery.instance-enabled=true +#only register IPv4 instance +#spring.cloud.nacos.discovery.ip-type=IPv4 +#only register IPv6 instance +#spring.cloud.nacos.discovery.ip-type=IPv6 +spring.cloud.nacos.username=nacos +spring.cloud.nacos.password=nacos +management.endpoints.web.exposure.include=* +management.endpoint.health.show-details=always +spring.main.allow-circular-references=true \ No newline at end of file diff --git a/polaris-agent-plugins/spring-cloud-plugins/spring-cloud-plugin-common/src/main/conf/plugin/spring-cloud/application.properties b/polaris-agent-plugins/spring-cloud-plugins/spring-cloud-plugin-common/src/main/conf/plugin/spring-cloud/application.properties index d82a7b46..746fa650 100644 --- a/polaris-agent-plugins/spring-cloud-plugins/spring-cloud-plugin-common/src/main/conf/plugin/spring-cloud/application.properties +++ b/polaris-agent-plugins/spring-cloud-plugins/spring-cloud-plugin-common/src/main/conf/plugin/spring-cloud/application.properties @@ -1,7 +1,10 @@ # polaris server address spring.cloud.polaris.address=grpc\://127.0.0.1\:8091 +# polaris config address +spring.cloud.polaris.config.address=grpc\://127.0.0.1\:8093 # switch for spring cloud polaris all features spring.cloud.polaris.enabled=true +# Discovery # switch for spring cloud discovery feature spring.cloud.discovery.enabled=true # service discovery namespace value @@ -12,23 +15,35 @@ spring.cloud.polaris.discovery.enabled=true spring.cloud.polaris.discovery.register=true # switch for polaris loadbalancer feature spring.cloud.polaris.loadbalancer.enabled=true +# Lossless +# switch for lossless enable +spring.cloud.polaris.lossless.enabled=false +spring.cloud.polaris.lossless.port=28080 +spring.cloud.polaris.lossless.delayRegisterInterval=30000 +# Configuration +# switch for polaris configuration feature +spring.cloud.polaris.config.enabled=false +# Router # switch for polaris router feature -spring.cloud.polaris.router.enabled=true +spring.cloud.polaris.router.enabled=false # switch for polaris router(rule-router) feature spring.cloud.polaris.router.rule-router.enabled=true # switch for polaris router(metadata-router) feature spring.cloud.polaris.router.metadata-router.enabled=true # switch for polaris router(nearby-router) feature spring.cloud.polaris.router.nearby-router.enabled=true +# RateLimit # switch for polaris ratelimit feature -spring.cloud.polaris.ratelimit.enabled=true +spring.cloud.polaris.ratelimit.enabled=false # rejectHttpCode for polaris ratelimit, will be returned as limited spring.cloud.polaris.ratelimit.rejectHttpCode=429 -# switch for circuitbreaker -spring.cloud.polaris.circuitbreaker.enabled=true -feign.hystrix.enabled=true # maxQueuingTime for polaris ratelimit spring.cloud.polaris.ratelimit.maxQueuingTime=1000 +# CircuitBreaker +# switch for circuitbreaker +spring.cloud.polaris.circuitbreaker.enabled=false +feign.hystrix.enabled=true +# Rpc Enhancement # switch for rpc-enhancement feature spring.cloud.tencent.rpc-enhancement.enabled=true # switch for rpc-enhancement reporter feature @@ -39,10 +54,15 @@ spring.cloud.polaris.stat.enabled=false spring.cloud.polaris.stat.port=0 # path for polaris stat spring.cloud.polaris.stat.path=/metrics +# Metadata +# zone for instance +spring.cloud.tencent.metadata.content.region="" +# zone for instance +spring.cloud.tencent.metadata.content.zone="" +# campus for instance +spring.cloud.tencent.metadata.content.campus="" +# logger logging.level.root=INFO +# Nacos # switch for nacos discovery enable spring.cloud.nacos.discovery.enabled=false -# switch for lossless enable -spring.cloud.polaris.lossless.enabled=true -spring.cloud.polaris.lossless.port=28080 -spring.cloud.polaris.lossless.delayRegisterInterval=30000 \ No newline at end of file diff --git a/pom.xml b/pom.xml index eaa79429..8239a827 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ https://github.com/polarismesh/polaris-java-agent - 1.7.0-RC5 + 1.7.0-RC6 1.15.5 32.0.1-jre UTF-8