android implements a spring-like server-side framework based on annotations

Anyone who has used spring or Retrofit knows that binding and decoupling functions and http requests is very convenient. Here's a simple annotat...

Anyone who has used spring or Retrofit knows that binding and decoupling functions and http requests is very convenient. Here's a simple annotation framework based on NanoHttpd.
The first step is to define annotation classes:

//http control class, which is used by the annotated class to process http requests @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Controller { //Path prefix, which is used for requests with prefix String name() default "/"; boolean needPermissonControl() default true; }
//The annotated method is used to process http requests @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface RequestMapping { //Matching Path String path() default ""; Method method() default Method.GET; }
//This annotation is used for function parameters and for binding request parameters @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface Param { //Parameter name String name() default ""; //Default values of parameters String value() default ""; }

The second step is to register the Control class when implementing serverlet

@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); } } } }

Perhaps the idea is to find the uri path prefix of Controller annotation and the function with RequestMapping annotation when registering, to correspond the path to the function method, and then call the application method when matching the path when the request comes. The following is the processing when matching the path:

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"); } } //Matching Request Method 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) { //Get the corresponding http request parameters 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()); } } } } //Calling method return (Response)requestMappingParams.methodReflect.invoke(object, params.toArray()); }

Here is an example of 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; }

The preceding Controller matching path prefix is FileManager or http://host/filemanager/xxx? Requests like XXX are processed using this method, and then a list method is used to process http://host/filemanager/list?xxx requests. The request parameter has only one dir and parameter, and the default value is/sdcard, which is the value of the incoming function when no parameters are passed.
All the code is in github: Enlarge-Android

The method of using annotations here is called reflection, which will degrade performance. Another method based on annotation can be used to automatically generate code to improve performance. The advantage of using annotations to bind HTTP requests is quite obvious. If no annotations are used, the class implementation or inheritance of HTTP request processing classes are needed to process http requests, and the method of request processing is rewritten to achieve the processing time and do the related logical processing. This code is obviously much more and inflexible.

28 January 2019, 00:12 | Views: 7535

Add new comment

For adding a comment, please log in
or create account

0 comments