闲暇之余,仔细阅读了黄勇的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);
}
}