今天来讲解关于C-S框架内部,自动调用并执行应用层所编写的方法的一种处理方案。具体的应用场景为,客户端应用层通过我们的 C-S框架向服务器端发送请求,服务器端在收到请求后,根据请求的内容自动反射执行服务器应用层已编写好的程序,并将执行的返回值作为回复,发送给客户端。客户端在收到服务器端的回复后,根据回复内容自动反射执行客户端应用层已编写好的程序的过程。下面来讲解这一过程的具体实现方案。
首先,要做到请求与回复的协议化。我们的C-S框架只负责发送请求与接收回复,至于请求与回复的具体内容都是未来由应用层开发人员编写的,我们不得而知。例如,登录请求,在线好友名单请求,视频资源请求等等,与此产生的回复内容也不同。所以,要想实现开篇所说的自动反射执行相关程序,就需要对使用我们C-S框架的开发人员做出协议。即,必须通过调用C-S框架内部的sendRequest(......)方法发送请求,且请求的参数要符合协议。协议要求如下:第一个参数是自动反射执行的方法名,第二个参数是此方法的参数名与参数值构成的字符串。在这个协议下,我们服务器端就可以解析出相应的方法名与参数值,从而反射执行。其次,我们知道,在反射执行某一个方法时需要一个这个方法所在类的实例。而这个类却是应用层开发人员在未来编写的,我们又不得而知。所以,就需要另外一个功能模块,基于包扫描的方法名-实例工厂的建立(也可以基于XML解析来建立)。这个实例是一个类,里面包含了方法名所在的类的实例以及该方法名所对应的可以反射执行的Method类型的方法。有了这个工厂及解析的参数值,我们就真的可以反射执行该方法了。所以,在程序开始时,就要先把这个工厂建立起来。工厂的建立主要依赖于包扫描和注解,包扫描的具体实现在这里不做讲解。应用层的开发人员只要给某一个类,方法,参数加上我们给的注解,通过包扫描就可以把这个方法加到工厂里,供以后反射执行。反射执行的结果也就是请求的结果,通过C-S框架可以发送给客户端,客户端收到回复后,执行与服务器端类似的操作,自动反射执行客户端应用层的方法。
接下来对用户层发出登录请求并结合部分代码进行讲解。首先,客户端应用层的登陆界面具有账号和密码的输入框,再输入完账号密码后,通过点击登录按钮发出登录请求。下面是登录按钮的事件响应代码:
jbtnLogin.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String id = jtxtUserName.getText().trim();
String password = new String(jpswPassword.getPassword());
client.sendRequest("login", new ArgumentMaker()
.addArg("id", id)
.addArg("password", password)
.toString());
}
});
获得文本框内账号和密码的字符串后,用参数构造器ArgumentMaker()将它们打包成协议好的字符串形式,再加上方法名通过调用C-S框架客户端的sendRequest(String methodName, String parameter)方法将请求发送给服务器端。
在服务器端收到这个请求后,我们希望框架内部自动调用应用层相应的处理用户登录的方法,如下面这个应用层开发人员所编写的类里面处理登录请求的方法:
@ActionClass
public class StudentAction {
public StudentAction() {
}
@Action("login")
public StudentModel studentLogin(
@ActionParamter("id") String id,
@ActionParamter("password") String password) {
// 这里应该从数据库表中检测id和password的正确性
// 如果id和password都正确,则返回该账户除了password之外的一些有效信息
// 否则,返回的"ERROR"字符串。
// 具体代码由应用层开发人员编写,这里不给出具体代码。
return null;
}
}
这里我们看到,需要在类,方法及参数前加上规定的注解。这些类以及方法由应用层开发人员编写,只要向框架提供这些类所在的包名,框架内部就可以通过包扫描,扫描指定的目录路径下的所有类,并把带有规定注解的方法放到方法名-实例工厂里。这里的实例类并不复杂,如下(省略了成员的Getters和Setters):
public class ActionBeanDefinition {
private Class<?> klass;
private Object object;
private Method method;
ActionBeanDefinition() {
}
}
这个类主要是存储反射执行某方法所需要的类类型,类的实例,具体方法。
方法名-实例工厂类内部具体实现如下:
public class ActionBeanFactory {
private static final Map<String, ActionBeanDefinition> actionPool;
static {
actionPool = new HashMap<>();
}
public static void addActionClass(Object object) {
Class<?> klass = object.getClass();
processOneClass(klass, object);
}
//扫描指定目录下的类,并把带有@ActionClass注解的类实例化,加入到工厂中。
public static void scanActionPackage(String packageName) {
new PackageScanner() {
@Override
public void dealClass(Class<?> klass) {
if (klass.isPrimitive()
|| klass.isAnnotation()
|| klass.isInterface()
|| klass.isEnum()
|| klass.equals(String.class)
|| klass.isArray()
|| !klass.isAnnotationPresent(ActionClass.class)) {
return;
}
try {
Object object = klass.newInstance();
processOneClass(klass, object);
} catch (Exception e) {
e.printStackTrace();
}
}
}.packageScanner(packageName);
}
//将 方法名-实例 添加到工厂的具体代码
private static void processOneClass(Class<?> klass, Object object) {
Method[] methods = klass.getDeclaredMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(Action.class)) {
continue;
}
ActionBeanDefinition abd = new ActionBeanDefinition();
abd.setKlass(klass);
abd.setObject(object);
abd.setMethod(method);
Action action = method.getAnnotation(Action.class);
String actionName = action.value();
actionPool.put(actionName, abd);
}
}
//通过某一方法名得到反射执行所需要的实例
static ActionBeanDefinition getActionBean(String action) {
return actionPool.get(action);
}
}
需要注意的是,这个方法名-实例工厂类需要在服务器服务开始前就要建立好,不然可能发生逻辑错误。
在准备好了这个工厂后,当服务器端收到请求后会执行下面这个方法来处理请求:
public void dealRequest(NetMessage message) {
//在收到的消息中,解析出方法名与参数值
String action = message.getAction();
int index = action.indexOf(":");
String requestAction = action.substring(0, index);
String responseAction = action.substring(index + 1);
String para = message.getPara();
// 根据方法名,在工厂中得到对应的类和相关方法;
// 结合解析出的参数值,反射执行相关方法,并得到返回值。
String result = null;
try {
result = actionExecuter.doRequest(requestAction, para);
} catch (Exception e) {
e.printStackTrace();
}
// 将这个方法的返回值,转换成字符串,并回传给客户端的会话层。
send(new NetMessage()
.setCommand(ENetCommand.RESPONSE)
.setAction(responseAction)
.setPara(result));
}
其中,做反射执行的方法doRequest(requestAction, para);的具体代码如下:
public String doRequest(String action, String para) throws Exception {
//根据方法名得到相应的实例;
ActionBeanDefinition abd = ActionBeanFactory.getActionBean(action);
if (abd == null) {
throw new Exception("action:[" + action + "]未定义!");
}
Method method = abd.getMethod();
//反射执行该方法
Object result = method.invoke(abd.getObject(), getParaValues(method, para));
//将反射执行的结果打包成字符串,返回。
return gson.toJson(result);
}
值得注意的是,我们服务器端解析出的参数值都是String类型的,在反射执行前,需要把参数值还原回它本身的类型。这一过程也比较复杂,实现代码如下:
private Object[] getParaValues(Method method, String para) throws Exception {
Parameter[] parameters = method.getParameters();
if (parameters.length <= 0) {
return new Object[] {};
}
Object[] res = new Object[parameters.length];
ArgumentMaker am = new ArgumentMaker(para);
for (int index = 0; index < parameters.length; index++) {
Parameter parameter = parameters[index];
if (!parameter.isAnnotationPresent(ActionParamter.class)) {
throw new Exception("第" + (index+1) + "个参数没有注解!");
}
ActionParamter ap = parameter.getAnnotation(ActionParamter.class);
String paraName = ap.value();
Type paraType = parameter.getParameterizedType();
res[index] = am.getValue(paraName, paraType);
}
return res;
}
以上就是服务器端在收到客户端登录请求后,实现自动调用并执行服务器端应用层的方法的一系列过程。在处理完请求后,由服务器端发送给客户端一个回复,在客户端收到回复后,根据回复内容,也要自动调用并执行客户端应用层的方法,具体过程跟服务器端类似,不再赘述。
综上,可以看出灵活地使用反射机制技术,可以实现一些强大的功能。合理的层次划分,能使复杂的程序的编写变得清晰。补充:文字描述与具体代码中有不匹配的情况,如方法名对应于代码中的action。