美文网首页maven
【平台化引擎】根据maven坐标—获取到jar的Class文件(

【平台化引擎】根据maven坐标—获取到jar的Class文件(

作者: 小胖学编程 | 来源:发表于2024-02-03 10:51 被阅读0次

背景

低代码平台,需要实现一些UDF的能力,即用户在仓库或者平台上可以实现一块逻辑。然后会被打成SDK包,放到公司的私服仓库。

另一个服务在运行的时候,根据maven坐标与class名,动态的调用class中某些方法(该服务并不会显示的依赖这个maven坐标,而是通过动态加载的能力)

实现流程.png

实现

  1. 引入依赖(非必须,主要用于场景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>
  1. 获取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地址。

  1. 获取到私服的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);
    }
}

相关文章

  • 记:分布式单点登录框架 xxl-sso

    1、修改jar包的class文件步骤: 2、用maven打包后将修改好源代码生成的class文件上传到服务器特定的...

  • Maven

    为什么要使用Maven 添加第三方jar包,使用Maven后,jar包没必要复制到lib文件下了,只需要给一个坐标...

  • maven项目打包命令

    maven项目打包首先需执行mvn clean命令清除编译所产生的文件(包括编译的class、jar、war) 再...

  • Mac下生成Dex的Jar文件

    打包包含单个class文件的jar文件使用命令jar cvf output.jar origin.class注意:...

  • 二.Spring起步 (前期配置)

    本文根据此文章更改 一、maven的下载配置 下载maven安装包 非系统给盘符下创建(maven.jar)文件夹...

  • maven_3_仓库

    生成jar包 新建web_maven工程1 写3个Java类 mvn install:编译成class文件、执行测...

  • mybatis入门的前期准备

    使用步骤如下: 首先创建一个Maven工程,在pom.xml文件中引入mybatis的jar包坐标

  • jQuery获取元素值

    使用jQuery取值要引用jQuery文件。 先根据id或者class取到元素,再用.text()或.html()...

  • 四、实战过程

    1、将java文件编译成class: 2、将class打成jar包 【jar -cvf *****.jar *.c...

  • Intellij idea配置Spark开发环境,统计哈姆雷特词

    idea 新建maven 项目 输入maven坐标maven 坐标 编辑maven文件Spark 体系 中间层Sp...

网友评论

    本文标题:【平台化引擎】根据maven坐标—获取到jar的Class文件(

    本文链接:https://www.haomeiwen.com/subject/upeaodtx.html