Java中如何動態創建接口的實現


有很多應用場景,用到了接口動態實現,下面舉幾個典型的應用:

1、mybatis / jpa 等orm框架,可以在接口上加注解進行開發,不需要編寫實現類,運行時動態產生實現。

2、dubbo等分布式服務框架,消費者只需要引入接口就可以調用遠程的實現,分析源代碼,其實在消費端產生了接口的代理實現,再由代理調用遠程接口。

3、spring aop 這是最典型的動態代理了。

創建接口的動態實現,有二種最常用的方式:JDK動態代理和CGLIB動態代理。

代理模式是一種常用的設計模式,其目的就是為其他對象提供一個代理以控制對某個真實對象的訪問。

代理類負責為委托類預處理消息,過濾消息並轉發消息,以及進行消息被委托類執行后的后續處理。

這里寫圖片描述

通過代理層這一中間層,有效的控制對於真實委托類對象的直接訪問,同時可以實現自定義的控制策略(spring的AOP機制),設計上獲得更大的靈活性。

 

下面用JDK動態代理加一點簡單的代碼來演示這個過程:

1、接口

package com.yhouse.modules.daos;

public interface IUserDao {
    public String getUserName();
}

2、創建代理

package com.yhouse.modules.daos;

import java.lang.reflect.Proxy;
/**
 * 創建代理
 * @author clonen.cheng
 *
 */
public class Invoker {
    
   
    public Object getInstance(Class<?> cls){        
        MethodProxy invocationHandler = new MethodProxy();        
        Object newProxyInstance = Proxy.newProxyInstance(  
                cls.getClassLoader(),  
                new Class[] { cls }, 
                invocationHandler); 
        return (Object)newProxyInstance;
    }
}

3、運行時調用接口的方法時的實現(這一過程也稱為接口的方法實現)

package com.yhouse.modules.daos;

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

public class MethodProxy implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)  throws Throwable {        
        //如果傳進來是一個已實現的具體類(本次演示略過此邏輯)
        if (Object.class.equals(method.getDeclaringClass())) {  
            try {  
                return method.invoke(this, args);  
            } catch (Throwable t) {  
                t.printStackTrace();  
            }  
        //如果傳進來的是一個接口(核心)
        } else {  
            return run(method, args);  
        }  
        return null;
    }
    
    /**
     * 實現接口的核心方法 
     * @param method
     * @param args
     * @return
     */
    public Object run(Method method,Object[] args){  
        //TODO         
        //如遠程http調用
        //如遠程方法調用(rmi)
        //....
       return "method call success!";
    }  

}

4、測試

package com.yhouse.modules.daos;

public class ProxyTest {

    
    public static void main(String[] args) {
        IUserDao invoker=(IUserDao)new Invoker().getInstance(IUserDao.class);
        System.out.println(invoker.getUserName());
    }

}

在這段測試代碼中,並沒有接口的任何實現,大家猜猜會是什么結果?
控制台打印:

說明接口在調用時,把實現委托給了代理,最后具體要做的就是這個代理里面的處理:

在上面這段代碼當中,可以看出,拿到了接口的method以及args,那么就可以做很多的事情,如根據方法名或者配合方法上面的注解來實現比較豐富的功能。

 

一個簡單的例子只是用來說明這個原理,下面再舉一個遠程接口動態調用的例子來加深理解。

1、創建代理類和目標類需要實現共同的接口Service

package com.markliu.remote.service;
/**
 * Service接口。代理類和被代理類抖需要實現該接口
 */
public interface Service {
    public String getService(String name, int number);
}

 

2、服務器端創建RemoteService類,實現了Service 接口。

package com.markliu.remote.serviceimpl;
import com.markliu.remote.service.Service;
/**
 * 服務器端目標業務類,被代理對象
 */
public class RemoteService implements Service {
    @Override
    public String getService(String name, int number) {
        return name + ":" + number;
    }
}

 

3、創建封裝客戶端請求和返回結果信息的Call類

為了便於按照面向對象的方式來處理客戶端與服務器端的通信,可以把它們發送的信息用 Call 類來表示。一個 Call 對象表示客戶端發起的一個遠程調用,它包括調用的類名或接口名、方法名、方法參數類型、方法參數值和方法執行結果。

package com.markliu.local.bean;
import java.io.Serializable;
/**
 * 請求的javabean
 */
public class Call implements Serializable{
    private static final long serialVersionUID = 5386052199960133937L;
    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;
    }
    // 省略了get和set方法
}

 

4、創建動態代理模式中實際的業務處理類,實現了InvocationHandler 接口

package com.markliu.local.service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import com.markliu.local.bean.Call;

public class ServiceInvocationHandler implements InvocationHandler {

    private Class<?> classType;
    private String host;
    private Integer port;

    public Class<?> getClassType() {
        return classType;
    }
    public ServiceInvocationHandler(Class<?> classType, String host, Integer port) {
        this.classType = classType;
        this.host = host;
        this.port = port;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 封裝請求信息
        Call call = new Call(classType.getName(), method.getName(), method.getParameterTypes(), args);
        // 創建鏈接
        Connector connector = new Connector();
        connector.connect(host, port);
        // 發送請求
        connector.sendCall(call);
        // 獲取封裝遠程方法調用結果的對象
        connector.close();
        Object returnResult = call.getResult();
        return returnResult;
    }
}

 

5、創建獲取代理類的工廠RemoteServiceProxyFactory

package com.markliu.local.service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 * 動態創建RemoteService代理類的工廠
 */
public class RemoteServiceProxyFactory {

    public static Object getRemoteServiceProxy(InvocationHandler h) {
        Class<?> classType = ((ServiceInvocationHandler) h).getClassType();
        // 獲取動態代理類
        Object proxy = Proxy.newProxyInstance(classType.getClassLoader(), 
                new Class[]{classType}, h);
        return proxy;
    }
}

 

6、創建底層Socket通信的Connector類,負責創建攔截、發送和接受Call對象

package com.markliu.local.service;
// 省略import 

/**
 * 負責創建鏈接
 */
public class Connector {
    private Socket linksocket;
    private InputStream in;
    private ObjectInputStream objIn;
    private OutputStream out;
    private ObjectOutputStream objOut;

    public Connector(){}
    /**
     * 創建鏈接
     */
    public void connect(String host, Integer port) throws UnknownHostException, IOException {
        linksocket = new Socket(host, port);
        in = linksocket.getInputStream();
        out = linksocket.getOutputStream();
        objOut = new ObjectOutputStream(out);
        objIn = new ObjectInputStream(in);
    }
    /**
     * 發送請求call對象
     */
    public void sendCall(Call call) throws IOException {
        objOut.writeObject(call);
    }
    /**
     * 獲取請求對象
     */
    public Call receive() throws ClassNotFoundException, IOException {
        return (Call) objIn.readObject();
    }
    /**
     * 簡單處理關閉鏈接
     */
    public void close() {
        try {
            linksocket.close();
            objIn.close();
            objOut.close();
            in.close();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

7、創建遠程服務器

package com.markliu.remote.main;
// 省略import 

public class RemoteServer {

    private Service remoteService;
    public RemoteServer() {
        remoteService  = new RemoteService();
    }
    public static void main(String[] args) throws Exception {
        RemoteServer server = new RemoteServer();
        System.out.println("遠程服務器啟動......DONE!");
        server.service();
    }

    public void service() throws Exception {
        @SuppressWarnings("resource")
        ServerSocket serverSocket = new ServerSocket(8001);
        while (true) {
                Socket socket = serverSocket.accept();
                InputStream in = socket.getInputStream();
                ObjectInputStream objIn = new ObjectInputStream(in);
                OutputStream out = socket.getOutputStream();
                ObjectOutputStream objOut = new ObjectOutputStream(out);
                // 對象輸入流讀取請求的call對象
                Call call = (Call) objIn.readObject();
                System.out.println("客戶端發送的請求對象:" + call);
                call = getCallResult(call);
                // 發送處理的結果回客戶端
                objOut.writeObject(call);
                objIn.close();
                in.close();
                objOut.close();
                out.close();
                socket.close();
        }
    }

    /**
     * 通過反射機制調用call中指定的類的方法,並將返回結果設置到原call對象中
     */
    private Call getCallResult(Call call) throws Exception {
        String className = call.getClassName();
        String methodName = call.getMethodName();
        Object[] params = call.getParams();
        Class<?>[] paramsTypes = call.getParamTypes();

        Class<?> classType = Class.forName(className);
        // 獲取所要調用的方法
        Method method = classType.getMethod(methodName, paramsTypes);
        Object result = method.invoke(remoteService, params);
        call.setResult(result);
        return call;
    }
}

 

8、創建本地客戶端

package com.markliu.local.main;
import java.lang.reflect.InvocationHandler;
import com.markliu.local.service.RemoteServiceProxyFactory;
import com.markliu.local.service.ServiceInvocationHandler;
import com.markliu.remote.service.Service;

public class LocalClient {
    public static void main(String[] args) {
        String host = "127.0.0.1";
        Integer port = 8001;
        Class<?> classType = com.markliu.remote.service.Service.class;
        InvocationHandler h = new ServiceInvocationHandler(classType, host, port);
        Service serviceProxy = (Service) RemoteServiceProxyFactory.getRemoteServiceProxy(h);
        String result = serviceProxy.getService("SunnyMarkLiu", 22);
        System.out.println("調用遠程方法getService的結果:" + result);
    }
}

 控制台打印結果:

這個過程可以簡單的歸納為:本地接口調用(客戶端)--->本地接口代理實現(客戶端)---->遠程實現(服務器端)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM