工作中常用到的Java反射

李巖飛發表於2016-11-17

這次提到的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;
		}
	}
}

相關文章