Java Reflection

Java被视为动态或准动态语言的一个关键性质

  • Introduction
  • Usage
  • JavaBean及内省
    • 内省(Introspector)
    • 实际应用场景👀

Introduction

Reflection机制允许程序在运行时透过 Reflection APIs 取得任何一个已知名称的 class 的内部信息,包括 modifiers、superclass、interfaces,也包括 fields 和 methods 的所有信息,并可于运行时改变 fields 内容或调用 methods。换句话说,Java 程序可以加载一个运行时才得知名称的 class,获悉其完整构造但不包括methods定义,并生成其对象实体、或对其 fields 设值、或唤起其methods。这种“看透class”的能力被称为内省。Reflection和introspection是常被并提的两个术语。

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法;
  • 在运行时调用任意一个对象的方法;

在JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflet包中:

  • Class类:代表一个类
  • field类:代表类的成员变量(成员变量也被称为类的属性)
  • Method类:代表类的方法
  • Constructor类:代表类的构造方法
  • Array类:提供了动态创建数组,以及访问数组的元素的静态方法

Usage

Java中每个类被加载之后,系统就会为该类生成一个对应的Class对象,通过该Class对象就可以访问到JVM中的这个类。

Class对象可以获得该类里的方法(由Method对象表示)、构造器(由Constructor对象表示)、成员变量(由Field)对象表示,这三个类都位于java.lang.reflect包下并实现了java.lang.reflect.Member接口。程序可以通过Method对象来执行对应的方法,通过Constructor对象来调用对应的构造器创建实例,能通过Field对象直接访问并修改对象的成员变量值。

在Java程序中获得Class对象通常有如下三种方式:

//使用Class类的forName(String clazzName)静态方法
//该方法需要传入字符串参数,该字符串参数的值是某个类的全限定类名(必须包括完整包名)。
Class clazz=Class.forName(demo.Person);

//调用某个类的class属性来获取该类对应的Class对象
//Person.class将会返回Person类对应的Class对象
Class clazz=Person.class;

//调用某个对象的getClass()方法。
//该方法是java.lang.Object类中的一个方法,所以所有的Java对象都可以调用该方法,该方法将会返回该对象所属类对应的Class对象。
Person person=new Person();
Class clazz=person.getClass();

通过反射来生成实例对象有如下两种方式:

//使用Class对象的newInstance()方法来创建该Class对应类的实例。、
//这种方式要求该Class对象的对应类有默认构造器,而执行newInstance()时实际上是利用默认构造器来创建该类的实例
Class clazz=Person.class;
Object obj=clazz.newInstance();

//先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例。
//通过这种方式可以选择使用指定的构造器来创建实例
Class clazz=Person.class;
Constructor constructor=clazz.getConstructor(String.class);
Object obj=constructor.newInstance();

参考示例:http://blog.csdn.net/ljphhj/article/details/12858767

package com.iamwyj;

import java.lang.reflect.*;

/*
 1.工厂模式:Factory类中用反射的话,添加了一个新的类之后,就不需要再修改工厂类Factory了
 2.数据库JDBC中通过Class.forName(Driver).来获得数据库连接驱动
 3.分析类文件:毕竟能得到类中的方法等等
 4.访问一些不能访问的变量或属性:破解别人代码
*/
class Person {
    private int age;
    private String name;

    public Person() {

    }

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

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

interface ActionInterface {
    public void walk(int m);
}

class SuperMan extends Person implements ActionInterface {
    private boolean BlueBriefs;

    public void fly() {
        System.out.println("超人会飞耶~~");
    }

    public boolean isBlueBriefs() {
        return BlueBriefs;
    }

    public void setBlueBriefs(boolean blueBriefs) {
        BlueBriefs = blueBriefs;
    }

    @Override
    public void walk(int m) {
        // TODO Auto-generated method stub
        System.out.println("超人会走耶~~走了" + m + "米就走不动了!");
    }
}


public class ReflectionTest {

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, SecurityException, NoSuchFieldException, NoSuchMethodException {

        System.out.println("1.通过Java反射机制得到类的包名和类名");
        Person person = new Person();
        System.out.println("包名为:" + person.getClass().getPackage().getName());
        System.out.println("完整类名:" + person.getClass().getName());


        System.out.println("2.验证所有的类都是Class类的实例对象");
        // 定义两个类型都未知的Class,设置初值为null,看看如何给它们赋值成Person类
        Class<?> class1 = null;
        Class<?> class2 = null;
        // 写法1,可能抛出ClassNotFoundException[多用这个写法]
        class1 = Class.forName("com.iamwyj.Person");
        System.out.println("包名为:" + class1.getClass().getPackage().getName());
        System.out.println("完整类名:" + class1.getClass().getName());
        // 写法2
        class2 = Person.class;
        System.out.println("包名为:" + class2.getClass().getPackage().getName());
        System.out.println("完整类名:" + class2.getClass().getName());


        System.out.println("3.通过Java反射机制,用Class创建类对象[这也就是反射存在的意义所在],无参构造");
        Class<?> class3 = null;
        class3 = Class.forName("com.iamwyj.Person");
        //由于这里不能带参数,所以你要实例化的这个类Person,一定要有无参构造函数
        Person person3 = (Person) class3.newInstance();
        person3.setAge(20);
        person3.setName("wyj");
        System.out.println(person3.getName() + " : " + person3.getAge());


        System.out.println("4.通过Java反射机制得到一个类的构造函数并实现构造带参实例对象");
        Class<?> class4 = null;
        Person person4 = null;
        Person person5 = null;

        class4 = Class.forName("com.iamwyj.Person");
        //得到一系列构造函数集合
        Constructor<?>[] constructors = class4.getConstructors();

        person4 = (Person) constructors[0].newInstance();
        person4.setAge(30);
        person4.setName("xm");
        System.out.println(person4.getName() + " : " + person4.getAge());

        person5 = (Person) constructors[1].newInstance(32, "lk");
        System.out.println(person5.getName() + " : " + person5.getAge());


        System.out.println("5.通过Java反射机制操作成员变量set和get");
        Class<?> class6 = null;
        class6 = Class.forName("com.iamwyj.Person");
        Object obj = class6.newInstance();

        Field personNameField = class6.getDeclaredField("name");
        personNameField.setAccessible(true);
        personNameField.set(obj, "胖虎");
        System.out.println("修改属性之后得到属性变量的值:" + personNameField.get(obj));


        System.out.println("6.通过Java反射机制得到类的一些属性:继承的接口,父类,函数信息,成员信息,类型等");
        Class<?> class7 = null;
        class7 = Class.forName("com.iamwyj.SuperMan");
        //取得父类名称
        Class<?> superClass = class7.getSuperclass();
        System.out.println("SuperMan类的父类名: " + superClass.getName());

        Field[] fields = class7.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            System.out.println("类中的成员: " + fields[i]);
        }

        //取得类方法
        Method[] methods = class7.getDeclaredMethods();
        for (int i = 0; i < methods.length; i++) {
            System.out.println("取得SuperMan类的方法:");
            System.out.println("函数名:" + methods[i].getName());
            System.out.println("函数返回类型:" + methods[i].getReturnType());
            System.out.println("函数访问修饰符:" + Modifier.toString(methods[i].getModifiers()));
            System.out.println("函数代码写法: " + methods[i]);
        }

        //取得类实现的接口,因为接口类也属于Class,所以得到接口中的方法也是一样的方法得到哈
        Class<?> interfaces[] = class7.getInterfaces();
        for (int i = 0; i < interfaces.length; i++) {
            System.out.println("实现的接口类名: " + interfaces[i].getName());
        }


        System.out.println("7.通过Java反射机制调用类中方法");
        Class<?> class8 = null;
        class8 = Class.forName("com.iamwyj.SuperMan");

        System.out.println("调用无参方法fly():");
        Method method = class8.getMethod("fly");
        method.invoke(class8.newInstance());

        System.out.println("调用有参方法walk(int m):");
        method = class8.getMethod("walk", int.class);
        method.invoke(class8.newInstance(), 100);


        System.out.println("8.通过Java反射机制获得类加载器");
        Class<?> class9 = null;
        class9 = Class.forName("com.iamwyj.SuperMan");
        String nameString = class9.getClassLoader().getClass().getName();
        System.out.println("类加载器类名: " + nameString);
    }
}
/*output:
1.通过Java反射机制得到类的包名和类名
包名为:com.iamwyj
完整类名:com.iamwyj.Person
2.验证所有的类都是Class类的实例对象
包名为:java.lang
完整类名:java.lang.Class
包名为:java.lang
完整类名:java.lang.Class
3.通过Java反射机制,用Class创建类对象[这也就是反射存在的意义所在],无参构造
wyj : 20
4.通过Java反射机制得到一个类的构造函数并实现构造带参实例对象
xm : 30
lk : 32
5.通过Java反射机制操作成员变量set和get
修改属性之后得到属性变量的值:胖虎
6.通过Java反射机制得到类的一些属性:继承的接口,父类,函数信息,成员信息,类型等
SuperMan类的父类名: com.iamwyj.Person
类中的成员: private boolean com.iamwyj.SuperMan.BlueBriefs
取得SuperMan类的方法:
函数名:fly
函数返回类型:void
函数访问修饰符:public
函数代码写法: public void com.iamwyj.SuperMan.fly()
取得SuperMan类的方法:
函数名:walk
函数返回类型:void
函数访问修饰符:public
函数代码写法: public void com.iamwyj.SuperMan.walk(int)
取得SuperMan类的方法:
函数名:isBlueBriefs
函数返回类型:boolean
函数访问修饰符:public
函数代码写法: public boolean com.iamwyj.SuperMan.isBlueBriefs()
取得SuperMan类的方法:
函数名:setBlueBriefs
函数返回类型:void
函数访问修饰符:public
函数代码写法: public void com.iamwyj.SuperMan.setBlueBriefs(boolean)
实现的接口类名: com.iamwyj.ActionInterface
7.通过Java反射机制调用类中方法
调用无参方法fly():
超人会飞耶~~
调用有参方法walk(int m):
超人会走耶~~走了100米就走不动了!
8.通过Java反射机制获得类加载器
类加载器类名: sun.misc.Launcher$AppClassLoader*/

JavaBean及内省

遵循特定写法的Java类。Introspection means the ability of the program to examine itselt.

具有以下特点:

  1. 有无参构造函数 ==> 使用内省可以帮你创建对象.
  2. 所有作为属性保存的成员变量私有化 ==> java不能脱离基本特性==> 封装性
  3. 属性有对应get/set方法 ==> 1.满足了封装性;2.符合命名规则的话,内省就能分辩出哪些是操作属性的方法

JavaBean在J2EE开发中,通常用于封装数据,对于遵循以上写法的JavaBean组件,其它程序可以通过反射技术实例化JavaBean对象,并且通过反射那些遵守命名规范的方法,从而获知JavaBean的属性,进而调用其属性保存数据。

符合上面的格式,对我们开发来说有什么好处? 可以应用JAVA的内省机制,内省就是操作我们javabean提供的一套api

内省(Introspector)

访问JavaBean属性的两种方式:

  • 直接调用bean的setXXX或getXXX方法。
  • 通过内省技术访问(java.beans包提供了内省的API)

原理:

  1. 内省技术基于反射技术
  2. 通过Introspector类获得Bean对象的 BeanInfo,然后通过 BeanInfo 来获取属性的描述器( PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的 getter/setter 方法,然后通过反射机制来调用这些方法

例如: 利用内省原理设置u的名字为tom,密码为1234

package bean;

public class User {
    private String name;
    private String password;

    public User(String name, String password) {
        super();
        this.name = name;
        this.password = password;
    }

    public User() {
    }

    public String getName() {
        return name;
    }

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

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "name:" + this.name + " " + "password:" + password;
    }
}
package bean;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

public class Test {

    public static void main(String[] args) {
        //内省就是操作我们javabean提供的一套api
        User u = new User();
        try {
            // 1.获得javaBean的描述信息
            BeanInfo info = Introspector.getBeanInfo(User.class);
            // 2.获得User中的属性信息
            PropertyDescriptor[] pds = info.getPropertyDescriptors();
            // 3.遍历属性信息
            for (PropertyDescriptor pd : pds) {
                Method writer = pd.getWriteMethod();
                //判断当前遍历的属性是否是name属性
                if (pd.getName().equals("name")) {
                    //如果是name属性,获得操作name属性的写入方法(setName)
                    writer.invoke(u, "tom");
                } else if (pd.getName().equals("password")) {
                    //如果是password属性,获得操作password属性的写入方法(setPassword)
                    writer.invoke(u, "1234");
                }
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(u);
    }
}
// output: name:tom password:1234

实际应用场景👀

package bean;

import java.util.Date;

public class User {

    private String name;
    private String password;
    private int age;
    private Date birthday;

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public User() {
    }

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

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }


    @Override
    public String toString() {
        return "User [name=" + name + ", password=" + password + ", age=" + age
                + ", birthday=" + birthday + "]";
    }
}

应用一:

import bean.User;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.Date;

public class Test {
    private static void copyProperties(User u1, User u2) {
        try {
            // 1.获得u1中的属性描述
            // 2.遍历这些属性描述
            for (PropertyDescriptor pd : Introspector.getBeanInfo(u1.getClass()).getPropertyDescriptors()) {
                String propName = pd.getName();
                if (!propName.equals("class")) {
                    // 3.查看u2是否具有这些属性
                    PropertyDescriptor pd2 = new PropertyDescriptor(propName, u2.getClass());
                    // 有该属性:获得该属性值,查看是否是null
                    if (pd2 != null) {
                        Object obj = pd.getReadMethod().invoke(u1);
                        if (obj != null) {
                            //不是null 拷贝到u2
                            pd2.getWriteMethod().invoke(u2, obj);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        User u1 = new User();
        u1.setName("tom");
        u1.setPassword("1234");
        u1.setAge(18);
        u1.setBirthday(new Date());

        User u2 = new User();
        copyProperties(u1, u2);// struts1
        System.out.println(u2);
    }
}
//output: User [name=tom, password=1234, age=18, birthday=Thu Jun 28 15:42:40 CST 2018]

应用二: BeanUtils工具类的populate(Object bean, Map properties)方法 可以把表单提交的参数自动封装到Bean中,并且可以自动进行类型转换,转换范围是 8个基本数据类型,我们也可以注册一个转换器,让BeanUtils可以转换其他类型

自定义转换器:

package tool;

import org.apache.commons.beanutils.Converter;

import java.text.ParseException;
import java.text.SimpleDateFormat;

public class MyConverter implements Converter {
    //1990-01-01 ==> date
    public Object convert(Class arg0, Object arg1) {
        String birthdayStr = arg1.toString();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        try {
            return format.parse(birthdayStr);
        } catch (ParseException e) {
            e.printStackTrace();
            return null;
        }
    }
}

表单提交数据:

<body>
<form action="/Review/my" method="post">
    用户名:<input type="text" name="name"/><br>
    密码:<input type="password" name="password"/><br>
    年龄:<input type="text" name="age"/><br>
    生日:<input type="text" name="birthday"/><br>
    <input type="submit"/><br>
</form>
</body>

业务处理:

package servlet;

import bean.User;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import tool.MyConverter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import java.util.Map;


public class MyServlet extends HttpServlet {

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        User u = new User();
        try {
            //注册一个转换器,告诉BeanUtils如何进行转换
            ConvertUtils.register(new MyConverter(), Date.class);
            BeanUtils.populate(u, request.getParameterMap());
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(u);

    }

    // populate底层方法使用内省封装参数
    private void populate(Map<String, String[]> map, User u) {
        try {
            Map<String, String[]> params = map;
            BeanInfo info = Introspector.getBeanInfo(User.class);
            PropertyDescriptor[] pds = info.getPropertyDescriptors();
            for (PropertyDescriptor pd : pds) {
                //System.out.println(pd.getName());
                String[] param = params.get(pd.getName());
                if (param != null && param.length > 0) {
                    pd.getWriteMethod().invoke(u, param[0]);
                }
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}