运行时扫描Java注解(一)之探讨
本文参考英文原文Scanning Java Annotations at Runtime进行了转述,但是也并非完全一字一句的翻译过来。
当你在开发一个基于注解的框架时,你肯定需要扫描所有使用了特定注解的类,从而初始化你的框架。比如JPA,你需要找到一系列使用了@Entity注解的类,从而定义ORM映射。可以使用很多技术在运行时扫描Java注解,本文将对此作探讨。
指定扫描目录
我们需要找到从哪个目录开始扫描,很多框架可以让用户指定目录,比如SpringMVC,通过以下配置(base-package)指定扫描的包名。
<context:annotation-config />
<context:component-scan base-package="com.xxx.xx" />
根据开发环境,我们可以有不同的方法获取扫描目录。
1.Java classpath
通过系统属性java.class.path指定扫描目录。
2.classloader
通过ClassLoader.getResource() 和 ClassLoader.getResources()获取指定资源,从而解析出扫描目录。
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Enumeration<URL> urls = cl.getResources("com/deepoove/dubbo");
3.Web Applications
对于Web应用,扫描目录在WEB-INF/lib或者WEB-INF/classes下,我们可以通过ServletContext 类获取lib下的资源:
List<URL> urls = new ArrayList<URL>();
Set libJars = servletContext.getResourcePaths("/WEB-INF/lib");
for (Object jar : libJars){
try{
urls.add(servletContext.getResource((String) jar));
}
catch (MalformedURLException e){
throw new RuntimeException(e);
}
}
获取classes下的资源会额外采取一点小措施:
URL classesPath = null;
Set libJars = servletContext.getResourcePaths("/WEB-INF/classes");
for (Object jar : libJars){
try{
URL url = servletContext.getResource((String) jar);
String urlString = url.toString();
int index = urlString.lastIndexOf("/WEB-INF/classes/");
urlString = urlString.substring(0, index + "/WEB-INF/classes/".length());
classesPath = new URL(urlString);
//do somethings
}catch (MalformedURLException e){
throw new RuntimeException(e);
}
}
至于如何获取到servletContext,在web框架中,可以通过监听器ServletContextListener 获取。
开始扫描目录
遍历目录是简单的,值得一提的是我们可以通过JarInputStream扫描jar文件。
找到指定注解的类
至此,我们已经可以获得所有扫描的类路径了,通过ClassLoader加载这些类,然后通过Java反射API去扫描特定注解是一个糟糕的方法:
- Java反射API只能扫描运行时可见的注解,注解类型有Source、Class、Runtime,仅仅只有Runtime类型的注解才能被反射API获取到。
- 通常你不会使用每一个你扫描的类,所以通过ClassLoader加载这些类,将填充JVM永久代且浪费资源。
所以,如果不通过ClassLoader去加载这些类,那么我们又如何去扫描特定注解呢??? 答案是通过字节码操作库 Javassist或者ASM。
在Javassist中,你可以获取InputStream从而实例化ClassFile类,ClassFile对象无需加载这个类就可以获取到特定注解。
DataInputStream dstream = new DataInputStream(new BufferedInputStream(bits));
ClassFile cf = new ClassFile(dstream);
String className = cf.getName();
AnnotationsAttribute visible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.visibleTag);
AnnotationsAttribute invisible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.invisibleTag);
for (javassist.bytecode.Annotation ann : visible.getAnnotations()){
System.out.println("@" + ann.getTypeName());
}
visible对象对应Runtime类型的注解,invisible对象对象Class类型的注解。Javassist同样提供了遍历方法和属性的方法,获取方法和属性上的注解也是同理。
反射框架
Reflections,一个java 运行时的元数据解析框架,下一篇文章,我们将从Reflections源码层面探讨更多关于反射获取注解的议题。