C/S开发框架之action的处理
客户端向服务器请求“资源”, 服务器需要将“资源”准备好,并回送给客户端。
比如说,针对用户登录这一操作,客户端将用户输入的账号,密码等信息发送给服务器,请求服务器确认用户合法性;那么,服务器需要在数据库中进行确认用户的合法性,并将确认结果回送给客户端。
这里,对于客户端请求的系列操作代码,不是我们C/S工具可以做的,而应该是使用这个工具的开发者应该去实现的。但是,我们希望我们的C/S工具能够“自动识别,自动执行”开发者相关操作的代码,并将结果自动回送给客户端。
关于NetMessage中的action
这个问题的开始要从用户登录说起,
private void dealUserLogin() {
String id = jtxtUserName.getText();
String password = new String(jpswPassword.getPassword());
password = String.valueOf(password.hashCode());
ActionBeanFactory.setObject("userLogin", this);
client.sendRequest("userLogin", new ArgumentMaker()
.add("id", id)
.add("password", password)
.toString());
}
从上述代码可以看出,客户端将向服务器发送一个请求,其中action是userLogin,那么,用户的请求不仅仅只是登录,还可能是,比如查询课表等其他相应的对“资源”的请求,因此,NetMessage中的action的作用就在于此,不同的action应该对应着不同的“操作代码”。所以,我们以action为键,以其对应的“操作”为值,形成一个Map。
也就是说,我们C/S工具的工作是:
1.读取(扫描)相应的配置文件或者注解方式;
2.把action与对应的方法装到一个Map中;
3.在处理action时,找到Map中对应的类和方法;
4.根据用户提供的参数的值,填充方法的参数;
5.用反射机制执行这个方法。
首先,先来实现action对应的“操作”,它应该包括,object,method以及参数。
package com.mec.csFramework.action;
import java.lang.reflect.Method;
import java.util.List;
public class ActionBeanDefinition {
private Object object;
private Method method;
private List<ActionParameter> parameterList;
ActionBeanDefinition() {
}
List<ActionParameter> getParameterList() {
return parameterList;
}
void setParameterList(List<ActionParameter> parameterList) {
this.parameterList = parameterList;
}
Object getObject() {
return object;
}
void setObject(Object object) {
this.object = object;
}
Method getMethod() {
return method;
}
void setMethod(Method method) {
this.method = method;
}
}
这个类的实现很简单,三个成员及Setters和Getters,并且都是包内权限。关于三个成员,object和method很好理解,至于参数,我们等会儿再做解释。
接下来,就是准备一个以action为键,对应的“操作”为值(即ActionBeanDefinition)的一个Map,来负责通过包扫描找到对应“操作”的类及方法。这步操作有不同的方法可以实现,可以通过扫描XML文件反射机制获取方法和参数,
<?xml version="1.0" encoding="UTF-8"?>
<actions>
<action name = "userLogin" class = "com.mec.chatRoom.server.user.action.UserAction" method = "getUserById">
<parameter name = "id" type = "string"></parameter>
<parameter name = "password" type = "string"></parameter>
</action>
</actions>
也可以通过注解,将action与对应方法进行描述,这里需要准备三个注解,分别对类,方法和参数。
@Retention(RUNTIME)
@Target(TYPE)
public @interface Actioner {
}
@Retention(RUNTIME)
@Target(METHOD)
public @interface Mapping {
String value();
}
@Retention(RUNTIME)
@Target(PARAMETER)
public @interface Argument {
String value();
}
相应地,将对应注解加到action对应的具体的类(APP开发者写的)中相应位置。这里,我们编写了userLogin对应的”操作“类,及UserAction.
package com.mec.chat_room.server.user.action;
import com.mec.chat_room.server.user.model.UserInfo;
import com.mec.chat_room.server.user.service.UserService;
import com.mec.csframework.action.Actioner;
import com.mec.csframework.action.Argument;
import com.mec.csframework.action.Mapping;
@Actioner
public class UserAction {
private UserService userService;
public UserAction() {
this.userService = new UserService();
}
@Mapping("userLogin")
public UserInfo getUserById(
@Argument("id") String id,
@Argument("password") String password) {
UserInfo user = userService.getUserById(id, password);
if (user == null) {
user = new UserInfo();
user.setId("ERROR");
} else {
user.setPassword(null);
}
return user;
}
}
注解准备好后,就可以编写这个Map了。
package com.mec.csFramework.action;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.mec.application.dao.Annotationer;
import com.mec.application.dao.Argument;
import com.mec.application.dao.Mapping;
import com.mec.util.PackageScanner;
public class ActionBeanFactory {
private static final Map<String, ActionBeanDefinition> actionPool;
static {
actionPool = new HashMap<String, ActionBeanDefinition>();
}
public ActionBeanFactory() {
}
ActionBeanDefinition getActionBeanDefinition(String action) {
return actionPool.get(action);
}
//包扫描,找到action对应的类
public static void scanPackage(String packageName) {
new PackageScanner() {
@Override
public void dealClass(Class<?> klass) {
if (klass.isAnnotation()
|| klass.isArray()
|| klass.isEnum()
|| klass.isInterface()
|| klass.isPrimitive()
|| !klass.isAnnotationPresent(Annotationer.class)) {
return;
}
try {
Object object = klass.newInstance();
Method[] methods = klass.getDeclaredMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(Mapping.class)) {
continue;
}
Mapping mapping = method.getAnnotation(Mapping.class);
String action = mapping.value();
ActionBeanDefinition abd = new ActionBeanDefinition();
abd.setMethod(method);
abd.setObject(object);
dealParameter(abd, method);
actionPool.put(action, abd);
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}.scanPackage(packageName);
}
//独立出来专门处理参数
private static void dealParameter(ActionBeanDefinition abd, Method method) {
List<ActionParameter> parameterList = new ArrayList<ActionParameter>();
Parameter[] parameters = method.getParameters();
for (Parameter parameter : parameters) {
if (!parameter.isAnnotationPresent(Argument.class)) {
continue;
}
Argument argument = parameter.getAnnotation(Argument.class);
String parameterName = argument.value();
ActionParameter actionParameter = new ActionParameter(parameterName, parameter);
parameterList.add(actionParameter);
}
abd.setParameterList(parameterList);
}
}
现在,我们来说一下对参数的处理。之所以给参数加注解,是因为为了获取参数的名字,即"id" 和"password",因为hashMap中的键值对是无序的,就是说我们往Map里”写“的时候是先id后password的顺序,当”读“的时候有可能就是先password后id的顺序,直接输出也是没办法获取参数名字的,只能得到arg0,arg1这样的参数名。而用户输入的参数的值,实质在一开始就已经通过ArgumentMaker打包成了json字符串,而我们是通过ArgumentMaker中的getValue方法获取的参数的值,而这个方法需要的参数就是参数的名字和类型(这里可能说的比较绕,结合代码就可以看得更清楚了)。所以,基于这样的考虑,对参数做如下处理:
package com.mec.csframework.action;
import java.lang.reflect.Parameter;
public class ActionParameter {
private String name;
private Parameter parameter;
ActionParameter() {
}
ActionParameter(String name, Parameter parameter) {
this.name = name;
this.parameter = parameter;
}
String getName() {
return name;
}
void setName(String name) {
this.name = name;
}
Parameter getParameter() {
return parameter;
}
void setParameter(Parameter parameter) {
this.parameter = parameter;
}
}
反射机制自动执行方法
以上有关注解的读取和Map的构建,是在服务器启动前就应该完成的。
我们做的C/S工具是在将来会被包括我们在内的编程者用来编写APP等基于C/S模式的系统;APP开发者要用到我们C/S工具中提供的REQUEST网络命令,确定action的值,发送客户端资源请求;同时APP开发者需要自己编写与action对应的类和方法,比如上述的UserAction;我们的C/S工具能自动根据action的值,找到并执行这些方法,以完成相关资源请求。
package com.mec.csframework.action;
public interface IActionProcessor {
String dealRequest(String action, String parameter) throws Exception;
void dealResponse(String action, String parameter) throws Exception;
}
给一个默认的接口适配器,完成对请求和响应的具体处理:
package com.mec.csframework.action;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.List;
import com.mec.util.ArgumentMaker;
public class DefaultActionProcessor implements IActionProcessor {
public DefaultActionProcessor() {
}
@Override
public String dealRequest(String action, String parameter) throws Exception {
ActionBeanDefinition actionBean = ActionBeanFactory.getActionBean(action);
if (actionBean == null) {
throw new Exception("action:[" + action + "]未定义");
}
Object object = actionBean.getObject();
Method method = actionBean.getMethod();
List<ActionParameter> parameterList = actionBean.getParameterList();
Object[] args = prepareParameters(parameterList, parameter);
Object result = method.invoke(object, args);
return ArgumentMaker.gson.toJson(result);
}
private Object[] prepareParameters(List<ActionParameter> parameterList, String parameter) {
ArgumentMaker argumentMaker = new ArgumentMaker(parameter);
if (parameterList.isEmpty()) {
return new Object[] {};
}
Object[] result = new Object[parameterList.size()];
int index = 0;
for (ActionParameter actionParameter : parameterList) {
String parameterName = actionParameter.getName();
Type type = actionParameter.getParameter().getParameterizedType();
result[index++] = argumentMaker.getValue(parameterName, type);
}
return result;
}
@Override
public void dealResponse(String action, String parameter) throws Exception {
// 处理RESPONSE
ActionBeanDefinition abd = ActionBeanFactory.getActionBean(action);
Object object = abd.getObject();
Method method = abd.getMethod();
if (method.getParameterCount() <= 0) {
method.invoke(object, new Object[] {});
} else {
Type type = method.getParameters()[0].getParameterizedType();
Object para = ArgumentMaker.gson.fromJson(parameter, type);
method.invoke(object, new Object[] {para});
}
}
}
dealRequest()方法应该是在ServerConversation中处理REQUEST网络命令的时候执行的。即,客户端向服务器端发送REQUEST请求,并指定action和参数,服务器根据action在Map中找到对应的ActionBeanDefinition,并利用反射机制执行相关的方法,得到该方法的返回值,并将其字符串化;客户端接收RESPONSE命令,并根据action和字符串化的返回值,找到处理的方法,即dealResponse()方法,同样利用反射机制自动执行。
小结
这样的一个C/S框架,其实是非常粗糙简陋的,非常稚嫩的,一些细节问题并没有细化完整的解决,有些功能没有完全的实现,只是完成了一些基本的功能,程序有很大的改进余地。
至此,关于CSFramework的主要核心内容就介绍完了。