注解与反射

Posted tanshishi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了注解与反射相关的知识,希望对你有一定的参考价值。

注解

为什么要学注解与反射

? 所有的框架底层实现机制都是注解与反射,框架中有许多的注解,通过反射读取注解的值,来简化操作.
? 比如利用反射读取注解的值,通过值拼成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 不推荐程序员使用,但是可以使用,或者存在更好的方式

技术图片

//@SuppressWarnings镇压警告

元注解

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

技术图片

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

}

自定义注解

反射机制

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

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

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

技术图片

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 +
                ‘}‘;
    }
}

输出:技术图片

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类的常用方法(不用看)

技术图片

常见的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

了解类的加载过程

技术图片

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

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

技术图片

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

获取运行时类的完整结构

获取类名: (实体类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)

通过反射动态的创建对象

技术图片

此方法居然过时了??


获得User的Class对象

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

构造一个对象

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

输出:User{name=‘null‘, id=0, age=0}

很明显调用的是无参构造器,于是将User的无参构造器删除,编译器报错

但是可能每个方法都有无参构造器,那么没有无参构造器就不能传参了吗?

通过构造器创建对象

//通过构造器创建对象
Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
User user2 = (User) constructor.newInstance("tan", 01, 20);
System.out.println(user2);

输出:
User{name=‘tan‘, id=1, age=20}

通过反射获取一个方法,传参并调用方法

//通过反射获取一个方法,传参并调用方法
User user3 = (User) c1.newInstance();
Method setName = c1.getDeclaredMethod("setName", String.class);
setName.invoke(user3,"tan");
System.out.println(user3.getName());

通过反射操作属性

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

name.set(user4,"tan2");
System.out.println(user4.getName());

输出:
错误 class com.tan.reflection.Test09 cannot access a member of class com.tan.reflection.User with modifiers "private"

在前面可以得知getDeclaredField()可以获取Class的私有属性,但是报错中任显示无法操作私有属性.

但是可以关闭权限检测name.setAccessible(true);

User user4 = (User) c1.newInstance();
Field name = c1.getDeclaredField("name");

//不能直接操作私有属性,需要关闭程序的安全检测
name.setAccessible(true); //关闭程序的安全检测
name.set(user4,"tan2");
System.out.println(user4.getName());

输出:
tan2

重点方法:

.newInstance() //反射中的实例化方法

.invoke(对象,"方法的值") //激活的意思,传参并调用方法

.setAccessible(true); //关闭程序的安全检测

反射性能检测

package com.tan.reflection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author null
 * @date 2020/5/28
 */
public class Test10 {

    //普通方式调用
    public static void test01() {
        User user = new User();
        long startTime = System.currentTimeMillis();

        for (int i = 0; i < 1000000000; i++) {
            user.getName();
        }

        long endTime = System.currentTimeMillis();
        System.out.println("普通方式执行10亿次: " + (endTime - startTime) + "ms");
    }

    //反射方式调用
    public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();//获取User对象
        Class c1 = user.getClass();//获取对象的Class对象
        long startTime = System.currentTimeMillis();

        Method getName = c1.getDeclaredMethod("getName",null);//得到getName方法

        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user, null);//执行getName方法
        }

        long endTime = System.currentTimeMillis();
        System.out.println("反射方式执行10亿次: " + (endTime - startTime) + "ms");
    }

    //反射方式调用 关闭检测
    public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c1 = user.getClass();
        long startTime = System.currentTimeMillis();

        Method getName = c1.getDeclaredMethod("getName",null);
        getName.setAccessible(true);

        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user, null);
        }

        long endTime = System.currentTimeMillis();
        System.out.println("关闭检测的反射方式执行10亿次: " + (endTime - startTime) + "ms");
    }

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

输出:
普通方式执行10亿次: 4ms
反射方式执行10亿次: 2360ms
关闭检测的反射方式执行10亿次: 1045ms

至于为什么反射性能低这么多,涉及底层水平不足,没办法详细解释.??

反射操作泛型

  • Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题,但是,一旦编译完成,所有和泛型有关的类型全部擦除
  • 为了通过反射操作这些类型,Java新增了ParameterizedType,GenericArrayType,TypeVariable 和WildcardType 几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型.

通过反射获取泛型

获取Test01的泛型的参数类型:

public class Test11 {

    public void test01(Map<String,User> map,List<User> list) {
        System.out.println("test01");
    }

    public Map<String, User> test02() {
        System.out.println("test01");
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Method method = Test11.class.getMethod("test01", Map.class, List.class);//获取test01方法
        Type[] genericParameterTypes = method.getGenericParameterTypes();//获取通用参数类型
        for (Type genericParameterType : genericParameterTypes) {
            System.out.println("泛型参数类型: " + genericParameterType);
        }
    }
}

输出:
泛型参数类型: java.util.Map<java.lang.String, com.tan.reflection.User>
泛型参数类型: java.util.List<com.tan.reflection.User>

这样只能获得Map和List那么如何输出,如何把泛型取出来呢?

代码优化: 在for循环中再遍历genericParameterType

public static void main(String[] args) throws NoSuchMethodException {
    Method method = Test11.class.getMethod("test01", Map.class, List.class);//获取test01方法
    Type[] genericParameterTypes = method.getGenericParameterTypes();//获取通用参数类型
    for (Type genericParameterType : genericParameterTypes) {
        System.out.println("泛型参数类型: " + genericParameterType);

        if (genericParameterType instanceof ParameterizedType) {//如果这个通用的参数类型是一个参数化类型
            Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();//获取真实的参数类型
            for (Type actualTypeArgument : actualTypeArguments) {//遍历输出
                System.out.println(actualTypeArgument);
            }
        }
    }
}

输出:
泛型参数类型: java.util.Map<java.lang.String, com.tan.reflection.User>
class java.lang.String
class com.tan.reflection.User
泛型参数类型: java.util.List<com.tan.reflection.User>
class com.tan.reflection.User

获取Test02的泛型的返回值类型:

Method method = Test11.class.getMethod("test02");
Type genericReturnType = method.getGenericReturnType();//获取通用返回值类型

if (genericReturnType instanceof ParameterizedType) {
    Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
    for (Type actualTypeArgument : actualTypeArguments) {
        System.out.println(actualTypeArgument);
    }
}

输出:
class java.lang.String
class com.tan.reflection.User

反射操作注解

package com.tan.reflection;

import java.lang.annotation.*;
import java.lang.reflect.Field;

/**
 * @author null
 * @date 2020/5/28
 */
public class Test12 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("com.tan.reflection.Student2");

        //通过反射获得类注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        //获得注解的value的值
        TableTan annotation = (TableTan) c1.getAnnotation(TableTan.class);
        String value = annotation.value();
        System.out.println(value);

        //获得类属性的注解
        System.out.println("===============================");
        Field id = c1.getDeclaredField("id");

        Annotation[] annotations1 = id.getAnnotations();
        for (Annotation annotation1 : annotations1) {
            System.out.println(annotation1);
        }

        //获得类属性注解的值
        FieldTan annotation2 = id.getAnnotation(FieldTan.class);
        System.out.println(annotation2.columnName());
        System.out.println(annotation2.type());
        System.out.println(annotation2.length());

    }
}

@TableTan("db_student")
//实体类
class Student2 {
    //假装注解可以操作表
    @FieldTan(columnName = "db_id", type = "int", length = 10)
    private int id;
    @FieldTan(columnName = "db_age", type = "int", length = 10)
    private int age;
    @FieldTan(columnName = "db_name", type = "varchar", length = 3)
    private String name;

    public Student2() {
    }

    public Student2(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 "Test12{" +
                "id=" + id +
                ", age=" + age +
                ", name=‘" + name + ‘‘‘ +
                ‘}‘;
    }
}


//类名的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface TableTan {
    String value();
}

//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldTan {
    String columnName();

    String type();

    int length();
}

输出:
@com.tan.reflection.TableTan("db_student")
db_student
===============================
@com.tan.reflection.FieldTan(columnName="db_id", type="int", length=10)
db_id
int

没什么好讲的,就是方法的调用.

单例模式加强对反射的理解

单例模式







以上是关于注解与反射的主要内容,如果未能解决你的问题,请参考以下文章

注解与反射

理解Android中的注解与反射

Java 注解与反射 基础

反射与注解的使用

Java注解与反射

java反射与注解结合使用(根据传入对象输出查询sql)