背景
低代码平台,需要实现一些UDF的能力,即用户在仓库或者平台上可以实现一块逻辑。然后会被打成SDK包,放到公司的私服仓库。
另一个服务在运行的时候,根据maven坐标与class名,动态的调用class中某些方法(该服务并不会显示的依赖这个maven坐标,而是通过动态加载的能力)
实现流程.png实现
- 引入依赖(非必须,主要用于场景case的实现)
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.16.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<version>2.1.9.RELEASE</version>
</dependency>
- 获取maven坐标的版本号:
- 目的:提高操作体验,用户可以选择「最新Release版」「最新版」「指定版」的版本号。但如果用户未指定,需要去私服仓库的
maven-metadata.xml
配置中解析出版本号。
代码实现:
import com.tellme.maven.sax.MavenMetaSAXHandler;
import com.tellme.maven.sax.MavenSnapshotMetaSAXHandler;
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
/**
* 根据坐标,获取到版本号
*/
@Slf4j
public class MavenVersionHelper {
private static final String NEXUS_PREFIX = "http://私服仓库/nexus/content/groups/public/";
private static final String NEXUS_SUBFIX = "/maven-metadata.xml";
/**
* 根据坐标,获取版本号。
*/
public static MavenVersion getVersion(String groupId, String artifactId) {
try {
String metaUrl = buildMavenMetaDataFilePath(groupId, artifactId);
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
MavenMetaSAXHandler handler = new MavenMetaSAXHandler();
parser.parse(metaUrl, handler);
return MavenVersion.builder().releaseVersion(handler.getReleaseVersion())
.latestVersion(handler.getLatestVersion()).build();
} catch (Exception e) {
log.error("getVersion error", e);
return null;
}
}
/**
* 对于快照版本,获取到真实jarPath.
*/
public static String getRealVersionForSnapshot(String groupId, String artifactId, String latestVersion) throws Exception {
String metaUrl = buildMavenMetaDataFilePath(groupId, artifactId, latestVersion);
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
MavenSnapshotMetaSAXHandler handler = new MavenSnapshotMetaSAXHandler();
parser.parse(metaUrl, handler);
//拿到真实的版本地址(时间戳)目的是拼接jar地址。
return latestVersion.replace("SNAPSHOT",
handler.getTimestamp() + "-" + handler.getBuildNumber());
}
private static String buildMavenMetaDataFilePath(String groupId, String artifactId) {
String replaceGroupId = String.join("/", groupId.split("\\."));
return String.format(NEXUS_PREFIX + "%s/%s/" + NEXUS_SUBFIX, replaceGroupId, artifactId);
}
private static String buildMavenMetaDataFilePath(String groupId, String artifactId, String version) {
groupId = String.join("/", groupId.split("\\."));
return String.format(NEXUS_PREFIX + "%s/%s/%s/" + NEXUS_SUBFIX, groupId, artifactId, version);
}
@Getter
@Builder
@ToString
public static class MavenVersion {
private String version;
private String releaseVersion;
private String latestVersion;
}
}
上面代码,是获取到maven-metadata.xml
配置,需要解析XML文件,以获取“真实”版本号信息 。“真实”表示的是,如果是Snapshot版本,那么jar应该存在时间戳后缀。
得到版本号后,下一步就可以拼接出来私服仓库的jar地址。
- 获取到私服的jar地址,创建出类加载器。
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import com.tellme.maven.PbVersionTypeEnum;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.boot.loader.LaunchedURLClassLoader;
import java.net.URL;
import java.net.URLClassLoader;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static com.tellme.maven.helper.MavenVersionHelper.getRealVersionForSnapshot;
import static com.tellme.maven.helper.MavenVersionHelper.getVersion;
@Slf4j
public class MavenJarHelper1 {
private static final Integer EXPIRE_SECONDS = 60;
private static final Integer MAXIMUM_SIZE = 10000;
private static final String NEXUS_PREFIX = "http://私服地址/nexus/content/groups/public/";
//处理GRPC的PB中import关系
public static Map<String, List<MavenDependency>> loadDependency = new HashMap<>();
//引入jar的依赖项配置
static {
loadDependency.put("parent-artifactId", Lists.newArrayList(
new MavenDependency("com", "artifactId-1"),
new MavenDependency("com", "artifactId-2")
));
}
private static final LoadingCache<Pair<String, String>, String> JAR_RELEASE_CACHE = CacheBuilder.newBuilder()
.concurrencyLevel(5)
.expireAfterWrite(Duration.ofSeconds(EXPIRE_SECONDS))
.maximumSize(MAXIMUM_SIZE)
.build(new CacheLoader<Pair<String, String>, String>() {
@Override
public String load(Pair<String, String> param) throws Exception {
String groupId = param.getLeft();
String artifactId = param.getRight();
return getReleaseJarUrl(groupId, artifactId);
}
});
private static final LoadingCache<Pair<String, String>, String> JAR_LATEST_CACHE = CacheBuilder.newBuilder()
.concurrencyLevel(5)
.expireAfterWrite(Duration.ofSeconds(EXPIRE_SECONDS))
.maximumSize(MAXIMUM_SIZE)
.build(new CacheLoader<Pair<String, String>, String>() {
@Override
public String load(Pair<String, String> param) throws Exception {
String groupId = param.getLeft();
String artifactId = param.getRight();
return getLatestJarUrl(groupId, artifactId);
}
});
/**
* 单纯maven坐标的类加载器
*/
private static final LoadingCache<String, URLClassLoader> CLASSLOADER_CACHE = CacheBuilder.newBuilder()
.concurrencyLevel(5)
.expireAfterWrite(Duration.ofSeconds(EXPIRE_SECONDS))
.maximumSize(MAXIMUM_SIZE)
.build(new CacheLoader<String, URLClassLoader>() {
@Override
public URLClassLoader load(String jarUrl) throws Exception {
try {
URL[] urls = new URL[]{new URL(jarUrl)};
return new LaunchedURLClassLoader(urls, MavenJarHelper1.class.getClassLoader());
} catch (Exception e) {
log.error("类库加载失败", e);
return null;
}
}
});
/**
* 加载依赖项的类加载器
*/
private static final LoadingCache<Pair<String, String>, URLClassLoader> CLASSLOADER_DEPENDENCY_CACHE =
CacheBuilder.newBuilder()
.concurrencyLevel(5)
.expireAfterWrite(Duration.ofSeconds(EXPIRE_SECONDS))
.maximumSize(MAXIMUM_SIZE)
.build(new CacheLoader<Pair<String, String>, URLClassLoader>() {
@Override
public URLClassLoader load(Pair<String, String> param) throws Exception {
try {
String groupId = param.getLeft();
String artifactId = param.getRight();
String jarUrl = getLatestJarUrl(groupId, artifactId);
if (StringUtils.isEmpty(jarUrl)) {
return null;
}
List<URL> urlList = Lists.newArrayList(new URL(jarUrl));
//读取需要加载的依赖项
List<MavenDependency> dependencyList = loadDependency.get(artifactId);
if (CollectionUtils.isNotEmpty(dependencyList)) {
for (MavenDependency dependency : dependencyList) {
String dependencyUrl;
if (StringUtils.isEmpty(dependency.getVersion())) {
dependencyUrl = getLatestJarUrl(dependency.getGroupId(),
dependency.getArtifactId());
} else {
dependencyUrl = getJarUrlByCoordinate(dependency.getGroupId(), dependency.getArtifactId(),
dependency.getVersion());
}
if (StringUtils.isNotEmpty(dependencyUrl)) {
urlList.add(new URL(dependencyUrl));
}
}
}
return new LaunchedURLClassLoader(urlList.toArray(new URL[0]),
MavenJarHelper1.class.getClassLoader());
} catch (Exception e) {
log.error("类库加载失败", e);
return null;
}
}
});
private static final LoadingCache<Pair<MavenCoordinate, Integer>, URLClassLoader> CLASSLOADER_DEPENDENCY_COORDINATE =
CacheBuilder.newBuilder()
.concurrencyLevel(5)
.expireAfterWrite(Duration.ofSeconds(EXPIRE_SECONDS))
.maximumSize(MAXIMUM_SIZE)
.build(new CacheLoader<Pair<MavenCoordinate, Integer>, URLClassLoader>() {
@Override
public URLClassLoader load(Pair<MavenCoordinate, Integer> param) throws Exception {
try {
int versionType = param.getRight();
MavenCoordinate mavenCoordinate = param.getLeft();
log.info("mavenCoordinate: {}", mavenCoordinate);
String groupId = mavenCoordinate.getGroupId();
String artifactId = mavenCoordinate.getArtifactId();
String jarUrl = "";
//自定义版本
if (versionType == PbVersionTypeEnum.CUSTOMIZE_VERSION.getCode()) {
jarUrl = getJarUrlByCoordinate(groupId, artifactId, mavenCoordinate.getVersion());
}
//最新release版本
if (versionType == PbVersionTypeEnum.LAST_RELEASE_VERSION.getCode()) {
jarUrl = getReleaseJarUrlWithCache(groupId, artifactId);
}
//最新版本
if (versionType == PbVersionTypeEnum.LASTED_VERSION.getCode()
|| StringUtils.isEmpty(jarUrl) || versionType == 0) {
jarUrl = getLatestJarUrl(groupId, artifactId);
}
if (StringUtils.isEmpty(jarUrl)) {
return null;
}
log.info("jarUrl: {}", jarUrl);
List<URL> urlList = Lists.newArrayList(new URL(jarUrl));
//读取依赖的jar
List<MavenDependency> dependencyList = loadDependency.get(artifactId);
if (CollectionUtils.isNotEmpty(dependencyList)) {
for (MavenDependency dependency : dependencyList) {
String dependencyUrl = getLatestJarUrl(dependency.getGroupId(),
dependency.getArtifactId());
if (StringUtils.isNotEmpty(dependencyUrl)) {
urlList.add(new URL(dependencyUrl));
}
}
}
return new LaunchedURLClassLoader(urlList.toArray(new URL[0]),
MavenJarHelper1.class.getClassLoader());
} catch (Exception e) {
log.error("类库加载失败", e);
return null;
}
}
});
public static String getReleaseJarUrlWithCache(String groupId, String artifactId) {
try {
return JAR_RELEASE_CACHE.get(Pair.of(groupId, artifactId));
} catch (Exception e) {
log.error("getReleaseJarUrlWithCache error", e);
return null;
}
}
public static String getLatestJarUrlWithCache(String groupId, String artifactId) {
try {
return JAR_LATEST_CACHE.get(Pair.of(groupId, artifactId));
} catch (Exception e) {
log.error("getReleaseJarUrlWithCache error", e);
return null;
}
}
public static URLClassLoader getClassLoaderCache(String jarUrl) {
try {
return CLASSLOADER_CACHE.get(jarUrl);
} catch (Exception e) {
log.error("getClassLoaderCache error", e);
return null;
}
}
public static URLClassLoader getClassLoaderDependencyCache(String groupId, String artifactId) {
try {
return CLASSLOADER_DEPENDENCY_CACHE.get(Pair.of(groupId, artifactId));
} catch (Exception e) {
log.error("getClassLoaderCache error", e);
return null;
}
}
public static URLClassLoader getClassLoaderByCoordinateCache(String groupId, String artifact, int versionType, String version) {
try {
MavenCoordinate mavenCoordinate = MavenCoordinate.builder()
.groupId(groupId)
.artifactId(artifact)
.version(version)
.build();
return CLASSLOADER_DEPENDENCY_COORDINATE.get(Pair.of(mavenCoordinate, versionType));
} catch (Exception e) {
log.error("pb getClassLoader error: ", e);
return null;
}
}
private static String getReleaseJarUrl(String groupId, String artifactId) {
try {
//根据maven坐标,获取到maven的pom文件,获取到最新的版本号
MavenVersionHelper.MavenVersion mavenVersion = getVersion(groupId, artifactId);
if (Objects.isNull(mavenVersion)) {
return null;
}
String releaseVersion = mavenVersion.getReleaseVersion();
if (StringUtils.isEmpty(releaseVersion)) {
return null;
}
return buildRemoteJarFilePath(groupId, artifactId, releaseVersion, releaseVersion);
} catch (Exception e) {
log.error("getReleaseJarUrl error", e);
return null;
}
}
private static String getLatestJarUrl(String groupId, String artifactId) {
try {
MavenVersionHelper.MavenVersion mavenVersion = getVersion(groupId, artifactId);
if (Objects.isNull(mavenVersion)) {
return null;
}
String latestVersion = mavenVersion.getLatestVersion();
if (StringUtils.isEmpty(latestVersion)) {
return null;
}
String realVersion = latestVersion;
if (latestVersion.contains("SNAPSHOT")) {
realVersion = getRealVersionForSnapshot(groupId, artifactId, latestVersion);
}
return buildRemoteJarFilePath(groupId, artifactId, latestVersion, realVersion);
} catch (Exception e) {
log.error("getLatestJarUrl error", e);
return null;
}
}
public static String getJarUrlByCoordinate(String groupId, String artifactId, String version) {
try {
if (StringUtils.isBlank(version)) {
return getLatestJarUrl(groupId, artifactId);
}
MavenVersionHelper.MavenVersion mavenVersion = MavenVersionHelper.MavenVersion.builder().version(version).build();
String customizeVersion = mavenVersion.getVersion();
String realVersion = customizeVersion;
if (customizeVersion.contains("SNAPSHOT")) {
realVersion = getRealVersionForSnapshot(groupId, artifactId, customizeVersion);
}
return buildRemoteJarFilePath(groupId, artifactId, customizeVersion, realVersion);
} catch (Exception e) {
log.error("getLatestJarUrl error", e);
return null;
}
}
/**
* 拼接URL地址
*/
private static String buildRemoteJarFilePath(String groupId, String artifactId, String version,
String realVersion) {
groupId = String.join("/", groupId.split("\\."));
return String.format(NEXUS_PREFIX + "%s/%s/%s/%s-%s.jar", groupId, artifactId, version, artifactId,
realVersion);
}
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public static class MavenDependency {
private String groupId;
private String version;
private String artifactId;
public MavenDependency(String groupId, String artifactId) {
this.groupId = groupId;
this.artifactId = artifactId;
}
}
@Data
@Builder
public static class MavenCoordinate {
private String groupId;
private String artifactId;
private String version;
private String scope;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MavenCoordinate that = (MavenCoordinate) o;
return Objects.equals(groupId, that.groupId) && Objects.equals(artifactId, that.artifactId);
}
@Override
public int hashCode() {
return Objects.hash(groupId, artifactId);
}
}
}
共用枚举
public enum PbVersionTypeEnum {
LASTED_VERSION(1, "全部最新版"),
LAST_RELEASE_VERSION(2, "Release最新版"),
CUSTOMIZE_VERSION(3, "自定义版本");
}
使用方式
public class Test {
public static void main(String[] args) throws Exception {
//根据maven坐标,将jar读取到本地容器中
URLClassLoader classLoader = MavenJarHelper.getClassLoaderByCoordinateCache("com.tellme",
"model", 3, "3.12.0");
if (Objects.isNull(classLoader)) {
System.out.println("classLoader 不存在");
return;
}
//加载某个类(PB文件)
Class<?> clazz = classLoader.loadClass("com.tellme.model.protobuf.AdIncModel$AdInstance");
//反射调用某个方法
Method method = clazz.getMethod("getDescriptor");
//获取到pb的结构
Descriptors.Descriptor descriptor = (Descriptors.Descriptor) method.invoke(null);
}
}
网友评论