API扫描工具类的实现

公司的架构师开发了一套接口扫描工具类,我想着,如果自己以后开公司了可能也用得着,所以研究了下,整理出了下面的开发思路和相关技术代码。
API扫描工具类的主要开发思路如下:
1、定义接口上的注解;
2、引用注解
3、扫描注解
首先定义注解:

定义接口上的注解

package com.fs.comment;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD, ElementType.FIELD,ElementType.LOCAL_VARIABLE,ElementType.PARAMETER})
public @interface PersistenceContext {
    String unitName();
}

关于注解:请参照下面的文章:
Java注解详解
Java注解之Retention、Documented、Target介绍
实用的java注解工具类
如果想要动态的修改注解的值【如动态切换数据源】,一般看来注解的值是不能动态修改的,但是借助特殊工具还是可以的,参照下面的文章:
JAVAssist—动态修改注解

引用注解

package com.fs.model.model2.model3.model4.model5;

import com.fs.comment.PersistenceContext;
import com.fs.model.EntityManager;

public class CollectionBase{  
    /** 
     * 注入实体单元 
     */  
    @PersistenceContext(unitName="collection-entity")  
    protected EntityManager em;  
    /**EntityManger 
     * 实例化 
     */
    @PersistenceContext(unitName="collection-entity-01-01-01-01-01-01")  
    public EntityManager getEntityManager() {          
        return this.em;  
    }  

    @PersistenceContext(unitName="collection-entity-01-02-02-01-01-01")
    public EntityManager getEntityManager2() {          
        return this.em;  
    }  
} 

扫描注解
扫描注解的工具类是参考了别人的代码写出来的。

package com.fs.commentUtils;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import com.fs.comment.PersistenceContext;

/**
 * 
 * 注解扫描工具类;
 *
 */
public class CommentScanUtil {
    /**    
       * getRequestMappingValue方法描述:  
       * 作者:JunJun
       * 日期:2018年6月7日 下午5:41:00         
       * 异常对象:@param packageName
       * 异常对象:@return 
    */
        public static List<String> getRequestMappingValue(String packageName) {

       // GetAnnotationValueUtil getAnnotationValueUtil = new GetAnnotationValueUtil();

             //第一个class类的集合  
            List<Class<?>> classes = new ArrayList<Class<?>>();  

            //是否循环迭代  
            boolean recursive = true;  

            //获取包的名字 并进行替换  
            String packageDirName = packageName.replace('.', '/');  

            //定义一个枚举的集合 并进行循环来处理这个目录下的文件 
            Enumeration<URL> dirs;
            try {  
                //读取指定package下的所有class
                dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName); 
                while (dirs.hasMoreElements()){  
                    URL url = dirs.nextElement();  
                    //得到协议的名称  
                    String protocol = url.getProtocol();
                    //判断是否以文件的形式保存在服务器上  
                    if ("file".equals(protocol)) {
                        //获取包的物理路径  
                         String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                         filePath = filePath.substring(1);
                        //以文件的方式扫描整个包下的文件 并添加到集合中  
                        findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
                    } 
                }  
            } catch (IOException e) {
                e.printStackTrace();  
            } 
            List<String> stringList = new ArrayList<String>();

            for (Class<?> clazz : classes) {
                //循环获取所有的类
                Class<?> c = clazz;
                //获取类的所有方法
                Method[] methods = c.getMethods();
                for (Method method : methods) {
                    //获取RequestMapping注解
                    PersistenceContext annotation = method.getAnnotation(PersistenceContext.class);
                    if (annotation != null) {
                        //获取注解的value值
                        String value = annotation.unitName();
                        System.out.println("-------anoValue--------"+value);
                    }
                }
            }
            return stringList;
        }


        /**    
         * findAndAddClassesInPackageByFile方法描述:  
         * 作者:JunJun
         * 日期:2018年6月7日 下午5:41:12         
         * 异常对象:@param packageName
         * 异常对象:@param packagePath
         * 异常对象:@param recursive
         * 异常对象:@param classes 
         */
        public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, List<Class<?>> classes){  
            //packagePath = packagePath.trim().substring(1);
            //获取此包的目录 建立一个File  
            File dir = new File(packagePath);
            //如果不存在或者 也不是目录就直接返回  
            if (!dir.exists() || !dir.isDirectory()) {
                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);
                }  
                else {  
                    //如果是java类文件 去掉后面的.class 只留下类名  
                    String className = file.getName().substring(0, file.getName().length() - 6); 
                    try {  
                        //添加到集合中去  
                        classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + "." + className));  
                    } catch (ClassNotFoundException e) {  
                        e.printStackTrace();  
                    }  
                }  
            } 
        }}

经过测试,证实这段代码对深层代码的递归扫描性能很高。测试的递归路径如下图所示:
这里写图片描述

另外在研究过程中尝试了自动解析API参数为JSON ,虽然最终以失败告终,失败的原因是写出来的代码无法适应参数中泛型以及泛型中属性名的动态匹配。下面是在尝试中总结出来的工具类:

扫描API中参数的类型工具类:

package com.fs;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

import com.fs.comment.NoNull;
import com.fs.model.BlueChartProjectSearchFilter;
import com.fs.model.ReqObject;
import com.fs.model.ReqQuery;
import com.fs.model.Student;
import com.fs.model.Teacher;

/**
 * 通过反射获取泛型信息
 * 
 * @author Administrator
 *
 */
public class Generic {
    /**
     * 方法一
     * 
     * @param map
     * @param list
     */
    public static void test01(@NoNull ReqObject<ReqQuery<BlueChartProjectSearchFilter>>  map, @NoNull List<Student> list,
            @NoNull Teacher<Student> a) {
        System.out.println("Generic.test01()");
    }


    public static void main(String[] args) {
        try {
            Method[] methods = Generic.class.getDeclaredMethods();
            Class<?>[] parameterTypes2 = methods[1].getParameterTypes();
            Method m = Generic.class.getMethod("test01", parameterTypes2);
            Class<?>[] parameterTypes = m.getParameterTypes();
            for (Class<?> class1 : parameterTypes) {
                System.out.println("---------><><><>" + class1);
            }
            Type[] t = m.getGenericParameterTypes();// 获取参数泛型
            for (Type paramType : t) {
                System.out.println("#" + paramType);
                if (paramType instanceof ParameterizedType) {
                    Type[] genericTypes = ((ParameterizedType) paramType).getActualTypeArguments();
                    for (@SuppressWarnings("unused") Type genericType : genericTypes) {
                        System.out.println("#"+paramType);
                    }
                }
            }

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }
}

字符串转化为JAVA代码的工具类:

package com.fs.commentUtils;

import java.util.Map;

import org.apache.commons.jexl2.Expression;
import org.apache.commons.jexl2.JexlContext;
import org.apache.commons.jexl2.JexlEngine;
import org.apache.commons.jexl2.MapContext;

import com.fs.model.BlueChartProjectSearchFilter;
import com.fs.model.ReqObject;
import com.fs.model.ReqQuery;

import net.sf.json.JSONObject;

/**
 * 字符串转化为代码;
 * 
 * @author xdsm
 *
 */
public class StringToCode {
    /**
     * 
     * @author: Longjun
     * @Description: 使用commons的jexl可实现将字符串变成可执行代码的功能
     * @date:2016年3月21日 下午1:45:13
     */
    public static Object convertToCode(String jexlExp, Map<String, Object> map) {
        JexlEngine jexl = new JexlEngine();
        Expression e = jexl.createExpression(jexlExp);
        JexlContext jc = new MapContext();
        for (String key : map.keySet()) {
            jc.set(key, map.get(key));
        }
        if (null == e.evaluate(jc)) {
            return "";
        }
        return e.evaluate(jc);
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {  
        /*ReqObject req = (ReqObject) Class.forName("com.fs.model.ReqObject").newInstance();
        ReqQuery reqq = (ReqQuery) Class.forName("com.fs.model.ReqQuery").newInstance();
        BlueChartProjectSearchFilter bcsf = (BlueChartProjectSearchFilter) Class.forName("com.fs.model.BlueChartProjectSearchFilter").newInstance();
        reqq.setObj(bcsf);
        req.setObj(reqq);
        JSONObject json = JSONObject.fromObject(req);*/
        /*Object req = Class.forName("com.fs.model.ReqObject").cast(Class.forName("com.fs.model.ReqObject").newInstance());
        Object reqq = Class.forName("com.fs.model.ReqQuery").cast(Class.forName("com.fs.model.ReqQuery").newInstance());
        Object bcsf = Class.forName("com.fs.model.BlueChartProjectSearchFilter").cast(Class.forName("com.fs.model.BlueChartProjectSearchFilter").newInstance());
        Field[] fields = req.getClass().getDeclaredFields();
        for (Field field : fields) {
            String fieldStr = field.toString();
            String[] fieldPreStrArray = fieldStr.split(" ");
            String fieldType = fieldPreStrArray[1];
            String fieldName = fieldPreStrArray[2].substring(fieldPreStrArray[2].lastIndexOf(".") + 1);
            System.out.println("----fieldType----"+fieldType);
            System.out.println("//----fieldName----"+fieldName);
        }*/
        ReqObject<ReqQuery<BlueChartProjectSearchFilter>> req = new ReqObject<ReqQuery<BlueChartProjectSearchFilter>>();
        JSONObject json = JSONObject.fromObject(req);
        System.out.println(json);
        /*try {  
            Map<String,Object> map=new HashMap<String,Object>();   
            map.put("money",2100);    
            String expression="money>=2000&&money<=4000";
            Object code = convertToCode(expression,map);
            System.out.println(code);
        } catch (Exception e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  */
    }  
}

参考文章:java将字符串转换成可执行代码


Reprint please specify: Blog4Jun API扫描工具类的实现