首页 > 技术文章 > 注解与反射

tanshishi 2020-06-02 10:46 原文

注解

为什么要学注解与反射

​ 所有的框架底层实现机制都是注解与反射,框架中有许多的注解,通过反射读取注解的值,来简化操作.
​ 比如利用反射读取注解的值,通过值拼成SQL语句,就可以动态地生成表,或者其他高级的功能.

什么是注解(Annotation)

Annotation的作用:

可以被其他程序(比如: 编译器等)读取

Annotation在哪里使用

可以附加在package,class,method,field等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问

内置注解

//什么是注解
public class Test01 extends Object{

    //@Override 重写的注解
    @Override
    public String toString() {
        return super.toString();
    }

    //@Deprecated 不推荐程序员使用,但是可以使用,或者存在更好的方式
    @Deprecated
    public static void test() {
        System.out.println("Deprecated");
    }

    //@SuppressWarnings镇压警告
    @SuppressWarnings("all")
    public void test02() {
        List list = new ArrayList();
    }


    public static void main(String[] args) {
        test();
    }
}

//@Override 重写的注解

//@Deprecated 不推荐程序员使用,但是可以使用,或者存在更好的方式

image-20200528172212971

//@SuppressWarnings镇压警告

元注解

点进注解里可以看到注解的定义方式:

image-20200528172358535

//定义一个注解
//@Target 定义作用域
@Target(value = {ElementType.METHOD, ElementType.TYPE})
//@Retention 表示注解在何时有效
//runtime>class>sources
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation {

}

自定义注解

反射机制

  • 反射是什么: 反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法.

  • 反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作对象的内部属性及方法

  • 反射获取类信息的原理: 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息.我们可以通过这个对象看到类的结构.

image-20200528175104320

java反射机制提供的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解(框架的底层实现机制)
  • 生成动态代理
  • ......

后面的笔记就是具体的代码实现

获得反射对象

//什么叫反射
public class test02 {
    public static void main(String[] args) throws ClassNotFoundException {
        //通过分反射获取类的Class对象
        Class c1 = Class.forName("com.tan.reflection.User");
        System.out.println(c1);

        Class c2 = Class.forName("com.tan.reflection.User");
        Class c3 = Class.forName("com.tan.reflection.User");
        Class c4 = Class.forName("com.tan.reflection.User");

        
        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());
        System.out.println(c4.hashCode());
    }
}

//实体类:与数据库字段做对应时实体类包一般命名: pojo 或 entity
class User {
    private String name;
    private int id;
    private int 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;
    }

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

输出:image-20200528180722641

hashCode相同,代表获取的确实是同一个类.

获取User类的Class对象语句:

Class c1 = Class.forName("com.tan.reflection.User");

获取Class对象,即代表获取了User类里面的所有东西,甚至是私有的属性,私有的方法等.

Class类

在Object类中定义了此方法: public final Class getClass() ,此方法被所有子类继承

所以此类是Java反射的源头.

此时我们再来分析一下这个语句:

Class.forName("com.tan.reflection.User");

Class是反射的源头,forName是Class的方法,通过String类型的路径"com.tan.reflection.User",找到User类并获取User类的所有内容.

Class的对象保存在哪: 对于每个类而言,在运行时JRE都为其保留一个不变的Class类型的对象,一个Class对象对应的是一个加载到 JVM 中的一个.class文件.

Class类的常用方法(不用看)

image-20200528182843184

常见的Class类的创建方式

被获取的对象:

class Person {
    public String name;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}

class Student extends Person {
    public Student() {
        this.name = "学生";
    }
}

class Teacher extends Person {
    public Teacher() {
        this.name = "老师";
    }
}

//方式一: 通过对象获得

Class c1 = person.getClass();
System.out.println(c1.hashCode());

//方式二: forname获取,需要抛出异常throws ClassNotFoundException

Class c2 = Class.forName("com.tan.reflection.Student");
System.out.println(c2.hashCode());

//方法三: 通过类名.class获得

Class c3 = Student.class;
System.out.println(c3.hashCode());

扩充:

//基本内置类型的包装类都有一个Type属性
Class c4 = Integer.TYPE;
System.out.println(c4);
//获取父类类型
Class c5 = c1.getSuperclass();
System.out.println(c5);

输出:
189568618
189568618
189568618
int
class com.tan.reflection.Person

所有类型的Class对象

public class Test04 {
    public static void main(String[] args) {
        Class<Object> c1 = Object.class;    //类
        Class<Comparable> c2 = Comparable.class;    //接口
        Class<String> c3 = String.class;    //一维数组
        Class<String[]> c4 = String[].class;    //二维数组
        Class<Override> c5 = Override.class;    //注解
        Class<ElementType> c6 = ElementType.class;  //枚举
        Class<Integer> c7 = Integer.class;  //基本引用类型
        Class<Void> c8 = void.class;    //void
        Class<Class> c9 = Class.class;  //Class

        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c3);
        System.out.println(c4);
        System.out.println(c5);
        System.out.println(c6);
        System.out.println(c7);
        System.out.println(c8);
        System.out.println(c9);

    }
}
class java.lang.Object
interface java.lang.Comparable
class java.lang.String
class [Ljava.lang.String;
interface java.lang.Override
class java.lang.annotation.ElementType
class java.lang.Integer
void
class java.lang.Class

了解类的加载过程

image-20200528190509656

类在被加载时会生成一个Class对象副本(我也不确定能不能叫做副本),反射就是获取在类加载时生成的这个Class对象副本,而这个副本包括了class文件里的所有信息.

什么时候会发生初始化(不用看)

image-20200528192245454

子类调用父类的静态方法,子类不会被初始化

获取运行时类的完整结构

获取类名: (实体类User在前面写了)

Class c1 = Class.forName("com.tan.reflection.User");

//获得类的名字与路径,获得类名
System.out.println(c1.getName());
System.out.println(c1.getSimpleName());

输出:
com.tan.reflection.User
User

前面写了类名现在又用这个获取是不是有点蠢,但如果类是这样创建的就不知道类的名字:

User user = new User();
c1 = user.getClass(); //这样创建就不知道类的路径

获取类的属性

//获得类的属性
System.out.println("===================================");
Field[] fields1 = c1.getFields(); //只能找到public属性
for (Field field : fields1) {
    System.out.println(field);
}

输出为空

由于user的属性为私有,无法用getFields()方法获取到.

使用getDeclaredFields()方法可以获得私有的属性

//获得类的所有属性
Field[] fields1 = c1.getDeclaredFields();//可以获得私有的属性
for (Field field : fields1) {
    System.out.println(field);
}

输出:
private java.lang.String com.tan.reflection.User.name
private int com.tan.reflection.User.id
private int com.tan.reflection.User.age

获得类的方法

本类与父类的public方法:

System.out.println("===================================");
Method[] methods1 = c1.getMethods();
for (Method method : methods1) {
    System.out.println("本类与父类的public方法: "+method);
}

本类的所有的方法:

Method[] methods2 = c1.getDeclaredMethods();
for (Method method2 : methods2) {
    System.out.println("本类的所有的方法: "+method2);
}

输出:

本类与父类的public方法: public java.lang.String com.tan.reflection.User.getName()
本类与父类的public方法: public java.lang.String com.tan.reflection.User.toString()
本类与父类的public方法: public void com.tan.reflection.User.setName(java.lang.String)
本类与父类的public方法: public int com.tan.reflection.User.getId()
本类与父类的public方法: public int com.tan.reflection.User.getAge()
本类与父类的public方法: public void com.tan.reflection.User.setId(int)
本类与父类的public方法: public void com.tan.reflection.User.setAge(int)
本类与父类的public方法: public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
本类与父类的public方法: public final void java.lang.Object.wait() throws java.lang.InterruptedException
本类与父类的public方法: public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
本类与父类的public方法: public boolean java.lang.Object.equals(java.lang.Object)
本类与父类的public方法: public native int java.lang.Object.hashCode()
本类与父类的public方法: public final native java.lang.Class java.lang.Object.getClass()
本类与父类的public方法: public final native void java.lang.Object.notify()
本类与父类的public方法: public final native void java.lang.Object.notifyAll()
    
本类的所有的方法: public java.lang.String com.tan.reflection.User.getName()
本类的所有的方法: public java.lang.String com.tan.reflection.User.toString()
本类的所有的方法: public void com.tan.reflection.User.setName(java.lang.String)
本类的所有的方法: public int com.tan.reflection.User.getId()
本类的所有的方法: public int com.tan.reflection.User.getAge()
本类的所有的方法: public void com.tan.reflection.User.setId(int)
本类的所有的方法: public void com.tan.reflection.User.setAge(int)

获得指定方法

//获得指定方法
System.out.println("===================================");
Method getName = c1.getMethod("getName", null);
Method setName = c1.getMethod("setName", String.class);
System.out.println(getName);
System.out.println(setName);

输出:
public java.lang.String com.tan.reflection.User.getName()
public void com.tan.reflection.User.setName(java.lang.String)

获得构造器

//获得构造器
System.out.println("===================================");
Constructor[] constructors = c1.getDeclaredConstructors();
for (Constructor constructor : constructors) {
    System.out.println(constructor);
}

输出:
public com.tan.reflection.User()
public com.tan.reflection.User(java.lang.String,int,int)

获得指定构造器

//获得指定构造器
Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
System.out.println("指定: " + constructor);

输出: 
指定: public com.tan.reflection.User(java.lang.String,int,int)

通过反射动态的创建对象

image-20200528202325648

此方法居然过时了

推荐阅读