maven plugin classloader加載class問題

今天在寫基於maven plugin的一個小程序,它的功能是在maven執行install階段將已經打好包,從這個包中抽取分佈式服務中所有標識@Dic註解的字典枚舉類,之後會將這些字典枚舉類打成一個jar包。也就是執行了mvn install之後會在工程的target中生成兩個jar包,一個是服務器端部署包,一個是字典依賴包,同時會把該依賴包depoly到私服,把jar包座標信息,字典信息上傳給相應的服務展示出來供他人使用。

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>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章