前面已经分析了,要实现的是一个Java Web应用。
但由于是dalvikvm环境,服务器需要自己去搭建。
再加上想向可拓展方面靠,于是仿SpringBoot,一个处理都在Controller的弱鸡服务器诞生了(其实是想吹爆的,可惜不够强。
前言
- 由于是dalvikvm环境,直接老一套不太行。
再加上以前实现的服务器Demo基础,可以考虑参考SpringBoot进行实现。
目标是搭建完成以后,争取所有逻辑都在Controller里面解决,便于拓展。
做到了什么
- 举个例子 Hello World
@Controller(path = "/test", note = "在线设备状态相关") public class ControllerHelloWorld { @Controller(path = "/hello", note="传参+返回结果测试") public String hello(@Value(key = "param") String param, @Value(key = "_t") String time) { System.out.println(param); System.out.println(time); return "Hello World"; } @Controller(path = "/hello2", note="返回结果测试2") public String hello(BufferedWriter out) { out.write("Hello World"); return null; } @Controller(path = "/hello3", note="返回结果测试3") public String hello(BufferedWriter out) { out.write("<html>"); out.write("<head><title>Index</title></head>"); out.write("<body><h2>Hello World</h2></body>"); out.write("</html>"); return null; } }
- 假设要处理
/test/hello
路径的请求,我们可以仿照SpringBoot创建相应类+方法,注意@Controller注解 - 对于传参问题,需要加上@Value注解,处理时会自动赋值。若path里面不含对应参数,默认指向null
http://192.168.0.101:8888/test/hello?param=test hello方法里,param=="test",time==null; http://192.168.0.101:8888/test/hello?param=test&_t=123 hello方法里,param=="test",time=="123";
- 对于返回结果,举例
/test/hello ==> "Hello World" /test/hello2 ==> "Hello World" 即浏览器直接访问会收到并显示文本Hello World /test/hello3 ==> 浏览器直接访问会显示Hello World
- 假设要处理
怎么做到的
SocketServer监听TCP端口
HTTP基于TCP协议,所以直接监听TCP端口就行了,重要的是怎么处理
public class SocketServer {
int portServerListening; // 监听端口
boolean isRun = true;
ExecutorService httpThreadPool; // Socket处理线程池
ServerSocket serverSocket;
public SocketServer(int portServerListening) {
this.portServerListening = portServerListening;
httpThreadPool = Executors.newFixedThreadPool(20);
}
/**
* 关闭服务器
*/
public void stopServer() {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("正在关闭 SocketServer: 服务器... ");
}
/**
* 打开服务器
*/
public void startServer() {
Socket socket = null;
System.out.println("SocketServer: 服务器监听开始... ");
try {
serverSocket = new ServerSocket(portServerListening);
while (isRun) {
try {
socket = serverSocket.accept();
}catch (SocketTimeoutException e) {
continue;
}catch (SocketException e) {
break;
}
//System.out.println("收到新连接: " + socket.getInetAddress() + ":" + socket.getPort());
SocketDealer dealer = new SocketDealer(socket);
httpThreadPool.execute(dealer);
}
httpThreadPool.shutdownNow();
} catch (IOException e) {
e.printStackTrace();
} finally {
...
}
System.out.println("SocketServer: 服务器已经关闭... ");
}
}
对于Socket连接的处理
其实就是利用反射,不用写死,便于拓展。
- 获取Socket的io流in/out
- 参考HTTP协议,通过输入in获取URL,解析出path、params参数字符串(URL?号后面那一坨)
- 遍历@Controller注解类的@Controller注解方法,获得匹配path的class和method
- 根据Method的相关参数注解,配合params参数字符串生成方法的各种参数
- 实例化匹配的class对象,并传入参数,执行method方法,并得到结果
- 根据结果输出out
以下是Socket线程处理的run方法
public void run() {
String path = null, param = null;
try {
in = new BufferedReader(new InputStreamReader(socketClient.getInputStream()));
out = new BufferedWriter(new OutputStreamWriter(socketClient.getOutputStream()));
// 读取url请求, 得到path + param
String line = null;
while ((line = in.readLine()) != null) {
Pattern urlPattern = Pattern.compile("^GET ([^ \\?]+)\\??([^ \\?]*) HTTP.*$");
Matcher matcher = urlPattern.matcher(line);
if(path == null && matcher.find()) {
System.out.println("正在处理请求: " + line);
path = matcher.group(1);
param = matcher.group(2);
}
if(line.length() == 0)
break;
}
// 返回结果
out.write("HTTP/1.1 200 OK\r\n");
out.write("Content-Type: text/html; charset=UTF-8\r\n");
out.write("\r\n");
// 处理请求并返回内容
// 遍历Controller类,得到和Path匹配的处理方法, 目前仅一个Class
Method currentMethod = null;
Class<?> klass = Class.forName("nicelee.server.controller.ControllerOnlinerFinder");
Controller preAnno = klass.getAnnotation(Controller.class);
String pathPrefix = preAnno.path();
for(Method method: klass.getMethods()) {
Controller controller = method.getAnnotation(Controller.class);
if(controller!=null && (pathPrefix + controller.path()).equals(path)) {
currentMethod = method;
break;
}
}
// 找到Method方法后,根据param给Method变量赋值
if(currentMethod != null) {
dealWithPathKnown(param, currentMethod, klass);
}else {
dealWithPathUnknown(klass);
}
out.write("\r\n");
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
...
}
}
上接run方法,自动生成参数、实例化对象并调用处理方法:
private void dealWithPathKnown(String param, Method currentMethod, Class<?> klass) throws Exception {
Annotation[][] paramAnnos = currentMethod.getParameterAnnotations();
Class<?>[] paramTypes = currentMethod.getParameterTypes();
Object[] values = new Object[paramTypes.length];
for(int i=0; i<paramTypes.length; i++) {
// 如果是BufferedWriter,那么直接赋值,否则从params中找
if(paramTypes[i] == BufferedWriter.class) {
values[i] = out;
}else {
if(paramAnnos[i].length > 0) {
Value value = (Value) paramAnnos[i][0];
values[i] = getValue(param, value.key());
}
}
}
// 实例化Controller,并执行该方法
String result = (String) currentMethod.invoke(klass.newInstance(), values);
if(result!=null) {
out.write(result);
}
}
/**
* 从参数字符串中取出值 "key1=value1&key2=value2 ..."
*
* @param param
* @param key
* @return
*/
private static String getValue(String param, String key) {
Pattern pattern = Pattern.compile(key + "=([^&]*)");
Matcher matcher = pattern.matcher(param);
if (matcher.find()) {
return matcher.group(1);
}
return null;
}
有哪些缺陷
- 逻辑比较简单,没能考虑复杂的情况,比如:
- 只考虑简单的GET,没有考虑POST、PUT等,全都当作GET对待
- 方法参数自动赋值目前只考虑String类型
其它也不是不能自动适配,但暂时没这需求… - 这里方法的@Controller和SpringBoot的返回json的@RequestBody有点类似。
缺少返回return作为模板引擎路径,然后自动渲染结果的实现 - 由于dalvikvm环境下包扫描机制没想好咋解决,目前只能一个个Class.forName(“nicelee.xxx”)来加载。
这样一来需要人工维护一张class的信息表,不过工作量不大