这次提到的Java反射涉及的代码比较多。因为工作中经常用到反射,对代码做了很多抽象以及过滤器。虽然代码量很多,但是简单易用,过滤插件也易修改。
下面介绍下工作中哪些地方比较容易用到反射。比如插件或者过滤器,如果抽象的子类比较少,配置成XML等结构也是可以达到同样的效果。如果希望灵活一些,添加了插件或者过滤器代码子类后希望可以直接使用。可能反射会比较好点,通过扫描所有class或者jar文件,得到所有继承的子类。如果每次调用都扫描所有的文件会比较影响性能。所以在实现里面加入反射缓存,对所要获取反射子类时涉及的所有参数作为一个key缓存所有的反射结果。下次如果是同样的key,就不在重新扫描。
代码示例如下:
public static void main(String[] args) { //设置扫描范围,可以是class文件所在位置例如bin下或者是mysql开头或者mysql结尾的jar, //设置为""为全部都扫描,这种比较耗时 ReflectUtils.createSharedReflections("classes", "bin", "mysql"); try { //调试阶段可以设置每次都全扫描 //Beans.setDesignTime(true); final Collection<String> subTypes = ReflectUtils.listSubClass(IA.class);// for (final String subType : subTypes) { //这里获取的是所有继承IA的子类 System.out.println(subType); final IA impl = ReflectUtils.initClass(subType, IA.class); if (null == impl) continue; //通过该方式,可以统一做操作, impl.print(); } } catch (Exception e) { e.printStackTrace(); } }
代码执行结果:
//缓存文件,避免每次调用反射都重新扫描 //如果删除该文件,再次调用反射时,会重新扫描,一般会在代码里面有添加子类的时候会删除该文件 XmlUtils.readXml failure:.\configuration.REF (系统找不到指定的文件。) net.simple.reflect.test.B net.simple.reflect.test.B net.simple.reflect.test.D net.simple.reflect.test.V
具体的类里面如何实现的大家就看下源码吧,这里贴出两个核心类的代码。源码地址:https://git.oschina.net/eliyanfei/api_tools
package net.simple.reflect; import java.io.File; import java.io.IOException; import java.net.JarURLConnection; import java.net.URL; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import net.simple.reflect.filter.IPathURLFilter; import net.simple.reflect.filter.ISubTypeFilter; import net.simple.reflect.filter.ITypeFilter; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * * @author 李岩飞 * @email eliyanfei@126.com * 2016年11月2日 下午3:23:49 * */ public final class Reflections { private final Collection<URL> pathUrls; private final Collection<IPathURLFilter> pathURLfilters; private final Collection<ITypeFilter> typeFilters; private ISubTypeFilter subTypeFilter; public Reflections() { typeFilters = new ArrayList<ITypeFilter>(); pathURLfilters = new ArrayList<IPathURLFilter>(); this.pathUrls = ClasspathHelper.getUrlsForCurrentClasspath(); } public Reflections(final Collection<URL> pathUrls) { this.pathUrls = pathUrls; typeFilters = new ArrayList<ITypeFilter>(); pathURLfilters = new ArrayList<IPathURLFilter>(); } /** * @param subTypeFilter * the subTypeFilter to set */ public void setSubTypeFilter(final ISubTypeFilter subTypeFilter) { this.subTypeFilter = subTypeFilter; } /** * @return the subTypeFilter */ public ISubTypeFilter getSubTypeFilter() { return subTypeFilter; } public Reflections addPathURLFilter(final IPathURLFilter pathURLFilter) { if (null == pathURLFilter) return this; if (!this.pathURLfilters.contains(pathURLFilter)) this.pathURLfilters.add(pathURLFilter); return this; } public Reflections addTypeFilter(final ITypeFilter typeFilter) { if (null == typeFilter) return this; if (!this.typeFilters.contains(typeFilter)) this.typeFilters.add(typeFilter); return this; } private static final String histFile = "./configuration.REF"; private Document histDom; public Collection<String> getSubTypesFast(final Class<?> baseType) {//, final String... typeNames //首先过滤出当前允许扫描的路径 final StringBuilder bufPathsId = new StringBuilder(32); final Map<File, URL> fileUrls = new LinkedHashMap<File, URL>(8); for (final URL pathUrl : pathUrls) { if (!acceptPathUrl(pathUrl)) continue; File file = null; try { file = new File(URLDecoder.decode(pathUrl.getFile(), "UTF-8")); } catch (final Exception e) { file = new File(pathUrl.getFile()); } fileUrls.put(file, pathUrl); if (!file.exists())//is url file?ignore continue; bufPathsId.append(file.getName()).append(file.lastModified()); } final String domId = MD5.getHashString(bufPathsId.toString()); if (null == histDom) histDom = W3cUtils.readXml(histFile); if (null == histDom) histDom = W3cUtils.newDom("R"); Element rootEle = histDom.getDocumentElement(); if (null == rootEle) histDom.appendChild(rootEle = histDom.createElement("R")); if (!domId.equals(rootEle.getAttribute("id"))) { rootEle.getParentNode().removeChild(rootEle); histDom.appendChild(rootEle = histDom.createElement("R")); rootEle.setAttribute("id", domId); } final String baseTypeId = MD5.getHashString(baseType.getName()); Element refEle = W3cUtils.firstChildElement(rootEle, "E", "id", baseTypeId); if (null != refEle) { final List<Element> valueEles = W3cUtils.childElementList(refEle, "F"); final Collection<String> result = new ArrayList<String>(valueEles.size()); for (final Element valueEle : valueEles) { result.add(new String(Base64.decodeFast(valueEle.getAttribute("id")))); } return result; } final ThreadPool<ListSubTypes> pool = new ThreadPool<ListSubTypes>(); for (final File fileKey : fileUrls.keySet()) { pool.execute(new ListSubTypes(baseType, fileKey, fileUrls.get(fileKey))); } try { pool.shutdown(3, TimeUnit.MINUTES); } catch (final InterruptedException e) { e.printStackTrace();//for debug } final Collection<String> result = new ArrayList<String>(); for (final ListSubTypes task : pool.getThreadRunables()) { result.addAll(task.result); } refEle = W3cUtils.addEle(rootEle, "E"); refEle.setAttribute("id", baseTypeId); for (final String itm : result) { W3cUtils.addEle(refEle, "F").setAttribute("id", Base64.encodeToString(itm.getBytes(), false)); } try { W3cUtils.writeXmlDocument(histFile, histDom); } catch (final Exception e) { } return result; } /** * @see {@link ReflectUtils#createSharedReflections(String...)} * @see {@link ReflectUtils#setSharedReflections(Reflections)} * @see {@link ReflectUtils#listSubClass(Class)} * @param baseType * @return */ public Collection<String> getSubTypes(final Class<?> baseType, final String... typeNames) {// final ThreadPool<ListSubTypes> pool = new ThreadPool<ListSubTypes>(); for (final URL pathUrl : pathUrls) { if (!acceptPathUrl(pathUrl)) continue; File file = null; try { file = new File(URLDecoder.decode(pathUrl.getFile(), "UTF-8")); } catch (final Exception e) { file = new File(pathUrl.getFile()); } pool.execute(new ListSubTypes(baseType, file, pathUrl, typeNames)); } try { pool.shutdown(3, TimeUnit.MINUTES); } catch (final InterruptedException e) { e.printStackTrace();//for debug } final Collection<String> result = new ArrayList<String>(); for (final ListSubTypes task : pool.getThreadRunables()) { result.addAll(task.result); } return result; } class ListSubTypes implements Runnable { final File file; final Class<?> baseType; final URL pathUrl; final String[] typeNames; public ListSubTypes(final Class<?> baseType, final File file, final URL pathUrl, final String... typeNames) { this.baseType = baseType; this.file = file; this.pathUrl = pathUrl; this.typeNames = typeNames; } Collection<String> result = new ArrayList<String>(4); @Override public void run() { if (file.isDirectory()) { listSubTypesFromDirectory(file, baseType, pathUrl, file, result, typeNames); } else listSubTypesFromJar(baseType, pathUrl, result, typeNames); } } /** * @param baseType * @param pathUrl * @param result */ public void listSubTypesFromDirectory(final File baseDirectory, final Class<?> baseType, final URL pathUrl, final File directory, final Collection<String> result, final String... typeNames) { File[] files = directory.listFiles(); if (null == files) files = new File[] {}; String clazzPath; final int baseDirLen = baseDirectory.getAbsolutePath().length() + 1; for (final File file : files) { if (file.isDirectory()) { listSubTypesFromDirectory(baseDirectory, baseType, pathUrl, file, result, typeNames); } else { clazzPath = file.getAbsolutePath().substring(baseDirLen); clazzPath = clazzPath.replace('\\', '/'); doTypesFilter(baseType, pathUrl, result, clazzPath, typeNames); } } } /** * @param baseType * @param pathUrl * @param result */ public void listSubTypesFromJar(final Class<?> baseType, URL pathUrl, final Collection<String> result, final String... typeNames) { try { // It does not work with the filesystem: we must // be in the case of a package contained in a jar file. JarFile jarFile = null; try { if ("file".equals(pathUrl.getProtocol())) pathUrl = new URL("jar:" + pathUrl.toExternalForm() + "!/"); jarFile = ((JarURLConnection) pathUrl.openConnection()).getJarFile(); } catch (final Exception e) { final String filePath = pathUrl.getFile(); // if on win platform if (filePath.indexOf(':') != -1) { if (pathUrl.getFile().charAt(0) == '/') jarFile = new JarFile(filePath.substring(1)); } if (null == jarFile) jarFile = new JarFile(filePath); } final Enumeration<JarEntry> e = jarFile.entries(); ZipEntry entry; while (e.hasMoreElements()) { entry = e.nextElement(); doTypesFilter(baseType, pathUrl, result, entry.getName(), typeNames); } } catch (final IOException ioex) { } } private void doTypesFilter(final Class<?> baseType, final URL pathUrl, final Collection<String> result, final String clazzPath, final String... typeNames) { if (!clazzPath.endsWith(".class")) return; final int lastDotIdx = clazzPath.lastIndexOf('.'); if (-1 == lastDotIdx) return; final String typeDef = clazzPath.substring(0, lastDotIdx).replace('/', '.'); if (null != typeNames && typeNames.length > 0) { final int lastDot = typeDef.lastIndexOf('.'); if (lastDot == -1) return; final String typeName = typeDef.substring(lastDot + 1); boolean withLiked = false; for (final String tmpTypeName : typeNames) { if (!typeName.contains(tmpTypeName)) continue; withLiked = true; break; } if (withLiked == false) return; } if (this.typeFilters.isEmpty()) { if (null == this.subTypeFilter || this.subTypeFilter.accept(baseType, pathUrl, clazzPath)) result.add(typeDef); } else { for (final ITypeFilter typeFilter : this.typeFilters) { if (!typeFilter.accept(clazzPath)) continue; if (null == this.subTypeFilter || this.subTypeFilter.accept(baseType, pathUrl, clazzPath)) result.add(typeDef); } } } /** * @param pathUrl * @return */ private boolean acceptPathUrl(final URL pathUrl) { if (this.pathURLfilters.isEmpty()) return true; for (final IPathURLFilter pathURLFilter : this.pathURLfilters) { if (pathURLFilter.accept(pathUrl)) return true; } return false; } }
package net.simple.reflect; import java.beans.Beans; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.JarURLConnection; import java.net.URL; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import net.simple.reflect.filter.PathURLFilter; import net.simple.reflect.filter.SampleSubInstanceFilter; import net.simple.reflect.filter.TypeFilter; /** * * @author 李岩飞 * @email eliyanfei@126.com * 2016年11月2日 下午3:24:02 * */ public final class ReflectUtils { public static final String VAR_START_FLAG = "${"; public static final String VAR_END_FLAG = "}"; private static Reflections sharedReflections; static final Collection<String> EMP_COLL = Collections.emptyList(); public static final void createSharedReflections(final String... filterExts) { final Reflections refs = new Reflections(); refs.addPathURLFilter(new PathURLFilter(filterExts));// refs.addTypeFilter(TypeFilter.DEFAULT); refs.setSubTypeFilter(SampleSubInstanceFilter.DEFAULT); ReflectUtils.setSharedReflections(refs); } /** * 此方法用于绑定一个通用的共享类型遍列工具. * @param sharedReflections */ public static final void setSharedReflections(final Reflections sharedReflections) { ReflectUtils.sharedReflections = sharedReflections; } /** * 调用此方法之前必须先设置共享的类型遍列工具,参考:{@link #setSharedReflections(Reflections)}, * 此方法主要使更方便的遍列给定类的实现, */ public static final Collection<String> listSubClass(final Class<?> baseType, final String... typeNames) {// if (null == sharedReflections) return EMP_COLL; //调用阶段由于可能增加新的子类实现,需要每次都重新扫描,只有在发布的产品时使用保存记录的方法以提高启动速度. return Beans.isDesignTime() ? sharedReflections.getSubTypes(baseType, typeNames) : sharedReflections.getSubTypesFast(baseType); } public static List<Class<?>> listClassOfPackage(final Class<?> cType, final String extenion) { final List<Class<?>> result = new ArrayList<Class<?>>(); final List<String> cPath = ReflectUtils.listClassCanonicalNameOfPackage(cType, extenion); for (final String path : cPath) { try { result.add(Class.forName(path, false, Thread.currentThread().getContextClassLoader())); } catch (final Exception e) { // ignore } } return result; } public static List<String> listClassCanonicalNameOfPackage(final Class<?> clazz, final String extenion) { return ReflectUtils.listNameOfPackage(clazz, extenion, true); } public static List<String> listClassNameOfPackage(final Class<?> clazz, final String extenion) { return ReflectUtils.listNameOfPackage(clazz, extenion, false); } public static List<String> listNameOfPackage(final Class<?> clazz, final String extenion, final boolean fullPkgName) { return ReflectUtils.listNameOfPackage(clazz.getName().replace('.', '/') + ".class", extenion, fullPkgName); } public static List<String> listNameOfPackage(final String clazzPkg, final String extenion, final boolean fullPkgName) { final List<String> result = new ArrayList<String>(); final StringBuffer pkgBuf = new StringBuffer(clazzPkg); if (pkgBuf.charAt(0) != '/') pkgBuf.insert(0, '/'); final URL urlPath = ReflectUtils.class.getResource(pkgBuf.toString()); if (null == urlPath) return result; String checkedExtenion = extenion; if (!extenion.endsWith(".class")) checkedExtenion = extenion + ".class"; if (pkgBuf.toString().endsWith(".class")) pkgBuf.delete(pkgBuf.lastIndexOf("/"), pkgBuf.length()); pkgBuf.deleteCharAt(0); final StringBuffer fileUrl = new StringBuffer(); try { fileUrl.append(URLDecoder.decode(urlPath.toExternalForm(), "UTF-8")); } catch (final UnsupportedEncodingException e1) { fileUrl.append(urlPath.toExternalForm()); } if (fileUrl.toString().startsWith("file:")) { fileUrl.delete(0, 5);// delete file: flag if (fileUrl.indexOf(":") != -1) fileUrl.deleteCharAt(0);// delete flag final String baseDir = fileUrl.substring(0, fileUrl.lastIndexOf("classes") + 8); ReflectUtils.doListNameOfPackageInDirectory(new File(baseDir), new File(baseDir), result, pkgBuf.toString(), checkedExtenion, fullPkgName); } else { ReflectUtils.doListNameOfPackageInJar(urlPath, urlPath, result, pkgBuf.toString(), checkedExtenion, fullPkgName); } return result; } /** */ private static void doListNameOfPackageInJar(final URL baseUrl, final URL urlPath, final List<String> result, final String clazzPkg, final String extenion, final boolean fullPkgName) { try { // It does not work with the filesystem: we must // be in the case of a package contained in a jar file. final JarURLConnection conn = (JarURLConnection) urlPath.openConnection(); final JarFile jfile = conn.getJarFile(); final Enumeration<JarEntry> e = jfile.entries(); ZipEntry entry; String entryname; while (e.hasMoreElements()) { entry = e.nextElement(); entryname = entry.getName(); if (entryname.startsWith(clazzPkg) && entryname.endsWith(extenion)) { if (fullPkgName) result.add(entryname.substring(0, entryname.lastIndexOf('.')).replace('/', '.')); else result.add(entryname.substring(entryname.lastIndexOf('/') + 1, entryname.lastIndexOf('.'))); } } } catch (final IOException ioex) { } } private static void doListNameOfPackageInDirectory(final File baseDirectory, final File directory, final List<String> result, final String clazzPkg, final String extenion, final boolean fullPkgName) { File[] files = directory.listFiles(); if (null == files) files = new File[] {}; String clazzPath; final int baseDirLen = baseDirectory.getAbsolutePath().length() + 1; for (final File file : files) { if (file.isDirectory()) { ReflectUtils.doListNameOfPackageInDirectory(baseDirectory, file, result, clazzPkg, extenion, fullPkgName); } else { if (!file.getName().endsWith(extenion)) continue; if (fullPkgName) { clazzPath = file.getAbsolutePath().substring(baseDirLen); clazzPath = clazzPath.substring(0, clazzPath.length() - 6); result.add(clazzPath.replace(File.separatorChar, '.')); } else { result.add(file.getName().substring(0, file.getName().length() - 6)); } } } } public static final <T> T initClass(final String implClass, final Class<T> tType) { return ReflectUtils.initClass(implClass, tType, true); } public static final <T> T initClass(final String implClass, final Class<T> tType, final boolean doInit) { try { final Object object = Class.forName(implClass, doInit, Thread.currentThread().getContextClassLoader()).newInstance(); return tType.cast(object); } catch (final Throwable e) { return null; } } }