Java SE 练习篇 P3 CS框架的搭建(消息传送,资源分发)

1 CS框架的搭建

1.1 结构

采用一对多的方式,一个服务器端程序为多个客户端程序提供服务,为每个客户端建立一对会话层以传递信息,服务器有着所有客户端会话层的会话池
在这里插入图片描述

1.2 信息结构

由于信息在网络之间传输的局限,需要一种辨识信息含义的方式,这里采取:信令+信息的方式来解析信息:

信令枚举:
在这里插入图片描述
信息格式:

/**
 * @author 雫
 * @date 2021/1/25 - 22:19
 * @function 信息
 */
public class NetMessage {
    
    
    public static final Gson gson = new GsonBuilder().create();

    private ENetCommand command;
    private String sourceId;
    private String targetId;
    private String action;
    private String message;

    public NetMessage() {
    
    }

    public NetMessage(String strNetMessage) {
    
    
        NetMessage netMessage = gson.fromJson(strNetMessage, NetMessage.class);
        this.command = netMessage.getCommand();
        this.sourceId = netMessage.getSourceId();
        this.targetId = netMessage.getTargetId();
        this.action = netMessage.getAction();
        this.message = netMessage.getMessage();
    }

    public ENetCommand getCommand() {
    
    
        return command;
    }

    public NetMessage setCommand(ENetCommand command) {
    
    
        this.command = command;
        return this;
    }

	//省略了其它成员的setter和getter方法
	//这里的所有set方法,返回值全部改成了NetMessage类,方便调用


    @Override
    public String toString() {
    
    
        return gson.toJson(this);
    }

信息在经过底层通信信道时,必须是字符串的形式,这里采用toJson在发送时将整个对象包装成字符串,接收时将字符串通过fromJson转换成NetMessage对象

1.3 通信层

由Socket产生的输入,输出流来生成底层的通信层,通信层负责不间断地侦听来自对端的信息,并提供发送信息和处理信息的方法,由于这里只做框架,所以接收消息该如何处理的工作做成抽象方法,交给基于CS框架的app开发人员进行

基本网络配置接口:
在这里插入图片描述

抽象通信层:

/**
 * @author 雫
 * @date 2021/1/25 - 22:14
 * @function 通信层
 */
public abstract class AbstractCommunication implements Runnable, INetConfig {
    
    
    protected Socket socket;
    protected DataInputStream dis;
    protected DataOutputStream dos;

    protected volatile boolean goon;
    protected volatile boolean startListening;

    protected AbstractCommunication(Socket socket) {
    
    
        this.socket = socket;
        try {
    
    
            this.dis = new DataInputStream(this.socket.getInputStream());
            this.dos = new DataOutputStream(this.socket.getOutputStream());
        } catch (IOException e) {
    
    
        }
        this.goon = true;
        this.startListening = false;
        new Thread(this).start();
    }

    public boolean isStartListening() {
    
    
        return startListening;
    }

    protected void close() {
    
    
        this.goon = false;
        this.startListening = false;
        closeIO(this.dis, this.dos, this.socket);
    }

    protected void send(NetMessage netMessage) {
    
    
        try {
    
    
            this.dos.writeUTF(netMessage.toString());
        } catch (IOException e) {
    
    
        }
    }

    @Override
    public void run() {
    
    
        this.startListening = true;
        while(this.goon) {
    
    
            try {
    
    
                String strNetMessage = this.dis.readUTF();
                dealNetMessage(new NetMessage(strNetMessage));
            } catch (IOException e) {
    
    
                this.goon = false;
                peerAbnormalDrop();
            }
        }
        close();
    }

    protected abstract void peerAbnormalDrop();
    protected abstract void dealNetMessage(NetMessage netMessage);

}

1.4 会话层

会话层继承了通信层,每有一个客户端需要连接时,该客户端需要指定服务器的ip地址和端口号生成一个socket,通过这个socket创建一个客户端会话层,这个socket被已经启动的服务器监听连接请求线程监听到,通过同一个socket创建一个服务器会话层,这一对会话层用了同一个socket,所以可以通过通信层来传递信息

客户端会话层:

/**
 * @author 雫
 * @date 2021/1/25 - 22:35
 * @function
 */
public class ClientConversation extends AbstractCommunication {
    
    
    private String id;
    private Client client;

    protected ClientConversation(Socket socket, Client client) {
    
    
        super(socket);
        this.client = client;
    }

    String getId() {
    
    
        return id;
    }

    void toOne(String targetId, String message) {
    
    
        send(new NetMessage()
                        .setCommand(ENetCommand.TO_ONE)
                        .setSourceId(this.id)
                        .setTargetId(targetId)
                        .setMessage(message));
    }

    void toOther(String message) {
    
    
        send(new NetMessage()
                        .setCommand(ENetCommand.TO_OTHER)
                        .setSourceId(this.id)
                        .setMessage(message));
    }

    void offLine() {
    
    
        send(new NetMessage()
                        .setCommand(ENetCommand.OFF_LINE)
                        .setSourceId(this.id));
        close();
    }


    void messageFromClient(String message) {
    
    
        send(new NetMessage()
                        .setCommand(ENetCommand.MESSAGE_FROM_CLIENT)
                        .setSourceId(this.id)
                        .setMessage(message));
    }

    void sendRequest(String action, String parameter) {
    
    
        send(new NetMessage()
                        .setCommand(ENetCommand.REQUEST)
                        .setSourceId(this.id)
                        .setAction(action)
                        .setMessage(parameter));
    }

    @Override
    protected void dealNetMessage(NetMessage netMessage) {
    
    
        ENetCommand command = netMessage.getCommand();
        switch (command) {
    
    
            case OUT_OFF_ROOM:
                this.client.getClientAction().outOfRoom();
                close();
                break;
            case ID:
                this.id = netMessage.getMessage();
                break;
            case TO_ONE:
                this.client.getClientAction().toOne(netMessage.getSourceId(), netMessage.getMessage());
                break;
            case TO_OTHER:
                this.client.getClientAction().toOther(netMessage.getSourceId(), netMessage.getMessage());
                break;
            case KILL_BY_SERVER:
                this.client.getClientAction().killByServer(netMessage.getMessage());
                close();
                break;
            case FORCE_DOWN:
                this.client.getClientAction().serverForceDown();
                close();
                break;
            case RESPONSE:
                try {
    
    
                    this.client.getRequestResponseAction().dealResponse(netMessage.getAction(), netMessage.getMessage());
                } catch (Exception e) {
    
    
                }
            default:
                break;
        }
    }

    @Override
    protected void peerAbnormalDrop() {
    
    
        this.client.getClientAction().peerAbnormalDrop();
    }

}

服务器会话层:

/**
 * @author 雫
 * @date 2021/1/25 - 22:35
 * @function
 */
public class ServerConversation extends AbstractCommunication {
    
    
    private String id;
    private String ip;
    private Server server;

    protected ServerConversation(Socket socket, Server server) {
    
    
        super(socket);
        this.server = server;
    }

    String getId() {
    
    
        return id;
    }

    void outOfRoom() {
    
    
        send(new NetMessage()
                        .setCommand(ENetCommand.OUT_OFF_ROOM));
        close();
    }

    void createID() {
    
    
        this.id = String.valueOf((this.ip + System.currentTimeMillis()).hashCode());
        send(new NetMessage()
                        .setCommand(ENetCommand.ID)
                        .setMessage(this.id));
    }

    void toOne(String sourceId, String message) {
    
    
        send(new NetMessage()
                        .setCommand(ENetCommand.TO_ONE)
                        .setSourceId(sourceId)
                        .setMessage(message));
    }

    void toOther(String sourceId, String message) {
    
    
        send(new NetMessage()
                        .setCommand(ENetCommand.TO_OTHER)
                        .setSourceId(sourceId)
                        .setMessage(message));
    }

    void killByServer(String reason) {
    
    
        send(new NetMessage()
                        .setCommand(ENetCommand.KILL_BY_SERVER)
                        .setMessage(reason));
        close();
    }

    void forceDown() {
    
    
        send(new NetMessage()
                        .setCommand(ENetCommand.FORCE_DOWN)
                        .setSourceId(Server.SERVER));
        close();
    }


    @Override
    protected void dealNetMessage(NetMessage netMessage) {
    
    
        ENetCommand command = netMessage.getCommand();
        switch (command) {
    
    
            case TO_ONE:
                this.server.toOne(netMessage.getSourceId(), netMessage.getTargetId(), netMessage.getMessage());
                break;
            case TO_OTHER:
                this.server.toOther(netMessage.getSourceId(), netMessage.getMessage());
                break;
            case OFF_LINE:
                this.server.offLine(netMessage.getSourceId());
                close();
                break;
            case MESSAGE_FROM_CLIENT:
                this.server.getServerAction().messageFromClient(netMessage.getSourceId(), netMessage.getMessage());
                break;
            case REQUEST:
                Object result = null;
                try {
    
    
                    result = this.server.getRequestResponseAction().dealRequest(netMessage.getAction(), netMessage.getMessage());
                } catch (InvocationTargetException | IllegalAccessException e) {
    
    
                }
                NetMessage mess = new NetMessage()
                                        .setCommand(ENetCommand.RESPONSE)
                                        .setAction(netMessage.getAction())
                                        .setMessage(Server.gson.toJson(result));
                send(mess);
                break;
            default:
                break;
        }
    }

    @Override
    protected void peerAbnormalDrop() {
    
    
        this.server.getServerAction().peerAbnormalDrop();
    }

}

1.5 方法层

方法层是给app服务的,app可以通过调用方法层的public方法完成自己的需求,方法层通过调用会话层中的具体方法来完成通信过程

客户端方法层:

/**
 * @author 雫
 * @date 2021/1/25 - 22:36
 * @function
 */
public class Client implements IListener {
    
    
    public static final Gson gson = new GsonBuilder().create();

    private int port;
    private String ip;
    private Socket socket;

    private ClientConversation cc;

    private IClientAction clientAction;
    private IRequestResponseAction requestResponseAction;

    public Client() {
    
    
        this.ip = INetConfig.ip;
        this.port = INetConfig.port;
        this.clientAction = new ClientActionAdapter();
        this.requestResponseAction = new RequestResponseActionAdapter();
    }

    public String getID() {
    
    
        return this.cc.getId();
    }

    public IClientAction getClientAction() {
    
    
        return clientAction;
    }

    public IRequestResponseAction getRequestResponseAction() {
    
    
        return requestResponseAction;
    }

    public boolean connectToServer() {
    
    
        try {
    
    
            this.socket = new Socket(this.ip, this.port);
            this.cc = new ClientConversation(socket, this);
            return true;
        } catch (IOException e) {
    
    
        }
        return false;
    }

    public void toOne(String targetId, String message) {
    
    
        this.cc.toOne(targetId, message);
    }

    public void toOther(String message) {
    
    
        this.cc.toOther(message);
    }

    public void offLine() {
    
    
        this.clientAction.confrimOffLine();
        this.clientAction.beforeOffLine();
        this.cc.offLine();
        this.clientAction.afterOffLine();
    }

    public void messageFromClient(String message) {
    
    
        this.cc.messageFromClient(message);
    }

    public void sendRequest(String action, String parameter) {
    
    
        this.cc.sendRequest(action, parameter);
    }

    @Override
    public void readMessage(String message) {
    
    }

}

服务器方法层:

/**
 * @author 雫
 * @date 2021/1/25 - 22:35
 * @function
 */
public class Server implements Runnable, ISpeaker {
    
    
    public static final String SERVER = "SERVER";
    public static final int DEFAULT_MAX_CLIENT_COUNT = 20;
    public static final Gson gson = new GsonBuilder().create();

    private int port;
    private ServerSocket serverSocket;
    private volatile boolean goon;

    private ServerConversationPool scPool;

    private IServerAction serverAction;
    private IRequestResponseAction requestResponseAction;

    public Server() {
    
    
        this.port = INetConfig.port;
        this.goon = false;
        this.serverAction = new ServerActionAdapter();
        this.requestResponseAction = new RequestResponseActionAdapter();
    }

    public IServerAction getServerAction() {
    
    
        return serverAction;
    }

    public IRequestResponseAction getRequestResponseAction() {
    
    
        return requestResponseAction;
    }

    public void startServer() {
    
    
        if(this.goon) {
    
    
            speakOut("服务器已启动");
            return;
        }

        try {
    
    
            this.serverSocket = new ServerSocket(this.port);
        } catch (IOException e) {
    
    
        }
        this.goon = true;
        this.scPool = new ServerConversationPool();
        new Thread(this).start();
    }

    public void closeServer() {
    
    
        this.goon = false;
        if(this.serverSocket != null && !this.serverSocket.isClosed()) {
    
    
            try {
    
    
                this.serverSocket.close();
            } catch (IOException e) {
    
    
            } finally {
    
    
                this.serverSocket = null;
            }
        }
    }

    public void shutDown() {
    
    
        if(!this.goon) {
    
    
            speakOut("服务器尚未启动");
            return;
        }
        if(!this.scPool.isSCPoolEmpty()) {
    
    
            speakOut("尚有在线客户端");
            return;
        }
        closeServer();
    }

    public void toOne(String sourceId, String targetId, String message) {
    
    
        this.scPool.getOneSC(targetId).toOne(sourceId, message);
    }

    public void toOther(String sourceId, String message) {
    
    
        List<ServerConversation> SCList = this.scPool.getSCListExceptMe(sourceId);
        for(ServerConversation sc : SCList) {
    
    
            sc.toOther(sourceId, message);
        }
    }

    public void offLine(String sourceId) {
    
    
        this.scPool.removeOneSC(sourceId);
    }

    public void killByServer(String targetId, String reason) {
    
    
        ServerConversation sc = this.scPool.getOneSC(targetId);
        sc.killByServer(reason);
    }

    public void forceDown() {
    
    
        List<ServerConversation> SCList = this.scPool.getAllSC();
        for(ServerConversation sc : SCList) {
    
    
            sc.forceDown();
        }
    }

    @Override
    public void run() {
    
    
        while(this.goon) {
    
    
            try {
    
    
                Socket socket = this.serverSocket.accept();
                ServerConversation sc = new ServerConversation(socket, this);
                if(scPool.getSCPoolSize() >= DEFAULT_MAX_CLIENT_COUNT) {
    
    
                    sc.outOfRoom();
                    continue;
                }
                sc.createID();
                this.scPool.addOneSC(sc);
            } catch (IOException e) {
    
    
                this.goon = false;
            }
        }
        closeServer();
    }

    @Override
    public void addListener(IListener listener) {
    
    }

    @Override
    public void removeListener(IListener listener) {
    
    }

    @Override
    public void speakOut(String message) {
    
    }

}

服务器管理的会话池:

/**
 * @author 雫
 * @date 2021/1/25 - 22:46
 * @function
 */
public class ServerConversationPool {
    
    
    private Map<String, ServerConversation> scPool;

    public ServerConversationPool() {
    
    
        this.scPool = new HashMap<>();
    }

    public void addOneSC(ServerConversation sc) {
    
    
        this.scPool.put(sc.getId(), sc);
    }

    public void removeOneSC(String id) {
    
    
        this.scPool.remove(id);
    }

    public boolean isSCPoolEmpty() {
    
    
        return this.scPool.isEmpty();
    }

    public int getSCPoolSize() {
    
    
        return this.scPool.size();
    }

    public ServerConversation getOneSC(String id) {
    
    
        return this.scPool.get(id);
    }

    public List<ServerConversation> getSCListExceptMe(String meID) {
    
    
        List<ServerConversation> SCList = new ArrayList<>();
        for(String id : this.scPool.keySet()) {
    
    
            if(id.equals(meID)) {
    
    
                continue;
            }
            SCList.add(this.scPool.get(id));
        }
        return SCList;
    }

    public List<ServerConversation> getAllSC() {
    
    
        List<ServerConversation> SCList = new ArrayList<>();
        SCList = getSCListExceptMe(null);
        return SCList;
    }

}

1.6 app层

app层可以基于整个CS框架完成开发,通过CS框架提供的机制,可以制作聊天室,棋牌室,对战平台等项目

底层无法实现的具体处理信息的方法,必须在app层由业务需求来设计完成,如toOne私聊信息的方法,CS框架通过一个接口将这些方法抛给上层,上层可以替换掉该接口或做一个适配器来具体实现

2 各种通信功能的实现过程

2.1 客户端连接到服务器的过程

在这里插入图片描述

2.2 私聊信息的过程

在这里插入图片描述

2.3 群聊信息的过程

在这里插入图片描述

在这里插入图片描述

2.4 客户端下线的过程

在这里插入图片描述

2.5 服务器强制宕机的过程

在这里插入图片描述

3 资源分发器

3.1 资源分发器

3.1.1 分发器概念

在这里插入图片描述

3.1.2 分发器需求分析

在这里插入图片描述

用两个方法来实现:
在这里插入图片描述

3.1.3 参数的传递形式和解析过程

在这里插入图片描述
参数创建及解析工具:

/**
 * @author 雫
 * @date 2021/1/22 - 10:27
 * @function
 */
public class ArgumentMaker {
    
    

    /*需要Gson的原因是:我需要把用户通过add方法加进来的参数值转换成字符串的形式
    * 因为后续客户端将要把所有参数通过ClientConversation发给ServerConversation,readUTF和writeUTF只能处理字符串类型的值*/
    public static final Gson gson = new GsonBuilder().create();

    /*完整参数键值对,键为参数名的字符串,值为参数值但经过了gson转换形成了字符串
    * 也就是说加入我要传递一个参数,名称为"雫",即用户登录账号
    * 值本来是int类型的123456,参数本来是("雫", 123456),也有参数如参数名为"Device",参数值为device
    * 这种参数关系中,名称都是字符串,值是对应的对象, 但是由于用于框架,值是不确定的类型
    * 但值一定可以经过gson转换成字符串,可以这样来形成<String, String>的键值对*/
    private Map<String, String> argMap;

    /*由于我们经过第二次gson将整个argMap转换成字符串,传过去的argMap中有着泛型
    * 会引起类型擦除,本来想要的Map<String, String>丢失了键值对类型,变成了Map<Object, Object>
    * 所以我们需要记录下来这个argMap的类型,以便为了得到键值对类型,并对值进行第二次gson还原以得到参数值*/
    private Type mapType = new TypeToken<Map<String, String>>() {
    
    }.getType();

    /**
     * @Author 雫
     * @Description 初始化<String, String>类型的HashMap
     * 这个构造器用于用户传递参数时使用
     * 即new ArgumentMaker().add(String, Objet).add...
     * 通过add将object经过gson转换成字符串
     * 最后调用toString方法将整个argMap经过gson做成字符串发送出去
     * 整个过程有两次gson: 1,将参数值进行gson装入argMap  2,将argMap整个gson发送出去
     * 但是由于其中存在着泛型,就回引起类型擦除,失去了Map中的<String, String>必须通过别的方法保存这个键值对类型
     * @Date 2021/1/22 11:30
     * @Param []
     * @return
     **/
    public ArgumentMaker() {
    
    
        this.argMap = new HashMap<>();
    }

    /**
     * @Author 雫
     * @Description 根据提供的参数字符串来产生ArgumentMaker对象
     * 这个构造器用于将先前用gson转换过的argMap转换回原先的argMap
     * 即先将字符串转换成argMap,但由于泛型的类型擦除,我们失去了Map中的<String, String>
     * 这就是argMap存在的原因,通过typeToken我们记录了argMap的泛型,即<String, String>
     * 还原成带有泛型的argMap后就可以对值进行还原,得到真正类型的值
     * @Date 2021/1/22 11:26
     * @Param [parameterString]
     **/
    public ArgumentMaker(String parameterString) {
    
    
        this.argMap = gson.fromJson(parameterString, mapType);
    }

    /**
     * @Author 雫
     * @Description 提供各用户构建参数键值对的方法
     * 需要两个参数一个是字符串类型的参数名称,一个是Object的对象即参数值
     * 返回值设置成ArgumentMaker是为了方便调用
     * 【注意】:add到HashMap中的键值对是无序的,但是用参数是必须根据顺序使用的,光放进去还不够,还得有序的取出来
     * @Date 2021/1/22 10:37
     * @Param [parameterName, parameterValue]
     * @return com.coisini.util.ArgumentMaker
     **/
    public ArgumentMaker add(String parameterName, Object parameterValue) {
    
    
        //把参数名和经过gson转换后的参数值放入argMap中,即<String, String>类型的HashMap
        this.argMap.put(parameterName, gson.toJson(parameterValue));
        return this;
    }

    /**
     * @Author 雫
     * @Description 根据明确的类型从键值对中取得正确类型的值的方法
     * 经过add加入到键值对的值都变成了gson字符换
     * 想要真正的使用值就给给该值的类型,通过gson还原成对应类型的值
     * @Date 2021/1/22 11:50
     * @Param [name, type]
     * @return java.lang.Object
     **/
    public Object getParameter(String name, Class<?> type) {
    
    
        String strValue = this.argMap.get(name);
        return gson.fromJson(strValue, type);
    }

    /**
     * @Author 雫
     * @Description 根据泛型从键值对中取得正确类型的值的方法
     * @Date 2021/1/22 12:25
     * @Param [name, type]
     * @return java.lang.Object
     **/
    public Object getParameter(String name, Type type) {
    
    
        String strValue = this.argMap.get(name);
        return gson.fromJson(strValue, type);
    }

    /**
     * @Author 雫
     * @Description ArgumentMaker中的argMap是我们所需要的
     * 这里通过重写toString是必要的,因为使用原先的toString方法,argumentMaker对象就彻底变成了一个不可逆字符串
     * 所以必须通过gson将存放argMap的argumentMaker对象转换成可逆的字符串,以便就收到时可以先将argumentMaker对象还原
     * 再还原argumentMaker对象里面的argMap里的参数值对应的类型,把<String参数名, String参数值>中String类型的参数值还原成原来的类型
     * @Date 2021/1/22 10:47
     * @Param []
     * @return java.lang.String
     **/
    @Override
    public String toString() {
    
    
        return gson.toJson(this.argMap);
    }

}

3.2 构建action对应的方法映射

由于CS框架可以应用于多种场合中,所以可以由开发者配置XML文件,里面规定好action对应的方法及所需所有元素,框架内通过反射机制来找到该方法并执行且返回结果
在这里插入图片描述
通过XML配置文件,开发者已经写好了方法,那么框架内就必须找到该方法并反射执行,但在此之前,需要构建action与方法及所有必须元素的映射关系,有了这个映射关系才能得到方法,执行方法的对象,参数真正的类型

/**
 * @author 雫
 * @date 2021/1/20 - 9:28
 * @function 包含着action对应的所有需要的元素
 * 即ActionBeanDefinition包含配置XML文件中处理action行为的对应类,特定方法,特定方法的参数个数,参数名称,参数类型
 */
public class ActionBeanDefinition {
    
    
    private Class<?> klass;
    private Object object;
    private Method method;

    private int index;

    /*一个action对应着一个方法,这个方法可能需要多个参数,每个参数都要确定参数类型和参数名
    * 于是一个action对应的方法还需要有一个参数列表,这个参数列表存储着这个方法所需的所有参数类型和参数名
    * 且这个parameterList是根据XML文件配置的,顺序绝对正确
    * 只要我遍历这个parameterList依次通过pd.getName()作为键就可以从无序的argMap中取得对应的字符串类型的参数值
    * 经过gson转换后得到正确类型的参数值,进而实现给method.invoke()提供了参数列表*/
    private List<ParameterDefinition> parameterList;

    public ActionBeanDefinition() {
    
    
        this.parameterList = new ArrayList<>();
        this.index = 0;
    }

    Class<?> getKlass() {
    
    
        return klass;
    }

    void setKlass(Class<?> klass) {
    
    
        this.klass = klass;
    }

    Object getObject() {
    
    
        return object;
    }

    void setObject(Object object) {
    
    
        this.object = object;
    }

    Method getMethod() {
    
    
        return method;
    }

    void setMethod(Method method) {
    
    
        this.method = method;
    }

    /**
     * @Author 雫
     * @Description 向参数列表中添加PD的方法
     * PD是整体,包含参数名称和参数类型
     * @Date 2021/1/21 16:06
     * @Param [parameterDefinition]
     * @return void
     **/
    void addParameter(ParameterDefinition parameterDefinition) {
    
    
        this.parameterList.add(parameterDefinition);
    }

    /**
     * @Author 雫
     * @Description 判断参数列表中的PD是否还有下一个
     * 有则返回true,无则将index清0返回fasle
     * @Date 2021/1/21 16:08
     * @Param []
     * @return boolean
     **/
    boolean hasNextPD() {
    
    
        if(this.index < this.parameterList.size()) {
    
    
            return true;
        } else {
    
    
            this.index = 0;
            return false;
        }
    }

    /**
     * @Author 雫
     * @Description 从参数列表中取得当前index对应的PD
     * @Date 2021/1/21 16:12
     * @Param []
     * @return com.coisini.action.ParameterDefinition
     **/
    ParameterDefinition nextPD() {
    
    
        if(hasNextPD()) {
    
    
            ParameterDefinition parameterDefinition = this.parameterList.get(this.index);
            this.index++;
            return parameterDefinition;
        }
        return null;
    }

    /**
     * @Author 雫
     * @Description 返回当前参数列表的个数
     * @Date 2021/1/21 16:56
     * @Param []
     * @return int
     **/
    int getParameterCount() {
    
    
        return this.parameterList.size();
    }

}

在这里插入图片描述
映射工厂:

/**
 * @author 雫
 * @date 2021/1/20 - 9:27
 * @function action与处理它的所需要的全部元素的键值对
 */
public class ActionBeanFactory {
    
    

    //用来将XML文件中的映射关系从外存搬到内存,并建立actionPool
    //即键值对,键对应的是action的名字,如login,registry
    //值为ABD,即这个action需要的方法,这个方法所需要的所有元素存放在ABD中
    //包括(方法所在的类名,方法名,参数列表PD,和执行方法的对象)
    private static final Map<String, ActionBeanDefinition> actionPool;

    static {
    
    
        actionPool = new HashMap<>();
    }

    /**
     * @Author 雫
     * @Description 解析XML文件以获取actionPool
     * @Date 2021/1/21 16:18
     * @Param [xmlPath]
     * @return void
     **/
    public static void scanActionMapping(String xmlPath) throws Throwable {
    
    

        /*解析第一层action标签,获取action名,action对应类名,action对应方法名
        * 并同时通过类名获取元数据klass,通过klass获取一个通过无参构造生成的对象
        * 实例化一个ABD,将元数据和对象设置到其中*/
        new AbstractXMLParser() {
    
    
            @Override
            public void dealElement(Element element, int i) throws Throwable {
    
    
                String action = element.getAttribute("name");
                String className = element.getAttribute("class");
                String methodName = element.getAttribute("method");

                Class<?> klass = Class.forName(className);
                Object object = klass.newInstance();

                ActionBeanDefinition abd = new ActionBeanDefinition();
                abd.setKlass(klass);
                abd.setObject(object);

                /*解析第二层parameter标签,获取参数名称,和字符串形式的参数类型
                * 生成一个PD,这个参数列表需要的是参数名称和参数类型
                * 将解析后得到的参数名和经过类型转换后的参数类型设置到PD中
                * 将PD加入到ABD的PD列表中,XML解析器是循环工作的,所以ABD的PD列表得到了所有XML中声明的参数配置*/
                new AbstractXMLParser() {
    
    
                    @Override
                    public void dealElement(Element element, int i) throws Throwable {
    
    
                        String parameterName = element.getAttribute("name");
                        String strParameterType = element.getAttribute("type");

                        ParameterDefinition pd = new ParameterDefinition();
                        pd.setName(parameterName);

                        /*由于XML文件中的type看起来是在描述类型,但实际上都是字符串,不能拿来直接用
                        * 所以就需要根据字符串类的类型将其解析为类的类型*/
                        pd.setType(TypeParser.stringToClass(strParameterType));

                        //将所有pd装入abd的PD列表中
                        abd.addParameter(pd);
                    }
                }.parseTag(element, "parameter");

                /*现在有了一个较完整地ABD,但是ABD中的方法却没有找到,只有方法名字
                * 为了得到完整地方法,需要该方法的参数类型和参数个数
                * 由于采用了反射机制,所以这些参数的类型都是不确定的,用Class<?>处理
                * 且由于参数不止一个,但参数的个数可以通过ABD提供的getParameterCount()得到
                * 所以需要一个Class<?>类型的数组来将PD中的参数类型转移到该数组中
                * 该数组接下来要用于寻找action对应的method,这个method将用于invoke得到结果*/
                Class<?>[] parameterTypes = new Class<?>[abd.getParameterCount()];

                /*有了等待存储参数类型的数组parameterTypes后,就需要将pd中对应的参数类型取出给它
                * 根据PD提供的hasNextPD()和nextPD()方法就可以得到从XML文件中取得的经过类型转换后合适的有序的参数类型
                * 遍历abd的pd列表,将每一个pd的参数类型取出装到parameterTypes*/
                int index = 0;
                while (abd.hasNextPD()) {
    
    
                    ParameterDefinition pd = abd.nextPD();
                    parameterTypes[index++] = pd.getType();
                }

                /*根据完整的parameterTypes得到需要的方法,将该方法设置到abd中,
                这样就有了一个action对应的完整的abd,将这个abd加入到actionPool中*/
                Method method = klass.getDeclaredMethod(methodName, parameterTypes);
                abd.setMethod(method);
                actionPool.put(action, abd);

            }
        }.parseTag(AbstractXMLParser.getOneDocument(xmlPath), "action");

    }

    /**
     * @Author 雫
     * @Description 从actionPool中根据action名获取对应的ABD
     * @Date 2021/1/21 16:28
     * @Param [action]
     * @return com.coisini.action.ActionBeanDefinition
     **/
    public static ActionBeanDefinition getActionBeanDefinition(String action) {
    
    
        return actionPool.get(action);
    }


    /**
     * @Author 雫
     * @Description 在action对应的abd中替换掉方法执行所需的对象
     * 因为由ABD生成的对象是调用无参构造生成的,没有经过任何初始化
     * 但是在反射action对应的方法时,该方法内部可能进行了一些无参构造没有完成的成员使用
     * 如果直接使用反射机制的无参构造对象,就一定会出现空指针异常
     * 所以必须使用已经使用过的和整个app层有逻辑关联有依赖的对象
     * @Date 2021/1/24 16:51
     * @Param [action, object]
     * @return void
     **/
    public static void changeActionObject(String action, Object object) throws Exception {
    
    
        ActionBeanDefinition abd = actionPool.get(action);
        if(abd == null) {
    
    
            throw new Exception("actio没有定义");
        }
        abd.setObject(object);
    }

}

3.3 服务器处理来自客户端的资源请求

	/**
     * @Author 雫
     * @Description ServerConversation处理来自ClientConversation发来的请求
     * 根据为服务器配置的action和XML配置文件,找到对应的类和方法,通过反射机制执行这个方法,并得到结果并返回
     * @Date 2021/1/20 9:24
     * @Param [action, parameter]
     * @return java.lang.Object
     **/
    @Override
    public Object dealRequest(String action, String parameter) throws Exception {
    
    
        ActionBeanDefinition abd = ActionBeanFactory.getActionBeanDefinition(action);
        if(abd == null) {
    
    
            throw new Exception("对应action尚未定义");
        }
        Object object = abd.getObject();
        Method method = abd.getMethod();

        /*为了通过反射机制执行这个方法,光有ABD内的对象还不够
        * 还需要真正的参数值,这些值的类型应该和该方法的形参类型对应
        * 即将字符串化的参数parameter转换成method执行时所需要的实参数组,且顺序一致*/

        /*根据用户的参数parameter,获取argMap且没有丢失掉其中的<String, String>泛型*/
        ArgumentMaker argumentMap = new ArgumentMaker(parameter);

        int index = 0;
        //执行方法所需要的值数组,可以通过abd中的pd个数取得
        Object[] parameterValues = new Object[abd.getParameterCount()];
        //根据方法获取它对应的参数列表,用于后续取得参数类型,getParameterizedType()方法
        Parameter[] parameters = method.getParameters();

        /*遍历abd,依次取得pd的名字,通过正确顺序的参数名称从argMap中取得正确顺序的参数值列表parameterValues*/
        while(abd.hasNextPD()) {
    
    
            ParameterDefinition pd = abd.nextPD();
            String parameterName = pd.getName();

            /*从argMap中根据参数名把值取出来依次赋给parameterValues数组
            * 由于要返回的对象类型不明,所以要给泛型类型来让argument解析并获取对应泛型的值
            * 就必须用到Parameter类的getParameterizedType()方法来取得参数的泛型*/
            parameterValues[index] =
                    argumentMap.getParameter(parameterName, parameters[index].getParameterizedType());
            index++;
        }

        /*根据abd中的对象和参数值来执行action对应的方法并返回对应的结果*/
        Object result = null;
        result = method.invoke(object, parameterValues);

        return result;
    }

3.4 客户端响应来自服务器的资源

/**
     * @Author 雫
     * @Description clientConversation处理来自serverConversation传递的结果
     * 根据为客户端配置的action和XML配置文件,找到对应的类和方法,通过反射机制执行这个方法,并得到结果并返回
     * @Date 2021/1/24 16:09
     * @Param [action, parameter]
     * @return void
     **/
    @Override
    public void dealResponse(String action, String parameter) throws Exception {
    
    
        ActionBeanDefinition abd = ActionBeanFactory.getActionBeanDefinition(action);
        if(abd == null) {
    
    
            throw new Exception("对应action尚未定义");
        }
        Object object = abd.getObject();
        Method method = abd.getMethod();

        /*由于处理请求的方法最多只能返回一个参数
        * 所以要对未来action对应处理请求的方法进行检查,只允许用户定义的该方法接收0或1个参数*/
        Parameter[] parameters = method.getParameters();
        if(parameters.length > 1) {
    
    
            throw new Exception("对应执行方法只能有1个或0个参数");
        }

        //通常action对应的方法只处理一个参数
        Parameter para = parameters[0];

        //用于执行action对应方法的实参数组
        Object[] parameterValues = new Object[parameters.length];

        /*dealRequest方法最后得到一个Object类对象并通过gson转换成字符串
        * 这里要将这个字符换解析成action对应方法所需要的参数类型*/
        parameterValues[0] = Client.gson.fromJson(parameter, para.getParameterizedType());

        //执行action对应的方法
        method.invoke(object, parameterValues);
    }

3.5 执行流程

在这里插入图片描述
在这里插入图片描述

3.6 使用示例

服务器端需要反射执行的action对应的方法的XML文件
在这里插入图片描述
服务器端写好的待执行的方法:
在这里插入图片描述

客户端需要反射执行的action对应的方法的XML文件
在这里插入图片描述
客户端写好的待执行的方法:
在这里插入图片描述
测试结果:
在这里插入图片描述

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43541094/article/details/112846848