1. 程式人生 > >android基於註解實現類似spring服務端框架

android基於註解實現類似spring服務端框架

用過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請求處理類,並重寫請求處理的方法,以達到得到處理的時機再做相關的邏輯處理,這樣的程式碼顯然多了很多也不靈活。