java基础复习-自定义注解2(反射技术)

java基础复习-自定义注解2(反射技术)

写在前面:

反射技术在java基础中也是容易被忽略的一部分内容,也是在初学阶段无法体会反射技术的作用。其实,在java的众多框架中都是靠反射技术来进行实现。如果想成为能够自己去打造自己的框架,那么学习反射技术这是十分有必要的。该节被列为自定义注解的第二节,将介绍反射技术的使用和如何反射获取注解上的信息,编写一个简单的注解解析器。

1、反射介绍

考虑到有部分大学java课程中没有收录该部分内容,因此作为一个简短的介绍。

众说周知,java代码的编译是编译为.class文件,.class文件是运行在jvm上的,而并不是像C\C++语言一样,编译为二进制语言,运行在真实的操作系统上。即然,.class文件还处于jvm中,那么就能够从其中获取信息。

JAVA反射机制是在运行状态中,对于任何一个类,都能够知道这个类的所有属性和方法;对于任何一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

想要使用反射机制,就必须要先获取到该类的字节码文件对象(.class),通过字节码文件对象,就能够通过该类中的方法获取到我们想要的所有信息(方法,属性,类名,父类名,实现的所有接口等等),每一个类对应着一个字节码文件也就对应着一个Class类型的对象,也就是字节码文件对象。

因为有反射技术的存在,也使得java这种静态语言有了动态语言的特性。

2、获取类的class对象的三种方式

2.0、为了方便演示怎样通过反射技术获取对象,因此先创建一个User类,代码如下:

/**
 * 实体类
 */
class User {
    private String name;
    private int id;
    private int age;


    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", age=" + age +
                '}';
    }


    public User() {
    }

    public User(String name, int id, int age) {
        this.name = name;
        this.id = id;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

}

2.1、方式一:使用Class.forName(包名+类名)进行获取,代码如下:

//该方式适用于知道类的包名和类名,并且未创建过对象时使用
Class clazz2 = Class.forNam("com.xgp.company.reflection.User");

2.2、方式二:通过对象.getClass()函数进行获取,代码如下:

User user1 = new User();
//该方式适用于已经创建了对象时使用
Class clazz2 = user1.getClass();

2.3、方式三:通过类名.class获取,代码如下:

//该方式适用于未创建对象,知道类名时使用
Class clazz3 = User.class;

三种方式创建对象的截图如下:
java基础复习-自定义注解2(反射技术)_第1张图片

注意:一个类在内存中只有一个class对象,因此这三种方式获得的clazz1、clazz2、clazz3都是一个对象,我们可以打印出他们的哈希值来看看,都是一样的。

java基础复习-自定义注解2(反射技术)_第2张图片

3、获取类的信息(类名、方法、属性)

3.0、获取类的信息,就是操作class对象的方法,在IDEA中,通过clazz.就能提示出class对象所就有的全部方法,然后根据名字进行使用。下面就举几个常用的函数进行示范:

java基础复习-自定义注解2(反射技术)_第3张图片

3.1、通过clazz.getName()获取类的完整类名(包名+类名)

System.out.println(clazz.getName());

运行结果:

com.xgp.company.reflection.User

3.2、通过clazz.getSimpleName()获取类名

System.out.println(clazz.getSimpleName());

运行结果:

User

3.3、通过clazz.getFields()获取类的全部属性

    Field[] fields = clazz.getFields();
    for (Field field : fields) {
        System.out.println(field);
    }

运行结果为空,解释见后小节的暴力反射

3.4、通过clazz.getMethods()获得本类及父类的全部方法

    Method[] methods = clazz.getMethods();
    for (Method method : methods) {
        System.out.println(method);
    }

运行结果:

public java.lang.String com.xgp.company.reflection.User.toString()
public java.lang.String com.xgp.company.reflection.User.getName()
public int com.xgp.company.reflection.User.getId()
public void com.xgp.company.reflection.User.setName(java.lang.String)
public void com.xgp.company.reflection.User.setId(int)
public void com.xgp.company.reflection.User.setAge(int)
public int com.xgp.company.reflection.User.getAge()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

3.5、通过clazz.getDeclaredMethods()获得本类的全部方法

    methods = clazz.getDeclaredMethods();
    for (Method method : methods) {
        System.out.println(method);
    }

运行结果:

public java.lang.String com.xgp.company.reflection.User.toString()
public java.lang.String com.xgp.company.reflection.User.getName()
public int com.xgp.company.reflection.User.getId()
public void com.xgp.company.reflection.User.setName(java.lang.String)
public void com.xgp.company.reflection.User.setId(int)
public void com.xgp.company.reflection.User.setAge(int)
public int com.xgp.company.reflection.User.getAge()

4、暴力反射

对于上面一节中,获取User类中的对象时,程序运行的结果为空,原因是User对象的三个属性都为私有属性。若想使用反射技术获取对象的私有属性则需要使用clazz.getDeclaredFields()方法进行暴力获取。

    //暴力反射
    fields = clazz.getDeclaredFields();
    for (Field field : fields) {
        System.out.println(field);
    }

输出结果为:

private java.lang.String com.xgp.company.reflection.User.name
private int com.xgp.company.reflection.User.id
private int com.xgp.company.reflection.User.age

5、利用反射技术动态的创建对象

演示前先获取到一个User类的class对象:

Class clazz = User.class;

5.1、通过反射获取到的无参构造器创建对象

    //构造一个对象
    User user = clazz.newInstance();
    System.out.println(user);

5.2、通过反射获取到的有参构造器创建对象

    //通过构造器创建对像
    Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class, int.class);
    User user = constructor.newInstance("xgp", 001, 18);
    System.out.println(user);

clazz.getDeclaredConstructor() 获取有参构造器,其参数传递的是反射对象有参构造器的各个参数的类的class对象

clazz.newInstance() 该函数用于使用构造器创建对象

6、通过反射技术操作对象方法和属性

6.1、通过反射技术调用普通方法

    //通过反射调用普通方法
    User user = clazz.newInstance();
    Method setName = clazz.getDeclaredMethod("setName", String.class);
    setName.invoke(user,"xgp");     //激活
    System.out.println(user);

其中invoke()函数用于激活反射获取到的方法,激活后才能使用反射获取到的方法。

6.2、通过反射技术操作类的属性

    //通过反射操作属性
    User user = clazz.newInstance();
    Field name = clazz.getDeclaredField("name");

    name.setAccessible(true);      //关闭权限检测

    name.set(user,"xgp");
    System.out.println(user);

特别注意的一点就是,在操纵私有属性时,一定得关闭权限检测,关闭权限检测反射函数的执行效率也跟高,这在下一节将会进行实验。关闭权限检测的代码为:

name.setAccessible(true);      //关闭权限检测

7、三种方法调用的性能对比

下面进行三种方法调用的性能对比,通过调用user对象的getName()方法执行十亿次进行实验:

7.1、普通方式调用的函数test01()的编写

//普通方式
public static void test01() {
    User user = new User();

    long start = System.currentTimeMillis();
    for(int i = 0;i < 1000000000;i++) {
        user.getName();
    }
    long end = System.currentTimeMillis();

    System.out.println("普通方法执行10亿次:" + (end - start) + "ms");
}

7.2、反射方式调用函数test02()的编写

//反射方式
public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    User user = new User();
    Class clazz = user.getClass();

    Method getName = clazz.getDeclaredMethod("getName", null);

    long start = System.currentTimeMillis();
    for(int i = 0;i < 1000000000;i++) {
        getName.invoke(user,null);
    }
    long end = System.currentTimeMillis();

    System.out.println("反射方法执行10亿次:" + (end - start) + "ms");
}

7.3、关闭权限检测后反射方式调用函数test03()的编写

//反射方式
public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    User user = new User();
    Class clazz = user.getClass();

    Method getName = clazz.getDeclaredMethod("getName", null);

    getName.setAccessible(true);

    long start = System.currentTimeMillis();
    for(int i = 0;i < 1000000000;i++) {
        getName.invoke(user,null);
    }
    long end = System.currentTimeMillis();

    System.out.println("关闭反射检测方法执行10亿次:" + (end - start) + "ms");
}

7.4、主函数的编写

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    test01();
    test02();
    test03();
}

7.5、运行结果分析

java基础复习-自定义注解2(反射技术)_第4张图片
通过运行结果可以很明显的发现,普通方法的执行效率远远不是其他方法能够比拟的,因次在开发中慎用反射技术。

关闭了权限检测后的反射方法的调用效率是未关闭的1/3左右,因此在要使用反射技术时,并且对于安全性没有过高的要求时,可以关闭权限检测,提高效率。

8、利用反射技术获取泛型

8.1、为了要获取泛型,首先准备一个就有集合泛型的函数,编写的代码如下:

public void test01(Map map, List list) {

    System.out.println("test01");
}

8.2、获取到当前类的test01方法:

    Method method = test05.class.getMethod("test01", Map.class, List.class);

8.3、通过method.getGenericParameterTypes()方法获取方法上的泛型集合,并且遍历

    Type[] types = method.getGenericParameterTypes();

    for (Type type : types) {   //遍历
        System.out.println(type);
    }

此时运行的结果为:

java.util.Map
java.util.List

由运行结果可知,并不是我们想要获取的形式

8.4、获取真实的泛型类型,并且遍历

//获得真实类型
Type[] guns = ((ParameterizedType) type).getActualTypeArguments();
for (Type gun : guns) {
      System.out.println(gun);
 }

8.5、该获取泛型程序的完整代码为:

public static void main(String[] args) throws NoSuchMethodException {
    Method method = test05.class.getMethod("test01", Map.class, List.class);

    Type[] types = method.getGenericParameterTypes();

    for (Type type : types) {
        System.out.println(type);
        if(type instanceof ParameterizedType) {
            //获得真实类型
            Type[] guns = ((ParameterizedType) type).getActualTypeArguments();
            for (Type gun : guns) {
                System.out.println(gun);
            }
        }
    }
}

运行的结果为:

java.util.Map
class java.lang.String
class com.xgp.company.reflection.User
java.util.List
class com.xgp.company.reflection.User

9、利用反射技术获取注解信息

上面讲了这么多的反射技术,终于来到了自定义注解的核心,通过反射技术获取自定义注解上的信息。

9.1、首先,我们先自定义一个注解,该注解的含义不做解释了。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table {
    String value();
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Field{
    String columnName();
    String type();
    int length();
}

9.2、将注解标注在我们的Student类上,准备工作完成。

@Table("db_student")
class Student {

    @Field(columnName = "t_id",type = "int",length = 10)
    private int id;
    @Field(columnName = "t_age",type = "varchar",length = 10)
    private int age;
    @Field(columnName = "t_name",type = "varchar",length = 3)
    private String name;

    public Student() {
    }

    public Student(int id, int age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

9.3、获取Student类的class对象

Class clazz = Student.class;

9.4、通过clazz.getAnnotations()获取该Student类上的全部注解,并且遍历

    //通过反射获取注解
    Annotation[] annotations = clazz.getAnnotations();
    //遍历
    for (Annotation annotation : annotations) {
        System.out.println(annotation);
    }

输出的结果为:

@com.xgp.company.reflection.Table(value=db_student)

9.5、获取Table注解上的value值

    //获得注解的value的值
    Table table = clazz.getAnnotation(Table.class);
    String value = table.value();
    System.out.println(value);

输出的结果为:

db_student

9.6、获取属性name字段上的注解,并解析该注解上的三个属性

    java.lang.reflect.Field name = clazz.getDeclaredField("name");
    Field ano = name.getAnnotation(Field.class);
    System.out.println(ano.columnName());
    System.out.println(ano.type());
    System.out.println(ano.length());

输出的结果为:

t_name
varchar
3   

9.7、该解析自定义注解的demo完整代码为:

public class Test06 {
    public static void main(String[] args) throws NoSuchFieldException {
        Class clazz = Student.class;

        //通过反射获取注解
        Annotation[] annotations = clazz.getAnnotations();

        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        //获得注解的value的值
        Table table = clazz.getAnnotation(Table.class);
        String value = table.value();
        System.out.println(value);

        java.lang.reflect.Field name = clazz.getDeclaredField("name");
        Field ano = name.getAnnotation(Field.class);
        System.out.println(ano.columnName());
        System.out.println(ano.type());
        System.out.println(ano.length());
    }
}

完整的输出为:

@com.xgp.company.reflection.Table(value=db_student)
db_student
t_name
varchar
3

本节介绍了反射技术,下一节将介绍自定义注解怎样在SpringBoot项目中的使用,并结合数据库技术进行实战,编写一个能够记录日志的自定义注解。

你可能感兴趣的:(java基础复习-自定义注解2(反射技术))