注解、反射、动态编译、字节码操作
注解、反射、动态编译、字节码操作
前言:本篇博客将介绍Java中注解的定义、使用以及反射对Java动态性的支持和Java字节码操作,通过本篇内容,读者将对Java知识有更加深刻的理解,同时为后面Android的高级框架学习打好基础。
目录
一、注解
二、反射(java动态特性表现之一)
三、动态编译
四、字节码操作
总结
一、注解
概述:
Annotation是从JDK5.0开始引入的,注解并不是程序本身,但可以被其他程序读取,如果没有注解信息处理流程,注解将毫无意义。注解可以在Java很多地方使用,如类、属性、方法等等。
1-注解入门(以Override为例看注解的声明)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
可以看出,注解以@interface标注,后面为注解名,同时注解上面还有元注解,用以标注注解的注解。元注解分别有@Retention、 @Target、 @Document、 @Inherited和@Repeatable(JDK1.8加入)五种。
@Retention
- Retention英文意思有保留、保持的意思,它表示注解存在阶段是保留在源码(编译期),字节码(类加载)或者运行期(JVM中运行)。在@Retention注解中使用枚举RetentionPolicy来表示注解保留时期
- @Retention(RetentionPolicy.SOURCE),注解仅存在于源码中,在class字节码文件中不包含
- @Retention(RetentionPolicy.CLASS), 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
- @Retention(RetentionPolicy.RUNTIME), 注解会在class字节码文件中存在,在运行时可以通过反射获取到
- 如果我们是自定义注解,则通过前面分析,我们自定义注解如果只存着源码中或者字节码文件中就无法发挥作用,而在运行期间能获取到注解才能实现我们目的,所以自定义注解中肯定是使用 @Retention(RetentionPolicy.RUNTIME)
@Target
- Target的英文意思是目标,使用@Target元注解表示我们的注解作用的范围就比较具体了,可以是类,方法,方法参数变量等,同样也是通过枚举类ElementType表达作用类型
- @Target(ElementType.TYPE) 作用接口、类、枚举、注解
- @Target(ElementType.FIELD) 作用属性字段、枚举的常量
- @Target(ElementType.METHOD) 作用方法
- @Target(ElementType.PARAMETER) 作用方法参数
- @Target(ElementType.CONSTRUCTOR) 作用构造函数
- @Target(ElementType.LOCAL_VARIABLE)作用局部变量
- @Target(ElementType.ANNOTATION_TYPE)作用于注解(@Retention注解中就使用该属性)
- @Target(ElementType.PACKAGE) 作用于包
- @Target(ElementType.TYPE_PARAMETER) 作用于类型泛型,即泛型方法、泛型类、泛型接口 (jdk1.8加入)
- @Target(ElementType.TYPE_USE) 类型使用.可以用于标注任意类型除了 class (jdk1.8加入)
@Documented
- Document的英文意思是文档。它的作用是能够将注解中的元素包含到 Javadoc 中去。
@Inherited
- Inherited的英文意思是继承,但是这个继承和我们平时理解的继承大同小异,一个被@Inherited注解了的注解修饰了一个父类,如果他的子类没有被其他注解修饰,则它的子类也继承了父类的注解。
@Repeatable
- Repeatable的英文意思是可重复的。顾名思义说明被这个元注解修饰的注解可以同时作用一个对象多次,但是每次作用注解又可以代表不同的含义。
/**一个人喜欢玩游戏,他喜欢玩英雄联盟,绝地求生,极品飞车,尘埃4等,
则我们需要定义一个人的注解,他属性代表喜欢玩游戏集合,
一个游戏注解,游戏属性代表游戏名称*/
/**玩家注解*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface People {Game[] value() ;
}
/**游戏注解*/
@Repeatable(People.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Game {String value() default "";
}
/**玩游戏类*/
@Game(value = "LOL")
@Game(value = "PUBG")
@Game(value = "NFS")
@Game(value = "Dirt4")
public class PlayGame {
}
注解的属性:注解中没有方法,只有属性
属性以(FieldType+fieldName()+default;)方式声明,即由属性类型+属性名+默认值(如果有的话)组成。
注解的本质:
注解实际上就是Annotation接口,源码如下:
/**Annotation接口源码*/
public interface Annotation {boolean equals(Object obj);int hashCode();Class<? extends Annotation> annotationType();
}
通过以上源码,我们知道注解本身就是Annotation接口的子接口,也就是说注解中其实是可以有属性和方法,但是接口中的属性都是static final的(不能继承或重写),对于注解来说没什么意义,而我们定义接口的方法就相当于注解的属性,也就对应了前面说的为什么注解只有属性成员变量,其实他就是接口的方法,这就是为什么成员变量会有括号,不同于接口我们可以在注解的括号中给成员变量赋值。
2-自定义注解:
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyTestAnnotation {String name() default "mao";int age() default 18;
}@MyTestAnnotation(name = "father",age = 50)
public class Father {
}
二、反射(java动态特性表现之一)
反射技术,是学习Android插件化技术、Hook技术等必不可少的!下面我们开始学习他。
Java中编译类型有两种:
- 静态编译:在编译时确定类型,绑定对象即通过。
- 动态编译:运行时确定类型,绑定对象。动态编译最大限度地发挥了Java的灵活性,体现了多态的应用,可以减低类之间的耦合性。
1-获得Class对象:(3种方式)
- Object ——> getClass();
- 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
- 通过Class类的静态方法:forName(String className)(常用)
2-相关API介绍:
- 获得类相关的方法:
方法 | 用途 |
---|---|
asSubclass(Class<U> clazz) | 把传递的类的对象转换成代表其子类的对象 |
Cast | 把对象转换成代表类或是接口的对象 |
getClassLoader() | 获得类的加载器 |
getClasses() | 返回一个数组,数组中包含该类中所有公共类和接口类的对象 |
getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象 |
forName(String className) | 根据类名返回类的对象 |
getName() | 获得类的完整路径名字 |
newInstance() | 创建类的实例 |
getPackage() | 获得类的包 |
getSimpleName() | 获得类的名字 |
getSuperclass() | 获得当前类继承的父类的名字 |
getInterfaces() | 获得当前类实现的类或是接口 |
- 获得类中属性相关的方法:
方法 | 用途 |
---|---|
getField(String name) | 获得某个公有的属性对象 |
getFields() | 获得所有公有的属性对象 |
getDeclaredField(String name) | 获得某个属性对象 |
getDeclaredFields() | 获得所有属性对象 |
- 获得类中注解相关的方法:
方法 | 用途 |
---|---|
getAnnotation(Class<A> annotationClass) | 返回该类中与参数类型匹配的公有注解对象 |
getAnnotations() | 返回该类所有的公有注解对象 |
getDeclaredAnnotation(Class<A> annotationClass) | 返回该类中与参数类型匹配的所有注解对象 |
getDeclaredAnnotations() | 返回该类所有的注解对象 |
- 获得类中构造器相关的方法:
方法 | 用途 |
---|---|
getConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
- 获得类中方法相关的方法:
方法 | 用途 |
---|---|
getMethod(String name, Class...<?> parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(String name, Class...<?> parameterTypes) | 获得该类某个方法 |
getDeclaredMethods() | 获得该类所有方法 |
- 类中其他重要的方法:
方法 | 用途 |
---|---|
isAnnotation() | 如果是注解类型则返回true |
isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果是指定类型注解类型则返回true |
isAnonymousClass() | 如果是匿名类则返回true |
isArray() | 如果是一个数组类则返回true |
isEnum() | 如果是枚举类则返回true |
isInstance(Object obj) | 如果obj是该类的实例则返回true |
isInterface() | 如果是接口类则返回true |
isLocalClass() | 如果是局部类则返回true |
isMemberClass() | 如果是内部类则返回true |
Field类:
Field代表类的成员变量(成员变量也称为类的属性)。
方法 | 用途 |
---|---|
equals(Object obj) | 属性与obj相等则返回true |
get(Object obj) | 获得obj中对应的属性值 |
set(Object obj, Object value) | 设置obj中对应属性值 |
Method类:
Method代表类的方法。
方法 | 用途 |
---|---|
invoke(Object obj, Object... args) | 传递object对象及参数调用该对象对应的方法 |
Constructor类:
Constructor代表类的构造方法。
方法 | 用途 |
---|---|
newInstance(Object... initargs) | 根据传递的参数创建类的对象 |
反射操作泛型:
- ParameterizedType:表示一种参数化的类型,比如Collection< String >
- GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
- TypeVariable:是各种类型变量的公共父接口
WildcardType:代表一种通配符类型表达式,比如?、? extends Number、? super Integer。(wildcard是一个单词:就是”通配符“)
package reflection;import .../*** 通过反射获取泛型信息**/
public class Demo{ //定义两个带泛型的方法 一个参数是泛型;一个返回值是泛型public void test01(Map<String,Person> map,List<Person> list){System.out.println("Demo.test01()");} public Map<Integer,Person> test02(){System.out.println("Demo.test02()");return null;} public static void main(String[] args) {try { //获得指定方法参数泛型信息Method m = Demo.class.getMethod("test01", Map.class,List.class);Type[] t = m.getGenericParameterTypes();for (Type paramType : t) {System.out.println("#"+paramType);if(paramType instanceof ParameterizedType){//获取泛型中的具体信息Type[] genericTypes = ((ParameterizedType) paramType).getActualTypeArguments();for (Type genericType : genericTypes) {System.out.println("泛型类型:"+genericType);}}} //获得指定方法返回值泛型信息Method m2 = Demo.class.getMethod("test02", null);Type returnType = m2.getGenericReturnType();if(returnType instanceof ParameterizedType){Type[] genericTypes = ((ParameterizedType) returnType).getActualTypeArguments();for (Type genericType : genericTypes) {System.out.println("返回值,泛型类型:"+genericType);} } } catch (Exception e) {e.printStackTrace();} }
}
三、动态编译
在某些情况下,我们需要动态生成java代码,通过动态编译,然后执行代码。JAVA API提供了相应的工具(JavaCompiler)来实现动态编译。
1-动态编译的两种做法:
- 通过Runtime调用javac,启动新的进程去操作
Runtime run = Runtime.getRuntime();
Process process = run.exec("javac -cp d:/myjava/"HelloWorld.java);
- 通过JavaCompiler动态编译
public class Test {public static int compileFile(String sourceFile){JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();int result = compiler.run(null,null,null,sourceFile);System.out.println(result==0?"编译成功":"编译失败");return result;}
}
2-动态运行的两种方式:
之一:
Runtime run = Runtime.getRuntime();
Process process = run.exec("java -cp dirName/HelloWorld.class");
之二:
public static void runJavaClassByReflect(String dir,String classFile) throws Exception {URL[] urls = new URL[]{new URL("file:/"+dir)};URLClassLoader loader = new URLClassLoader(urls);Class<?> c = loader.loadClass(classFile);Method main = c.getMethod("main",String[].class);main.invoke(null,(Object)new String[]{});//注意一定要加Object}
四、字节码操作
1-简介:
- 在程序运行时,操作字节码可以实现动态生成新的类、动态改变某个类的结构(添加/删除/修改 新的属性/方法)。
- 相比于反射开销小、效率高。
2-常见的字节码操作类库:
- BCEL
- ASM
- CGLIB
- Javaassist 性能较ASM差,跟cglib差不多,但是用用简单,很多框架都在使用
3-使用Javaassist库为例实现字节码操作:(建立一个新类)
import javassist.*;import java.io.IOException;/*** @ClassName Demo01* @Description 测试Javassist创建一个新的类* @Author xwd* @Date 2018/10/23 16:55*/
public class Demo01 {public static void main(String[] args) throws CannotCompileException, NotFoundException, IOException {//创建类ClassPool pool = ClassPool.getDefault();CtClass ctClass = pool.makeClass("pri.xiaowd.bean.Stu");//创建属性并添加CtField field1 = CtField.make("private int age;",ctClass);CtField field2 = CtField.make("private String name;",ctClass);ctClass.addField(field1);ctClass.addField(field2);//创建方法并添加CtMethod method1 = CtMethod.make("public int getAge(){return this.age;}",ctClass);CtMethod method2 = CtMethod.make("public void setAge(int age){this.age=age;}",ctClass);ctClass.addMethod(method1);ctClass.addMethod(method2);//创建构造器并添加CtConstructor constructor1 = new CtConstructor(new CtClass[]{CtClass.intType,pool.get("java.lang.String")},ctClass);constructor1.setBody("{this.age=age;this.name=name;}");ctClass.addConstructor(constructor1);//写到本地,生成.class文件ctClass.writeFile("D:/myJava");}
}
4-使用Javaassist库为例实现字节码操作:(获得一个已有的类进行修改)
/*** 获取已有类信息* @throws Exception*/public static void test01()throws Exception{ClassPool pool = ClassPool.getDefault();CtClass cc = pool.get("com.houbo.test.Emp");byte[] bytes = cc.toBytecode();System.out.println(cc.getName());//获取类名System.out.println(cc.getSimpleName());//获取简单类名System.out.println(cc.getSuperclass());//获取父类名System.out.println(cc.getInterface());//获取接口}/*** 修改已有方法信息* @throws Exception*/public static void test02()throws Exception{ClassPool pool = ClassPool.getDefault();CtClass cc = pool.get("com.houbo.test.Emp");CtMethod cm = cc.getDeclaredMethod("sayHello",CtClass.intType);cm.insertBefore("System.out.println($1);System.out.println(\"start!!!\");");cm.insertAfter("System.out.println($1);System.out.println(\"end!!!\");");cm.insertAt(11,"System.out.println($1);System.out.println(\"end!!!\");");}/*** 添加属性方法二* @throws Exception*/public static void test03()throws Exception{ClassPool pool = ClassPool.getDefault();CtClass cc = pool.get("com.houbo.test.Emp");CtField cf = new CtField(/*参数类型*/CtClass.intType,/*参数名字*/"stuAge",cc/*对象*/);cf.setModifiers(ModiFier.PRIVATE);cc.addField(cf,20/*默认值*/);}
总结
本篇博文主要介绍了注解、反射、动态编译、字节码操作等java高级知识,采用深入浅出的方式为读者解析他们的用法和原理,其中反射和字节码操作重点掌握,因为他们体现了java的动态性。
注解、反射、动态编译、字节码操作相关推荐
- 菜鸟学习笔记:Java提升篇12(Java动态性2——动态编译、javassist字节码操作)
菜鸟学习笔记:Java提升篇12(Java动态性2--动态编译.javassist字节码操作) Java的动态编译 通过脚本引擎执行代码 Java字节码操作 JAVAssist的简单使用 常用API ...
- asm字节码操作 方法的动态修改增加
asm 4.0 版本 http://forge.ow2.org/plugins/scmsvn/index.php?group_id=23 asm是java的字节码操作框架,可以动态查看类的信息,动态修 ...
- 【Java字节码操作】JavaAssist的使用方式,代码示例
BCEL和ASM效率最高,因为他们基于虚拟机的指令操作,但是这两个最难学. JavaAssist 库,官网介绍如下: 让Java操作字节码更加简单,可以用Java语言直接编辑字节码 提供两种层次的AP ...
- idea如何反编译字节码指令_美团点评:Java字节码增强技术,线上问题诊断利器...
作者简介:泽恩,美团到店住宿业务研发团队工程师.文章转载于公众号:美团技术团队 1. 字节码 1.1 什么是字节码? Java之所以可以"一次编译,到处运行",一是因为JVM针对各 ...
- 深入字节码操作:使用ASM和Javassist创建审核日志
深入字节码操作:使用ASM和Javassist创建审核日志 原文链接:https://blog.newrelic.com/2014/09/29/diving-bytecode-manipulation ...
- Java反编译字节码文件
如何查看 Java 的字节码文件? 在 Java 中,字节码文件.class实际上是二进制文件,并不能直接查看.要想查看,我们只能通过反编译对其进行解析,然后查看解析后的源码. 如何反编译字节码文件 ...
- Tolua for Unity3d 编译字节码
想必折腾过Tolua的都知道, 为了最简化对Lua文件的支持.最优方案就是 把Lua文件 以文件的形式载入.而不是用 AssetBundle 的方式.这样会最优化的使用 Lua本身的一些文件 机制. ...
- 【ASM】字节码操作 ClassWriter COMPUTE_FRAMES 的作用 与 visitMaxs 的关系
1.概述 看这个首先看文章:[ASM]字节码操作 MethodVisitor 案例实战 调用方法 在创建ClassWriter对象时,使用了ClassWriter.COMPUTE_FRAMES 选项. ...
- 使用javaassist动态修改字节码文件
package mock;import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import ...
最新文章
- 【全网最短】实现倒计时:时分秒的功能
- 2016年3月全球操作系统版本份额:Win10仅次win7
- linux为3种人准备了权限,linux的3种特殊权限
- Yii2.0 数据库更新update
- android 链接分享到朋友圈,android 分享到微信朋友圈或微信好友
- LeedCode Q344 Reverse String(Easy)
- [MSDN]Design Guidelines for Developing Class Libraries with .NET Framework 4
- Linux电源管理系统架构和驱动(1)-Linux电源管理全局架构
- pytorch使用模型预测_使用PyTorch从零开始对边界框进行预测
- 安居客头部导航栏的编写
- 计算机网络的资源子网,在计算机网络中,资源子网的功能是什么?
- android专业拍照软件,安卓最专业的拍照软件排行榜 优质滤镜相机软件推荐
- 简单处理点击EditText外部区域关闭软键盘
- 【Unity开发小技巧】Unity组合键的代码编写
- 华为智慧屏鸿蒙20,华为智慧屏SE系列首销 搭载鸿蒙分布式跨屏技术
- 七张图,学会做有价值的经营分析
- 云课堂服务器技术支持,中学语音教室云课堂云服务器
- 京冀41所医院和急救中心成为北京冬奥会定点医院
- Oracle错误一览表3
- Netty handler无法进行依赖注入的问题