JAVA反射机制

反射概述

  在JAVA运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象能否调用它的任意一个方法?

答案是肯定的,这种动态获取类的信息,以及动态调用对方方法的功能来自于JAVA语言的反射(Reflection)机制:——孙卫琴

反射也可以动态生成类

  通过类的Class对象拿到这个类拥有哪些方法,甚至可以方法调用

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

Class类:代表一个类

Field类:代表类的成员变量

Method类:代表类的方法

Constructor类:代表类的构造方法

Array类:提供了动态创建数据,以及访问数组元素的静态方法。

  上一篇博客说明了泛型是在编译期间做检查,而反射是运行期间的操作 下面在通过一段代码说明

public class TestDynamicLoading{    
    public static void main(String[] args) throws Exception {
Map
<String,Integer> map = new HashMap<>(); Method m = map.getClass().getMethod("put",Object.class,Object.class); m.invoke(map,"1","我是反射加载进来的String类型"); System.out.println(map.get("1"));//通过反射运行期间加载,绕过了编译器的检查 } }

getClass

反射可以拿到一个对象的成员变量的值,但方法的参数是出现在方法被调用的时候,

你拿到了class,method,参数类型等这些都没用,这些都是静态的,固定的。

也就是说反射拿不到参数的值

理解java动态加载机制

 在java语言里面,类的加载、连接、解析和初始化都是在程序运行期间完成的,这种策略虽然会令类加载时稍微增加一些性能开销,但是会为java应用程序提供高度的灵活性。

 java里天生可以扩展的语言特性就是依赖运行期间动态加载和动态连接这个特点实现的。

下面程序可证明java的加载机制是动态加载

public class TestDynamicLoading{

    public static void main(String[] args) {
        new A();
        //如果A和B是同时加载 那么这条横线会在加载完成之后在打印出来 如果看到A才加载A看到B才加载B 那么这条横线位于两个加载中间
        System.out.println("-------------------------");
        new B();
    }
}

class A    {
    static{
        System.out.println("A Load!");
    }
}
class B    {
    static{
        System.out.println("B Load!");
    }
}

//这是main方法打印出来的信息 -verbose:class 会把class怎么load进来的给打印出来
//[Loaded javaee.net.cn.tree.ch02.A from file:/D:/EclipseProject/workspace/Tab3/build/classes/]
//A Load!
//-------------------------
//[Loaded javaee.net.cn.tree.ch02.B from file:/D:/EclipseProject/workspace/Tab3/build/classes/]
//B Load!

其中static语句块是class被load到内存中被调用 并且只调用一次。

在远程方法中利用反射机制

利用反射和动态加载机制:如果编写一个面向接口的应用程序,可以等到运行时再指定其实际实现的类:用户可以通过java预定义的和自定义的类加载器,让一个本地的应用程序 可以在运行期间从网络或者其他地方加载一个二进制流作为程序代码的一部分。比如JSP。。。比如以下列子

假定在SimpleServer服务器端创建了一个HelloServiceImpl对象 它具有getTime()和echo()方法。HelloServiceImpl类实现了HelloService接口

下面分别是HelloService接口和HelloServiceImpl的源程序

HelloService

public interface HelloService {
    public String echo(String name);
    public Date getTime();
}
View Code

HelloServiceImpl

    @Override
    public String echo(String name) {
        return "echo:"+name;
    }
    @Override
    public Date getTime() {
        return new Date();
    }
}
View Code

为了方便按照面向对象的方式来处理客户端与服务端的通信,可以把他们发送的信息用Call类来表示 它包括类名,接口名,方法参数类型,方法参数值和方法执行结果

Call

public class Call implements Serializable{

    private static final long serialVersionUID = 1L;
    
    private String className; //表示类名或接口名
     
    private String methodName; //表示方法名
    
    private Class[] paramTypes; //表示方法参数类型
    
    private Object[] params; //表示方法参数值
    
    private Object result;//表示方法的执行结果
    

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

    public Object getResult() {
        return result;
    }

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

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

    @Override
    public String toString() {
        return "Call [className=" + className + ", methodName=" + methodName + "]";
    }
    
}
View Code

 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对象,从中获得方法执行结果

SimpleClient

public class SimpleClient {
    public void invoke()throws Exception {
        Socket socket = new Socket("127.0.0.1",8000);
        OutputStream out = socket.getOutputStream();
        ObjectOutputStream oos=new ObjectOutputStream(out);
        InputStream in = socket.getInputStream();
        ObjectInputStream ois=new ObjectInputStream(in);
        Call call = new Call("com.sun.reflect.HelloService","echo",new Class[] {String.class},new Object[] {"Hello"});
        oos.writeObject(call);
        call=(Call) ois.readObject();
        System.out.println(call.getResult());
        while(ois.available()!=0) {
            ois.close();
            oos.close();
            socket.close();
        }
    }
    public static void main(String[] args)throws Exception {
        new SimpleClient().invoke();
    }
}
View Code

SimpleServer

public class SimpleServer {
    private Map<String,Object> 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();
            System.out.println("开始连接");
            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对象
            while(ois.available()!=0) {
                ois.close();
                oos.close();
                socket.close();
            }
        }
    }
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public Call invoke(Call call) throws Exception {
        Object result = null;
        String className = call.getClassName();
        String methodName=call.getMethodName();
        Object[] params = call.getParams();
        Class classType=Class.forName(className);
        Class[] parameterTypes=call.getParamTypes();
        Method method = classType.getMethod(methodName, parameterTypes);
        Object remoteObject = remoteObjects.get(className); //从缓存中取出相关的远程对象
        result = method.invoke(remoteObject, params);
        call.setResult(result);
        return call;
    }
    public static void main(String[] args) throws Exception {
        SimpleServer server = new SimpleServer();
        server.register("com.sun.reflect.HelloService", new HelloServiceImpl());
        server.service();
    }
}
View Code

 

在远程方法中运用代理类

SimpleClient客户端调用远程对象的方法与调用本地对象的方法很相似。

Connector类负责建立与远程服务器的连接,以及接收和发送Socket对象。

package com.sun.proxy;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * Connector类负责建立与远程服务器的连接,以及接收和发送Socket对象
 */
public class Connector {

    private String host;
    private int port;
    private Socket sk1;
    private InputStream is;
    private ObjectOutputStream oos;
    private OutputStream os;
    private ObjectInputStream ois;
    
    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(String host,int port) throws Exception {//建立与远程服务器的连接
        sk1=new Socket(host,port);
        os=sk1.getOutputStream();
        oos=new ObjectOutputStream(os);
        is=sk1.getInputStream();
        ois=new ObjectInputStream(is);
    }
    
    public void close() throws Exception  { //关闭连接
        ois.close();
        oos.close();
        sk1.close();
    }
    
}
View Code

ProxyFactory负责创建动态代理类及其实列

package com.sun.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import com.sun.reflect.Call;
/**
 * 负责创建代理类及其实列
 */
public class ProxyFactory {
    public static Object getProxy(final Class classType,final String host, final int port) {
        InvocationHandler handler = new InvocationHandler() {
            
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                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 RuntimeException("错误。。。。。。");
                    }else {
                        return result;
                    }
                } finally {
                    if(connector!=null) {
                        connector.close();
                    }
                }
            }
        };
        return Proxy.newProxyInstance(classType.getClassLoader(), new Class[] {classType}, handler);
    }
}
View Code
    public static void main(String[] args)throws Exception {
        HelloService helloService = (HelloService) ProxyFactory.getProxy(HelloService.class, "127.0.0.1", 8000);
        System.out.println(helloService.echo("hello"));
        System.out.println(helloService.getTime());
    }

无论HelloService的静态代理类还是动态代理类,都通过Connector类来发送和接收Call对象。ProxyFactory工厂类的getProxy()方法的第一个参数classType指定代理类实现接口的类型。

如果参数的取值为HelloService.class,那么getProxy()方法就创建HelloService动态代理类的实列。

如果参数的取值为Foo.class,那么getProxy()方法就创建Foo代理类的实列。

由此可见,getProxy()方法可以创建任意类型的动态代理类的实列,并且他们都具有调用被调用代理类的远程对象的方法的能力。

如果使用静态代理的方式,那么对于每一个需要代理的类,都要手工编写静态代理类的源代码;

静态代理类HelloServiceProxy

package com.sun.proxy;

import java.util.Date;

import com.sun.reflect.Call;
import com.sun.reflect.HelloService;

public class HelloServiceProxy implements HelloService{
    private String host;
    private int port;
    
    
    
    public HelloServiceProxy(String host, int port) {
        super();
        this.host = host;
        this.port = port;
    }
    
    @Override
    public String echo(String msg) throws Exception {
        Connector connector =null;
        try {
            connector = new Connector(host, port);
            Call call = new Call("com.sun.reflect.HelloService","echo",new Class[] {String.class},new Object[] {msg});
            connector.send(call);
            call =(Call) connector.receive();
            Object result = call.getResult();
            return (String) result;
        }finally {
            if(connector!=null) {
                connector.close();
            }
        }
    }
    @Override
    public Date getTime() throws Exception {
        Connector connector =null;
        try {
            connector = new Connector(host, port);
            Call call = new Call("com.sun.reflect.HelloService","getTime",new Class[] {},new Object[] {});
            connector.send(call);
            call =(Call) connector.receive();
            Object result = call.getResult();
            return (Date) result;
        }finally {
            if(connector!=null) {
                connector.close();
            }
        }
    }
}
View Code

如果使用动态代理的方式,那么只需要编写一个动态代理工厂类,它就能够创建各种类型的动态代理类。

小结

java的反射机制是java语言的一个重要特性。考虑实现一个newInstance(String className)的方法,它的作用是根据参数className指定的类名,通过该类的不带参数的构造方法创建这个类的对象,将其返回。

如果不用java反射机制,必须在newInstance()方法中罗列参数className所有可能的取值,然后创建相应的对象。

if(className.equals("xx")){

  return new xx();

}

if(className.equals("yy")){

  return new yy();

}。。。。。。

posted @ 2018-03-06 11:06  palapala  阅读(312)  评论(0编辑  收藏  举报