注解、反射、动态编译、字节码操作

本文深入讲解Java中的注解、反射、动态编译和字节码操作等高级特性,通过实例帮助读者理解这些特性如何增强Java的动态性和灵活性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

注解、反射、动态编译、字节码操作

前言:本篇博客将介绍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的动态性。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值