Matrix42

不饱食以终日,不弃功于寸阴

Java动态性(2) - 之反射机制(Reflection)

1.Java的动态性

  • 反射机制
  • 动态编译
  • 动态执行JavaScript代码
  • 动态字节码操作

2.动态语言

程序运行时,可以改变程序得结构或变量类型.典型语言:

  • Python,Ruby,JavaScript等.
  • 如下JavaScript代码
function test(){
    var s = "var a=3;var b=5;alert(a+b);";
    eval(s);
}
  • C,C++,Java不是动态语言,但Java有一定的动态性,我们可以利用反射机制,字节码操作获得类似动态语言的特性

  • Java的动态性让编程的时候更加灵活

3.反射机制

反射机制指的是可以在运行期间加载一些知道名字的类
对于任意一个已加载的类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能调用它的任意一个方法或属性

Class c = Class.forName("com.test.User");

类加载完之后,在堆内存中会产生一个Class类的对象(一个类只有一个Class对象),这个对象包含了完整的类的结构信息,我们可以通过这个对象看到类的结果

4.Class类介绍

  • java.lang.Class类十分特殊,用来表示java中类型(class/interface/enum/annotation/primitive type/void)本身

    Class类的对象包含了某个被加载类的结构,一个被加载的类对应一个Class对象
    当一个class被加载,或当加载器(class loader)的defineClass()被JVM调用,JVM便会自动产生一个Class对象

  • Class类是Reflection的根源

    针对任何你想动态加载,运行的类,只有先获得相应的Class对象

User bean:

package com.lorinda.bean;

public class User {

    private int id;
    private int age;
    private String uname;

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

    public User() {
    }

    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 getUname() {
        return uname;
    }

    public void setUname(String uname) {
        this.uname = uname;
    }

}

Demo01 测试各种类型对应Class对象的获取方式:

/**
 * 测试各种类型对应Class对象的获取方式
 * @author Matrix42
 *
 */
public class ReflectionDemo01 {

    public static void main(String[] args) {

        String path = "com.lorinda.bean.User";

        try {
            Class<?> clazz = Class.forName(path);
            System.out.println(clazz);              //class com.lorinda.bean.User
            System.out.println(clazz.hashCode());   //366712642
            //同样的类只会被加载一次
            Class<?> clazz2 = Class.forName(path);
            System.out.println(clazz2.hashCode());  //366712642

            Class<String> strClazz = String.class;  //类名.class

            Class<?> strClazz2 = path.getClass();   //对象.getClass();

            System.out.println(strClazz==strClazz2);//true

            Class<?> intClazz = int.class;

            int[] arr01 = new int[10];
            int[] arr02 = new int [30];

            int[][] arr03 = new int[30][3];

            //数组的Class对象只与类型和维度有关
            System.out.println(arr01.getClass()==arr02.getClass()); //true

            System.out.println(arr01.getClass().hashCode());        //1829164700
            System.out.println(arr03.getClass().hashCode());        //2018699554

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

5.Class类的对象如何获取?

  • 对于对象可以使用getClass()

  • 使用Class.forName() (最常使用)

  • 使用.class

6.反射机制的常见作用

  • 动态加载类,动态获取类的信息(属性,方法,构造器)

  • 动态构造对象

  • 动态调用类和对象的任意方法,构造器

  • 动态调用和处理属性

  • 获取泛型信息

  • 处理注解

Demo02 获取方法,属性,构造器等的信息:

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

/**
 * 获取方法,属性,构造器等的信息
 * @author Matrix42
 *
 */
public class ReflectionDemo02 {

    public static void main(String[] args) {

        String path = "com.lorinda.bean.User";

        try {
            Class<?> clazz = Class.forName(path);

            //获取类的名字
            System.out.println(clazz.getName());//获得包名+类名:com.lorinda.bean.User
            System.out.println(clazz.getSimpleName());//获得类名:User

            //获取属性信息
            //Field[] fields = clazz.getFields();//只能获取public的field
            Field[] fields = clazz.getDeclaredFields();//获得所有的field
            Field field = clazz.getDeclaredField("uname");//根据名字获取field

            for(Field temp:fields){
                System.out.println("属性: "+temp);
            }

            //获取方法
            Method[] methods = clazz.getDeclaredMethods();
            Method method01 = clazz.getDeclaredMethod("getUname", null);
            //如果方法有参数,则必须传递参数类型对应的Class对象
            Method method02 = clazz.getDeclaredMethod("setUname", String.class);

            for(Method m:methods){
                System.out.println("方法: "+m);
            }

            //获得构造器信息
            Constructor[] constructors = clazz.getDeclaredConstructors();
            //单独获取,无参
            Constructor c1 = clazz.getDeclaredConstructor(null);
            System.out.println("构造器: "+c1);
            //单独获取,有参
            Constructor c2 = clazz.getDeclaredConstructor(int.class,int.class,String.class);
            System.out.println("构造器: "+c2);
            for(Constructor c:constructors){
                System.out.println("构造器: "+c);
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

    }

Demo03 通过反射动态操作构造器,方法,属性

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import com.lorinda.bean.User;

/**
 * 通过反射动态操作构造器,方法,属性
 * @auther Matrix42
 */
public class ReflectionDemo03 {

    public static void main(String[] args) {

        String path = "com.lorinda.bean.User";

        try {
            Class clazz = Class.forName(path);

            //动态操作构造器
            User u = (User) clazz.newInstance();    //调用了User的无参构造方法

            Constructor<User> c = clazz.getConstructor(int.class,int.class,String.class);

            User u2 = c.newInstance(1000,20,"Matrix42");
            System.out.println(u2.getUname());

            //通过反射调用普通方法
            //好处:方法名,参数都可以是变量,可以从数据库读取
            User u3 = (User) clazz.newInstance();
            Method method = clazz.getDeclaredMethod("setUname", String.class);
            method.invoke(u3, "Matrix42");
            System.out.println(u3.getUname());

            //通过反射操作属性
            User u4 = (User) clazz.newInstance();
            Field f = clazz.getDeclaredField("uname");
            f.setAccessible(true);
            f.set(u4, "24xirtaM");
            //默认会报错,添加f.setAccessible(true);关闭安全检查
            //can not access a member of class com.lorinda.bean.User with modifiers "private"
            System.out.println(u4.getUname());  //正常调用
            System.out.println(f.get(u4));      //通过反射调用

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

7.反射机制性能问题

当你获得灵活性的时候也会牺牲你的性能

  • setAccessible
    • 启用和禁用安全检查的开关,值为true则表示反射的对象在使用时应取消Java语言访问检查.值为fals则表示反射的对象应该实施Java语言访问检查.并不是为true就能访问,为false就不能访问

    • 禁止安全检查,可以提高反射的运行速度

  • 可以考虑使用:cglib/javasssist字节码操作

反射性能测试:

import java.lang.reflect.Method;
import com.lorinda.bean.User;

public class ReflectionDemo04 {

    public static void test01(){

        User user = new User();

        long startTime = System.currentTimeMillis();

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

        long endTime = System.currentTimeMillis();

        //421ms
        System.out.println("普通方法调用,执行10亿次,耗时:"+(endTime-startTime)+"ms");

    }

    public static void test02() throws Exception{

        User user = new User();
        Class clazz = user.getClass();
        Method m = clazz.getDeclaredMethod("getUname", null);

        long startTime = System.currentTimeMillis();

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

        long endTime = System.currentTimeMillis();

        //1650ms
        System.out.println("反射动态调用,执行10亿次,耗时:"+(endTime-startTime)+"ms");

    }

    public static void test03() throws Exception{

        User user = new User();
        Class clazz = user.getClass();
        Method m = clazz.getDeclaredMethod("getUname", null);
        m.setAccessible(true);

        long startTime = System.currentTimeMillis();

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

        long endTime = System.currentTimeMillis();

        //1153ms
        System.out.println("反射动态调用,跳过安全检查,执行10亿次,耗时:"+(endTime-startTime)+"ms");

    }

    public static void main(String[] args) throws Exception {

        test01();
        test02();
        test03();

    }

}

可以看出在java8中使用安全检查的反射耗时大约是普通调用的4倍,不使用安全检查是普通调用的2.5倍

8.反射操作泛型(Generic)

  • Java采用泛型擦除机制来引入泛型.Java中泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的麻烦.但是,一旦编译完成,所有和泛型有关的类型全部擦除.

  • 为了通过反射操作这些类型以迎合实际开发的需要,Java就新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型.

  • ParameterizedType:表示一种参数化类型,比如Collection

  • GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型

  • TypeVariable:是各种类型变量的公共父接口

  • WildcardType:表示一种通配符类型表达式,比如?,? extends Number,? super Integer [wildcard就是通配符的意思]

Demo05 通过反射读取泛型

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import com.lorinda.bean.User;

/**
 * 通过反射读取泛型
 * @author Matrix42
 *
 */
public class ReflectionDemo05 {

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

    public Map<Integer, User>test02(){
        System.out.println("ReflectionDemo05.test2");
        return null;
    }

    public static void main(String[] args) {

        try {

            //获取指定方法参数泛型信息
            Method m = ReflectionDemo05.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);
                    }
                }
            }
            /*
               #java.util.Map<java.lang.String, com.lorinda.bean.User>
                                            泛型类型: class java.lang.String
                                            泛型类型: class com.lorinda.bean.User
               #java.util.List<com.lorinda.bean.User>
                                            泛型类型: class com.lorinda.bean.User
             */
            //获得指定方法返回值泛型信息
            Method m2 = ReflectionDemo05.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);
                }
            }
            /*
                                       返回值,泛型类型: class java.lang.Integer
                                       返回值,泛型类型: class com.lorinda.bean.User
           */

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

9.反射操作注解

Student类:

package com.lorinda.bean;

import com.demo.util.MField;
import com.demo.util.MTable;

@MTable("tb_student")
public class MStudent {

    @MField(columnName="id",type="int",length=10)
    private int id;
    @MField(columnName="sname",type="varchar",length=10)
    private String studentName;
    @MField(columnName="age",type="int",length=3)
    private int age;

    public MStudent(int id, String studentName, int age) {
        super();
        this.id = id;
        this.studentName = studentName;
        this.age = age;
    }

    public MStudent() {
    }

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getStudentName() {
        return studentName;
    }
    public void setStudentName(String studentName) {
        this.studentName = studentName;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

}

Table注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(value={ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MTable {

    String value();

}

Field注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(value={ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MField {

    String columnName();
    String type();
    int length();

}

Demo06 通过反射读取注解

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

public class ReflectionDemo06 {

    public static void main(String[] args) {

        try {
            Class clazz = Class.forName("com.lorinda.bean.MStudent");

            //获得类的所有有效注解
            Annotation[] annotations = clazz.getAnnotations();
            for(Annotation a:annotations){
                System.out.println(a);
            }

            //获得类的指定注解
            MTable table = (MTable) clazz.getAnnotation(MTable.class);
            System.out.println(table.value());

            //获得类的属性的注解
            Field f = clazz.getDeclaredField("studentName");
            MField field = f.getAnnotation(MField.class);
            System.out.println(field.columnName()+"--"+field.type()+"--"+field.length());

            //可以根据获得的表名,字段的信息,拼出DDL语句,然后使用JDBC执行这个SQL,在数据库中生成相关的表
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}
点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注

14 − 5 =