在之前的文章中,我们学习了如何建立一个DartVM服务器,在我对Flutter群分享时,有些群友会疑问,学习这个还不如学习golang,Dart服务器有什么用....等等,我这里先说明一下,就目前来说确实没什么用,dart服务器运行的是语言VM,而像java服务器运行的是jvm,我们简单来讲一下什么是语言VM跟JVM,语言VM是专门针对某种语言去开发,而JVM是一个字节码VM,这个字节码VM是规定了一个规则,只要是遵守它的规则,无论你是什么语言都可以开发,但前提是根据这个规则转换为字节码,所以说:JVM相对性能没有语言VM要好,但适用范围广泛,语言VM因为根据该语言设计的,所以,是可以通过该语言完全的操控VM,可以两个类似的相比较, JVM跟语言VM就好比万能驱动与原配驱动 ,当然这是类似的比较,不用太较真!好了,巴拉巴拉,说了一大堆,总结: 目前Flutter基于dart语言,学习DartVM开发有助于打好Dart基础,基础打好了,开发Flutter的骚操作也就更多!同样也适合走Dart web开发
下面,如果你跟着仔细操作,你将学会如何使用注解,使用反射获取注解、通过反射调用方法。
image
在java中,如果自定义一个注解,需要添加 @Target 作用域注解, @Retention 注解类型注解,添加 @interface ,然后定义注解参数,那么现在告诉你,在dart都不用,我们只需要定义实体类一样就可以了,代码如下
class Controller{ final String path; //构造方法定义为编译时常量 const Controller({this.path}); @override String toString() =>'Controller';//这里是区别其它注解 }
要注意的是构造方法需要添加一个const修饰,定义为编译时常量,然后下面就是使用
@Controller(path: '/user') class UserController { }
然后我们以Controller标识过的作为该Controller的第一路径,如果没有就忽略掉,
接下来我们再定义一个@Request注解
class Request{ final String path; final String method; const Request({this.path,this.method}); @override String toString() =>'Request'; }
上面定义了一个第二路径,跟一个请求方法,然后我们根据这个Request再弄Get跟Post的注解
class Get extends Request{ final String path; const Get({this.path}) : super(path : path,method: 'GET'); @override String toString() =>'Get'; } class Post extends Request{ final String path; const Post({this.path}) : super(path : path, method: 'POST'); @override String toString() =>'Get'; }
可以看到Get跟Post注解都继承了Request,传递了特定的请求方法
现在我们都准备好了这些注解,我们现在用这些注解写一下请求
@Controller(path: '/user') class UserController{ @Get(path: '/login') void login(HttpRequest request) { request.response ..statusCode = HttpStatus.ok ..writeln('LoginSuccess') ..close(); } @Post(path: '/logout') void logout(HttpRequest request){ request.response ..statusCode = HttpStatus.ok ..writeln('logoutSuccess') ..close(); } @Request(path: '/delete', method: 'DELETE') void editUser(HttpRequest request){ request.response ..statusCode = HttpStatus.ok ..writeln('DeleteSuccess') ..close(); } }
好了请求写玩了,那么,我们怎么去关联这些注解呢,下面就要用到反射了!
dart里面含有一个镜子包,这个包可以通过传入的类,去解析元数据(即注解),并可以通过镜子传递参数去调用方法,为了统一管理这些Controller,我们定义一个BaseController,让处理请求的Controller都继承这个类
//抽象类 abstract class BaseController{ }
上面这个方法是一个空方法,我们不添加任何东西,然后让UserController继承这个类
@Controller(path: '/user') class UserController extends BaseController{ @Get(path: '/login') void login(HttpRequest request) { request.response ..statusCode = HttpStatus.ok ..writeln('LoginSuccess') ..close(); } @Post(path: '/logout') void logout(HttpRequest request){ request.response ..statusCode = HttpStatus.ok ..writeln('logoutSuccess') ..close(); } @Request(path: '/delete', method: 'DELETE') void editUser(HttpRequest request){ request.response ..statusCode = HttpStatus.ok ..writeln('DeleteSuccess') ..close(); } }
下面,我们导入镜子包,新建一个ControllerManager,用来管理这Controller
import 'dart:mirrors'; import 'dart:io'; class ControllerManager{ static ControllerManager manager=new ControllerManager(); //该list用于判断Controller是否已经被添加 List<BaseController> controllers=[]; //这是一个map,对应的是请求链接,跟对应的controller信息 Map<String,ControllerInfo> urlToMirror=new Map(); //添加控制器 void addController(BaseController controller){ //判断当前是否已经添加过控制器 if(!controllers.contains(controller)){ controllers.add(controller); //添加map urlToMirror.addAll(getRequestInfo(controller)); } } //该controllerManager处理请求的方法 void requestServer(HttpRequest request){ //当前请求的路径 String path=request.uri.toString(); //当前请求的方法 String method=request.method; //判断map中是否包含该请求地址 if(urlToMirror.containsKey(path)){ ControllerInfo info=urlToMirror[path]; //获取到该请求,传递路径、请求方法跟请求 info.invoke(path, method, request); }else{ //没有该地址返回一个404 request.response ..statusCode=HttpStatus.notFound ..write('''{ "code": 404, "msg": "链接不存在!" }''') ..close(); } } }
上面的思路是,在初始化时,将所有的Controller都添加到map中以请求路径为key去查找,当请求时,请求地址在map中查找到,就为它处理请求,如果查找不到,就给它丢一个404的信息,下面是ControllerInfo
class ControllerInfo{ //请求地址对应Controller中的方法,Symbol包含方法标识 final Map<String,Symbol> urlToMethod; //该参数包含通过类初始化得到的实例镜子,可以通过该参数调用方法 final InstanceMirror instanceMirror; //构造方法 ControllerInfo(this.instanceMirror,this.urlToMethod); //调用请求方法 void invoke(String url,String method,HttpRequest request){ //判断是否该请求地址是对应的请求方法 if(urlToMethod.containsKey('$url#$method')){ //调用方法 instanceMirror.invoke(urlToMethod['$url#$method'], [request]); }else { //请求方法不对,返回一个错误 request.response ..statusCode=HttpStatus.methodNotAllowed ..write('''{ "code": 405, "msg": "请求出错!" }''') ..close(); } } }
主要的是Symbol、InstanceMirror这两个类,通过instanceMirror.invoke(Symbol,[传递的参数]),就能调用对应的方法了
我们在ControllerManager中还有一个添加Controller到map的方法 getRequestInfo(controller) 没有介绍,这个就是获取InstanceMirror跟Symbol的关键,下面介绍获取InstanceMirror跟Symbol
//传递一个Controller进去 Map<String,ControllerInfo> getRequestInfo(BaseController controller) { // 实际返回的Map Map<String,ControllerInfo> info=new Map(); //请求地址对应的方法 Map<String,Symbol> urlToMethod=new Map(); // 获取Controller实例的镜子 InstanceMirror im = reflect(controller); //获取Controller运行时类型的镜子 ClassMirror classMirror = im.type; //请求的根路径 List<String> path = []; //该Controller的所有接收的请求地址 List<String> urlList=[]; //获取元数据,就是获取@Controller(path: xxx)中的xxx classMirror.metadata.forEach((medate) { path.add(medate.reflectee.path); }); //获取该类的所有方法 classMirror.declarations.forEach((symbol, declarationMirror) { //将自身的构造方法剔除 if (symbol.toString() != classMirror.simpleName.toString()) { //获取方法的元数据,就是@Get(path: path) declarationMirror.metadata.forEach((medate) { //请求的地址 String requestPath = path.join() + medate.reflectee.path; //请求的类型 String method = medate.reflectee.method; // print('请求地址为:$requestPath,请求方法为:$method'); //添加到请求地址集合 urlList.add(requestPath); //添加到请求地址对应方法的集合 urlToMethod.putIfAbsent('$requestPath#$method', ()=>symbol); } ); } }); //实例化一个Controller信息 ControllerInfo controllerInfo=new ControllerInfo(im, urlToMethod); //循环添加到实际需要的Map,对应请求地址根ControllerInfo信息 urlList.forEach((url){ info.putIfAbsent(url, ()=>controllerInfo); }); //返回需要的map return info; }
上面的注释已经写的很明细了,如果不清楚,我再讲解下:
首先我们需要在运行服务器之前,将我们需要的Controller添加到ControllerManager中(这个比较笨的方法,如果有大佬知道怎么自动去扫描Controller添加到ControllerManager,请告知小弟,谢谢!)
//添加控制器 ControllerManager.manager.addController(new UserController());
然后将我们之前的 handleMessage(request)方法替换为
//.... ControllerManager.manager.requestServer(request); //....
当所有操作完成之后,我们休息一会,然后点击绿色按钮,启动我们的服务器,并输入 http://localhost:8080/user/login
见证奇迹!
成功.png
可以看到,我们成功的利用注解处理请求!
今天的内容基本上是这些了,如果你仔细学习了该文章,对于Flutter开发也可以使用注解去登陆,去请求数据,好了,谢谢!我们明天见!
如果想继续学习DartVM服务器开发,请关注我,学习更多骚操作!