分发器的应用场合
在csFrameWork中,最主要的就是彼此的交互问题。客户端与客户端之间的“交流”,事实上,是通过服务器这个中间媒介,也就是,客户端会向服务器发出各种请求,而服务器就要根据请求做出对应的响应。其中用到的就是分发器。
分发器的实现
在csFrameWork里面,我们实现的是 request 和 response 分发器。客户端发出 request 动作请求,在服务器端回应 response 动作请求。
由于,客户端会向服务器端所发出请求,我们把这个请求作为request,传递给服务器端,服务器进行解析,再去执行对应的方法,将执行方法的返回值作为response,传送给客户端,在客户端进行解析,再处理相关的结果。
由此可见,分发器的作用是,客户端需要执行什么操作,服务器就回应什么操作。比如:客户端要进行app的登录,他想验证账号和密码是否正确,就需要把账号、密码和dealUserLogin作为一个request,发送给服务器,服务器检测到了并进行解析,然后找到对应的dealUserLogin的方法,反射机制执行,将结果的返回值和dealUserLogin作为一个request返回到客户端,客户端进行解析,再找到对应的dealUserLogin方法反射机制执行。
下面,将用两种方法实现上述讲的csFrameWork里面的分发器。
方法一:用注解+包扫描+反射机制实现:
我们先编写的是ActionBeanDefinition类
public class ActionBeanDefinition {
// 需要执行的方法
private Method method;
// 该方法所在的类
private Class<?> klass;
// 该类的对象
private Object object;
public ActionBeanDefinition() {
}
Method getMethod() {
return method;
}
void setMethod(Method method) {
this.method = method;
}
Class<?> getKlass() {
return klass;
}
void setKlass(Class<?> klass) {
this.klass = klass;
}
Object getObject() {
return object;
}
void setObject(Object object) {
this.object = object;
}
}
这个类是存放的是一个方法,这个方法在以后,如果发送的某个请求需要执行的是该方法的话,那么它将会被执行,否则,不执行该方法。这样的方法是由使用我们这套工具的用户编写的,方法不止一个,于是,我们需要一个ActionBeanFactory类,用这个类来存储这些方法。
懒汉vs饿汉
包扫描技术
public class ActionBeanFactory {
// 使用单例工厂,目的是为了不管怎么掉用,这个actionPool只有一份,
// 防止因出现多份而找不到所需要执行的方法
// 以传递过来的线管操作的名字作为键,该操作对应的方法、方法所在的类及类的对象作为值
private static final Map<String, ActionBeanDefinition> actionPool;
// 饿汉模式
static {
actionPool = new HashMap<String, ActionBeanDefinition>();
}
public ActionBeanFactory() {
}
// 包扫描技术
// 通过扫描指定的包,将满足要求的方法扫描到antionPool里面
public static void actionPackageScan(String packageName) {
new PackageScanner() {
@Override
public void dealClass(Class<?> klass) {
if (klass.isPrimitive() || klass.isEnum()
|| klass.isInterface()
|| klass.isAnnotation()
|| klass.isArray()
|| !klass.isAnnotationPresent(ActionClass.class)) {
return ;
}
// 执行到这里,证明满足了类含有ActionClass注解
try {
Object object = klass.newInstance();
Method[] methods = klass.getMethods();
for (Method method : methods) {
if (! method.isAnnotationPresent(Action.class)) {
continue ;
}
// 执行到这里,证明满足了方法含有Action注解
ActionBeanDefinition abd = new ActionBeanDefinition();
abd.setObject(object);
abd.setKlass(klass);
abd.setMethod(method);
Action action = method.getAnnotation(Action.class);
String actionName = action.name();
actionPool.put(actionName, abd);
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}.packageScanner(packageName);
}
// 方便外面调用,取得该BeanMethodDefinition
public static ActionBeanDefinition getAction(String actionName) {
return actionPool.get(actionName);
}
}
由于上述实现了包扫描,那么需要把满足要求的类和方法加上对应的注解,确保能将他们扫描到actionPool里面。
// 类的注解
@Retention(RUNTIME)
@Target(TYPE)
public @interface ActionClass {
String name() default "";
}
// 方法的注解
@Retention(RUNTIME)
@Target(METHOD)
public @interface Action {
String name() default "";
}
// 参数的注解
@Retention(RUNTIME)
@Target(PARAMETER)
public @interface ActionParameter {
String value() default "";
}
代码写到这里,我们已经可以将满足要求的方法,通过包扫描,放到一个actionMap里面,现在就需要客户端发送请求,我们根据请求,在actionMap里面找到对应的方法,并执行,并将结果返回到客户端,在客户端解析请求,执行与之对应的方法。
下面将要编写我们的执行过程
编写一个IActionExecuter接口
public interface IActionExecuter {
String doRequest(String action,String parameter) throws Exception;
void doResponse(String action,String parameter) throws Exception;
}
实现该接口的类必须实现上面两个方法,通过方法的名字可以看出,一个是处理来自客户端的request请求的,一个是处理来自服务器的response请求的。
我们可以再编写一个ActionExecuter类来实现刚才的接口,把他作为一个默认的实现类。因为大多数的csFrameWork的app,都需要的是实现服务器和客户端之间的“沟通”,所以可以默认实现。因为我们写的是接口,用户如果想完善其他的功能,(比如从不同的数据库里面获取数据),可以通过实现接口写自己的类。
这里运用Gson类,Gson的巧妙使用 和 将Gson化的Map解析成对应的Map
public class ActionExecuter implements IActionExecuter {
private static final Gson gson = new GsonBuilder().create();
public ActionExecuter() {
}
private Object[] getParameterObject(Method method,String parameter) throws Exception {
// ArgumentMaker类存在的意义,请看上述的[将Gson化的Map解析成对应的Map]博文
ArgumentMaker argumentMaker = new ArgumentMaker(parameter);
Parameter[] parameters = method.getParameters();
Object[] objects = new Object[parameters.length];
for (int index = 0;index < parameters.length;index++) {
if (!parameters[index].isAnnotationPresent(ActionParameter.class)) {
throw new Exception("参数" + parameters[index].getName() + "没有注解");
}
ActionParameter actionParameter = parameters[index]
.getAnnotation(ActionParameter.class);
String parameterName = actionParameter.value();
// 这里.getParameterizedType()方法可以解决泛型问题
Type parameterKlass = parameters[index].getParameterizedType();
Object object = argumentMaker.getObject(parameterName, parameterKlass);
objects[index] = object;
}
return objects;
}
@Override
public String doRequest(String action, String parameter) throws Exception {
// 在actionPool中找到对应的类
ActionBeanDefinition abd = ActionBeanFactory.getAction(action);
if (abd == null) {
throw new Exception("服务器端 未找到action对应的类");
}
Object object = abd.getObject();
Method method = abd.getMethod();
// 反射机制执行该方法
Object result = method.invoke(object, getParameterObject(method,parameter));
// 将该方法的执行结果过 Gson 巧妙转化成字符串返回到客户端
return gson.toJson(result);
}
@Override
public void doResponse(String action, String parameter) throws Exception {
ActionBeanDefinition abd = ActionBeanFactory.getAction(action);
System.out.println("-------------------");
if (abd == null) {
throw new Exception("客户端 未找到action对应的类");
}
Method method = abd.getMethod();
Object object = abd.getObject();
if (parameter.equalsIgnoreCase("null")) {
method.invoke(object, new Object[] {});
return ;
}
ArgumentMaker argumentMaker = new ArgumentMaker(parameter);
Parameter para = method.getParameters()[0];
if (!para.isAnnotationPresent(ActionParameter.class)) {
throw new Exception("参数" + para.getName() + "没有注解");
}
Type paraType = para.getParameterizedType();
Object paraObject = argumentMaker.getObject(action, paraType);
method.invoke(object, paraObject);
}
}
到此为止,我们可以将上述的几个类打成一个.jar包,下面我来测试一下。
先写一个Action类
@ActionClass
public class StudentAction {
public StudentAction() {
}
@Action(name="login")
public StudentInfo getUserById(@ActionParameter("id") String id
,@ActionParameter("password") String password) {
System.out.println("id : " + id );
System.out.println("password : " + password);
StudentInfo studentInfo = new StudentInfo();
studentInfo.setId(id);
studentInfo.setPassword(password);
return studentInfo;
}
}
写一个Model类
public class StudentInfo {
private String id;
private String password;
public StudentInfo() {
}
public synchronized String getId() {
return id;
}
public synchronized void setId(String id) {
this.id = id;
}
public synchronized String getPassword() {
return password;
}
public synchronized void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "id=" + id + ", password=" + password;
}
}
进行测试:
public class Test {
public static void main(String[] args) {
Gson gson = new GsonBuilder().create();
ActionBeanFactory.actionPackageScan("com.csframework.annotation.action");
ActionExecuter actionExecuter = new ActionExecuter();
try {
String result = actionExecuter.doRequest("login",new ArgumentMaker()
.addAction("id", "123123")
.addAction("password", "dadasd")
.toString());
StudentInfo student = gson.fromJson(result, StudentInfo.class);
System.out.println(student);
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行结果如下:
方法二:用xml文件配置+反射机制实现:
与方法一相比,方法二的扫描不是通过注解工具,而是通过xml文件解析工具XML文件的解析(工具思想)。将上述需要加注解的类,编写成xml文件,再扫描到actionPool里面,需要执行的时候,和方法一一样,找出来并执行。
来看具体代码:
public class ActionBeanFactory {
private static final Map<String, ActionBeanDefinition> actionPool;
static {
actionPool = new HashMap<String, ActionBeanDefinition>();
}
// 将一个类设置进来,表明这个类里面存在着在外面需要反射机制执行的方法
public static void setObject(String action, Object object) {
ActionBeanDefinition actionBeanDefinition = actionPool.get(action);
if (actionBeanDefinition != null && actionBeanDefinition.getObject() == null) {
actionBeanDefinition.setObject(object);
}
}
private static void getMethod(Element element
,ActionBeanDefinition actionBeanDefinition) {
String methodName = element.getAttribute("method");
List<String> parameterTypes = new ArrayList<String>();
new XmlParser() {
@Override
public void dealElement(Element element, int index) {
String name = element.getAttribute("name");
String typeName = element.getAttribute("type");
actionBeanDefinition.add(name);
parameterTypes.add(typeName);
}
}.parseTag(element, "parameter");;
Class<?>[] parameters = new Class<?>[parameterTypes.size()];
for (int index = 0;index < parameters.length;index++) {
String type = parameterTypes.get(index);
parameters[index] = MecType.toType(type);
}
Class<?> klass = actionBeanDefinition.getKlass();
try {
Method method = klass.getMethod(methodName, parameters);
actionBeanDefinition.setMethod(method);
} catch (NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
}
//xml文件解析工具
public static void scanXml(String path) {
new XmlParser() {
@SuppressWarnings("unused")
@Override
public void dealElement(Element element, int index) {
String actionName = element.getAttribute("name");
String klassName = element.getAttribute("class");
try {
Class<?> klass = Class.forName(klassName);
Object object = klass.newInstance();
ActionBeanDefinition actionBeanDefinition = new ActionBeanDefinition();
actionBeanDefinition.setKlass(klass);
getMethod(element,actionBeanDefinition);
actionPool.put(actionName, actionBeanDefinition);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}.parseTag(XmlParser.getDocument(path), "action");;
}
public static ActionBeanDefinition getAction(String action) {
return actionPool.get(action);
}
}
.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<actions>
<action name="userLogin" class="com.csframework.model.UserLogin" method="getUserById">
<parameter name="netId" type="string"></parameter>
<parameter name="id" type="string"></parameter>
<parameter name="password" type="string"></parameter>
</action>
</actions>
IActionProcesser接口与方法一的IActionExecuter接口类似
public class DefaultActionProcesser implements IActionProcesser {
public DefaultActionProcesser() {
}
@Override
public String dealRequest(String action, String parameter) throws Exception{
//需要进行扫描
ActionBeanDefinition actionBeanDefinition = ActionBeanFactory.getAction(action);
if (actionBeanDefinition == null) {
throw new ActionDoNotFondException("actionBeanDefinition 未定义");
}
Object object = actionBeanDefinition.getObject();
if (object == null) {
object = actionBeanDefinition.getKlass().newInstance();
}
Method method = actionBeanDefinition.getMethod();
Object[] parametersValue = getParameters(parameter,actionBeanDefinition,method);
Object result = (String) method.invoke(object, parametersValue);
// 返回Gson字符串
return ArgumentMaker.gson.toJson(result);
}
private Object[] getParameters(String parameter,ActionBeanDefinition actionBeanDefinition,Method method) {
Parameter[] parameters = method.getParameters();
ArgumentMaker argumentMaker = new ArgumentMaker(parameter);
List<String> parameterNames = actionBeanDefinition.getParameterList();
Object[] objects = new Object[parameters.length];
for (int index = 0;index < parameterNames.size();index++) {
Object object = argumentMaker.getValue(parameterNames.get(index),
parameters[index].getParameterizedType());
objects[index] = object;
}
return objects;
}
@Override
public void dealResponse(String action, String parameter) throws Exception{
ActionBeanDefinition actionBeanDefinition = ActionBeanFactory.getAction(action);
if (actionBeanDefinition == null) {
throw new ActionDoNotFondException(action + "未定义");
}
Object object = actionBeanDefinition.getObject();
Method method = actionBeanDefinition.getMethod();
Parameter[] parameters = method.getParameters();
Object[] parameterObject = new Object[1];
parameterObject[0] = ArgumentMaker.gson.fromJson(parameter, parameters[0].getParameterizedType());
method.invoke(object, parameterObject);
}
}
Model类
public class UserLogin {
public UserLogin() {
}
// 为了简单化,这个方法的返回值为String类型
public String getUserById(String netId,String id,String password) {
System.out.println("执行了");
return netId + id + password;
}
}
Test类:
public class Test {
public static void main(String[] args) {
Gson gson = new GsonBuilder().create();
ActionBeanFactory.scanXml("/chat_room_action.mapping.xml");
DefaultActionProcesser actionProcessor = new DefaultActionProcesser();
try {
String result = actionProcessor.dealRequest("userLogin"
,new ArgumentMaker().add("netId", "SSSSS")
.add("id", "23123")
.add("password", "ferd321").toString());
System.out.println("result : " + result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行结果:
总结
到这里,我们的这两套工具就可以完全独立出来。对比发现,第二套采用xml解析工具的分发器没有第一套灵活。现在我们只需要用户向我们的框架中注入规定的方法,而不去关心其具体实现,达到了解耦的目的。我们以后可以在服务器和客户端分别写出请求和响应的方法,通过网络间字符串的传递,在服务器和客户端分别执行我们注入的方法!