C-S小型框架中解决用户动作问题

应用场景

对于这篇文章,如果有什么地方简述有误,还请多多指教!!!

概述

假设用户发送一个登录请求,那么服务器该如何处理呢?
  1. 首先,用户在发送请求的时候向服务器发送:动作、和对应的参数(账户,密码)。
  2. 那么,这里我们就需要明白,用户发送不同动作,服务器就会有不同的响应,这时我们就需要根据不同的响应执行相应的方法。
  3. 但是,问题来了,我们在编写服务器端的时候是不知道用户会有哪些动作的,因此我们需要用到注解或者配置XML。
  4. 对于第二个参数,动作不同,参数也就不一样,因此我们需要一个对象,将所需的参数写在一个类里,然后将该类的对象传过去。但是Java里是值传递,没有引用传递。因此我们需要用到Json这个工具(可以将对象转换成字符串,也可以将反转成对象)这里,所用方法是以参数名为键,参数值为对象的字符串存放到一个Map里,然后再将Map利用Json转换为字符串。
  5. 此外,我们还需用到包扫描和反射机制。因为我们不知道处理用户动作的方法在哪个类里,而且用反射机制执行方法的时候,我们需要知道方法名,参数类型,参数名。但是,反射机制提供的Parameter类里不能得到对应的参数名,因此需要给参数也加上注解,最后通过注解得到参数名,然后根据参数名在Map里找到对应的字符串值,再将该值转换成对应的对象。因此,需要在有该方法的类上加一个注解,通过包扫描找到该类然后通过注解找到Action对应的方法和参数名。
  6. 最后再根据反射机制执行该方法,这样就可以动态的解决问题了。
下面我们从代码角度考虑该问题
//这是服务器端的方法
protected void dealNetMessage(NetMessage netMessage) {
		ENetCommand command = netMessage.getCommand();
		//客户端发送一个REQUEST命令,服务器端获得该命令,然后执行相应的方法。
		if (command.equals(ENetCommand.REQUEST)) {
			//action(动作)由request 以及 & 和response三部分组成,根据不同的动作服务器有不同的响应
			String action = netMessage.getAction();
			int colonIndex = action.indexOf('&');
			//获得请求命令
			String request = action.substring(0, colonIndex);
			
			try {
				//根据请求和参数,利用注解包扫描,并且利用反射机制,反射执行
				String result = server.getActioner()
						.executeRequest(request, netMessage.getPara());
				send(new NetMessage()
						.setCommand(ENetCommand.RESPONSE)
						.setAction(action)
						//把执行完的结果当成参数传给客户端
						//这里的result是执行完动作对应的方法的结果的返回值
						.setPara(result));
			} catch (Exception e) {
				e.printStackTrace();
			}
			
			return;
		}
		try {
			CommandDispatcher.dispatcherCommand(this, netMessage);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
//根据Action对应出相应的方法
public class ActionDefinition {
	//对应的方法在哪个类里
	private Class<?> klass;
	//该action对应的方法
	private Method method;
	//要反射执行一个方法必须要一个对象
	private Object object;
	//需要若干个参数,所以需要一个List来存储参数
	private List<ActionParameter> parameterList;
	
	ActionDefinition() {
		parameterList = new ArrayList<>();
	}

	List<ActionParameter> getParameterList() {
		return parameterList;
	}
	
	Class<?> getKlass() {
		return klass;
	}

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

	Method getMethod() {
		return method;
	}

	/**
	 * 将包扫描时扫描到的方法设置给该类,并且将该方法的参数保存到List列表里面
	 * @param method
	 * @throws Exception
	 */
	void setMethod(Method method) throws Exception {
		this.method = method;
		
		Parameter[] parameters = method.getParameters();
		for (int i = 0; i < parameters.length; i++) {
			Parameter parameter = parameters[i];
			//扫描该方法的注解
			//注释Para是否在parameter上,是返回true,否返回false
			if (!parameter.isAnnotationPresent(Para.class)) {
				throw new Exception("方法[]的第" + (i+1) + "个参数无注解!");
			}
			Para para = parameter.getAnnotation(Para.class);
			parameterList.add(new ActionParameter()
					.setName(para.name())
					.setType(parameter.getType()));
		}
	}

	Object getObject() {
		return object;
	}

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

//解决action反射对应的方法的参数问题
public class ActionParameter {
	//参数名称
	private String name;
	//参数类型
	private Class<?> type;
	
	ActionParameter() {
	}
	
	ActionParameter setName(String name) {
		this.name = name;
		return this;
	}
	
	String getName() {
		return name;
	}
	
	ActionParameter setType(Class<?> type) {
		this.type = type;
		return this;
	}
//将字符串json转换为对象
	Object getValue(Gson gson, String json) {
		return gson.fromJson(json, type);
	}
}

//这里我们需要定义一个接口,给定一个方法
public interface IActionFactory {
	String executeRequest(String action, String para) throws Exception;
}
//这和类需要实现上面那个接口,实现定义的方法
public class ActionFactory implements IActionFactory {
	private static final Type type;
	//map存放的是action所对应的方法名和ActionDefinition对象 
	private static final Map<String, ActionDefinition> actionMap;
	private static final Gson gson;
	
	//静态块是线程安全的
	static {
		actionMap = new HashMap<>();
		//将Map的字符串转换为Map类型所需的类型
		type = new TypeToken<Map<String, String>>() {}.getType();
		gson = new GsonBuilder().create();
	}
	
	//根据包扫描得到想要找的类,然后处理该类
	private static void processClass(Class<?> klass) {
		try {
		    //根据类,实例化一个对象
			Object object = klass.newInstance();
			//得到该类里所有的方法,包括private修饰的方法
			Method[] methods = klass.getDeclaredMethods();
			for (Method method : methods) {
				//如果该方法没有注解,就继续
				if (!method.isAnnotationPresent(Actioner.class)) {
					continue;
				}
				//根据注解得到相应的动作
				Actioner actioner = method.getAnnotation(Actioner.class);
				ActionDefinition ad = new ActionDefinition();
				ad.setKlass(klass);
				ad.setObject(object);
				//保存方法和参数
				ad.setMethod(method);
				//以action动作为键,以ActionDefinition对象为值保存到Map里
				actionMap.put(actioner.action(), ad);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	//包扫描方法
	public static void scanActioner(String packageName) {
		//实例化包扫描那个类
		new PackageScanner() {
			@Override
			public void dealClass(Class<?> klass) {
			//包扫描的时候只扫描带有注解的类,其余的类不扫描
				if (klass.isInterface() || klass.isPrimitive()
						|| klass.isArray() || klass.isAnnotation()
						|| klass.isEnum()
						|| !klass.isAnnotationPresent(MecAction.class)) {
					return;
				}
				try {
					//扫描到该类调用处理类方法
					processClass(klass);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			//将要扫描的包当作实参传递过去
		}.scannerPackage("com.mec");
	}
	/**
	 * 根据action的取值,映射出对应的method
	 */
	//这里的参数para是一个map,保存了用户的账号和密码,键为参数名,值为对象类型的字符串
	@Override
	public String executeRequest(String action, String para) throws Exception {
		ActionDefinition ad = actionMap.get(action);
		if (ad == null) {
			throw new Exception("action:[" + action + "]没有配置!");
		}
		Object object = ad.getObject();
		Method method = ad.getMethod();
		//用反射机制提供的parameter方法只能得到参数的类型,不能得到参数的名称,因此这里需要对参数另做处理
		List<ActionParameter> paras = ad.getParameterList();
		//将Map字符串转换成对应的Map类型
		Map<String, String> paraMap = gson.fromJson(para, type);
		
		Object result = null;
		//反射机制调用无参的方法
		if (paras.size() <= 0) {
			result = method.invoke(object);
		} else {
			Object[] values = new Object[paras.size()];
			int i = 0;
			for (ActionParameter parameter : paras) {
				//根据参数名在map里找到字符串类型的参数对象类型的字符串,
				//然后利用getValue方法将参数对象类型字符串类型转换为对象,得到该参数值
				values[i] = parameter.getValue(gson, 
						paraMap.get(parameter.getName()));
			}
			result = method.invoke(object, values);
		}
		//将反射机制调用执行的方法后的结过通过字符串的形式返回
		return gson.toJson(result);
	}
}
//带有注解的类和方法,方便包扫描能找到
@MecAction
public class UserAction {
	public UserAction() {
	}
	//给方法带注解
	@Actioner(action="login")
	public UserModel userLogin(
	//给参数带注解
			@Para(name="user") String user, 
			@Para(name="password") String password) {
		UserModel stu = new UserModel();
		
		return stu;
	}
}

总结
  1. 上述是解决该问题的核心代码,主要就是通过注解配置将来要执行的方法,然后通过包扫描找到该方法,利用反射机制执行该方法,并且将执行后的结果返回。
  2. 这种方法主要用于动态的执行未知方法的场景中。

你可能感兴趣的:(Java)