20.Java Class类和Java反射

目录


Java专栏目录(点击进入…)



Class类和Java反射

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

通过Java反射机制,可以在程序中访问已经装载到JVM中的Java对象的描述,实现访问、检测和修改描述Java对象本身的信息的功能

Java 反射机制主要提供了以下功能:
1、在运行时判断任意一个对象所属的类;
2、在运行时构造任意一个类的对象;
3、在运行时判断任意一个类所具有的成员变量和方法;
5、在运行时调用任意一个对象的方法;
6、生成动态代理。


Java Reflection API 简介

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

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

如例所示 DumpMethods 类 演示了 Reflection API 的基本作用,它读取命令行参数指定的类名,然后打印这个类所具有的方法信息:
DumpMethods.java

import java.lang.reflect.*;

public class DumpMethods {
    public static void main(String args[]) throws Exception {
        //加载并初始化命令行参数指定的类 
        Class classType = Class.forName(args[0]);
        //获得类的所有方法
        Method methods[] = classType.getDeclaredMethods();
        for (int i = 0; i < methods.length; i++) {
            System.out.println(methods[i].toString());
        }
    }
}

运行命令“java DumpMethods java.util.Stack”,就会显示 java.util.Stack 类所具有的
方法,程序的打印结果如下:

public synchronized java.lang.Object java.util.Stack.pop() 
public java.lang.Object java.util.Stack.push(java.lang.Object) 
public boolean java.util.Stack.empty() 
public synchronized java.lang.Object java.util.Stack.peek() 
public synchronized int java.util.Stack.search(java.lang.Object)

如例所示 ReflectTester 类进一步演示了 Reflection API 的基本使用方法。
ReflectTester 类有一个 copy(Object object)方法,这个方法能够创建一个和参数 object 同样类型的对象,然后把 object 对象中的所有属性复制到新建的对象中,并将它返回。这个例子只能复制简单的 JavaBean,假定 JavaBean的每个属性都有 public 类型的getXXX()和 setXXX()方法。

import java.lang.reflect.*;

public class ReflectTester {
    public Object copy(Object object) throws Exception {
        //获得对象的类型
        Class classType = object.getClass();
        System.out.println("Class:" + classType.getName());
        //通过默认构造方法创建一个新的对象
        Object objectCopy = classType.getConstructor(new Class[]{}).
                newInstance(new Object[]{});
        //获得对象的所有属性
        Field fields[] = classType.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            String fieldName = field.getName();
            String firstLetter = fieldName.substring(0, 1).toUpperCase();
            //获得和属性对应的 getXXX()方法的名字
            String getMethodName = "get" + firstLetter + fieldName.substring(1);
            //获得和属性对应的 setXXX()方法的名字
            String setMethodName = "set" + firstLetter + fieldName.substring(1);
            //获得和属性对应的 getXXX()方法
            Method getMethod = classType.getMethod(getMethodName, new Class[]{});
            //获得和属性对应的 setXXX()方法
            Method setMethod = classType.getMethod(setMethodName, new Class[]{field.getType()});
            //调用原对象的 getXXX()方法
            Object value = getMethod.invoke(object, new Object[]{});
            System.out.println(fieldName + ":" + value);
            //调用复制对象的 setXXX()方法
            etMethod.invoke(objectCopy, new Object[]{value});
        }
        return objectCopy;
    }

    public static void main(String[] args) throws Exception {
        Customer customer = new Customer("Tom", 21);
        customer.setId(new Long(1));
        Customer customerCopy = (Customer) new ReflectTester().copy(customer);
        System.out.println("Copy information:" + customerCopy.getName() + "" +
                customerCopy.getAge());
    }
}

//Customer 类是一个 JavaBean 
class Customer {
    private Long id;
    private String name;
    private int age;

    public Customer() {
    }

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

    public Long getId() {
        return id;
    }

    public void setId(Long 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;
    }
}

ReflectTester 类的 copy(Object object)方法依次执行以下步骤。
(1)获得对象的类型:

Class classType=object.getClass(); 
System.out.println("Class:"+classType.getName());

在 java.lang.Object 类中定义了 getClass()方法,因此对于任意一个 Java 对象,都可以通过此方法获得对象的类型。Class 类是 Reflection API 中的核心类,它有以下方法。

方法描述
getName()获得类的完整名字。
getFields()获得类的 public 类型的属性。
getDeclaredFields()获得类的所有属性。
getMethods()获得类的 public 类型的方法。
getDeclaredMethods()获得类的所有方法。
getMethod(String name, Class[] parameterTypes)获得类的特定方法,name 参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
getConstrutors()获得类的 public 类型的构造方法。
getConstrutor(Class[] parameterTypes)获得类的特定构造方法,parameterTypes参数指定构造方法的参数类型。
newInstance()通过类的不带参数的构造方法创建这个类的一个对象。

(2)通过默认构造方法创建一个新的对象:

Object objectCopy=classType.getConstructor(new Class[]{}).newInstance(new Object[]{});

以上代码先调用 Class 类的 getConstructor()方法获得一个 Constructor 对象,它代
表默认的构造方法,然后调用 Constructor 对象的 newInstance()方法构造一个实例。

(3)获得对象的所有属性:

Field fields[]=classType.getDeclaredFields(); 

Class 类的 getDeclaredFields()方法返回类的所有属性,包括 public、protected、默认和 private 访问级别的属性。

(4)获得每个属性相应的 getXXX()和 setXXX()方法,然后执行这些方法,把原来对象的属性复制到新的对象中:

for (int i = 0; i < fields.length; i++) {
    Fieldfield = fields[i];
    String fieldName = field.getName();
    String firstLetter = fieldName.substring(0, 1).toUpperCase();
    //获得和属性对应的 getXXX()方法的名字
    String getMethodName = "get" + firstLetter + fieldName.substring(1);
    //获得和属性对应的 setXXX()方法的名字
    String setMethodName = "set" + firstLetter + fieldName.substring(1);
    //获得和属性对应的 getXXX()方法
    Method getMethod = classType.getMethod(getMethodName, new Class[]{});
    //获得和属性对应的 setXXX()方法
    Method setMethod = classType.getMethod(setMethodName, new Class[]{field.getType()});
    //调用原对象的 getXXX()方法
    Object value = getMethod.invoke(object, new Object[]{});
    System.out.println(fieldName + ":" + value);
    //调用复制对象的 setXXX()方法
    setMethod.invoke(objectCopy, new Object[]{value});
}

以上代码假定每个属性都有相应的 getXXX()和 setXXX()方法,并且在方法名中,“get”和“set”的后面一个字母为大写。例如,Customer 类的 name 属性对应 getName() 和 setName()方法。Method 类的 invoke(Object obj,Object args[])方法用于动态执行一个对象的特定方法,它的第一个 obj 参数指定具有该方法的对象,第二个 args 参数指定向该方法传递的参数。


如例所示的 InvokeTester 类的 main()方法中,运用反射机制调用一个InvokeTester 对象的 add()和 echo()方法。

InvokeTester.java

import java.lang.reflect.*;

public class InvokeTester {

    public int add(int param1, int param2) {
        return param1 + param2;
    }

    public String echo(String msg) {
        return "echo:" + msg;
    }

    public static void main(String[] args) throws Exception {
        Class classType = InvokeTester.class;
        Object invokeTester = classType.newInstance();

        //调用 InvokeTester 对象的 add()方法
        Method addMethod = classType.getMethod("add", new Class[]{int.class, int.class});
        Object result = addMethod.invoke(invokeTester,
                new Object[]{new Integer(100), new Integer(200)});
        System.out.println((Integer) result);
        //调用 InvokeTester 对象的 echo()方法
        Method echoMethod = classType.getMethod("echo", new Class[]{String.class});
        result = echoMethod.invoke(invokeTester, new Object[]{"Hello"});
        System.out.println((String) result);
    }
}

add()方法的两个参数为 int 类型,获得表示 add()方法的 Method 对象的代码如下:

Method addMethod = classType.getMethod("add",new Class[]{int.class,int.class});

Method 类的 invoke(Object obj,Object args[])方法接收的参数必须为对象,如果参数为基本类型数据,必须转换为相应的包装类型的对象。invoke()方法的返回值总是对象,如果实际被调用的方法的返回类型是基本类型数据,那么 invoke()方法会把它转换为相应的包装类型的对象,再将其返回。

在本例中,尽管 InvokeTester 类的 add()方法的两个参数及返回值都是 int 类型,调用 addMethod 对象的 invoke()方法时,只能传递 Integer 类型的参数,并且 invoke()方法的返回类型也是 Integer 类型,Integer 类是 int 基本类型的包装类:

Object result=addMethod.invoke(invokeTester, new Object[]{new Integer(100),new Integer(200)}); 
System.out.println((Integer)result); //result 为 Integer 类型

java.lang.Array 类提供了动态创建和访问数组元素的各种静态方法。
如例所示的 ArrayTester1 类的 main()方法创建了一个长度为 10 的字符串数组,接着把索引位置为 5 的元素设为“hello”,然后再读取索引位置为 5 的元素的值。

ArrayTester1.java

import java.lang.reflect.*;

public class ArrayTester1 {
    public static void main(String args[]) throws Exception {
        Class classType = Class.forName("java.lang.String");
        //创建一个长度为 10 的字符串数组
        Object array = Array.newInstance(classType, 10);
        //把索引位置为 5 的元素设为"hello" 
        Array.set(array, 5, "hello");
        //读取索引位置为 5 的元素的值
        String s = (String) Array.get(array, 5);
        System.out.println(s);
    }
}

如例所示的 ArrayTester2 类的 main()方法创建了一个 5×10×15 的整型数组,并把索引位置为[3][5][10]的元素的值为设 37。

ArrayTester2.java

import java.lang.reflect.*;

public class ArrayTester2 {
    public static void main(String args[]) {
        int dims[] = new int[]{5, 10, 15};
        Object array = Array.newInstance(Integer.TYPE, dims);
        //使 arrayObj 引用 array[3] 
        Object arrayObj = Array.get(array, 3);
        Class cls = arrayObj.getClass().getComponentType();
        System.out.println(cls);
        //使 arrayObj 引用 array[3][5] 
        arrayObj = Array.get(arrayObj, 5);
        //把元素 array[3][5][10]设为 37 
        Array.setInt(arrayObj, 10, 37);
        int arrayCast[][][] = (int[][][]) array;
        System.out.println(arrayCast[3][5][10]);
    }
}

在远程方法调用中运用反射机制

假定在 SimpleServer 服务器端创建了一个 HelloServiceImpl 对象,它具有 getTime() 和 echo()方法。HelloServiceImpl 类实现了 HelloService 接口。如例所示分别是 HelloService 接口和 HelloServiceImpl 类的源程序。

HelloService.java

package remotecall;

import java.util.Date;

public interface HelloService {
    public String echo(String msg);

    public Date getTime();
}

HelloServiceImpl.java

package remotecall;

import java.util.Date;

public class HelloServiceImpl implements HelloService {
    public String echo(String msg) {
        return "echo:" + msg;
    }

    public Date getTime() {
        return new Date();
    }
}

SimpleClient客户端如何调用服务器端的HelloServiceImpl对象的getTime()和echo()方法呢?显然,SimpleClient 客户端需要把调用的方法名、方法参数类型、方法参数值,以及方法所属的类名或接口名发送给 SimpleServer,SimpleServer 再调用相关对象的方法,然后把方法的返回值发送给 SimpleClient。为了便于按照面向对象的方式来处理客户端与服务器端的通信,可以把它们发送的信息用 Call 类来表示。一个 Call 对象表示客户端发起的一个远程调用,它包括调用的类名或接口名、方法名、方法参数类型、方法参数值和方法执行结果。

Call.java

package remotecall;

import java.io.*;

public class Call implements Serializable {
    private String className; //表示类名或接口名
    private String methodName; //表示方法名
    private Class[] paramTypes; //表示方法参数类型
    private Object[] params; //表示方法参数值
    //表示方法的执行结果
    //如果方法正常执行,则 result 为方法返回值,如果方法抛出异常,那么 result 为该异常。 
    private Object result;

    public Call() {
    }

    public Call(String className, String methodName, Class[] paramTypes, Object[] params) {
        this.className = className;
        this.methodName = methodName;
        this.paramTypes = paramTypes;
        this.params = params;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Class[] getParamTypes() {
        return paramTypes;
    }

    public void setParamTypes(Class[] paramTypes) {
        this.paramTypes = paramTypes;
    }

    public Object[] getParams() {
        return params;
    }

    public void setParams(Object[] params) {
        this.params = params;
    }

    public Object getResult() {
        return result;
    }

    public void setResult(Object result) {
        this.result = result;
    }

    public String toString() {
        return "className=" + className + " methodName=" + methodName;
    }
}

SimpleClient 调用 SimpleServer 端的 HelloServiceImpl 对象的 echo()方法的流程如下。
(1)SimpleClient 创建一个 Call 对象,它包含了调用 HelloService 接口的 echo()方法的信息。
(2)SimpleClient 通过对象输出流把 Call 对象发送给 SimpleServer。
(3)SimpleServer 通 过 对 象输入流读取 Call 对 象,运用 反射机制 调用HelloServiceImpl 对象的 echo()方法,把 echo()方法的执行结果保存到 Call 对象中。
(4)SimpleServer 通过对象输出流把包含了方法执行结果的 Call 对象发送给SimpleClient。
(5)SimpleClient 通过对象输入流读取 Call 对象,从中获得方法执行结果。

SimpleServer 和 SimpleClient 的源程序

package remotecall;

import java.io.*;
import java.net.*;
import java.util.*;
import java.lang.reflect.*;

public class SimpleServer {
    private Map remoteObjects = new HashMap(); //存放远程对象的缓存

    /**
     * 把一个远程对象放到缓存中
     */
    public void register(String className, Object remoteObject) {
        remoteObjects.put(className, remoteObject);
    }

    public void service() throws Exception {
        ServerSocket serverSocket = new ServerSocket(8000);
        System.out.println("服务器启动.");
        while (true) {
            Socket socket = serverSocket.accept();
            InputStream in = socket.getInputStream();
            ObjectInputStream ois = new ObjectInputStream(in);
            OutputStream out = socket.getOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(out);
            Call call = (Call) ois.readObject(); //接收客户发送的 Call 对象
            System.out.println(call);
            call = invoke(call); //调用相关对象的方法
            oos.writeObject(call); //向客户发送包含了执行结果的 Call 对象

            ois.close();
            oos.close();
            socket.close();
        }
    }

    public Call invoke(Call call) {
        Object result = null;
        try {
            String className = call.getClassName();
            String methodName = call.getMethodName();
            Object[] params = call.getParams();
            Class classType = Class.forName(className);
            Class[] paramTypes = call.getParamTypes();
            Method method = classType.getMethod(methodName, paramTypes);
            Object remoteObject = remoteObjects.get(className); //从缓存中取出相关的远程对象
            if (remoteObject == null) {
                throw new Exception(className + "的远程对象不存在");
            } else {
                result = method.invoke(remoteObject, params);
            }
        } catch (Exception e) {
            result = e;
        }
        call.setResult(result); //设置方法执行结果
        return call;
    }

    public static void main(String args[]) throws Exception {
        SimpleServer server = new SimpleServer();
        //把事先创建的 HelloServiceImpl 对象加入到服务器的缓存中
        server.register("remotecall.HelloService", new HelloServiceImpl());
        server.service();
    }
}

SimpleClient.java

package remotecall;

import java.io.*;
import java.net.*;
import java.util.*;

public class SimpleClient {
    public void invoke() throws Exception {
        Socket socket = new Socket("localhost", 8000);
        OutputStream out = socket.getOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(out);
        InputStream in = socket.getInputStream();
        ObjectInputStream ois = new ObjectInputStream(in);

        //Call call = new Call("remotecall.HelloService","getTime", 
        new Class[]{}, new Object[]{});
        Call call = new Call("remotecall.HelloService", "echo",
                new Class[]{String.class}, new Object[]{"Hello"});
        oos.writeObject(call); //向服务器发送 Call 对象
        call = (Call) ois.readObject(); //接收包含了方法执行结果的 Call 对象
        System.out.println(call.getResult());
        ois.close();
        oos.close();
        socket.close();
    }

    public static void main(String args[]) throws Exception {
        new SimpleClient().invoke();
    }
}

先运行命 令“ java remotecall.SimpleServer ”, 再运 行 命令 “ java remotecall.SimpleClient”,SimpleClient 端将打印“echo:Hello”。该打印结果是服务器端执行HelloServiceImpl 对象的 echo()方法的返回值。


代理模式

代理模式是常用的 Java 设计模式,它的特征是代理类与委托类有同样的接口。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。

代理类可分为两种:
(1)静态代理类:
由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class 文件就已经存在了

(2)动态代理类:
在程序运行时,运用反射机制动态创建而成。

静态代理类

下面HelloServiceProxy类是代理类,HelloServiceImpl 类是委托类,这两个类都实现了HelloService接口。其中HelloServiceImpl类是HelloService接口的真正实现者,而HelloServiceProxy 类是通过调用HelloServiceImpl类的相关方法来提供特定服务的。HelloServiceProxy类的echo()方法和getTime()方法会分别调用被代理的HelloServiceImpl对象的echo()方法和getTime()方法,并且在方法调用前后都会执行一些简单的打印操作。由此可见,代理类可以为委托类预处理消息、把消息转发给委托类和事后处理消息等。

HelloService.java

package proxy; 

import java.util.Date; 

public interface HelloService{ 
	public String echo(String msg); 
	public Date getTime(); 
}

HelloServiceImpl.java

package proxy; 

import java.util.Date; 

public class HelloServiceImpl implements HelloService{ 
	public String echo(String msg){ 
		return "echo:"+msg; 
	} 
	public Date getTime(){ 
		return new Date(); 
	} 
}

HelloServiceProxy.java

package proxy; 

import java.util.Date; 

public class HelloServiceProxy implements HelloService { 
	private HelloService helloService; //表示被代理的 HelloService 实例
		public HelloServiceProxy(HelloService helloService){ 
		this.helloService=helloService; 
	} 
	public void setHelloServiceProxy(HelloService helloService) { 
		this.helloService=helloService; 
	} 
	
	public String echo(String msg) { 
		System.out.println("before calling echo()"); //预处理
		String result=helloService.echo(msg); //调用被代理的 HelloService 实例的 echo()方法
		System.out.println("after calling echo()"); //事后处理
		return result; 
	} 
	
	public Date getTime(){ 
		System.out.println("before calling getTime()"); //预处理
		Date date=helloService.getTime(); //调用被代理的 HelloService 实例的 getTime()方法
		System.out.println("after calling getTime()"); //事后处理
		return date; 
	} 
	
}

在 Client1 类的 main()方法中,先创建了一个 HelloServiceImpl对象,又创建了一个 HelloServiceProxy 对象,最后调用 HelloServiceProxy 对象的 echo() 方法。

Client1.java

package proxy; 

public class Client1 { 
	public static void main(String args[]){ 
		HelloService helloService=new HelloServiceImpl(); 
		HelloService helloServiceProxy=new HelloServiceProxy(helloService); 
		System.out.println(helloServiceProxy.echo("hello")); 
	} 
}

运行 Client1 类,打印结果如下:

before calling echo() 
after calling echo() 
echo:hello

HelloServiceProxy类的源代码是由程序员编写的,在程序运行前,它的.class文件就已经存在了,这种代理类称为静态代理类。


动态代理类

与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由 Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为 Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的 Proxy 类和 InvocationHandler 接口提供了生成动态代理类的能力。

Proxy 类提供了创建动态代理类及其实例的静态方法。

(1)getProxyClass()静态方法负责创建动态代理类,它的完整定义如下:

public static Class<?> getProxyClass(ClassLoader loader, Class<?>[] interfaces) throws IllegalArgumentException

参数 loader 指定动态代理类的类加载器,参数 interfaces 指定动态代理类需要实现的所有接口。

(2)newProxyInstance()静态方法负责创建动态代理类的实例,它的完整定义如下:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) throws IllegalArgumentException

参数 loader 指定动态代理类的类加载器,参数 interfaces 指定动态代理类需要实现的所有接口,参数 handler 指定与动态代理类关联的 InvocationHandler 对象。

以下两种方式都创建了实现 Foo 接口的动态代理类的实例:

/**** 方式一 ****/ 
//创建 InvocationHandler 对象
InvocationHandler handler = new MyInvocationHandler(...); 
//创建动态代理类
Class proxyClass = Proxy.getProxyClass( 
 Foo.class.getClassLoader(), new Class[] { Foo.class }); 
//创建动态代理类的实例
Foo foo = (Foo) proxyClass. 
	getConstructor(new Class[] { InvocationHandler.class }). 
	newInstance(new Object[] { handler }); 
 
 /**** 方式二 ****/ 
//创建 InvocationHandler 对象
InvocationHandler handler = new MyInvocationHandler(...); 
//直接创建动态代理类的实例
Foo foo = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), 
	new Class[] { Foo.class }, 
	handler);

由 Proxy 类的静态方法创建的动态代理类具有以下特点:
(1)动态代理类是 public、final 和非抽象类型的;
(2)动态代理类继承了 java.lang.reflect.Proxy 类;
(3)动态代理类的名字以“$Proxy”开头;
(4)动态代理类实现 getProxyClass()和 newProxyInstance()方法中参数 interfaces 指定的所有接口;
(5)Proxy 类的 isProxyClass(Class<?> cl)静态方法可用来判断参数指定的类是否为动态代理类。只有通过 Proxy 类创建的类才是动态代理类;
(6)动态代理类都具有一个 public 类型的构造方法,该构造方法有一个InvocationHandler 类型的参数。

由 Proxy 类的静态方法创建的动态代理类的实例具有以下特点:
(1)假定变量 foo 是一个动态代理类的实例,并且这个动态代理类实现了 Foo 接口,那么“foo instanceof Foo”的值为 true。把变量 foo 强制转换为 Foo 类型是合法的:

(Foo) foo //合法

(2)每个动态代理类实例都和一个 InvocationHandler 实例关联。Proxy 类的getInvocationHandler(Object proxy)静态方法返回与参数proxy指定的代理类实例所关联的 InvocationHandler 对象。

(3)假定 Foo 接口有一个 amethod()方法,那么当程序调用动态代理类实例 foo 的amethod()方法时,该方法会调用与它关联的 InvocationHandler 对象的 invoke()方法。

InvocationHandler 接口为方法调用接口,它声明了负责调用任意一个方法的invoke()方法:

Object invoke(Object proxy,Method method,Object[] args) throws Throwable

参数 proxy 指定动态代理类实例,参数 method 指定被调用的方法,参数 args 指定向被调用方法传递的参数,invoke()方法的返回值表示被调用方法的返回值。

HelloServiceProxyFactory 类的 getHelloServiceProxy()静态方法负责创建实现了 HelloService 接口的动态代理类的实例。

HelloServiceProxyFactory.java

package proxy; 
import java.lang.reflect.*; 

public class HelloServiceProxyFactory { 
	/** 创建一个实现了 HelloService 接口的动态代理类的实例 
	* 参数 helloService 引用被代理的 HelloService 实例
	*/ 
	public static HelloService getHelloServiceProxy(final HelloService helloService) { 
		//创建一个实现了 InvocationHandler 接口的匿名类的实例 
		InvocationHandler handler=new InvocationHandler(){ 
			public Object invoke(Object proxy,Method method,Object args[])throws Exception{ 
				System.out.println("before calling "+method); //预处理
				Object result=method.invoke(helloService,args); 
				//调用被代理的 HelloService 实例的方法
				System.out.println("after calling "+method); //事后处理
				return result; 
			} 
		}; 
		Class classType=HelloService.class; 
		return (HelloService)Proxy.newProxyInstance(classType.getClassLoader(), new Class[]{classType}, handler); 
	} //# getHelloServiceProxy()
}

所示的 Client2 类先创建了一个 HelloServiceImpl 实例,然后创建了一个动态代理类实例 helloServiceProxy,最后调用动态代理类实例的 echo()方法。

Client2.java

package proxy; 

public class Client2{ 
	public static void main(String args[]){ 
		HelloService helloService=new HelloServiceImpl(); 
		HelloService helloServiceProxy= 
		HelloServiceProxyFactory.getHelloServiceProxy(helloService); 
		System.out.println("动态代理类的名字为" 
		+helloServiceProxy.getClass().getName()); 
		System.out.println(helloServiceProxy.echo("Hello")); 
	} 
}

运行 Client2,打印结果如下:

动态代理类的名字为$Proxy0 
before calling public abstract java.lang. 
String proxy.HelloService.echo(java.lang.String) 
after calling public abstract java.lang. 
String proxy.HelloService.echo(java.lang.String) 
echo:Hello

在远程方法调用中运用代理类
SimpleClient 客户端通过 HelloService 代理类来调用 SimpleServer服务器端的 HelloServiceImpl 对象的方法。客户端的 HelloService 代理类也实现了HelloService 接口,这可以简化 SimpleClient 客户端的编程。对于 SimpleClient 客户端而言,与远程服务器的通信的细节被封装到 HelloService 代理类中。SimpleClient 客户端可以按照以下方式调用远程服务器上的 HelloServiceImpl 对象的方法:

//创建 HelloService 代理类的对象
HelloService helloService1 = new HelloServiceProxy(connector);
//通过代理类调用远程服务器上的 HelloServiceImpl 对象的方法
System.out.println(helloService1.echo("hello"));

从以上程序代码可以看出,SimpleClient 客户端调用远程对象的方法的代码与调用本地对象的方法的代码很相似,由此可以看出,代理类简化了客户端的编程。

Connector 类负责建立与远程服务器的连接,以及接收和发送 Socket 对象。如下所示是 Connector 类的源程序。
Connector.java

package proxy1; 
import java.io.*; 
import java.net.*; 
import java.util.*; 

public class Connector { 
	private String host; 
	private int port; 
	private Socket skt; 
	private InputStream is; 
	private ObjectInputStream ois; 
	private OutputStream os; 
	private ObjectOutputStream oos; 
	
	public Connector(String host,int port)throws Exception{ 
		this.host=host; 
		this.port=port; 
		connect(host,port); 
	} 
	public void send(Object obj)throws Exception{ //发送对象
		oos.writeObject(obj); 
	} 
	public Object receive() throws Exception{ //接收对象
		return ois.readObject(); 
	} 
	public void connect()throws Exception{ //建立与远程服务器的连接
		connect(host,port); 
	} 
	public void connect(String host,int port)throws Exception{ //建立与远程服务器的连接
		skt=new Socket(host,port); 
		os=skt.getOutputStream(); 
		oos=new ObjectOutputStream(os); 
		is=skt.getInputStream(); 
		ois=new ObjectInputStream(is); 
	} 
	
	public void close(){ //关闭连接
		try{ 
			ois.close(); 
			oos.close(); 
			skt.close(); 
		}catch(Exception e){ 
			System.out.println("Connector.close: "+e); 
		} 
	} 
}

HelloService 代理类有两种创建方式:一种方式是创建一个 HelloServiceProxy 静态代理类,如下所示;还有一种方式是创建 HelloService 的动态代理类,如下所示 ProxyFactory 类的静态 getProxy()方法就负责创建 HelloService 的动态代理类,并且返回它的一个实例。

HelloServiceProxy.java(静态代理类)

package proxy1; 
import java.util.Date; 

public class HelloServiceProxy implements HelloService { 
	private String host; 
	private int port; 
	
	public HelloServiceProxy(String host,int port){ 
		this.host=host; 
		this.port=port; 
	} 
	
	public String echo(String msg) throws RemoteException { 
		Connector connector=null; 
		try { 
			connector=new Connector(host,port); 
			Call call=new Call("proxy1.HelloService","echo", 
			new Class[]{String.class},new Object[]{msg}); 
			connector.send(call); 
			call=(Call)connector.receive(); 
			Object result=call.getResult(); 
			if(result instanceof Throwable) 
			throw new RemoteException((Throwable)result); //把异常都转换为 RemoteException 
			else 
			return (String)result; 
		} catch(Exception e) { 
			throw new RemoteException(e); //把异常都转换为 RemoteException
		} finally {
			if(connector!=null)connector.close();
		} 
	} 
	
	public Date getTime()throws RemoteException { 
		Connector connector=null; 
		try { 
			connector=new Connector(host,port); 
			Call call=new Call("proxy1.HelloService","getTime",new Class[]{},new Object[]{}); 
			connector.send(call); 
			call=(Call)connector.receive();
			Object result=call.getResult(); 
			if(result instanceof Throwable) 
			throw new RemoteException((Throwable)result); //把异常都转换为 RemoteException
			else 
			return (Date)result; 
		} catch(Exception e) { 
			throw new RemoteException(e); //把异常都转换为 RemoteException 
		} finally {
			if(connector!=null)connector.close();
		} 
	} 
	
}

ProxyFactory.java(负责创建动态代理类及其实例)

package proxy1; 
import java.lang.reflect.*; 

public class ProxyFactory { 
	public static Object getProxy(final Class classType,final String host,final int port){ 
		InvocationHandler handler=new InvocationHandler() { 
			public Object invoke(Object proxy,Method method,Object args[]) throws Exception{ 
				Connector connector=null; 
				try { 
					connector=new Connector(host,port); 
					Call call=new Call(classType.getName(), method.getName(), method.getParameterTypes(),args); 
					connector.send(call); 
					call=(Call)connector.receive(); 
					Object result=call.getResult(); 
					if(result instanceof Throwable) 
						throw new RemoteException((Throwable)result); //把异常都转换为 RemoteException 
					else 
						return result; 
				} finally {
					if(connector!=null)connector.close();
				} 
			} 
		}; 
		return Proxy.newProxyInstance(classType.getClassLoader(), new Class[]{classType}, handler); 
	} 
}

无论 HelloService 的静态代理类还是动态代理类,都通过 Connector 类来发送和接收 Call 对象。ProxyFactory 工厂类的 getProxy()方法的第一个参数 classType 指定代理类实现的接口的类型,如果参数 classType 的取值为 HelloService.class,那么getProxy()方法就创建 HelloService 动态代理类的实例。如果参数 classType 的取值为Foo.class,那么 getProxy()方法就创建 Foo 动态代理类的实例。由此可见,getProxy()方法可以创建任意类型的动态代理类的实例,并且它们都具有调用被代理的远程对象的方法的能力。

如果使用静态代理方式,那么对于每一个需要代理的类,都要手工编写静态代理类的源代码;如果使用动态代理方式,那么只要编写一个动态代理工厂类,它就能自动创建各种类型的动态代理类,从而大大简化了编程,并且提高了软件系统的可扩展性和可维护性。

如下所示的 SimpleClient 类的 main()方法中,分别通过静态代理类和动态代理类去访问 SimpleServer 服务器上的 HelloServiceImpl 对象的各种方法。

SimpleClient.java

package proxy1; 
import java.io.*; 
import java.net.*; 
import java.util.*; 

public class SimpleClient { 
	public static void main(String args[])throws Exception { 
		//创建静态代理类实例
		HelloService helloService1=new HelloServiceProxy("localhost",8000); 
		System.out.println(helloService1.echo("hello")); 
		System.out.println(helloService1.getTime()); 
		//创建动态代理类实例
		HelloService helloService2= (HelloService)ProxyFactory.getProxy(HelloService.class,"localhost",8000); 
		System.out.println(helloService2.echo("hello")); 
		System.out.println(helloService2.getTime()); 
	} 
}

先运行命令“java proxy1.SimpleServer”,再运行命令“java proxy1.SimpleClient”,SimpleClient 端的打印结果如下:

echo:hello 
Thu Nov 02 10:54:48 CST 2006 
echo:hello 
Thu Nov 02 10:54:49 CST 2006

Class类

除了int等基本类型外,Java的其他类型全部都是class(包括interface)
结论:class(包括interface)的本质是数据类型(Type)。无继承关系的数据类型无法赋值

每加载一种class,JVM就为其创建一个Class类型的实例,并关联起来
注意:这里的Class类型是一个名叫Class的class。它长这样:

public final class Class {
	private Class() {}
}

以String类为例,当JVM加载String类时,它首先读取String.class文件到内存,然后,为String类创建一个Class实例并关联起来

Class cls = new Class(String);

这个Class实例是JVM内部创建的,如果查看JDK源码,可以发现Class类的构造方法是private,只有JVM能创建Class实例,自己的Java程序是无法创建Class实例的。所以,JVM持有的每个Class实例都指向一个数据类型(class或interface)

JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等。因此,如果获取了某个Class实例,就可以通过这个Class实例获取到该实例对应的class的所有信息。这种通过Class实例获取class信息的方法称为反射(Reflection)

Class类的实例表示正在运行的Java程序中的类和接口,它没有公共的构造方法,要创建Class类的对象,有三种方法:Demo为定义的一个类

(1)使用类的class属性

直接通过一个class的静态变量class获取

Class c = Demo.class;

(2)使用Class类的forName方法

如果知道一个class的完整类名,可以通过静态方法Class.forName()获取

try {
	Class c = Class.forName("com.my.Demo"); //全限定类名
} catch (ClassNotFoundException e) {
	e.printStackTrace();
}

(3)使用Object对象的getClass方法

有一个实例变量,可以通过该实例变量提供的getClass()方法获取

Demo demo = new Demo();
Class c = demo.getClass();

Class实例在JVM中是唯一的。所以,上述方法获取的Class实例是同一个实例。可以用==比较两个Class实例

用instanceof不但匹配指定类型,还匹配指定类型的子类。而用 == 判断class实例可以精确地判断数据类型,但不能作子类型比较

Integer n = new Integer(123);
boolean b1 = n instanceof Integer;		//true,因为n是Integer类型
boolean b2 = n instanceof Number;		//true,因为n是Number类型的子类
boolean b3 = n.getClass() == Integer.class;		//true,因为n.getClass()返回Integer.class
boolean b4 = n.getClass() == Number.class;		//false,因为Integer.class!=Number.class	

通常情况下,应该用instanceof判断数据类型,因为面向抽象编程的时候,不关心具体的子类型。只有在需要精确判断一个类型是不是某个class的时候,才使用==判断class实例


动态加载

JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载

public class Main {
	public static void main(String[] args){
		if (args.length > 0) {
			create(args[0]);
		}
	}
	
	static void create(String name) {
		Person p = new Person(name);
	}
}

当执行Main.java时,由于用到了Main,因此,JVM首先会把Main.class加载到内存。然而,并不会加载Person.class,除非程序执行到create()方法,JVM发现需要加载Person类时,才会首次加载Person.class。如果没有执行create()方法,那么Person.class根本就不会被加载。这就是JVM动态加载class的特性

动态加载class的特性对于Java程序非常重要。利用JVM动态加载class的特性,才能在运行期根据条件加载不同的实现类。例如,Commons Logging总是优先使用Log4j,只有当Log4j不存在时,才使用JDK的logging

// Commons Logging优先使用Log4j:
LogFactory factory = null;
if (isClassPresent("org.apache.logging.log4j.Logger")) {
	factory = createLog4j();
} else {
	factory = createJdkLog();
}

boolean isClassPresent(String name) {
	try {
		Class.forName(name);
		return true;
	} catch (Exception e) {
		return true;
	}
}

这就是为什么只需要把Log4j的jar包放到classpath中,Commons Logging就会自动使用Log4j的原因

(1)JVM为每个加载的class及interface创建了对应的Class实例来保存class及interface的所有信息
(2)获取一个class对应的Class实例后,就可以获取该class的所有信息
(3)通过Class实例获取class信息的方法称为反射(Reflection)
(4)JVM总是动态加载class,可以在运行期根据条件来控制加载class


Java反射

Class类获得的对象调用反射方法

包路径、类名称、继承类

方法返回值描述
getPackage()Package 对象获得该类的存放路径
getName()String 对象获得该类的名称
getSuperclass()Class 对象获得该类继承的类(直接继承)
// Integer i = ?
// true,因为Integer可以赋值给Integer
Integer.class.isAssignableFrom(Integer.class); 
// Number n = ?
// true,因为Integer可以赋值给Number
Number.class.isAssignableFrom(Integer.class); 
// Object o = ?
// true,因为Integer可以赋值给Object
Object.class.isAssignableFrom(Integer.class); 
// Integer i = ?
// false,因为Number不能赋值给Integer
Integer.class.isAssignableFrom(Number.class); 

通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现

实现接口

方法返回值描述
getInterfaces()Class型数组获得该类实现的所有接口

构造方法

获取类的构造方法可以使用Class类提供的getConstructors方法、getConstructor方法、getDeclaredConstructors方法和getDeclaredConstructor方法实现,它们将返回Constructor类型的对象或数组

构造方法描述
getConstructors()返回一个包含某些Constructor对象的数组,这些对象反映此Class对象所表示的类的所有公共构造方法
getConstructor(Class<?>…parameterTypes)返回对象,获得权限为public的指定构造方法
getDeclaredConstructors()返回Constructor对象的一个数组,获得所有的构造方法,按声明顺序返回
getDeclaredConstructor(Class<?>…parameterTypes)返回数组,获得指定构造方法

如果访问指定的构造方法,需要根据该构造方法的入口参数的类型来访问。例如,访问一个入口参数类型依次为String和int型的构造方法,以下实现:

objectClass.getDeclaredConstructor(String.class,int.class);
objectClass.getDeclaredConstructor(new Class[]{ String.class,int.class });

通过反射来创建新的实例,可以调用Class提供的newInstance()方法

Person p = Person.class.newInstance();

调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用

Constructor类的常用方法

方法功能
isVarArgs()查看该构造方法是否允许带有可变数量的参数,允许返回true
getParameterTypes()按照声明顺序以Class数组形式获得该构造方法的各个参数的类型
getExceptionTypes()以Class数组的形式获得该构造方法可能会抛出的异常
newInstance(Object…initargs)通过该构造方法利用指定参数创建一个该类的对象,如果未设置参数则表示采用默认无参数的构造方法
setAccessible(boolean flag)如果该构造方法的权限为private,默认为不允许通过反射利用
newInstance(Object…initargs)创建对象。如果先执行该方法,并将入口参数设置为true,则允许创建
getModifiers()获得可以解析出该构造方法所采用修饰符的整数

通过java.lang.reflect.Modifier类可以解析出getModifier()方法的返回值所表示的修饰符信息,在该类中提供了一系列用来解析的静态方法,既可以查看是否被指定的修饰符修饰,还可以以字符串的形式获得所有修饰符

Modifier类中的常用解析方法

方法功能
isPublic(int mod)查看是否被public修饰符修饰,如果是返回true
isProtected(int mod)查看是否被protected修饰符修饰,如果是返回true
isPrivate(int mod)查看是否被privaate修饰符修饰
isStatic(int mod)查看是否被static修饰符修饰
isFinal(int mod)查看是否被final修饰符修饰
toString(int mod)以字符串的形式返回所有修饰符

判断对象constructor所代表的构造方法是否被private修饰,以及以字符串形式获得该构造方法的所有修饰符的代码如下:

int mod = constructor.getModifiers();
String str = Modifier.isPrivate(mod);
String embe = Modifier.toString(mod);

成员变量(字段)

方法描述
Field getField(name)根据字段名获取某个public的field(包括父类)
Field[] getFields()获取所有public的field(包括父类)
Field getDeclaredField(name)根据字段名获取当前类的某个field(不包括父类)
Field[] getDeclaredFields()获取当前类的所有field(不包括父类)

如果访问指定的成员变量,可以通过该成员变量的名称来访问。如访问一个名称为birthday的成员变量

Class cs = StringOne.class;
cs.getDeclaredField("birthday");

Filed类的常用方法

方法功能
getName()获得该成员变量的名称
getType()获得表示该成员变量类型的Class对象
get(Object obj)获得指定对象obj中成员变量的值,返回值为Object型
get(Object obj,Object value)将指定对象obj中成员变量的值设置为value
getInt(Object obj)获得指定对象obj中类型为int的成员变量的值
setInt(Object obj,int i)将指定对象obj中类型为int的成员变量的值设置为i
getFlaot(Object obj)获得指定对象obj中类型为Flaot的成员变量的值
setFlaot(Object obj,Flaot f)将指定对obj中类型为flaot的成员变量的值设置为f
getBoolean(Object obj)获得指定对象obj中类型为boolean的成员变量的值
setBoolean(Object obj,boolean b)将指定obj中类型为boolean的成员变量的值设置为b
setAccessible(boolean flag)此方法可以设置是否忽略权限限制直接访问private等私有权限的成员变量
getModifier()获得可以解析出该成员变量所采用修饰符的整数

获取方法

方法描述
Method getMethod(name, Class…)获取某个public的Method(包括父类)
Method[] getMethods()获取所有public的Method(包括父类)
Method getDeclaredMethod(name, Class…)获取当前类的某个Method(不包括父类)
Method[] getDeclaredMethods()获取当前类的所有Method(不包括父类)

如果是访问指定的方法,需要根据该方法的名称和入口参数的类型来访问。例如,访问一个名称为print、入口参数类型依次为String和int型的方法,两种方法实现:

objectClass.getDeclaredMethod("print",String.Class,int.class);
objectClass.getDeclaredMethod("print",new Class[]{ String.class,int.class }

objectClass是Class类的对象

Method类的常用方法

方法功能
getName()获得该方法的名称
getParameterTypes()按照声明顺序以Class数组形式获得该方法的各个参数类型
getReturnType()以Class对象的形式获得该方法的返回值类型
getExceptionType()以Class数组的形式获得该方法可能抛出的异常类型
Invoke(Object obj,Object…args)利用指定参数args执行指定对象obj中的该方法,返回Object型
isVarArgs()查看该构造方法是否允许带有可变数量的参数,如果允许则允许返回true
getModifiers()获得可以解析出该方法所采用修饰符的整数

在反射中执行具有可变数量的参数的构造方法时,需要将入口参数定义成二维数组

调用方法
当获取到一个Method对象时,就可以对它进行调用

// String对象:
String s = "Hello world";
// 获取String substring(int)方法,参数为int:
Method m = String.class.getMethod("substring", int.class);
// 在s对象上调用该方法并获取结果:
String r = (String) m.invoke(s, 6);
// 打印调用结果:
System.out.println(r);

注意到substring()有两个重载方法,获取的是String substring(int)这个方法。思考一下如何获取String substring(int, int)方法
对Method实例调用invoke就相当于调用该方法,invoke的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错

调用静态方法
如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke方法传入的第一个参数永远为null

// 获取Integer.parseInt(String)方法,参数为String:
Method m = Integer.class.getMethod("parseInt", String.class);
// 调用该静态方法并获取结果:
Integer n = (Integer) m.invoke(null, "12345");
// 打印调用结果:
System.out.println(n);

调用非public方法
和Field类似,对于非public方法,虽然可以通过Class.getDeclaredMethod()获取该方法实例,但直接对其调用将得到一个IllegalAccessException。为了调用非public方法,通过Method.setAccessible(true)允许其调用

public class Main {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        Method m = p.getClass().getDeclaredMethod("setName", String.class);
        m.setAccessible(true);
        m.invoke(p, "Bob");
        System.out.println(p.name);
    }
}

class Person {
    String name;
    private void setName(String name) {
        this.name = name;
    }
}

此外,setAccessible(true)可能会失败。如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true),这样可以保证JVM核心库的安全

多态调用

// 获取Person的hello方法:
Method h = Person.class.getMethod("hello");
// 对Student实例调用hello方法:
h.invoke(new Student());

class Person {
	public void hello() {
		System.out.println("Person:hello");
	}
}

class Student extends Person {
	public void hello() {
		System.out.println("Student:hello");
	}
}

使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型的覆写方法(如果存在)

内部类、内部类的声明类

方法返回值描述
getClasses()Class型数组获得所有权限为public的内部类
getDeclaredClasses()Class型数组获得所有内部类
getDeclaringClass()Class对象如果该类为内部类,则返回它的成员变量,否则返回null
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

未禾

您的支持是我最宝贵的财富!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值