maven plugin classloader加载class问题

顾鸣
2023-12-01

今天在写基于maven plugin的一个小程序,它的功能是在maven执行install阶段将已经打好包,从这个包中抽取分布式服务中所有标识@Dic注解的字典枚举类,之后会将这些字典枚举类打成一个jar包。也就是执行了mvn install之后会在工程的target中生成两个jar包,一个是服务器端部署包,一个是字典依赖包,同时会把该依赖包depoly到私服,把jar包坐标信息,字典信息上传给相应的服务展示出来供他人使用。

使用URLClassLoader urlClassLoader = new URLClassLoader(urls,Thread.currentThread().getContextClassLoader());

不要使用ContextClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();

否则会发现执行maven命令的时候加载的是从maven的repository里的jar,而不是当前工程的classpath

package com.annotation.processor;

import com.alibaba.fastjson.JSONObject;
import com.annotition.Dic;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;

import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.*;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author lmfeng
 * @goal dic
 * @requiresDependencyResolution runtime
 * @phase deploy
 */
public class DicGenerateMojo extends AbstractMojo {

    /**
    * @parameter default-value="${project}"
    */
    private MavenProject project;

    /**
     * @parameter property="scanDir" default-value="${basedir}/src/main/java/"
     */
    private String scanDir;

    /**
     * @parameter property="scanPackage" default-value="com.xxx"
     */
    private String scanPackage;

    /**
     * @parameter property="groupId" default-value="com.xxx"
     */
    private String groupId;

    /**
     * @parameter property="version" default-value="1.0"
     */
    private String version;

    /**
     * @parameter property="packaging" default-value="jar"
     */
    private String packaging;

    /**
     * @parameter property="url" default-value="https://repo.xxx-inc.com/repository/snapshots"
     */
    private String url;
    
    /**
     * @parameter property="reportUrl"
     */
    private String reportUrl;

    /**
     * @parameter property="repositoryId" default-value="nexus-snapshots"
     */
    private String repositoryId;

    /**
     * @parameter property="classPath" default-value="${basedir}/target/classes/"
     */
    private String classPath;

    /**
     * 字典类全路径集合
     */
    private Set<String> dicClassesRealPath = new LinkedHashSet<String>();

    private Set<Object> dicInformation = new HashSet<Object>();

    // class类的集合
    private Set<Class<?>> classes = new LinkedHashSet<Class<?>>();

    public static void main(String[] args) throws IOException, MojoFailureException, MojoExecutionException {
        DicGenerateMojo dicGenerateMojo = new DicGenerateMojo();
        dicGenerateMojo.execute();
    }

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        // package
        String dicJarPath = packageing();
        // deploy
        boolean deployDicJar = deployDicJar(dicJarPath);
        getLog().info(deployDicJar ? "deploy dic jar successed":"deploy dic jar failed");
        if (deployDicJar){
            // delete jar
            deleteJar(dicJarPath);
            // 上报信息 url
            jsonPost(reportUrl,dicInformation);
        }
    }

    /**
     * 删除 jar
     *
     * @param filePath
     * @return
     */
    private boolean deleteJar(String filePath){
        return new File(filePath).delete();
    }

    // mvn deploy:deploy-file -DgroupId=com.test -DartifactId=test -Dversion=1.0 -Dpackaging=jar
    private boolean deployDicJar(String dicJarPath){
        try {
            if (dicJarPath == null){
                getLog().warn("dicJarPath is null+");
                return false;
            }
            StringBuffer cmd = new StringBuffer("mvn deploy:deploy-file");
            String artifactId = app+"-dic";
            cmd.append(" -DgroupId="+groupId).append(" -DartifactId="+artifactId).append(" -Dversion="+version).append(" -Dpackaging="+packaging)
                    .append(" -Dfile="+dicJarPath).append(" -Durl="+url).append(" -DrepositoryId="+repositoryId);
            System.err.println(cmd.toString());
            // Linux or Mac下
            Process process = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", cmd.toString()});
            process.waitFor();
            process.destroy();
            System.err.println("dic 坐标是:=======>");
            StringBuffer sb = new StringBuffer("<dependency>");
            sb.append("\n").append("<groupId>").append(groupId).append("<groupId>").append("\n")
                    .append("<artifactId>").append(artifactId).append("<artifactId>").append("\n")
                    .append("<version>").append(version).append("<version>").append("\n").append("<dependency>");
            System.err.println(sb.toString());
            return true;
        } catch (Exception e) {
            getLog().error("deploy is failed !",e);
        }
        return false;
    }

    // 通过 jar cvf appName-dic.jar 将 class 生成 jar 包
    private String packageing(){
        try {
            Set<String> dicClassesRealPath = getDicClassesRealPath(scanPackage);
            if (dicClassesRealPath.size() == 0){
                getLog().warn("scan "+scanPackage+", no class is used for @Dic annotation");
                return null;
            }
            // 打成 jar
            StringBuffer cmd = new StringBuffer("jar cvf ");
            if (app == null){
                app = "flm_test";
            }
            String jarName = app+"-dic"+'-'+version+'.'+packaging;
            cmd.append(jarName);
            for (String str : dicClassesRealPath) {
                cmd.append(" ").append(str);
            }
            System.err.println(cmd.toString());
            // Linux or Mac下
            Process process = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", cmd.toString()});
            process.waitFor();
            process.destroy();
            File dicJarFile = new File(jarName);
            if (dicJarFile.exists()){
                String dicJarPath = dicJarFile.getAbsolutePath();
                System.out.println("dicJarPath is => "+dicJarPath);
                return dicJarPath;
            }
        } catch (Exception e) {
            getLog().error("package failed",e);

        }
        return null;
    }

    // TODO 判断当前系统是 Linux or windows

    /**
     * 获取字典类全路径
     *
     * @param scanPackage
     * @return
     */
    public Set getDicClassesRealPath(String scanPackage){
        // 包下面的类
        if (scanPackage == null){
            scanPackage = "com.xxx";
            System.err.println("scanPackage is default => "+scanPackage);
        }
        System.out.println("scanPackage is =>"+scanPackage);
        getClasses(scanPackage);
        return dicClassesRealPath;
    }

    /**
     * 获取方法上的注解
     *
     * @param clazz
     */
    private void getMethodsAnnotation(Class<?> clazz) {
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            Annotation[] annotations = method.getDeclaredAnnotations();
            for (Annotation annotation : annotations) {
                System.out.println(clazz.getSimpleName().concat(".").concat(method.getName()).concat(".")
                        .concat(annotation.annotationType().getSimpleName()));
            }
        }
    }

    /**
     * 从包package中获取所有的Class
     *
     * @param pack
     * @return
     */
    public void getClasses(String pack) {

        // 是否循环迭代
        boolean recursive = true;
        // 获取包的名字 并进行替换
        String packageName = pack;
        String packageDirName = packageName.replace('.', '/');
        // 定义一个枚举的集合 并进行循环来处理这个目录下的things
        Enumeration<URL> dirs;
        try {
            URL[] urls = new URL[1];
            urls[0] = new URL("file:"+classPath);
            URLClassLoader urlClassLoader = new URLClassLoader(urls,Thread.currentThread().getContextClassLoader());
            dirs = urlClassLoader.getResources(packageDirName);

            // 使用上下文类加载器有问题。。。
//            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            // 循环迭代下去
            System.out.println("dirs.hasMoreElements()"+dirs.hasMoreElements());
            while (dirs.hasMoreElements()) {
                // 获取下一个元素
                URL url = dirs.nextElement();
                System.out.println("URL is => "+url);
                // 得到协议的名称
                String protocol = url.getProtocol();
                // 如果是以文件的形式保存在服务器上
                if ("file".equals(protocol)) {
                    System.err.println("file类型的扫描");
                    // 获取包的物理路径
                    String packagePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    System.out.println("packagePath is => "+packagePath);
                    // 以文件的方式扫描整个包下的文件 并添加到集合中
                    findAndAddClassesInPackageByFile(packageName, packagePath, recursive, classes,urlClassLoader);
                }  
//                else if ("jar".equals(protocol)) {
//                    packageName = scanJarFile(classes, recursive, packageName, packageDirName, url);
//                }
            }
        } catch (IOException e) {
            getLog().error("getClasses failed ", e);
        }
    }

    /**
     * load jar
     */
    private String scanJarFile(Set<Class<?>> classes, boolean recursive, String packageName, String packageDirName, URL url) {

        // 如果是jar包文件 定义一个JarFile System.err.println("jar类型的扫描");
        JarFile jar;
        try {
            // 获取jar
            jar = ((JarURLConnection) url.openConnection()).getJarFile();
            // 从此jar包 得到一个枚举类
            Enumeration<JarEntry> entries = jar.entries();
            // 同样的进行循环迭代
            while (entries.hasMoreElements()) {
                // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
                JarEntry entry = entries.nextElement();
                String name = entry.getName();
                // 如果是以/开头的
                if (name.charAt(0) == '/') {
                    // 获取后面的字符串
                    name = name.substring(1);
                }
                // 如果前半部分和定义的包名相同
                if (name.startsWith(packageDirName)) {
                    int idx = name.lastIndexOf('/');
                    // 如果以"/"结尾 是一个包
                    if (idx != -1) {
                        // 获取包名 把"/"替换成"."
                        packageName = name.substring(0, idx).replace('/', '.');
                    }
                    // 如果可以迭代下去 并且是一个包
                    if ((idx != -1) || recursive) {
                        // 如果是一个.class文件 而且不是目录
                        if (name.endsWith(".class") && !entry.isDirectory()) {
                            // 去掉后面的".class" 获取真正的类名
                            String className = name.substring(packageName.length() + 1, name.length() - 6);
                            try {
                                // 添加到classes
                                classes.add(Class.forName(packageName + '.' + className));
                            } catch (ClassNotFoundException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        } catch (IOException e) {
            // log.error("在扫描用户定义视图时从jar包获取文件出错");
            e.printStackTrace();
        }
        return packageName;
    }

    /**
     * 以文件的形式来获取包下的所有Class
     *
     * @param packageName
     * @param packagePath
     * @param recursive
     * @param classes
     */
    public void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive,
                                                        Set<Class<?>> classes,ClassLoader classLoader) {
        // 获取此包的目录 建立一个File
        File dir = new File(packagePath);
        // 如果不存在或者 也不是目录就直接返回
        if (!dir.exists() || !dir.isDirectory()) {
             getLog().warn("用户定义包名 " + packagePath + " 下没有任何文件");
            return;
        }
        // 如果存在 就获取包下的所有文件 包括目录
        File[] dirfiles = dir.listFiles(new FileFilter() {
            // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
            public boolean accept(File file) {
                return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
            }
        });
        // 循环所有文件
        for (File file : dirfiles) {
            // 如果是目录 则继续扫描
            if (file.isDirectory()) {
                findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive,
                        classes,classLoader);
            } else {
                // 如果是java类文件 去掉后面的.class 只留下类名
                String className = file.getName().substring(0, file.getName().length() - 6);
                try {
                    // 添加到集合中去 这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
                    // classes.add(Class.forName(packageName + '.' + className));
                    System.out.println("packageName+className => "+packageName + '.' + className);
//                    Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className);
                    Class<?> clazz = classLoader.loadClass(packageName + '.' + className);
                    System.out.println("clazz is => "+clazz);
                    if (clazz.isEnum() && isDicClass(clazz)){
                        dicClassesRealPath.add(file.getAbsolutePath());
                        // 获得枚举类详细信息
                        dicInformation.add(enumInformation(clazz));
                    }
                } catch (Exception e) {
                    getLog().error("load class failed" ,e);
                }
            }
        }
    }

    /**
     * 是否使用@Dic 注解
     *
     * @param clazz
     * @return
     */
    private boolean iDicClass(Class<?> clazz){
        Annotation[] annos = clazz.getAnnotations();
        for (Annotation anno : annos) {
            if(Dic.class.equals(anno.annotationType())){
                return true;
            }
        }
        return false;
    }

    /**
     * 枚举类详细信息
     *
     * @param clazz
     */
    private Map<String,List<Map<String,Object>>> enumInformation(Class<?> clazz){
        Map<String,List<Map<String,Object>>> enumTypeMap = new HashMap<String,List<Map<String,Object>>>();
        Class<Enum> enumClass = (Class<Enum>) clazz;
        List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();
        // TODO 把信息添加到DicJar里
        String clazzName = enumClass.getName();
        Enum[] enumConstants = enumClass.getEnumConstants();
        Map<String, Method> methods = getMethods(enumClass, enumConstants);
        for (Enum enumType : enumConstants) {
            Map<String,Object> map = new HashMap<String,Object>();
            for (String key : methods.keySet()) {
                try {
                    Method method = methods.get(key);
                    Object invoke = method.invoke(enumType);
                    map.put(key,invoke);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            String name = enumType.name();
            int ordinal = enumType.ordinal();
            map.put("name",name);
            map.put("ordinal",ordinal);
            list.add(map);
        }
        enumTypeMap.put(clazzName,list);
        return enumTypeMap;
    }

    private Map<String,Method> getMethods(Class<Enum> enumClass,Enum[] enumConstants){
        List<String> enumNames = new ArrayList<String>();
        Map<String,Method> methods = new HashMap<String, Method>();
        for (Enum enumType : enumConstants) {
            enumNames.add(enumType.name());
        }
        Field[] declaredFields = enumClass.getDeclaredFields();
        for (Field field:declaredFields) {
            String fieldName = field.getName();
            if (!enumNames.contains(fieldName) && !fieldName.equals("$VALUES")){
                try {
                    Method method = enumClass.getMethod("get" + (fieldName.charAt(0) + "").toUpperCase() + fieldName.substring(1, fieldName.length()));
                    methods.put(fieldName,method);
                } catch (NoSuchMethodException e) {
                    getLog().error(e.getMessage(),e);
                }
            }
        }
        return methods;
    }

    /**
     * 父类、接口
     *
     * @param clazz
     * @param classesAll
     * @return
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public Set<Class<?>> getByInterface(Class clazz, Set<Class<?>> classesAll) {
        Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
        // 获取指定接口的实现类
        if (!clazz.isInterface()) {
            try {
                /**
                 * 循环判断路径下的所有类是否继承了指定类 并且排除父类自己
                 */
                Iterator<Class<?>> iterator = classesAll.iterator();
                while (iterator.hasNext()) {
                    Class<?> cls = iterator.next();
                    /**
                     * isAssignableFrom该方法的解析,请参考博客:
                     * http://blog.csdn.net/u010156024/article/details/44875195
                     */
                    if (clazz.isAssignableFrom(cls)) {
                        if (!clazz.equals(cls)) {// 自身并不加进去
                            classes.add(cls);
                        } else {

                        }
                    }
                }
            } catch (Exception e) {
                System.out.println("出现异常");
            }
        }
        return classes;
    }

    /**
     * 发送HttpPost请求
     *
     * @param strURL 服务地址
     * @param params
     *
     * @return 成功:返回json字符串<br/>
     */
    public String jsonPost(String strURL, Object params) {
        try {
            URL url = new URL(strURL);// 创建连接
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setUseCaches(false);
            connection.setInstanceFollowRedirects(true);
            connection.setRequestMethod("POST"); // 设置请求方式
            connection.setRequestProperty("Accept", "application/json"); // 设置接收数据的格式
            connection.setRequestProperty("Content-Type", "application/json"); // 设置发送数据的格式
            connection.connect();
            OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream(), "UTF-8"); // utf-8编码
            out.append(JSONObject.toJSONString(params));
            out.flush();
            out.close();

            int code = connection.getResponseCode();
            InputStream is = null;
            if (code == 200) {
                is = connection.getInputStream();
            } else {
                is = connection.getErrorStream();
            }

            // 读取响应
            int length = (int) connection.getContentLength();// 获取长度
            if (length != -1) {
                byte[] data = new byte[length];
                byte[] temp = new byte[512];
                int readLen = 0;
                int destPos = 0;
                while ((readLen = is.read(temp)) > 0) {
                    System.arraycopy(temp, 0, data, destPos, readLen);
                    destPos += readLen;
                }
                String result = new String(data, "UTF-8"); // utf-8编码
                return result;
            }

        } catch (IOException e) {
            getLog().error("Exception occur when send http post request!", e);
        }
        return "error"; // 自定义错误信息
    }
}
  • @Dic字典注解

    package com.annotation.dic;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @author lmfeng
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Dic {
    
        public String value() default "";
    
        /**
         * 字典描述信息
         *
         * @return string
         */
        public String dicDecription() default "";
    
    }

     

  • maven依赖

<dependencies>
		<dependency>
			<groupId>org.apache.maven</groupId>
			<artifactId>maven-plugin-api</artifactId>
			<version>2.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.maven</groupId>
			<artifactId>maven-plugin-descriptor</artifactId>
			<version>2.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.maven</groupId>
			<artifactId>maven-project</artifactId>
			<version>2.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.maven</groupId>
			<artifactId>maven-model</artifactId>
			<version>2.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.maven</groupId>
			<artifactId>maven-artifact</artifactId>
			<version>2.0</version>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.29</version>
		</dependency>
		<dependency>
		    <groupId>org.apache.commons</groupId>
		    <artifactId>commons-lang3</artifactId>
		    <version>3.5</version>
		</dependency>
		<dependency>
		    <groupId>org.apache.commons</groupId>
		    <artifactId>commons-compress</artifactId>
		    <version>1.3</version>
		</dependency>
		<dependency>
		    <groupId>commons-io</groupId>
		    <artifactId>commons-io</artifactId>
		    <version>2.6</version>
		</dependency>
	</dependencies>

 

 类似资料: