文章目录
- 1 CS框架的搭建
- 1.1 结构
- 1.2 信息结构
- 1.3 通信层
- 1.4 会话层
- 1.5 方法层
- 1.6 app层
- 2 各种通信功能的实现过程
- 2.1 客户端连接到服务器的过程
- 2.2 私聊信息的过程
- 2.3 群聊信息的过程
- 2.4 客户端下线的过程
- 2.5 服务器强制宕机的过程
- 3 资源分发器
- 3.1 资源分发器
- 3.1.1 分发器概念
- 3.1.2 分发器需求分析
- 3.1.3 参数的传递形式和解析过程
- 3.2 构建action对应的方法映射
- 3.3 服务器处理来自客户端的资源请求
- 3.4 客户端响应来自服务器的资源
- 3.5 执行流程
- 3.6 使用示例
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文件
客户端写好的待执行的方法:
测试结果: