Skip to content

Commit

Permalink
Merge pull request alibaba#381 from zhaoyb1990/master
Browse files Browse the repository at this point in the history
Add class routing feature
  • Loading branch information
oldmanpushcart authored Oct 17, 2022
2 parents 8804d42 + 1145875 commit eee7a1b
Show file tree
Hide file tree
Showing 22 changed files with 1,062 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.alibaba.jvm.sandbox.api.routing;

/**
* {@link AbstractRouting}
* <p>
* 抽象路由规则实现;包装了一层{@link AbstractRouting#appRouting()}逻辑,帮助找到应用的业务类加载器并优先使用
*
* @author zhaoyb1990
*/
public abstract class AbstractRouting implements RoutingExt {

@Override
public RoutingInfo getSpecialRouting() {
// 优先根据启动方式使用业务类加载器路由
RoutingInfo appRouting = appRouting();
if (appRouting != null) {
return appRouting;
}
return getSecondary();
}

/**
* 应用类加载器
*
* @return 类路由器
*/
protected RoutingInfo appRouting() {
return LaunchedRouting.useLaunchingRouting(getPattern());
}

/**
* 兜底路由信息
*
* @return 类路由器
*/
protected abstract RoutingInfo getSecondary();

/**
* 类路由的匹配器
*
* @return 匹配表达式
*/
protected abstract String[] getPattern();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.alibaba.jvm.sandbox.api.routing;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* {@link LaunchedRouting}
* <p>
* 根据应用启动方式,匹配对应的容器的类加载器进行路由
*
* @author zhaoyb1990
*/
public class LaunchedRouting {

private final static Pattern CLASS_PATTERN = Pattern.compile("\\S*([_a-zA-Z0-9.*])\\S*");

private final static Map<String, String> CLASSLOADER_WRAPPER = new HashMap<String, String>();

static {
/* tomcat */
CLASSLOADER_WRAPPER.put("org.apache.catalina.startup.Bootstrap",
"org.apache.catalina.loader.ParallelWebappClassLoader");
/* springboot */
CLASSLOADER_WRAPPER.put("org.springframework.boot.loader.JarLauncher",
"org.springframework.boot.loader.LaunchedURLClassLoader");
/* pandora boot*/
CLASSLOADER_WRAPPER.put("com.taobao.pandora.boot.loader.SarLauncher",
"com.taobao.pandora.boot.loader.LaunchedURLClassLoader");
}

/**
* 获取sun.java.command系统属性
* |
* v
* 找到启动类
* |
* v
* 映射classloader
*
* @param pattern 类匹配表达式
* @return 类路由规则
*/
public static RoutingInfo useLaunchingRouting(String... pattern) {
String sunJavaCommand = System.getProperty("sun.java.command");
String launchClass = matching(sunJavaCommand);
if (isEmpty(launchClass)) {
return null;
}
String classLoaderName = CLASSLOADER_WRAPPER.get(launchClass);
if (isEmpty(classLoaderName)) {
return null;
}
return RoutingInfo.withTargetClassloaderName(classLoaderName, pattern);
}

private static String matching(String source) {
Matcher matcher = CLASS_PATTERN.matcher(source);
if (matcher.find()) {
return matcher.group(0);
}
return "";
}

private static boolean isEmpty(String sequence) {
return sequence == null || sequence.isEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.alibaba.jvm.sandbox.api.routing;

/**
* <p>
* 提供给模块的扩展类路由SPI
* <p>
* 使用该扩展能力需要在模块资源路径(src/main/resources)下创建yaml配置文件(META-INFO/class-routing-config.yaml),开启路由开关并配置SPI模式
* <p>
* 详细配置方式,可参考(META-INFO/class-routing-config.yaml)
*
* @author zhaoyb1990
* @since {@code sandbox-common-api:1.4.0}
*/
public interface RoutingExt {

/**
* 获取类的特殊路由方式
*
* @return 类路由方式
*/
RoutingInfo getSpecialRouting();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package com.alibaba.jvm.sandbox.api.routing;

/**
* <p>
* 提供给模块使用的目标类路由白名单,匹配到pattern的类将通过目标类加载器加载
* <p>
* - 模块可以通过引入业务包进行编码,不用通过反射调用(引入jar: scope=provided)
* - 一些框架类型自定义类隔离机制(类似SOFA/Pandora)可自由选择类加载
*
* @author zhaoyb1990
*/
public class RoutingInfo {

/**
* 路由类型
*/
public enum Type {
/**
* 被pattern匹配的类,由目标的类的类加载器进行加载
*/
TARGET_CLASS,
/**
* 被pattern匹配的类,由目标类载器进行加载
*/
TARGET_CLASS_LOADER,
/**
* 被pattern匹配的类,由目标类加载器进行加载
*/
TARGET_CLASS_LOADER_NAME
}

/**
* 路由匹配正则表达式
*/
private String[] pattern;

/**
* 路由类型
*/
private Type type;

/**
* 目标类
*/
private String targetClass;

/**
* 目标类加载器的名字
*/
private String targetClassLoaderName;

/**
* 目标类加载器
*/
private ClassLoader targetClassloader;

private RoutingInfo() {}

/**
* 使用目标类的加载器进行路由
*
* @param targetClass 目标类
* @param pattern 匹配类正则
*/
public RoutingInfo(String targetClass, String... pattern) {
this.type = Type.TARGET_CLASS;
this.pattern = pattern;
this.targetClass = targetClass;
}

/**
* 使用目标类的加载器进行路由
*
* @param classLoader 目标类加载器
* @param pattern 匹配类正则
*/
public RoutingInfo(ClassLoader classLoader, String... pattern) {
this.type = Type.TARGET_CLASS_LOADER;
this.pattern = pattern;
this.targetClassloader = classLoader;
}

/**
* 使用目标类名称进行路由
*
* @param targetClass 目标类名称(如果出现多个类加载器,会使用第一个,默认会屏蔽sandbox自身的类加载器)
* @param pattern 匹配类正则
* @return 路由规则
*/
public static RoutingInfo withTargetClass(String targetClass, String... pattern) {
RoutingInfo routingInfo = new RoutingInfo();
routingInfo.type = Type.TARGET_CLASS;
routingInfo.pattern = pattern;
routingInfo.targetClass = targetClass;
return routingInfo;
}

/**
* 使用目标类加载器进行路由
*
* @param targetClassloader 目标加载器
* @param pattern 匹配类正则
* @return 路由规则
*/
public static RoutingInfo withTargetClassloader(ClassLoader targetClassloader, String... pattern) {
RoutingInfo routingInfo = new RoutingInfo();
routingInfo.type = Type.TARGET_CLASS_LOADER;
routingInfo.pattern = pattern;
routingInfo.targetClassloader = targetClassloader;
return routingInfo;
}

/**
* 使用目标类路由器名称进行路由
*
* @param targetClassLoaderName 目标类路由器名称, 通过classLoader#getClass()#getName()提取
* @param pattern 匹配类正则
* @return 路由规则
*/
public static RoutingInfo withTargetClassloaderName(String targetClassLoaderName, String... pattern) {
RoutingInfo routingInfo = new RoutingInfo();
routingInfo.type = Type.TARGET_CLASS_LOADER_NAME;
routingInfo.pattern = pattern;
routingInfo.targetClassLoaderName = targetClassLoaderName;
return routingInfo;
}

public String[] getPattern() {
return pattern;
}

public Type getType() {
return type;
}

public String getTargetClass() {
return targetClass;
}

public ClassLoader getTargetClassloader() {
return targetClassloader;
}

public String getTargetClassLoaderName() {
return targetClassLoaderName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.alibaba.jvm.sandbox.api.util;

import java.util.HashSet;
import java.util.Set;

/**
* {@link ClassloaderUtil}
* <p>
* 类加载器工具
*
* @author zhaoyb1990
*/
public class ClassloaderUtil {

private final static String PANDORA_CLASSLOADER = "com.taobao.pandora.service.loader.ModuleClassLoader";

private final static Set<String> BIZ_CLASS_LOADERS = new HashSet<String>();

static {
// tomcat
BIZ_CLASS_LOADERS.add("org.apache.catalina.loader.ParallelWebappClassLoader");
// spring boot
BIZ_CLASS_LOADERS.add("org.springframework.boot.loader.LaunchedURLClassLoader");
// pandora boot
BIZ_CLASS_LOADERS.add("com.taobao.pandora.boot.loader.LaunchedURLClassLoader");
}

/**
* 包装类加载器名称
*
* @param classLoader 类加载器
* @return 类加载器名称
*/
public static String wrapperName(ClassLoader classLoader) {
if (classLoader == null) {
return "BootstrapClassLoader";
}
String name = classLoader.getClass().getName();
if (PANDORA_CLASSLOADER.equals(name)) {
return classLoader.toString();
}
return name;
}

/**
* 查询当前应用的容器类加载器
*
* @param classLoaders 类加载器
* @return true / false
*/
public static ClassLoader findLaunchedClassLoader(Set<ClassLoader> classLoaders) {
for (ClassLoader classLoader : classLoaders) {
if (BIZ_CLASS_LOADERS.contains(wrapperName(classLoader))) {
return classLoader;
}
}
return null;
}
}
33 changes: 33 additions & 0 deletions sandbox-api/src/main/resources/META-INF/class-routing-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 路由启用总开关: true - 打开 | false - 关闭
classRoutingEnable: true

# 路由配置读取方式:
# 方式一: yaml - 通过yaml方式配置,比较纯净轻量级方式,通过文件读取生成配置,不支持targetClassLoader模式
# 方式二: spi - 通过SPI方式配置,支持targetClassLoader(指定classloader)路由,该方式会多一次模块jar的加载,有额外内存开销
routingConfigType: yaml

# yaml方式配置列表,配置参数如下:
# usingApp: 是否优先使用业务容器类加载器
# type: 路由匹配类型
# targetName: 匹配器目标类型
# pattern: 路由匹配表达式,{@link RoutingURLClassLoader$Routing }, 支持多个匹配表达式
routingConfigs:
# dubbo 目标类名方式路由参考
-
usingApp: true
type: 'targetClass'
targetName: 'org.apache.dubbo.rpc.model.ApplicationModel'
pattern: ['^org.apache.dubbo..*']

# http 目标类名方式路由参考
-
usingApp: true
type: 'targetClass'
targetName: 'javax.servlet.http.HttpServlet'
pattern: [ '^javax.servlet..*' ]

# dubbo 目标类加载器模式路由参考
-
type: 'targetClassloaderName'
targetName: 'org.apache.catalina.loader.WebappClassLoader'
pattern: ['^org.apache.dubbo..*']
Loading

0 comments on commit eee7a1b

Please sign in to comment.