Java类扫描器的实现(使用模板Template模式)

闲暇之余,仔细阅读了黄勇的smart-framework框架的源码,发现在他的框架中多次使用了模板模式,一直对模板模式比较陌生,就对该框架中的精粹提取出来学习了。
本文将利用模版模式,实现一个类扫描器。想深入了解的朋友们可以去阅读黄勇的smart-framework框架源码。

1.首先编写模板类(获取类的模板类)

import org.apache.commons.lang3.StringUtils;
import org.muran.framework.FinalLogger;
import org.muran.framework.util.ClassUtil;

import java.io.File;
import java.io.FileFilter;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Created by MURAN on 2017/7/27.
 *
 * 用于获取类的模版类
 */
public abstract class ClassTemplate {

    private static final FinalLogger logger = new FinalLogger(ClassTemplate.class);

    /**
     * 包的路径
     */
    protected final String packageName;

    protected ClassTemplate(String packageName){
        this.packageName = packageName;
    }

    /**
     * 类的筛选条件
     *
     * 要求实现类必须实现该方法
     *
     * @param cls
     * @return
     */
    public abstract boolean checkAddClass(Class cls);

    /**
     * 将类加载并且放入list中
     * @param classList
     * @param className
     */
    private void doAddClass(List<Class<?>> classList, String className) throws Exception{
        Class<?> cls = ClassUtil.loadClass(className,false);
        //判断是否符合筛选条件
        if(checkAddClass(cls)){
            //添加类
            classList.add(cls);
        }
    }

    /**
     * 添加类
     * @param classList
     * @param packagePath
     * @param packageName
     */
    private void addClass(List<Class<?>> classList, String packagePath, String packageName){
        try{
            File[] files = new File(packagePath).listFiles(new FileFilter() {
                @Override
                public boolean accept(File file) {
                    //获取所有的 以.class结尾的文件或者是文件夹
                    return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
                }
            });

            if(null != files){
                //遍历文件或者目录
                for(File file : files){
                    String fileName = file.getName();
                    if(file.isFile()){//如果是文件
                        //获取类名
                        String className = fileName.substring(0,fileName.lastIndexOf("."));
                        if(StringUtils.isNotEmpty(className)){
                            //解决扫描全部包的时候,缺省包报错
                            if(!packageName.equals(""))
                                className = packageName + "." + className;
                        }
                        //添加类
                        doAddClass(classList, className);
                    }else { //如果是目录
                        //获取子包目录
                        String subPackagePath = fileName;
                        if(StringUtils.isNotEmpty(subPackagePath)){
                            //解决扫描全部包的时候 如果不限定包名会出现//
                            if(!packagePath.endsWith("/")){
                                subPackagePath = packagePath + "/" + subPackagePath;
                            }else{
                                subPackagePath = packagePath + subPackagePath;
                            }
                        }
                        //子包名
                        String subPackageName = fileName;
                        if(StringUtils.isNotEmpty(subPackageName)){
                            //解决扫描全部包的时候 如果不限定包名会出现..
                            if(!packageName.equals("")){
                                subPackageName = packageName + "." + subPackageName;
                            }else {
                                subPackageName = packageName + subPackageName;
                            }
                        }
                        //递归调用
                        addClass(classList, subPackagePath, subPackageName);
                    }
                }
            }
        } catch (Exception e){
            logger.error("添加类出错", e);
        }
    }

    /**
     * 获取所有的类
     * @return
     */
    public final List<Class<?>> getClassList(){
        List<Class<?>> classList = new ArrayList<Class<?>>();
        try {
            //从包名获取资源
            Enumeration<URL> urls = ClassUtil.getClassLoader().getResources(packageName.replace(".", "/"));
            //遍历URL资源
            while (urls.hasMoreElements()){
                URL url = urls.nextElement();

                if(null != url){
                    //获取协议名(分为file和jar)
                    String protocol = url.getProtocol();
                    if(protocol.equals("file")){
                        // 若在 class 目录中,则执行添加类操作
                        String packagePath = url.getPath().replaceAll("%20", " ");//%20代表空格
                        addClass(classList,packagePath,packageName);
                    }else if(protocol.equals("jar")){
                        // 若在 jar 包中,则解析 jar 包中的 entry
                        JarURLConnection jarURLConnection = (JarURLConnection)url.openConnection();
                        JarFile jarFile = jarURLConnection.getJarFile();
                        Enumeration<JarEntry> jarEntries = jarFile.entries();
                        while (jarEntries.hasMoreElements()){
                            JarEntry jarEntry = jarEntries.nextElement();
                            String jarEntryName = jarEntry.getName();
                            //判断是否为class
                            if(jarEntryName.endsWith(".class")){
                                String className = jarEntryName.substring(0,jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
                                doAddClass(classList, className);
                            }
                        }
                    }
                }
            }

        }catch (Exception e){
            logger.error("获取类出错", e);
        }
        return classList;
    }
}

拥有了获取所有类的模板类,当我们需要获得指定特征的类的时候,一般可以先通过模板类获取所有类,然后遍历筛选即可,不过我觉得这就忽略了模板模式的优势了,我们可以这样做

2.编写获取基础了指定父类的子类的模板

/**
 * Created by MURAN on 2017/7/27.
 *
 * 获取继承指定类的父类
 */
public abstract class SuperClassTemplate extends ClassTemplate {

    /**
     * 指定的父类
     */
    protected final Class<?> superClass;

    protected SuperClassTemplate(String packageName, Class<?> superClass) {
        super(packageName);
        this.superClass = superClass;
    }
}

同理,我们还可以编写一个获取带有指定注解的类

3.编写获取带有指定注解的类模板

import java.lang.annotation.Annotation;

/**
 * Created by MURAN on 2017/7/27.
 *
 * 注解类的模版类
 */
public abstract class AnnotationClassTemplate extends ClassTemplate {

    /**
     * 指定的注解
     */
    protected final Class<? extends Annotation> annotationClass;

    protected AnnotationClassTemplate(String packageName, Class<? extends Annotation> annotationClass) {
        super(packageName);
        this.annotationClass = annotationClass;
    }
}

好了,现在基础的三种模板类已经写好,都是抽象类,并且checkAddClass()方法是抽象的,而此方法正是筛选扫描的类的过滤器。这样我们在要获取继承指定类的时候可以这样写

public List<Class<?>> getClassListBySuper(String packageName, Class<?> superClass) {
        return new SuperClassTemplate(packageName, superClass){
            @Override
            public boolean checkAddClass(Class cls) {
                return superClass.isAssignableFrom(cls) && !superClass.equals(cls);
            }
        }.getClassList();
}

怎么样,是不是非常的方便,使用匿名内部类筛选所需要的类。会思考的同学肯定会看出来,这样做比起获取所有类之后再筛选要节约了很多时间,也就是性能更出色。好了,知道了怎么用,我们就来编写扫描器吧!

4.编写类扫描器接口(为什么要写接口?往下看就知道了)

import java.lang.annotation.Annotation;
import java.util.List;

/**
 * Created by MURAN on 2017/7/27.
 *
 * 类扫描器
 */
public interface ClassScanner {

    /**
     * 获取指定包名中的所有类
     */
    List<Class<?>> getClassList(String packageName);

    /**
     * 获取指定包名中指定注解的相关类
     */
    List<Class<?>> getClassListByAnnotation(String packageName, Class<? extends Annotation> annotationClass);

    /**
     * 获取指定包名中指定父类或接口的相关类
     */
    List<Class<?>> getClassListBySuper(String packageName, Class<?> superClass);
}

5.编写实现类

package org.muran.framework.core.impl;

import org.muran.framework.core.ClassScanner;
import org.muran.framework.core.impl.support.AnnotationClassTemplate;
import org.muran.framework.core.impl.support.ClassTemplate;
import org.muran.framework.core.impl.support.SuperClassTemplate;

import java.lang.annotation.Annotation;
import java.util.List;

/**
 * Created by MURAN on 2017/7/27.
 *
 * 默认的类扫描器
 */
public class DefaultClassScanner implements ClassScanner {
    @Override
    public List<Class<?>> getClassList(String packageName) {
        return new ClassTemplate(packageName){
            @Override
            public boolean checkAddClass(Class cls) {
                String className = cls.getName();
                String pkgName = className.substring(0, className.lastIndexOf("."));
                return pkgName.startsWith(packageName);
            }
        }.getClassList();
    }

    @Override
    public List<Class<?>> getClassListByAnnotation(String packageName, Class<? extends Annotation> annotationClass) {
        return new AnnotationClassTemplate(packageName, annotationClass){
            @Override
            public boolean checkAddClass(Class cls) {
                return cls.isAnnotationPresent(annotationClass);
            }
        }.getClassList();
    }

    @Override
    public List<Class<?>> getClassListBySuper(String packageName, Class<?> superClass) {
        return new SuperClassTemplate(packageName, superClass){
            @Override
            public boolean checkAddClass(Class cls) {
                return superClass.isAssignableFrom(cls) && !superClass.equals(cls);
            }
        }.getClassList();
    }
}

好了,这样一个类扫描器就算完成了。那为什么要定义接口呢?细心的同学应该已经发现实现类的名字为DefaultClassScanner,原因应该不用我解释了吧。 如果没理解,那就在评论区请教一下别人吧,该知识不在本文知识点内。

6.最后来写一个类 测试一下

import org.muran.framework.core.ClassScanner;
import org.muran.framework.core.impl.DefaultClassScanner;
import org.muran.framework.util.ClassUtil;

import java.lang.reflect.Field;
import java.util.List;

/**
 * Created by MURAN on 2017/7/24.
 */
public class TEST{

    public static void main(String[] args) throws Exception {
        ClassScanner classScanner = new DefaultClassScanner();
        //获取所有路径下的类
        List<Class<?>> classList = classScanner.getClassList("");
    }
}

收工!

2017-08-09 更新
之前代码中设计到ClassUtil类是自己包装的,忘记放源码了,现在补上ClassUtil类的源码如下

package org.muran.framework.util;

import org.muran.framework.FinalLogger;
import org.muran.framework.core.scanner.ClassScanner;
import org.muran.framework.core.general.GeneralController;
import org.muran.framework.core.general.GeneralDao;
import org.muran.framework.core.general.GeneralService;
import org.muran.framework.core.scanner.impl.DefaultClassScanner;

import java.lang.annotation.Annotation;
import java.util.List;

/**
 * Created by MURAN on 2017/7/26.
 *
 * 类操作工具
 */
public final class ClassUtil {

    /**
    * 本人自己封装的log4j日志对象,大家可以不创建这个成员变量
    * 下文的logger.error(****) 可以用System.out.println(***)代替
    */
    private static final FinalLogger logger = new FinalLogger(ClassUtil.class);

    /**
     * 使用默认的类扫描器
     */
    private static final ClassScanner CLASS_SCANNER = new DefaultClassScanner();

    /**
     * 获取类加载器
     * @return 当前线程中的ClassLoader
     */
    public static ClassLoader getClassLoader(){
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     * 加载相关类
     * @param className 要加载的类名
     * @param isInitialized 参数(设为false可提高加载类的性能)
     * @return 返回类
     */
    public static Class<?> loadClass(String className, boolean isInitialized){
        Class<?> cls = null;
        try {
            cls = Class.forName(className, isInitialized, getClassLoader());
        } catch (Exception e){
            logger.error("加载类[{}]失败", className, e);
            throw new RuntimeException(e);
        }
        return cls;
    }

    /**
     * 加载类 (自动初始化)
     * @param className 要加载的类名
     * @return 返回类
     */
    public static Class<?> loadClass(String className){
        return loadClass(className, true);
    }

    /**
     * 获取类的实例
     * @param cls
     * @param <T>
     * @return
     */
    public static <T> T newInstance(Class<T> cls){
        T instance = null;
        try {
            instance = cls.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            logger.error("实例化类[{}]失败",cls , e);
            throw new RuntimeException(e);
        }
        return instance;
    }

    /**
     * 获取类的实例
     * @param className
     * @param <T>
     * @return
     */
    public static <T> T newInstance(String className){
        return (T)newInstance(loadClass(className));
    }

    /**
     * 获取所有的类
     * @return
     */
    public static List<Class<?>> getAllClass(String packageName){
        return CLASS_SCANNER.getClassList(packageName);
    }

    /**
     * 获取基础包名中指定父类或接口的相关类
     * @param packageName
     * @param superClass
     * @return
     */
    public static List<Class<?>> getClassBySuper(String packageName, Class<?> superClass){
        return CLASS_SCANNER.getClassListBySuper(packageName, superClass);
    }

    /**
     * 获取指定注解的类
     * @param packageName
     * @param annotationClass
     * @return
     */
    public static List<Class<?>> getClassByAnnotation(String packageName, Class<? extends Annotation> annotationClass){
        return CLASS_SCANNER.getClassListByAnnotation(packageName, annotationClass);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值