Java反射

1.物理反射

反射:从物理学角度出发,反射是光照射到镜子、水面及其他可以成像的表面时,光会改变传播方向又返回原来物质的现象,就是反射,反射成像与原来的物质没有差别,可以理解为就是本身。下图的蜡烛成像就是反射原理,右侧蜡烛具有左侧原始蜡烛所有特性。

java反射移除对象属性注解 java反射原理, 注解原理?_经验分享

从物理反射引入Java反射,以下所述反射都为Java反射。

2.反射概述

反射:在程序运行时,可以动态获取Class A(A类)所有特性(属性、构造方法、成员方法、注解等),这些特性的综合体就是Class类,Class类的对象可以获取对应A类的所有信息,就像A类通过镜子成的像一样。Java本身是静态语言,有了反射技术,可认为是准动态语言。

其中映射关系:

  • A类属性—》Field类
  • A类构造方法—》Constructor类
  • A类成员方法—》Method类
  • A类注解—》Annotation接口

A类中的大部分信息都被映射成对应的类。

3.反射作用
  1. 在运行时判断任意一个对象所属的类;
  2. 在运行时判断任意一个类所有的成员变量和方法;
  3. 在运行构建任意一个类的对象;
  4. 在运行时调用任意一个对象所有的成员变量和方法;
  5. 在运行时获取泛型信息;
  6. 在运行时处理注解信息;
  7. 生成动态代理
4.反射依赖

反射技术基于java.lang.Class类,这个类的构造方法是Prviate修饰,不能通过new获取对象,只能通过Jvm获取这个类对象,获取方式有三种,后面会阐述。抛开应用层,从Jvm角度看,获取Class类对象基于类加载技术,比如A类,A.java被编译后,生成A.class文件,当进行new A(),A.class,Class.forName(“包信息”)相关操作,Jvm会将A.class文件加载到内存中,将静态数据转换成方法区的运行时数据(类型信息、静态变量、常量、即使编译器编译后的代码缓存等),同时在堆上创建一个代表这个类,即A类的java.lang.Class对象。

这个Class也称为模板类,A类对象实例通过这个模板类创建。A类只会被Jvm加载一次,即堆内只存在一个代表A类的Class对象,多个A类对象指向同一个代表A类的Class对象。

下图是A类加载及创建Class对象过程,A类对象实例创建需要new,比如:new A(),先加载A类(如果A类已加载完成,就直接调用构造方法),再调用构造方法创建A对象。

第一次用win的画图工具画图,不是很好,同时下图创建步骤是个人理解,有些地方不对,请各位朋友在评论区留言。

java反射移除对象属性注解 java反射原理, 注解原理?_java反射移除对象属性注解_02

因为类加载机制涉及到Jvm相关内容,后面出一篇文章专门谈类加载机制。

5.获取相关信息
5.1 获取Class对象
  1. 第一种获取方式:Class.forName(“com.liqao.basic.reflect.Student”)
  2. 第二种获取方式:Student.class
  3. 第三种获取方式:new Student().getClass()

代码如下:

//Student
@Table(tableName = "tb_student")
public class Student {
  	public Student() {
    }
    public Student(String name) {
        this.name = name;
    }
    private Student(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    @column(name = "t_id",type = "int",length = 10,isNull = false)
    private int id;
    @column(name = "t_name",type = "varchar",length = 4)
    private String name;
    @column(name = "t_age",type = "int",length = 4)
    private int age;
  	public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
  	private void reflect() {
    }
}
//@table
@Documented
@Target(value = {ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Table {
    String tableName();
}
//@column
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface column {
    String name();
    String type();
    int length();
    boolean isNull() default true;
}
//测试类
public class Reflect01 {
    public static void main(String[] args) {
        Class clazz1 = null;
        try {
            clazz1 = Class.forName("com.liqiao.basic.reflect.Student");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        Class<Student> clazz2 = Student.class;
        Student student = new Student();
        Class<? extends Student> clazz3 = student.getClass();
        System.out.println(clazz1);
        System.out.println(clazz2);
        System.out.println(clazz3);
    }
}

输出结果如下,输出类型为Student类对应的Class对象:

class com.liqiao.basic.reflect.Student
class com.liqiao.basic.reflect.Student
class com.liqiao.basic.reflect.Student

使用以上三种方式,都会导致类的加载,第一种和第三种方式在类加载过程中,会初始化静态变量,同时调用静态代码块,第二种方式不会

在框架中使用最多的就是第一种方式,通过类的全路径加载类,获取类的相关信息

5.2获取类相关信息
String fullName = clazz1.getName();//结果:全类名com.liqiao.basic.reflect.Student
String simpleName = clazz1.getSimpleName();//结果:类名Student
Package packageInfo = clazz1.getPackage();//结果:包名package com.liqiao.basic.reflect
ClassLoader classLoader = clazz1.getClassLoader();//结果:加载器sun.misc.Launcher$AppClassLoader@18b4aac2
Class superclass = clazz1.getSuperclass();//结果:父类Class对象class java.lang.Object
5.3获取构造函数
Constructor[] constructors = clazz1.getConstructors();
//输出结果:public构造器
public com.liqiao.basic.reflect.Student(java.lang.String)
public com.liqiao.basic.reflect.Student()
  
Constructor[] declaredConstructors = clazz1.getDeclaredConstructors();
//输出结果:本类所有构造器
private com.liqiao.basic.reflect.Student(int,java.lang.String,int)//私有构造
public com.liqiao.basic.reflect.Student(java.lang.String)
public com.liqiao.basic.reflect.Student()
  
Constructor constructor = clazz1.getDeclaredConstructor(String.class);
//指定参数构造器
public com.liqiao.basic.reflect.Student(java.lang.String)
//总结:getDeclaredConstructors可以获取本类所有构造器,getConstructors获取public修饰构造器
5.4获取方法
Method[] methods = clazz1.getMethods();
//输出结果:public修饰方法,包含继承方法,继承Object中public方法
public java.lang.String com.liqiao.basic.reflect.Student.getName()
public int com.liqiao.basic.reflect.Student.getId()
public void com.liqiao.basic.reflect.Student.setName(java.lang.String)
public void com.liqiao.basic.reflect.Student.setAge(int)
public int com.liqiao.basic.reflect.Student.getAge()
public void com.liqiao.basic.reflect.Student.setId(int)
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 java.lang.String java.lang.Object.toString()
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()
  
Method[] declaredMethods = clazz1.getDeclaredMethods();
//输出结果:本类所有方法
public java.lang.String com.liqiao.basic.reflect.Student.getName()
public int com.liqiao.basic.reflect.Student.getId()
public void com.liqiao.basic.reflect.Student.setName(java.lang.String)
public void com.liqiao.basic.reflect.Student.setAge(int)
public int com.liqiao.basic.reflect.Student.getAge()
private void com.liqiao.basic.reflect.Student.reflect()//私有方法
public void com.liqiao.basic.reflect.Student.setId(int)
  
Method setName = clazz1.getDeclaredMethod("setName", String.class);
//输出结果:根据方法名和参数类型获取确定方法
public void com.liqiao.basic.reflect.Student.setName(java.lang.String)
//总结:getMethods获取本类和父类public方法,getDeclaredMethod获取本类所有方法
5.5获取属性
Field[] fields = clazz1.getFields();
//输出结果:获取本类及父类public修饰的属性
Field[] declaredFields = clazz1.getDeclaredFields();
//输出结果:获取本类所有属性
private int com.liqiao.basic.reflect.Student.id
private java.lang.String com.liqiao.basic.reflect.Student.name
private int com.liqiao.basic.reflect.Student.age
Field id = clazz1.getDeclaredField("id");
//输出结果:获取本类确定属性信息,id为属性名称
private int com.liqiao.basic.reflect.Student.id
//总结:getFields获取本类及父类public修饰属性;getDeclaredFields获取本类所有属性
5.6获取注释
Annotation[] annotations = clazz1.getAnnotations();
//输出结果:本类类上使用的所有注解
@com.liqiao.basic.reflect.Table(tableName=tb_student)
Annotation tableAnnotation = clazz1.getAnnotation(Table.class);
//输出结果:获取指定类型注解
@com.liqiao.basic.reflect.Table(tableName=tb_student)

//以下获取属性上注解
Annotation[] annotations = id.getAnnotations();//id为Filed
//输出结果:该id属性的所有注解
@com.liqiao.basic.reflect.column(isNull=false, name=t_id, type=int, length=10)
5.7使用反射创建对象
//1.使用反射创建对象 无参
try {
  Class<?> clazz = Class.forName("com.liqiao.basic.reflect.Student");
  //newInstance方法会调用Student的无参构造方法创建
  Student student = (Student)clazz.newInstance();
} catch (ClassNotFoundException e) {
  e.printStackTrace();
} catch (IllegalAccessException e) {
  e.printStackTrace();
} catch (InstantiationException e) {
  e.printStackTrace();
}
//2.使用反射创建对象 有参
Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(int.class, String.class, int.class);
declaredConstructor.setAccessible(true);//由于此构造方法是私有的,进行爆破,设置可以访问,默认为false
Object joy = declaredConstructor.newInstance(1, "joy", 30);//创建对象,调用有参构造
6反射和注解结合例子

例子:使用反射获取注解相关信息(主要是数据库相关注解),进行sql语句的拼接,根据语句可以创建数据库表等相关功能,这里进行简单的语句拼接,主要说明反射和注解结合可以做相当多的事情,spring框架使用反射和注解进行数据库相关操作(逆向工程,spring boot jpa等)

package com.liqiao.basic.reflect;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

public class Reflect03 {
    public static void main(String[] args) {
        String packageInfo = "com.liqiao.basic.reflect.Student";
        String tableValue = getTableValue(packageInfo);
        //拼接查询语句
        String sql = "select * from " + tableValue;
        System.out.println(tableValue);
        List<Annotation> fieldAnnotations = getFieldAnnotations(packageInfo);
        //fieldAnnotations.forEach(System.out::println);
        StringBuilder builder = new StringBuilder();
        for (Annotation fieldAnnotation : fieldAnnotations) {
            //拼接每个注解的name值
            builder.append(((Column)fieldAnnotation).name()).append(",");
        }
        int end = builder.lastIndexOf(",");
        String column = builder.substring(0, end);
        //使用表名和列名拼接插入语句
        String insertSql = getInsertSql(tableValue, column);
        System.out.println(insertSql);
    }
  	//获取表名称
    public static String getTableValue(String packageInfo) {
        if (packageInfo.isEmpty()) {
            throw new RuntimeException("类信息为空!");
        }
        try {
            //使用Class.forName("")获取该类的Class对象
            Class<?> clazz = Class.forName(packageInfo);
            //获取类的注解(根据注解的Class对象)
            Table table = clazz.getDeclaredAnnotation(Table.class);
            //Table table = clazz.getAnnotation(table.getClass());
            if (table == null) {
                throw new RuntimeException("该类没有@Table注解!");
            }
            //通过注解table获取注解的值,tableName为数据库中的表名
            String tableName = table.tableName();
            return tableName;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
  	//获取属性注解
    public static List<Annotation> getFieldAnnotations(String packageInfo) {
        List<Annotation> list = new ArrayList<>();
        if (packageInfo.isEmpty()) {
            throw new RuntimeException("类信息为空!");
        }
        try {
            //先获取类的Class对象
            Class<?> clazz = Class.forName(packageInfo);
            //根据具体注解获取注解信息
            Table table = clazz.getAnnotation(Table.class);
            //如果table存在说明该类是一个实体类,继续获取该类属性的注解信息
            if (table == null) {
                throw new RuntimeException("该类不是一个实体类!");
            }
            //获取该类所有属性
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                //获取该类属性注解(@Column注解)
                Column column = field.getAnnotation(Column.class);
                if (column == null) {
                    throw new RuntimeException("该类属性无Column注解!");
                }
                list.add(column);
            }
            return list;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
	//拼接插入语句
    public static String getInsertSql(String tableName, String column) {
        String insertSql = "insert into " + tableName + "(" + column + ") "
                + "value(" + 1 + "," + "joy" + "," + 18 + ")";
        return insertSql;
    }
}
//总结:Spring框架通过程序员添加的注解和反射技术帮助程序员实现很多通用功能
7反射其它特性
7.1调用方法
package com.liqiao.basic.reflect;

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

public class Reflect04 {
    public static void main(String[] args) {
        testReflectMethod();
    }
    public static void testReflectMethod() {
        try {
            Class<Student> studentClass = Student.class;
            //通过方法名称getName获取确定方法
            Method getName = studentClass.getMethod("getName");
            //通过方法名称setName和参数String.class获取确定方法,String为参数
            Method setName = studentClass.getMethod("setName", String.class);
            //使用无参构造创建对象
            Student student = studentClass.newInstance();
            //调用setName方法,设置name值
            setName.invoke(student,"king");
            //调用getName方法获取值
            String name = (String) getName.invoke(student, null);
            System.out.println(name);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
//通过反射调用方法的性能比较差,反射调用方法时会进行很多逻辑判断或过程;而通过对象.方法直接调用的性能相对来说要好很多
7.2属性操作
package com.liqiao.basic.reflect;

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

public class Reflect04 {
    public static void main(String[] args) {
        testReflectField();
    }
    public static void testReflectField() {
        try {
            Class<Student> studentClass = Student.class;
            //调用无参构造创建对象
            Student student = studentClass.newInstance();
            //根据属性名称获取确定属性
            Field name = studentClass.getDeclaredField("name");
            //设置私有属性可访问,默认是false,因此反射可以操作类中的任何方法和属性
            name.setAccessible(true);
            name.set(student,"king");
            String s = (String) name.get(student);
            System.out.println(s);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}