澳门新葡萄京官网注册 3

澳门新葡萄京官网注册Java 反射机制详解

动态语言

动态语言,是指程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化。比如众所周知的ECMAScript(JavaScript)便是一个动态语言。除此之外如Ruby、Python等也都属于动态语言,而C、C++等语言则不属于动态语言。(引自:
百度百科)

var execString = "alert(Math.floor(Math.random()*10));";
eval(execString);

接下来我们将介绍Java反射机制的一系列的知识。本篇文章主要针对Java反射机制的介绍以及反射API的使用知识。

一. 类的加载,连接,初始化

Class反射机制

  • 指的是可以于运行时加载,探知和使用编译期间完全未知的类.
  • 程序在运行状态中, 可以动态加载一个只有名称的类,
    对于任意一个已经加载的类,都能够知道这个类的所有属性和方法;
    对于任意一个对象,都能调用他的任意一个方法和属性;
  • 加载完类之后,
    在堆内存中会产生一个Class类型的对象(一个类只有一个Class对象),
    这个对象包含了完整的类的结构信息,而且这个Class对象就像一面镜子,透过这个镜子看到类的结构,所以被称之为:反射。

Instances of the class Class represent classes and interfaces in a
running Java application. An enum is a kind of class and an annotation
is a kind of interface. Every array also belongs to a class that is
reflected as a Class object that is shared by all arrays with the same
element type and number of dimensions(维度). The primitive Java types
(boolean, byte, char, short, int, long, float, anddouble), and the
keyword void are also represented as Class objects.

  • 每个类被加载进入内存之后,系统就会为该类生成一个对应的java.lang.Class对象,通过该Class对象就可以访问到JVM中的这个类.

一、概述

Java反射机制定义

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
Java 反射机制的功能

1.在运行时判断任意一个对象所属的类。

2.在运行时构造任意一个类的对象。

3.在运行时判断任意一个类所具有的成员变量和方法。

4.在运行时调用任意一个对象的方法。

5.生成动态代理。

Java 反射机制的应用场景

1.逆向代码 ,例如反编译

2.与注解相结合的框架 例如Retrofit

3.单纯的反射机制应用框架 例如EventBus

4.动态生成类框架 例如Gson

  1.1. JVM和类

当调用Java命令运行某个Java程序时,该命令将会启动一个Java虚拟机进程。不管Java程序多么复杂,启动多少个线程,它们都处于该Java虚拟机进程里,都是使用同一个Java进程内存区。

JVM程序终止的方式:

  • 程序运行到最后正常结束
  • 程序运行到使用System.exit()或Runtime.getRuntime().exit()代码处结束程序
  • 程序执行过程中遇到未捕获的异常或错误而结束
  • 程序所在平台强制结束了JVM进程

JVM进程结束,该进程所在内存中的状态将会丢失

Class对象的获取

  • 对象的getClass()方法;
  • 类的.class(最安全/性能最好)属性;
  • 运用Class.forName(String className)动态加载类,className需要是类的全限定名(最常用).

二、通过Java反射查看类信息

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

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

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

2.调用某个类的class属性来获取该类对应的Class对象。

3.调用某个对象的getClass()方法。该方法是java.lang.Object类中的一个方法。

//第一种方式 通过Class类的静态方法——forName()来实现
class1 = Class.forName("com.lvr.reflection.Person");
//第二种方式 通过类的class属性
class1 = Person.class;
//第三种方式 通过对象getClass方法
Person person = new Person();
Class<?> class1 = person.getClass();

获取class对象的属性、方法、构造函数等

1.获取class对象的成员变量

Field[] allFields = class1.getDeclaredFields();//获取class对象的所有属性
Field[] publicFields = class1.getFields();//获取class对象的public属性
Field ageField = class1.getDeclaredField("age");//获取class指定属性
Field desField = class1.getField("des");//获取class指定的public属性

2.获取class对象的方法

Method[] methods = class1.getDeclaredMethods();//获取class对象的所有声明方法
Method[] allMethods = class1.getMethods();//获取class对象的所有public方法 包括父类的方法
Method method = class1.getMethod("info", String.class);//返回次Class对象对应类的、带指定形参列表的public方法
Method declaredMethod = class1.getDeclaredMethod("info", String.class);//返回次Class对象对应类的、带指定形参列表的方法

3.获取class对象的构造函数

Constructor<?>[] allConstructors = class1.getDeclaredConstructors();//获取class对象的所有声明构造函数
Constructor<?>[] publicConstructors = class1.getConstructors();//获取class对象public构造函数
Constructor<?> constructor = class1.getDeclaredConstructor(String.class);//获取指定声明构造函数
Constructor publicConstructor = class1.getConstructor(String.class);//获取指定声明的public构造函数

4.其他方法

Annotation[] annotations = (Annotation[]) class1.getAnnotations();//获取class对象的所有注解
Annotation annotation = (Annotation) class1.getAnnotation(Deprecated.class);//获取class对象指定注解
Type genericSuperclass = class1.getGenericSuperclass();//获取class对象的直接超类的 Type
Type[] interfaceTypes = class1.getGenericInterfaces();//获取class对象的所有接口的type集合

获取class对象的信息
比较多。

boolean isPrimitive = class1.isPrimitive();//判断是否是基础类型
boolean isArray = class1.isArray();//判断是否是集合类
boolean isAnnotation = class1.isAnnotation();//判断是否是注解类
boolean isInterface = class1.isInterface();//判断是否是接口类
boolean isEnum = class1.isEnum();//判断是否是枚举类
boolean isAnonymousClass = class1.isAnonymousClass();//判断是否是匿名内部类
boolean isAnnotationPresent = class1.isAnnotationPresent(Deprecated.class);//判断是否被某个注解类修饰
String className = class1.getName();//获取class名字 包含包名路径
Package aPackage = class1.getPackage();//获取class的包信息
String simpleName = class1.getSimpleName();//获取class类名
int modifiers = class1.getModifiers();//获取class访问权限
Class<?>[] declaredClasses = class1.getDeclaredClasses();//内部类
Class<?> declaringClass = class1.getDeclaringClass();//外部类

  1.2 类的加载

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化三个步骤来对该类进行初始化。

类的加载时将该类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说,当程序使用任何类时,系统都会为之建立一个java.lang.Class对象。

系统中所有的类实际上也是实例,它们都是java.lang.Class的实例

类的加载通过JVM提供的类加载器完成,类加载器时程序运行的基础,JVM提供的类加载器被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。

通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源。

  1. 从本地文件系统加载class文件,这是前面绝大部分实例程序的类加载方式
  2. 从jar包加载class文件,这种方式也是很常见的,jdbc编程所用的驱动类就放在jar文件中,JVM可以直接从jar文件中加载该class文件。
  3. 通过网络加载class文件
  4. 把一个Java源文件动态编译,并执行加载

类加载器通常无需等到首次使用该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。

  1.3 类的连接

当类被加载后,系统会为之生成一个对应的Class对象,接着会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类的链接可分为如下三个阶段。

  1. 验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
  2. 准备:类准备阶段则负责为类的类变量分配内存,并设置默认初始值
  3. 解释:将类的二进制数据中的变量进行符号引用替换成直接引用

  1.4 类的初始化

再累舒适化阶段,虚拟机负责对类进行初始化,主要就是对类变量进行初始化。在Java类中对类变量指定初始值有两种方式:①声明类变量时指定初始值;②使用静态初始化块为类变量指定初始值。

JVM初始化一个类包含如下步骤

  1. 加载并连接该类
  2. 先初始化其直接父类
  3. 依次执行初始化语句

当执行第2步时,系统对直接父类的初始化也遵循1~3,以此类推

从Class中获取信息

Class类提供了大量的实例方法来获取该Class对象所对应的详细信息,Class类大致包含如下方法,其中每个方法都包含多个重载版本,因此我们只是做简单的介绍,详细请参考JDK文档

获取类内信息

获取内容 方法签名
构造器 Constructor<T> getConstructor(Class<?>... parameterTypes)
包含的方法 Method getMethod(String name, Class<?>... parameterTypes)
包含的属性 Field getField(String name)
包含的Annotation <A extends Annotation> A getAnnotation(Class<A> annotationClass)
内部类 Class<?>[] getDeclaredClasses()
外部类 Class<?> getDeclaringClass()
所实现的接口 Class<?>[] getInterfaces()
修饰符 int getModifiers()
所在包 Package getPackage()
类名 String getName()
简称 String getSimpleName()

一些判断类本身信息的方法

判断内容 方法签名
注解类型? boolean isAnnotation()
使用了该Annotation修饰? boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
匿名类? boolean isAnonymousClass()
数组? boolean isArray()
枚举? boolean isEnum()
原始类型? boolean isPrimitive()
接口? boolean isInterface()
obj是否是该Class的实例 boolean isInstance(Object obj)

使用反射生成并操作对象:

Method Constructor
Field这些类都实现了java.lang.reflect.Member接口,程序可以通过Method对象来执行相应的方法,通过Constructor对象来调用对应的构造器创建实例,通过Filed对象直接访问和修改对象的成员变量值.

三、通过Java反射生成并操作对象

生成类的实例对象

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

2.先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例。通过这种方式可以选择使用指定的构造器来创建实例。

//第一种方式 Class对象调用newInstance()方法生成
Object obj = class1.newInstance();
//第二种方式 对象获得对应的Constructor对象,再通过该Constructor对象的newInstance()方法生成
Constructor<?> constructor = class1.getDeclaredConstructor(String.class);//获取指定声明构造函数
obj = constructor.newInstance("hello");

调用类的方法

1.通过Class对象的getMethods()方法或者getMethod()方法获得指定方法,返回Method数组或对象。

2.调用Method对象中的Object invoke(Object obj, Object... args)方法。第一个参数对应调用该方法的实例对象,第二个参数对应该方法的参数。

 // 生成新的对象:用newInstance()方法
 Object obj = class1.newInstance();
//首先需要获得与该方法对应的Method对象
Method method = class1.getDeclaredMethod("setAge", int.class);
//调用指定的函数并传递参数
method.invoke(obj, 28);

当通过Method的invoke()方法来调用对应的方法时,Java会要求程序必须有调用该方法的权限。如果程序确实需要调用某个对象的private方法,则可以先调用Method对象的如下方法。
setAccessible(boolean
flag):将Method对象的acessible设置为指定的布尔值。值为true,指示该Method在使用时应该取消Java语言的访问权限检查;值为false,则知识该Method在使用时要实施Java语言的访问权限检查。

访问成员变量值

1.通过Class对象的getFields()方法或者getField()方法获得指定方法,返回Field数组或对象。

2.Field提供了两组方法来读取或设置成员变量的值:
getXXX(Object
obj):获取obj对象的该成员变量的值。此处的XXX对应8种基本类型。如果该成员变量的类型是引用类型,则取消get后面的XXX。
setXXX(Object obj,XXX val):将obj对象的该成员变量设置成val值。

//生成新的对象:用newInstance()方法 
Object obj = class1.newInstance();
//获取age成员变量
Field field = class1.getField("age");
//将obj对象的age的值设置为10
field.setInt(obj, 10);
//获取obj对象的age的值
field.getInt(obj);

还有很多其他操作,就不一一介绍了。

下篇文章我们将介绍反射和动态代理的相关知识。

  1.5 类初始化时机

当Java程序首次通过下面6种方式使用某个类或接口时,系统会初始化该类或接口

  • 创建类的实例。创建类的实例包括new操作符来创建实例,通过反射来创建实例,通过反射实例化创建实例
  • 调用某个类的类方法(静态方法)
  • 访问某个类或接口的类变量或为该类变量赋值
  • 使用反射方式来强制来创建某个类或接口的java.lang.Class对象。例如代码“Class.forname(“Person”)”,如果系统还未初始化Person类,则这行代码会导致Person类被初始化,并返回person类的java.lang.Class对象
  • 初始化某个类的子类
  • 使用java.exe命令来运行某个主类。当运行某个主类时,程序会初始化该主类

二. 类加载器

  2.1类加载器介绍

  类加载器负责将.class文件加载到内存中,并为之生成对应的java.lang.Class对象。

一个载入JVM的类有一个唯一的标识。在Java中,一个类使用全限定类名(包括包名和类名)作为标识;但在JVM中,一个类使用全限定类名和其类加载器作为唯一标识。

当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构

  • Bootstrap ClassLoader:跟类加载器
  • Extension ClassLoader:扩展类加载器
  • System ClassLoader:系统类加载器

Bootrap
ClassLoader被称为引导(也称为原始或跟)类加载器,它负责加载Java的核心类。跟类加载器不是java.lang.ClassLoader的子类,而是JVM自身实现的。

Extension
ClassLoader负责加载JRE拓展目录中的JAR包的类,它的父类加载器是跟类加载器

System
ClassLoader,它负责在JVM启动时加载来自Java命令的-classpath选项、java.class,path系统属性,或CLASSPATH指定的jar包和类历经。系统可通过ClassLoader的静态方法或区该系统类加载器。如果没有特别指定,则用户自定义的类加载器都已类加载器作为父加载器

创建对象

通过反射来生成对象的方式有两种:

  • 使用Class对象的newInstance()方法来创建该Class对象对应类的实例(这种方式要求该Class对象的对应类有默认构造器).
  • 先使用Class对象获取指定的Constructor对象,
    再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例(通过这种方式可以选择指定的构造器来创建实例).

通过第一种方式来创建对象比较常见,
像Spring这种框架都需要根据配置文件(如applicationContext.xml)信息来创建Java对象,从配置文件中读取的只是某个类的全限定名字符串,程序需要根据该字符串来创建对应的实例,就必须使用默认的构造器来反射对象.
下面我们就模拟Spring实现一个简单的对象池,
该对象池会根据文件读取key-value对, 然后创建这些对象, 并放入Map中.

配置文件

{
  "objects": [
    {
      "id": "id1",
      "class": "com.fq.domain.User"
    },
    {
      "id": "id2",
      "class": "com.fq.domain.Bean"
    }
  ]
}

ObjectPool

/**
 * Created by jifang on 15/12/31.
 */
public class ObjectPool {

    private Map<String, Object> pool;

    private ObjectPool(Map<String, Object> pool) {
        this.pool = pool;
    }

    private static Object getInstance(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        return Class.forName(className).newInstance();
    }

    private static JSONArray getObjects(String config) throws IOException {
        Reader reader = new InputStreamReader(ClassLoader.getSystemResourceAsStream(config));
        return JSONObject.parseObject(CharStreams.toString(reader)).getJSONArray("objects");
    }

    // 根据指定的JSON配置文件来初始化对象池
    public static ObjectPool init(String config) {
        try {
            JSONArray objects = getObjects(config);
            ObjectPool pool = new ObjectPool(new HashMap<String, Object>());
            if (objects != null && objects.size() != 0) {
                for (int i = 0; i < objects.size(); ++i) {
                    JSONObject object = objects.getJSONObject(i);
                    if (object == null || object.size() == 0) {
                        continue;
                    }
                    String id = object.getString("id");
                    String className = object.getString("class");

                    pool.putObject(id, getInstance(className));
                }
            }
            return pool;
        } catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    public Object getObject(String id) {
        return pool.get(id);
    }

    public void putObject(String id, Object object) {
        pool.put(id, object);
    }

    public void clear() {
        pool.clear();
    }
}

Client

public class Client {

    @Test
    public void client() {
        ObjectPool pool = ObjectPool.init("config.json");
        User user = (User) pool.getObject("id1");
        System.out.println(user);

        Bean bean = (Bean) pool.getObject("id2");
        System.out.println(bean);
    }
}

User

public class User {

    private int id;
    private String name;
    private String password;

    public int getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    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{" +
                "id=" + id +
                ", name='" + name + '/'' +
                ", password='" + password + '/'' +
                '}';
    }
}

Bean

public class Bean {
    private Boolean usefull;
    private BigDecimal rate;
    private String name;

    public Boolean getUsefull() {
        return usefull;
    }

    public void setUsefull(Boolean usefull) {
        this.usefull = usefull;
    }

    public BigDecimal getRate() {
        return rate;
    }

    public void setRate(BigDecimal rate) {
        this.rate = rate;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Bean{" +
                "usefull=" + usefull +
                ", rate=" + rate +
                ", name='" + name + '/'' +
                '}';
    }
}

注意: 需要在pom.xml中添加如下依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.7</version>
</dependency>

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
</dependency>

  2.2 类加载机制

JVM类加载机制主要有三种

  • 全盘负责。就是当类加载器负责加载某个Class时,该Class所依赖的和所引用的其他Class也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入
  • 父类委托。所谓父类委托,就是先让父类加载器试图加载该Class。只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
  • 缓存机制。缓存机制将会保证所有加载过的Class都会被缓存,当程序需要使用时,先从缓存中搜索该Class,当缓存中不存在该Class,系统菜才读取该类对应的二进制数据,并将其转为Class对象,存入缓存区中。这就是为什么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。

类加载器加载Class大致经过8个步骤

  1. 检测此Class是否载入过(即缓存区中是否有此Class),如果有则直接进入第8步,否者接着第2步
  2. 如果父类加载器(父类      gt+
    加载器,要么Parent一定是跟类加载器,要么本身就是跟类加载器)不存在,则调到第4步执行
  3. 请求使用父类加载器载入目标类,如果成功载入调到第8步
  4. 请求使用跟类加载器来载入目标类
  5. 当前类加载器尝试寻找Class文件(从与此ClassLoader相关的类路径中寻找),如果找到则执行第6步,如果找不到执行第7步
  6. 从文件中载入Class,成功载入调到第8步
  7. 抛出ClassNotFoundException异常
  8. 返回对应的java.lang.Class对象

其中,第5、6步允许重写ClassLoader的findClass()方法来实现自己的载入策略,甚至重写loadClass()方法来实现自己的载入过程。

调用方法

当获取到某个类对应的Class对象之后,
就可以通过该Class对象的getMethod来获取一个Method数组或Method对象.每个Method对象对应一个方法,在获得Method对象之后,就可以通过调用invoke方法来调用该Method对象对应的方法.

@CallerSensitive
public Object invoke(Object obj, Object... args)
    throws IllegalAccessException, IllegalArgumentException,
       InvocationTargetException
{
    ...
}

下面我们对上面的对象池加强:可以看到Client获取到的对象的成员变量全都是默认值,既然我们已经使用了JSON这么优秀的工具,我们又学习了动态调用对象的方法,那么我们就通过配置文件来给对象设置值(在对象创建时),
新的配置文件形式如下:

{
  "objects": [
    {
      "id": "id1",
      "class": "com.fq.domain.User",
      "fields": [
        {
          "name": "id",
          "value": 101
        },
        {
          "name": "name",
          "value": "feiqing"
        },
        {
          "name": "password",
          "value": "ICy5YqxZB1uWSwcVLSNLcA=="
        }
      ]
    },
    {
      "id": "id2",
      "class": "com.fq.domain.Bean",
      "fields": [
        {
          "name": "usefull",
          "value": true
        },
        {
          "name": "rate",
          "value": 3.14
        },
        {
          "name": "name",
          "value": "bean-name"
        }
      ]
    },
    {
      "id": "id3",
      "class": "com.fq.domain.ComplexBean",
      "fields": [
        {
          "name": "name",
          "value": "complex-bean-name"
        },
        {
          "name": "refBean",
          "ref": "id2"
        }
      ]
    }
  ]
}

其中fields代表该Bean所包含的属性, name为属性名称, value为属性值(属性类型为JSON支持的类型), ref代表引用一个对象(也就是属性类型为Object,但是一定要引用一个已经存在了的对象)

/**
 * @author jifang
 * @since 15/12/31下午4:00
 */
public class ObjectPool {

    private Map<String, Object> pool;

    private ObjectPool(Map<String, Object> pool) {
        this.pool = pool;
    }

    private static JSONArray getObjects(String config) throws IOException {
        Reader reader = new InputStreamReader(ClassLoader.getSystemResourceAsStream(config));
        return JSONObject.parseObject(CharStreams.toString(reader)).getJSONArray("objects");
    }

    private static Object getInstance(String className, JSONArray fields)
            throws ClassNotFoundException, NoSuchMethodException,
            IllegalAccessException, InstantiationException, InvocationTargetException {

        // 配置的Class
        Class<?> clazz = Class.forName(className);
        // 目标Class的实例对象
        Object targetObject = clazz.newInstance();
        if (fields != null && fields.size() != 0) {
            for (int i = 0; i < fields.size(); ++i) {
                JSONObject field = fields.getJSONObject(i);
                // 需要设置的成员变量名
                String fieldName = field.getString("name");

                // 需要设置的成员变量的值
                Object fieldValue;
                if (field.containsKey("value")) {
                    fieldValue = field.get("value");
                } else if (field.containsKey("ref")) {
                    String refBeanId = field.getString("ref");
                    fieldValue = OBJECTPOOL.getObject(refBeanId);
                } else {
                    throw new RuntimeException("neither value nor ref");
                }

                String setterName = "set" +
                        fieldName.substring(0, 1).toUpperCase() +
                        fieldName.substring(1);
                // 需要设置的成员变量的setter方法
                Method setterMethod = clazz.getMethod(setterName, fieldValue.getClass());
                // 调用setter方法将值设置进去
                setterMethod.invoke(targetObject, fieldValue);
            }
        }

        return targetObject;
    }

    private static ObjectPool OBJECTPOOL;

    // 创建一个对象池的实例(保证是多线程安全的)
    private static void initSingletonPool() {
        if (OBJECTPOOL == null) {
            synchronized (ObjectPool.class) {
                if (OBJECTPOOL == null) {
                    OBJECTPOOL = new ObjectPool(new ConcurrentHashMap<String, Object>());
                }
            }
        }
    }

    // 根据指定的JSON配置文件来初始化对象池
    public static ObjectPool init(String config) {
        // 初始化pool
        initSingletonPool();

        try {
            JSONArray objects = getObjects(config);
            for (int i = 0; objects != null && i < objects.size(); ++i) {
                JSONObject object = objects.getJSONObject(i);
                if (object == null || object.size() == 0) {
                    continue;
                }
                String id = object.getString("id");
                String className = object.getString("class");

                // 初始化bean并放入池中
                OBJECTPOOL.putObject(id, getInstance(className, object.getJSONArray("fields")));
            }
            return OBJECTPOOL;
        } catch (IOException | ClassNotFoundException |
                InstantiationException | IllegalAccessException |
                NoSuchMethodException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    public Object getObject(String id) {
        return pool.get(id);
    }

    public void putObject(String id, Object object) {
        pool.put(id, object);
    }

    public void clear() {
        pool.clear();
    }
}

Client

public class Client {

    @Test
    public void client() {
        ObjectPool pool = ObjectPool.init("config.json");
        User user = (User) pool.getObject("id1");
        System.out.println(user);

        Bean bean = (Bean) pool.getObject("id2");
        System.out.println(bean);

        ComplexBean complexBean = (ComplexBean) pool.getObject("id3");
        System.out.println(complexBean);
    }
}

ComplexBean

public class ComplexBean {

    private String name;

    private Bean refBean;

    public String getName() {
        return name;
    }

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

    public Bean getRefBean() {
        return refBean;
    }

    public void setRefBean(Bean refBean) {
        this.refBean = refBean;
    }

    @Override
    public String toString() {
        return "ComplexBean{" +
                "name='" + name + '/'' +
                ", refBean=" + refBean +
                '}';
    }
}

Spring框架就是通过这种方式将成员变量值以及依赖对象等都放在配置文件中进行管理的,从而实现了较好地解耦(不过Spring是通过XML作为配置文件).

  2.3 创建并使用自定义的类加载器

JVM除跟类加载器之外的所有类加载器都是ClassLoader子类的实例,开发者可以通过拓展ClassLoader的子类,并重写该ClassLoader所包含的方法实现自定义的类加载器。ClassLoader有如下两个关键方法。

  • loadClass(String name,boolean
    resolve):该方法为ClassLoader的入口点,根据指定名称来加载类,系统就是调用ClassLoader的该方法来获取指定类的class对象
  • findClass(String name):根据指定名称来查找类

如果需要是实现自定义的ClassLoader,则可以通过重写以上两个方法来实现,通常推荐重写findClass()方法而不是loadClass()方法。

classLoader()方法的执行步骤:

  1. findLoadedClass():来检查是否加载类,如果加载直接返回。
  2. 父类加载器上调用loadClass()方法。如果父类加载器为null,则使用跟类加载器加载。
  3. 调用findClass(String)方法查找类

从上面看出,重写findClass()方法可以避免覆盖默认类加载器的父类委托,缓冲机制两种策略;如果重写loadClass()方法,则实现逻辑更为复杂。

ClassLoader的一些方法:

  • Class defineClass(String name,byte[] b,int off,int
    len):负责将字节码分析成运行时数据结构,并检验有效性
  • findSystemClass(String name):从本地文件系统装入文件。
  • static getSystemClassLoader():返回系统类加载器
  • getParent():获取该类加载器的父类加载器
  • resolveClass(Class<?> c):链接指定的类
  • findClassLoader(String
    name):如果加载器加载了名为name的类,则返回该类对用的Class实例,否则返回null。该方法是类加载缓存机制的体现。

下面程序开发了一个自定义的ClassLoader。该classLoader通过重写findClass()方法来实现自定义的类加载机制。这个ClassLoader可以在加载类之前先编译该类的源文件,从而实现运行Java之前先编译该程序的目标,这样即可通过该classLoader运行Java源文件。

 

package com.gdut.basic;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;

public class CompileClassLoader extends ClassLoader {
private byte[] getBytes(String fileName) {
    File file = new File(fileName);
    Long len = file.length();
    byte[] raw = new byte[(int)len];

        FileInputStream fin = new FileInputStream(file);
        //一次读取class文件的二进制数据
        int r = fin.read(raw);
        if(r != len) {
            throw new IOException("无法读取文件"+r+"!="+raw);


    return null;
        }
}
    private boolean compile(String javaFile) throws IOException {
        System.out.println("正在编译"+javaFile+"...");
        Process p = Runtime.getRuntime().exec("javac"+javaFile);
        try {
            //其他线程都等待这线程完成
            p.waitFor();
        }catch(InterruptedException ie) {
            System.out.println(ie);
        }
        int ret = p.exitValue();
        return ret == 0;
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class clazz = null;
        String findStub = name.replace(".", "/");
        String javaFileName = findStub+".java";
        String classFileName = findStub+".class";
        File javaFile = new File(javaFileName);
        File classFile = new File(classFileName);

        //但指定Java源文件存在,class文件不存在,或者Java源文件的修改时间比class文件修改的时间更晚时,重新编译
        if(javaFile.exists() && classFile.exists()
                || javaFile.lastModified() > classFile.lastModified()) {
            try {
            if(!compile(javaFileName)|| !classFile.exists()) {
                throw new ClassNotFoundException("ClassNotFoundExcetion"+javaFileName);
            }
            }catch(IOException ie) {
                ie.printStackTrace();
            }
        }
        if(classFile.exists()) {

                byte[] raw = getBytes(classFileName);

                clazz = defineClass(name,raw,0,raw.length);
        }
        //如果clazz为null,表明加载失败,则抛出异常
        if(clazz == null) {
            throw new ClassNotFoundException(name);
        }
        return clazz;
    }

    public static void main(String[] args) throws Exception {
        //如果运行该程序时没有参数,即没有目标类
        if (args.length<1) {
            System.out.println("缺少目标类,请按如下格式运行Java源文件:");
            System.out.println("java CompileClassLoader ClassName");
        }

        //第一个参数是需要运行的类
        String progClass = args[0];

        //剩下的参数将作为运行目标类时的参数,将这些参数复制到一个新数组中
        String[] progArgs = new String[args.length - 1];
        System.arraycopy(args, 1,progArgs,0, progArgs.length);

        CompileClassLoader ccl = new CompileClassLoader();
        //加载需要运行的类
        Class<?> clazz = ccl.loadClass(progClass);
        //获取运行时的类的主方法
        Method main = clazz.getMethod("main", (new String[0]).getClass());
        Object argsArray[] = {progArgs};
        main.invoke(null, argsArray);

    }
}

接下来可以提供任意一个简单的主类,该主类无需编译就可以使用上面的CompileClassLoader来运行他

package com.gdut.basic;

public class Hello {

    public static void main(String[] args) {
        for(String arg:args) {
            System.out.println("运行Hello的参数:"+arg);
        }

    }

}

无需编译该Hello.java,可以直接运行下面命令来运行该Hello.java程序

java CompileClassLoader hello 疯狂Java讲义

运行结果如下:

CompileClassLoader:正常编译 Hello.java...
运行hello的参数:疯狂Java讲义

 

使用自定义的类加载器,可以实现如下功能

  1. 执行代码前自动验证数字签名
  2. 根据用户提供的密码解密代码,从而可以实现代码混淆器来避免反编译*.class文件
  3. 根据应用需求把其他数据以字节码的形式加载到应用中。

    2.4 URLClassLoader类

该类时系统类加载器和拓展类加载器的父类(此处的父类,是指类与类之间的的继承关系)。URLClassLoader功能比较强大,它可以从本地文件系统获取二进制文件来加载类,也可以从远程主机获取二进制文件加载类。

该类提供两个构造器

  • URLClassLoader(URL[]
    urls):使用默认的父类加载器创建一个ClassLoader对象,该对象将从urls所指定的路径来查询并加载类
  • URLClassLoader(URL[] urls,ClassLoader
    prarent):使用指定的父类加载器创建一个ClassLoader对象,该对象将从urls所指定的路径来查询并加载类。

下面程序示范了如何从文件系统中加载MySQL驱动,并使用该驱动获取数据库连接。通过这种方式来获取数据库连接,无需将MySQL驱动添加到CLASSPATH中。

package java.gdut;

import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection;
import java.sql.Driver;
import java.util.Properties;

public class URLClassLoaderTest {
    private static Connection conn;

    public static Connection getConn(String url,String user,String pass)throws Exception{
        if(conn == null){
            URL[] urls = {new URL("file:mysql-connection-java-5.1.46-bin.jar")};
            URLClassLoader myClassLoader = new URLClassLoader(urls);
            //加载MySQL,并创建实例
            Driver driver = (Driver)myClassLoader.loadClass("com.mysql.jdbc.Driveer").newInstance();

            Properties properties = new Properties();
            properties.setProperty("user",user);
            properties.setProperty("pass",pass);
            //调用driver的connect方法来取得数据库连接
            conn = driver.connect(url,properties);
        }
        return conn;
    }

    public static void main(String[] args) throws Exception {
        System.out.println(getConn("jdbc:mysql://localhost:3306/tb_test","sherman","a123"));
    }
}

本程序类加载器的加载路径是当前路径下的mysql-connection-java-5.1.46-bin.jar文件,将MySQL驱动复制到该路径下,这样保证ClassLoader可以正常加载到驱动类

访问成员变量

通过Class对象的的getField()方法可以获取该类所包含的全部或指定的成员变量Field,Filed提供了如下两组方法来读取和设置成员变量值.

  • getXxx(Object obj): 获取obj对象的该成员变量的值,
    此处的Xxx对应8中基本类型,如果该成员变量的类型是引用类型,
    则取消get后面的Xxx;
  • setXxx(Object obj, Xxx val):
    将obj对象的该成员变量值设置成val值.此处的Xxx对应8种基本类型,
    如果该成员类型是引用类型, 则取消set后面的Xxx;

注: getDeclaredXxx方法可以获取所有的成员变量,无论private/public;

/**
 * @author jifang
 * @since 16/1/2下午1:00.
 */
public class Client {

    @Test
    public void client() throws NoSuchFieldException, IllegalAccessException {
        User user = new User();
        Field idFiled = User.class.getDeclaredField("id");
        setAccessible(idFiled);
        idFiled.setInt(user, 46);

        Field nameFiled = User.class.getDeclaredField("name");
        setAccessible(nameFiled);
        nameFiled.set(user, "feiqing");

        Field passwordField = User.class.getDeclaredField("password");
        setAccessible(passwordField);
        passwordField.set(user, "ICy5YqxZB1uWSwcVLSNLcA==");

        System.out.println(user);
    }

    private void setAccessible(AccessibleObject object) {
        object.setAccessible(true);
    }
}

三. 通过反射查看类信息

Java程序中的许多对象在运行时都会出现收到外部传入的一个对象,该对象编译时类型是Object,但程序又需要调用该对象运行时的方法。

  • 第一种做法是假设编译时和运行时都知道该对象的的类型的具体信息,这种情况下,可以先用instanceof()运算符进行判断,再利用强制类型转换将其转换成运行时类型的变量即可
  • 第二种做法是编译时根本无法知道该对象和类可能属于那些类,程序只依靠运行时信息来发现该对象和类的真实信息,这就必须使用反射

使用反射获取泛型信息

为了通过反射操作泛型以迎合实际开发的需要,
Java新增了java.lang.reflect.ParameterizedType java.lang.reflect.GenericArrayType``java.lang.reflect.TypeVariable java.lang.reflect.WildcardType几种类型来代表不能归一到Class类型但是又和原始类型同样重要的类型.

类型 含义
ParameterizedType 一种参数化类型, 比如Collection<String>
GenericArrayType 一种元素类型是参数化类型或者类型变量的数组类型
TypeVariable 各种类型变量的公共接口
WildcardType 一种通配符类型表达式, 如? ? extends Number ? super Integer

其中, 我们可以使用ParameterizedType来获取泛型信息.

public class Client {

    private Map<String, Object> objectMap;

    public void test(Map<String, User> map, String string) {
    }

    public Map<User, Bean> test() {
        return null;
    }

    /**
     * 测试属性类型
     *
     * @throws NoSuchFieldException
     */
    @Test
    public void testFieldType() throws NoSuchFieldException {
        Field field = Client.class.getDeclaredField("objectMap");
        Type gType = field.getGenericType();
        // 打印type与generic type的区别
        System.out.println(field.getType());
        System.out.println(gType);
        System.out.println("**************");
        if (gType instanceof ParameterizedType) {
            ParameterizedType pType = (ParameterizedType) gType;
            Type[] types = pType.getActualTypeArguments();
            for (Type type : types) {
                System.out.println(type.toString());
            }
        }
    }

    /**
     * 测试参数类型
     *
     * @throws NoSuchMethodException
     */
    @Test
    public void testParamType() throws NoSuchMethodException {
        Method testMethod = Client.class.getMethod("test", Map.class, String.class);
        Type[] parameterTypes = testMethod.getGenericParameterTypes();
        for (Type type : parameterTypes) {
            System.out.println("type -> " + type);
            if (type instanceof ParameterizedType) {
                Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments();
                for (Type actualType : actualTypes) {
                    System.out.println("/tactual type -> " + actualType);
                }
            }
        }
    }

    /**
     * 测试返回值类型
     *
     * @throws NoSuchMethodException
     */
    @Test
    public void testReturnType() throws NoSuchMethodException {
        Method testMethod = Client.class.getMethod("test");
        Type returnType = testMethod.getGenericReturnType();
        System.out.println("return type -> " + returnType);

        if (returnType instanceof ParameterizedType) {
            Type[] actualTypes = ((ParameterizedType) returnType).getActualTypeArguments();
            for (Type actualType : actualTypes) {
                System.out.println("/tactual type -> " + actualType);
            }
        }
    }
}

  3.1 获得class对象

每个类被加载后,系统会为该类生成一个对应的Class对象,通过该Class对象可以访问到JVM中的这个类。获得Class对象通常三种方式

  1. 使用Class类的forName(String
    clazz)静态方法。字符串参数传入全限定类名(必须添加包名),可能会抛出ClassNotFoundexception异常。
  2. 调用某个类的class属性来获取该类的的Class对象。
  3. 调用某个对象的getClass()方法,该方法是Object类的一个方法。

对于第一种方式,第二种的优势:

  • 代码更安全。程序在编译阶段就可以检查需要访问的Class对象是否存在。
  • 程序性能更好。这的种方式无需调用方法,所以性能更好。

使用反射获取注解

使用反射获取注解信息的相关介绍,
请参看我的博客Java注解实践

  3.2 从Class中获取信息

Class类提供了大量的实例方法获取该Class对象所对应类的详细信息

下面4个方法用于获取Class对象对应类的构造器

  • ConStructor<T> getConStructor(Class<?>
    parameterTypes):返回Class对象对应类的,带指定参数列表的public构造器
  • ConStructor<?>[]
    getConStructor():返回此Class对象对应类的所有public构造器
  • ConStructor<T> getDeclaredConStructor(Class<?>…
    parameterTypes):返回此Class对象对应类的、带指定参数列表的构造器,与构造器的访问权限无关
  • ConStructor<?>[]
    getDeclaredConStructor():返回此Class对象对应类的所有构造器,与构造器的访问权限无关

下面四个方法获取Class对象对应类所包含方法。

  • Method getMethod(String name,Class<?>
    parameterTypes):返回Class对象对应类的,带指定形参列表的public方法
  • Method[] getMethods():返回Class对象对应类的所有public方法
  • Method getDeclaredMethod(String name,Class<?>
    parameterTypes):返回Class对象对应类的,带指定形参列表的方法,与访问权限无关
  • Method[]
    getDeclaredMethods():返回Class对象对应类的所有全部方法,与方法的访问权限无关

下面四个方法获取Class对象对应类所包含的成员变量。

  • Field getField(String
    name):返回Class对象对应类的,指定名称的public成员变量
  • Field[] getFIelds():返回Class对象对应类的所有public成员变量
  • Field getDeclaredField(String
    name):返回Class对象对应类的,指定名称的成员变量,与成员的访问权限无关
  • Field[]
    getFIelds():返回Class对象对应类的所有成员变量,与成员的访问权限无关

如下几个方法用于访问Class对应类的上所包含的Annotation.

  • <A extends Annotation>A getAnnotation(Class<A>
    annotationClass):尝试获取该Class对象对应类存在的,指定类型的Annotation;如果该类型的注解不存在,则返回null。
  • <A extends Annotation>A getDeclaredAnnotation(Class<A>
    annotationClass):Java
    8新增方法,尝试获取直接修饰该Class对象对应类存在的,指定类型的Annotation;如果该类型的注解不存在,则返回null。
  • Annotation[]
    getAnnotations():获取该Class对象对应类存在的所有Annotation
  • Annotation[]
    getDiclaredAnnotations():获取直接修饰该Class对象对应类存在的所有Annotation
  • <A extends Annotation>A[] getAnnotationByType(Class<A>
    annotationClass):由于Java
    8的新增了重复注解功能,因此需要使用该方法获取修饰该Class对象对应类,指定类型的多个Annotation
  • <A extends Annotation>A[]
    getDeclaredAnnotationByType(Class<A> annotationClass):由于Java
    8的新增了重复注解功能,因此需要使用该方法获取直接修饰该类的,指定类型的多个Annotation

如下方法用于访问Class对应类的内部类

  • Class<?>[]
    getDeclaredClass():返回该Class对象对应类里包含的内部类

如下方法用于访问Class对应类的所在的外部类

  • Class<?>[]
    getDeclaringClass():返回该Class对象对应类所在的外部类

如下方法用于访问Class对应类的所实现的接口

  • Class<?>[]
    getInterfaces():返回该Class对象对应类的所实现的接口

如下方法用于访问Class对应类的所继承的父类

  • Class<? super T>
    getSuperClass():返回该Class对象对应类的超类的Class对象

如下方法用于访问Class对应类的修饰符,所在包,类名等基本信息

  • int
    getModifiers():返回此类或接口的所有修饰符对应的常量,返回的整数需要Modifier工具类的方法来解码,才可以获取真正的修饰符
  • Package getPackage():获取此类的包
  • String getName():以字符串的形式返回该Class对象对应类的类名
  • String getSimpleName():以字符串的形式返回该Class对象对应类的简称

以下几个方法来判断该类是否为接口、枚举、注解类型

  • boolean
    isAnnotation():返回此Class对象是否表示一个注解类型(有@interface定义)
  • boolean isAnnotationPresent(Class<? extends
    Annotation>annotationClass):判断此Class对象是否使用了注解修饰
  • boolean isAnonymousClass():返回此Class对象是否为匿名类
  • boolean isArray():返回此Class对象是否为数组类
  • boolean isEnum():返回此Class对象是否为枚举类
  • boolean isInterface():返回此Class对象是否为接口
  • boolean isInstance(Object
    obj):判断obj是否为该Class对象的实例,该方法可以替代instanceof操作符

以上getMethod()方法和getConStructor()方法中,都需要传入多个类型为Class<?>的参数,用于获取指定的方法和构造器。要确定一个方法应该由方法名和形参列表确定。例如下面代码获取clazz对应类的带一个String参数的info方法:

clazz.getMethods("info",String.class)

  若要获取clazz对应类的带一个String参数,一个Integer参数的info方法

clazz.getMethods("info",String.class,Integer.class)

反射性能测试

Method/Constructor/Field/Element都继承了AccessibleObject,AccessibleObject类中有一个setAccessible方法:

public void setAccessible(boolean flag) throws SecurityException {
    ...
}

该方法有两个作用:

1. 启用/禁用访问安全检查开关:值为true,则指示反射的对象在使用时取消Java语言访问检查;值为false,则指示应该实施Java语言的访问检查;

  1. 可以禁止安全检查, 提高反射的运行效率.

    /**

    • @author jifang
    • @since 15/12/31下午4:53.
      */
      public class TestReflect {

      @Before
      public void testNoneReflect() {

       User user = new User();
      
       long start = System.currentTimeMillis();
       for (long i = 0; i < Integer.MAX_VALUE; ++i) {
           user.getName();
       }
       long count = System.currentTimeMillis() - start;
       System.out.println("没有反射, 共消耗 <" + count + "> 毫秒");
      

      }

      @Test
      public void testNotAccess() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

       User user = new User();
       Method method = Class.forName("com.fq.domain.User").getMethod("getName");
      
       long start = System.currentTimeMillis();
       for (long i = 0; i < Integer.MAX_VALUE; ++i) {
           method.invoke(user, null);
       }
       long count = System.currentTimeMillis() - start;
       System.out.println("没有访问权限, 共消耗 <" + count + "> 毫秒");
      

      }

      @After
      public void testUseAccess() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

       User user = new User();
       Method method = Class.forName("com.fq.domain.User").getMethod("getName");
       method.setAccessible(true);
      
       long start = System.currentTimeMillis();
       for (long i = 0; i < Integer.MAX_VALUE; ++i) {
           method.invoke(user, null);
       }
       long count = System.currentTimeMillis() - start;
       System.out.println("有访问权限, 共消耗 <" + count + "> 毫秒");
      

      }
      }

执行上面程序,在我的机器上会有如下结果:

澳门新葡萄京官网注册 1

机器配置信息如下:

澳门新葡萄京官网注册 2

可以看到使用反射会比直接调用慢3000毫秒,但是前提是该方法会执行20E+次(而且服务器的性能也肯定比我的机器要高),因此在我们的实际开发中,其实是不用担心反射机制带来的性能消耗的,而且禁用访问权限检查,也会有性能的提升。

  3.3 Java 8新增加的方法参数反射

Java
8新增了一个Executable抽象基类,该对象代表可执行的类成员,该类派生了Constructor和Method两个子类。

Executable抽象基类提供了大量方法来获取修饰该方法或构造器的注解信息;还提供了is
VarArgs()方法用于判断该方法或构造器是否包含数量可变的形参,以及通过getModifiers()方法获取该方法或构造器的修饰符。除此之外,还提供如下两个方法

  • int getParameterCount():获取该构造器或方法的形参个数
  • Parameter[] getParameters():获取该构造器或方法的所有形参

Parameter类是Java
8新增的api,提供了大量方法来获取声明该方法或参数个数的泛型信息,还提供了如下方法获取参数信息

  • getModifiers():获取修饰该形参的修饰符
  • String getName():获取形参名
  • Type getParameterizedType():获取带泛型的形参类型
  • Class<?> getType():获取形参类型
  • boolean
    isNamePresent():该方法返回该类的class文件中是否包含了方法的形参名信息
  • boolean isVarArgs():判断该参数是否为个数可变的形参

需要指出的是,使用javac命令编译Java源文件时,默认生成的class文件并不包含方法的形参名信息,因此调用isNamePresent()将返回false,调用getName()也不能得到该参数的形参名。需要编译时保留形参信息,则需要该命令指定-parameter选项。

下面示范了Java 8的参数反射功能

public class MethodParameterTest {
    public static void main(String[] args) throws Exception {
        Class<Test> clazz = Test.class;
        Method replace = clazz.getMethod("replace",String.class,List.class);
        System.out.println("replace方法的参数个数为:"+replace.getParameterCount());

        Parameter[] parameters = replace.getParameters();
        int index = 1;
        for(Parameter parameter:parameters){
            if(!parameter.isNamePresent()){
                System.out.println("-----第"+index+"行的参数信息-----");
                System.out.println("参数名:"+parameter.getName());
                System.out.println("形参类型:"+parameter.getType());
                System.out.println("泛型类型:"+parameter.getParameterizedType());
            }
        }
    }
}

澳门新葡萄京官网注册 3

  3.4 利用反射生成并操作对象

Class对象可以获得该类的方法,构造器,成员变量。程序可以通过Method对象来执行对应的方法,通过ConStructor对象调用对应的构造器创建实例,能通过Field对象直接访问并修改对象的成员变量值。

3.4.1 创建对象

通过反射生成对象有两种方式。

  • 使用Class对象的newInstance()方法来创建该Class对象对应类的实例,这种方式要求该Class对象的对应类有默认构造器。
  • 先使用Class对象获取指定的Constructor对象,在调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例。

3.4.2 调用方法

可以通过Class对象的getMethods()方法和getMethod()方法来获取全部方法和指定方法。

每个Method对象对应一个方法,可以通过它调用对应的方法,在Method里包含一个invoke()方法,该方法的签名如下。

  • Object invoke(Object obj,Object…
    args):该方法中的obj是执行该方法的主调,后面的args是执行该方法时传入该方法的实参。

下面程序是对象池工厂加强版,它允许在配置文件中增加配置对象的成员变量的值,对象池工厂会读取为该对象配置的成员变量值,并利用该对象的Setter方法设置成员变量的值。

package com.gdut.test0516;

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class ExtendedObjectPoolFactory {
    //定义一个对象池,前面是对象名,后面是实际对象
    private Map<String,Object> objectPool = new HashMap<>();
    private Properties config = new Properties();

    public void init(String fileName)
    {
        try(FileInputStream fis = new FileInputStream(fileName))
        {
            config.load(fis);
        }catch(IOException ex){
            System.out.println("读取"+fileName+"异常");
        }
    }

   private Object createObject(String clazzName)throws ClassNotFoundException,
           InstantiationException,IllegalAccessException{
        Class<?> clazz = Class.forName(clazzName);
        //使用clazz默认构造器创建实例
        return clazz.newInstance();
   }


   public void initPool()throws ClassNotFoundException,
           InstantiationException,IllegalAccessException{
       for (String name:config.stringPropertyNames())
       {
           //没取出一个key-value对。如果key中不包含百分号(%),即可认为该key用于
           // 控制调用对象的setter方法设置值,%前半为对象名字,后半控制setter方法名
       if( !name.contains("%")){
           objectPool.put(name,createObject(config.getProperty(name)));
       }
       }
   }
   public Object getObject(String name){
        return objectPool.get(name);
   }

   public void initProperty()throws NoSuchMethodException,
   IllegalAccessException,InvocationTargetException {
       for (String name:config.stringPropertyNames()) {
           if(name.contains("%")){
               String[] objAndProp = name.split("%");
               Object target = getObject(objAndProp[0]);
               String mtdName = "set"+objAndProp[1].substring(1);
               Class<?> targetClass = target.getClass();
               Method mtd = targetClass.getMethod(mtdName);
               mtd.invoke(target,config.getProperty(name));
           }
       }
   }

    public static void main(String[] args)throws Exception {
        ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory();
        epf.init("com/gdut/test0516/extObj.txt");
        epf.initPool();
        epf.initProperty();
        System.out.println(epf.getObject("a"));
    }
}

  3.4.3 访问成员变量

通过Class对象的getFields()方法和getField()方法可以获取该类包含的所有成员变量和指定成员变量。Field提供如下方法读取或设置成员变量值

  • getXxx(Object
    obj):获取Object对象的成员变量值。此处的Xxx对应8种基本类型,如果该成员变量类型时引用类型,则取消get后面的Xxx。
  • setXxx(Object obj,Xxx
    val):将obj对象的该成员变量设置成val值。此处的Xxx对应8种基本类型,如果该成员变量类型时引用类型,则取消set后面的Xxx。

3.4.4 操作数组

在java.lang.reflect包下还提供了一个Array类,Array对象可以代表所有的数组。程序可以通过使用该类来创建数组,操作数组元素等。

Array提供如下方法

  • static Object newInstance(Class<?>ComponentType,int…
    length):创建一个具有指定的元素类型,指定维度的新数组
  • static xxx getXxx(Object array,int
    index):返回数组array的第index个元素。此处的xxx对应8种基本类型,如果数组元素是引用类型,则该方法变为get(Object
    array,int index)。
  • static void setXxx(Object array,int index,Object
    val):将数组array的第index个元素设置为val。此处的xxx对应8种基本类型,如果数组元素是引用类型,则该方法变为set(Object
    array,int index,Object val)。

 

发表评论

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