用过spring或Retrofit的人都知道实现函数和http请求的绑定和解耦非常方便,这里分享一下基于NanoHttpd实现的简单注解框架。
第一步定义注解类:
//http控制类,被该注解的类用来处理http请求
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {
//路径前缀,有此前缀的请求会使用此类处理
String name() default "/";
boolean needPermissonControl() default true;
}
//被该注解的方法用来一个处理http请求
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequestMapping {
//匹配的路径
String path() default "";
Method method() default Method.GET;
}
//该注解用于函数的参数,用于绑定请求的参
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Param {
//参数名
String name() default "";
//参数默认值
String value() default "";
}
第二步,实现serverlet时注册Controll类
@Override
public void addController(Class<?> controller) {
// has controller annotation
if (controller.isAnnotationPresent(Controller.class)) {
Controller controllerAnnotation = controller.getAnnotation(Controller.class);
String name = controllerAnnotation.name();
boolean needPermissionControl = controllerAnnotation.needPermissonControl();
Method[] methods = controller.getDeclaredMethods();
// get all request mapping annotation
for (Method method : methods) {
if (method.isAnnotationPresent(RequestMapping.class)) {
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
String path = requestMapping.path();
org.nanohttpd.protocols.http.request.Method m = requestMapping.method();
// build full path
String fullPath = name + File.separatorChar + path;
// getparams
ArrayList<Param> params = new ArrayList<>();
Annotation[][] paramAnnotation = method.getParameterAnnotations();
for (Annotation[] an : paramAnnotation) {
if (an.length > 0) {
Param p = (Param)an[0];
params.add(p);
}
}
RequestMappingParams requestMappingParams = new RequestMappingParams();
requestMappingParams.path = fullPath;
requestMappingParams.handler = controller;
requestMappingParams.method = m;
requestMappingParams.methodReflect = method;
requestMappingParams.params = params;
requestMappingParams.needPermissionControl = needPermissionControl;
addRoute(requestMappingParams);
}
}
}
}
大概思路是在注册时,找到Controller注解的uri路径前缀,还有有RequestMapping注解的函数,将路径对应上函数方法,然后在请求来时匹配到该路径就调用对应用的方法,下面是匹配到路径时的处理:
private Response processController(Object object, Map<String, String> urlParams, IHTTPSession session) throws InvocationTargetException, IllegalAccessException {
if (requestMappingParams.needPermissionControl) {
if (!AppNanolets.PermissionEntries.isRemoteAllow(session.getRemoteIpAddress())) {
return Response.newFixedLengthResponse("not allow");
}
}
//匹配请求方法
if (requestMappingParams.method != session.getMethod()) {
return Response.newFixedLengthResponse(Status.INTERNAL_ERROR, "text/plain", "method not supply");
}
ArrayList<Object> params = new ArrayList<>();
Map<String, List<String>> requestParams = session.getParameters();
if (requestParams != null) {
//获取对应http请求参数
Type[] types = requestMappingParams.methodReflect.getGenericParameterTypes();
for (int i = 0; i < requestMappingParams.params.size(); i++) {
Param p = requestMappingParams.params.get(i);
if (!TextUtils.isEmpty(p.name())) {
List<String> values = requestParams.get(p.name());
if (values != null && values.size() > 0) {
String v = values.get(0);
Type t = types[i];
params.add(valueToObject(t, v));
} else {
params.add(p.value());
}
}
}
}
//调用方法
return (Response)requestMappingParams.methodReflect.invoke(object, params.toArray());
}
下面是Controller的一个实例:
@Controller(name = "filemanager")
public class FileManagerHandler {
@RequestMapping(path= "list")
public Response list(@Param(name = "dir", value = "/sdcard") String path) {
if (TextUtils.isEmpty(path)) {
path = Environment.getExternalStorageDirectory().getAbsolutePath();
}
Dir d = new Dir(new java.io.File(path));
String json = JSON.toJSONString(d);
Response response = Response.newFixedLengthResponse(Status.OK, "application/json", json);
response.addHeader("Access-Control-Allow-Origin", "*");
return response;
}
上面的Controller匹配路径前缀是filemanager就是http://host/filemanager/xxx?xxx这样的的请求都会使用此类来处理,然后有个list方法处理http://host/filemanager/list?xxx请求,请求参数只有一个dir又参数,默认值是/sdcard,就是没传参时会使用此值传入函数。
全部代码在github:Enlarge-Android
这里使用注解的方法是调用反射,性能上会有所降低,可以使用另外一种基于注解自动生成代码的方法提高性能。使用注解来绑定http请求好处相当明显,如果不使用注解,那么要处理http请求时需要类实现或继承http请求处理类,并重写请求处理的方法,以达到得到处理的时机再做相关的逻辑处理,这样的代码显然多了很多也不灵活。