preface

In this article, the blogger implements a simple framework step by step from the servlet layer to the Controller layer. With this framework, we can use the following basic annotations just like Spring does:

  • @XxgController
  • @XxgRequestMapping
  • @XxgParam
  • @XxgRequestBody

Before reading this article, you might want to know the following:

  • BeanUtils
  • ObjectMapper
  • Servlet Knowledge

Idea: Interceptors implement routing distribution. Using annotations?

Think about:

  1. An interceptor can block all request paths before the servlet
  2. You can find the method in the annotation where the path matches the request path
  3. The REQ and RESP are then forwarded to the method for execution

Question:

How does an interceptor find a method that uses the annotation? Package scanning? How?

Analysis:

Packet scan, which involves the IO stream, and the File class can recursively query all the files below it, we

It can be filtered:

  1. Just file with a.class suffix and get its className(including the package path)
  2. Retrieves the class by reflection, determines if it has the specified annotation, and filters it again

So when the interceptor intercepts the request path, we can make a match and call the method.

Have a lazy:

Because of the MVC design pattern, we usually put the API interface under the same package, so we can just specify the package to scan and leave the rest of the package alone

Scanning class version 1.0 implementation

public class FileScanner { private final String packetUrl = "com.dbc.review.controller"; private final ClassLoader classLoader = FileScanner.class.getClassLoader(); private List<Class> allClazz = new ArrayList<>(10); Public List<Class> getAllclazz (){return this.allclazz; public List<Class> getAllclazz (){return this.allclazz; } public String getPacketUrl(){ return this.packetUrl; } public void LoadAllClass (String PacketUrl) throws the desired class public void LoadAllClass (String PacketUrl) throws Exception{ String url = packetUrl.replace(".","/"); URL resource = classLoader.getResource(url); if (resource == null) { return; } String path = resource.getPath(); File file = new File(URLDecoder.decode(path, "UTF-8")); if (! file.exists()) { return; } if (file.isDirectory()){ File[] files = file.listFiles(); if (files == null) { return; } for (File f : files) { String classname = f.getName().substring(0, f.getName().lastIndexOf(".")); if (f.isDirectory()) { loadAllClass(packetUrl + "." + classname); } if (f.isFile() && f.getName().endsWith(".class")) { Class clazz = Class.forName(packetUrl + "." + classname); dealClass( clazz); } } } } private void dealClass(Class clazz) { if ((clazz.isAnnotationPresent(Controller.class))) { allClazz.add(clazz); }} public Boolean invoke(String url,String requestMethod) {for (Class clazz:);} public Boolean invoke(String url,String requestMethod) {for (Class clazz:); allClazz){ Controller controller = (Controller) clazz.getAnnotation(Controller.class); Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { RequestMapping requestMapping = method.getDeclaredAnnotation(RequestMapping.class); if (requestMapping == null) { continue; } for (String m : requestMapping.methods()) { m = m.toUpperCase(); if (! m.toUpperCase().equals(requestMethod.toUpperCase())) { continue; } StringBuilder sb = new StringBuilder(); String urlItem = sb.append(controller.url()).append(requestMapping.url()).toString(); If (urlItem. Equals (url)) {/ / get used to handle the API interface methods try {/ / method. GetGenericParameterTypes () / / may, according to this method to determine what is the method required parameters method.invoke(clazz.newInstance()); return true; } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); return false; } } } } } return false; } @Test public void test() throws Exception { // 1. FilesCanner = new FilesCanner (); FilesCanner = new FilesCanner (); // 2. Scanning FilesCanner. LoadAllClass (FilesCanner. GetPacketURL ()); }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}  // If the method fails, return false, then the 405 method is undefined. // This class does not implement a method for passing arguments to a controller.} // This class does not implement a method for passing arguments to a controller.

TestController

@Controller(url = "/test") public class TestController { @RequestMapping(url = "/get",methods = "GET") public void get(){ System.out.println(111); } @RequestMapping(url = "/post",methods = {"POST","get"}) public void post(){ System.out.println(22); } public void test(HttpServletRequest req, HttpServletResponse res){ System.out.println(req.getPathInfo()); }}

Scanning Classes Version 2.0

With version 1.0, we have initially implemented recursive scanning of all the controllers in the package and access them through pathmapping. But clearly there are at least the following problems:

  1. When a method is executed, the method cannot have arguments. Not in line with business requirements
  2. It is inefficient to process Class reflection repeatedly on each access to find the path map method.

In view of the above two problems, we will make modifications in Version 2.0:

  1. Store information about the Controller, RequestMapping, and method parameters in a container. The server is scanned and assembled into the container when it is first started. This makes it easier to iterate through the container on each visit than the 1.0 container.
  2. Define parameter types, via annotations@XxgRequestBodyAs well as@XxgParamDistinguish between parameters taken from the request body or from the URL? Get behind. In order to obtain the data from the front end
  3. throughObjectMapperAssemble parameters of different types, and finally call the methodinvokeImplement method handling with/without arguments.

BeanDefinition

/** * public class BeanDefinition {private; /** * public class BeanDefinition {private; /** * public class BeanDefinition {private Class typeClazz; Private String typeName; Private Object annotation; // private Object annotation; Private String ControllerUrlPath; Private List<MethodDefinition> MethodDefinitions; private List<MethodDefinition> MethodDefinitions; // annotate with RequestMapping}

MethodDefinition

/** * @data@NoArgconstructor @AllArgconstructor public class methodDefinition {private class parentClazz; // Class private Method Method; // private String methodName; // private Object annotation; // private String RequestMappingUrlPath; // url private String[] allowedRequestMethods; // allowedRequestMethods private List<ParameterDefinition> parameterDefinitions; Private Object result; private Object result; // return data}

ParameterDefinition

/** */ @Data @NoArgconstructor @AllArgConstructor public class ParameterDefinition {private class paramClazz; Private String ParamName; private String ParamName; Private Object ParamType; private Object ParamType; Private Boolean isRequestBody; // If you want to get data from body}

Singleton container

Give a method to scan the package and get the corresponding method based on the URI

/**
 * 用于存放请求路径 与 controller对应关系的类
 * 设计成单例模型
 */
public class RequestPathContainer {
 private static List<BeanDefinition> requestList = new ArrayList<>();
 private static final ClassLoader classLoader = RequestPathContainer.class.getClassLoader();
 private static volatile RequestPathContainer instance = null;
​
 public static RequestPathContainer getInstance() {
     if (instance == null) {
        synchronized(RequestPathContainer.class){
            if (instance == null) {
                instance = new RequestPathContainer();
            }
         }
     }
     return instance;
 }
​
 private RequestPathContainer() {
​
 }
​
 public List<BeanDefinition> getRequestList() {
    return requestList;
 }
​
 // 扫描包
 public void scanner(String packetUrl) throws UnsupportedEncodingException, ClassNotFoundException {
     String url = packetUrl.replace(".", "/");
     URL resource = classLoader.getResource(url);
     if (resource == null) {
         return;
     }
     String path = resource.getPath();
     File file = new File(URLDecoder.decode(path, "UTF-8"));
     if (!file.exists()) {
         return;
     }
     if (file.isDirectory()){
     File[] files = file.listFiles();
     if (files == null) {
        return;
     }
     for (File f : files) {
     if (f.isDirectory()) {
        scanner(packetUrl + "." + f.getName());
     }
     if (f.isFile() && f.getName().endsWith(".class")) {
         String classname = f.getName().replace(".class", ""); // 去掉.class后缀名
         Class clazz = Class.forName(packetUrl + "." + classname);
         dealClass(clazz);
         }
     }
     }
 }
​
 // 筛选包中的类,并添加到List中
 private void dealClass(Class clazz) {
     if (!clazz.isAnnotationPresent(XxgController.class)) {
     // 没有controller注解
        return;
     }
     List<MethodDefinition> methodDefinitions = new ArrayList<>();
     Method[] methods = clazz.getDeclaredMethods();
     for (Method method : methods) {
         // 方法转 方法描述类
         MethodDefinition methodDefinition = convertMethodToMethodDefinition(method, clazz);
         if (methodDefinition != null) {
            methodDefinitions.add(methodDefinition);
         }
         }
         if (methodDefinitions.size() == 0) {
            return;
         }
         // 设置类描述类
         BeanDefinition beanDefinition = convertBeanToBeanDefinition(clazz, methodDefinitions);
         requestList.add(beanDefinition);
    }
​
 // 根据uri 和 请求方法 获取执行方法
 public MethodDefinition getMethodDefinition(String uri, String method) {
     for (BeanDefinition beanDefinition: requestList) {
     if (!uri.contains(beanDefinition.getControllerUrlPath())) {
        continue;
     }
     List<MethodDefinition> methodDefinitions = beanDefinition.getMethodDefinitions();
     for (MethodDefinition methodDefinition: methodDefinitions) {
     StringBuilder sb = new StringBuilder().append(beanDefinition.getControllerUrlPath());
     sb.append(methodDefinition.getRequestMappingUrlPath());
     if (!sb.toString().equals(uri)) {
     continue;
     }
     String[] allowedRequestMethods = methodDefinition.getAllowedRequestMethods();
     for (String str : allowedRequestMethods) {
     if (str.toUpperCase().equals(method.toUpperCase())) {
     // 请求路径 与 请求方法 均满足,返回该方法描述类
     return methodDefinition;
     }
     }
     }
     }
     return null;
 }
​
 /**
 * 将controller类 转换为 类的描述类
 */
 private BeanDefinition convertBeanToBeanDefinition(Class clazz, List<MethodDefinition> methodDefinitions) {
     BeanDefinition beanDefinition = new BeanDefinition();
     beanDefinition.setTypeName(clazz.getName());
     beanDefinition.setTypeClazz(clazz);
     XxgController controller = (XxgController) clazz.getAnnotation(XxgController.class);
     beanDefinition.setAnnotation(controller);
     beanDefinition.setControllerUrlPath(controller.value());
     beanDefinition.setMethodDefinitions(methodDefinitions);// 增加方法体
     return beanDefinition;
 }
​
 /**
 * 将方法 转换为 方法描述类
 */
 private MethodDefinition convertMethodToMethodDefinition(Method method, Class clazz) {
 if (!method.isAnnotationPresent(XxgRequestMapping.class)) {
 // 没有RequestMapping注解
 return null;
 }
 method.setAccessible(true);
 Parameter[] parameters = method.getParameters();
 // 设置参数描述类
 List<ParameterDefinition> parameterDefinitions = new ArrayList<>();
 for ( Parameter parameter : parameters) {
 ParameterDefinition parameterDefinition = convertParamToParameterDefinition(parameter);
 parameterDefinitions.add(parameterDefinition);
 }
 // 设置方法描述类
 MethodDefinition methodDefinition = new MethodDefinition();
 methodDefinition.setParameterDefinitions(parameterDefinitions);  // 增加参数列表
 methodDefinition.setMethod(method);
 methodDefinition.setMethodName(method.getName());
 methodDefinition.setResult(method.getReturnType());
 XxgRequestMapping requestMapping = method.getAnnotation(XxgRequestMapping.class);
 methodDefinition.setRequestMappingUrlPath(requestMapping.value());
 methodDefinition.setAnnotation(requestMapping);
 methodDefinition.setAllowedRequestMethods(requestMapping.methods());
 methodDefinition.setParentClazz(clazz);
 return methodDefinition;
 }
​
 /**
 * 将参数 转换为 参数描述类
 */
 private ParameterDefinition convertParamToParameterDefinition(Parameter parameter) {
 ParameterDefinition parameterDefinition = new ParameterDefinition();
 if ( parameter.isAnnotationPresent(XxgParam.class)) {
 parameterDefinition.setParamName(parameter.getAnnotation(XxgParam.class).value());
 } else {
 parameterDefinition.setParamName(parameter.getName());
 }
 parameterDefinition.setParamClazz(parameter.getType());
 parameterDefinition.setParamType(parameter.getType());
 parameterDefinition.setRequestBody(parameter.isAnnotationPresent(XxgRequestBody.class));
 return parameterDefinition;
 }
​
}

Global servlet

Instead of using interceptors, servlets are still used for routing distribution. This servlet listens for /

public class DispatcherServlet extends HttpServlet { private ObjectMapper objectMapper = new ObjectMapper(); @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// Set the code resp.setContentType("text/json; charset=utf-8"); RequestPathContainer requestPathContainer = RequestPathContainer.getInstance(); MethodDefinition methodDefinition = requestPathContainer.getMethodDefinition(req.getRequestURI(), req.getMethod()); if (methodDefinition == null) { resp.setStatus(404); SendResponse (R.Failed (" Request path does not exist "), req, resp); return; } List<ParameterDefinition> parameterDefinitions = methodDefinition.getParameterDefinitions(); List<Object> params = new ArrayList<>(parameterDefinitions.size()); for (ParameterDefinition parameterDefinition : parameterDefinitions) { try { Object value = dealParam(parameterDefinition, req, resp); params.add(value); } catch (ParamException e) { resp.setStatus(404); sendResponse(R.failed(e.getMessage()), req, resp); return ; } } try { Object result = methodDefinition.getMethod().invoke(methodDefinition.getParentClazz().newInstance(), params.toArray()); sendResponse(result, req, resp); } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) { e.printStackTrace(); sendResponse(e.getMessage(), req, resp); }} /** * ParameterDefinition * @Param ParameterDefinition * @Param Req * @Param Resp */ Private Object DealParam (ParameterDefinition parameterDefinition, HttpServletRequest req, HttpServletResponse resp) throws ParamException, IOException { Object value; String data = ""; If (parameterDefinition isRequestBody ()) {/ / from the request body (the request input stream) to get the data in data = getJsonString (the req); } else if (parameterDefinition.getParamType() == HttpServletRequest.class) { return req; } else if (parameterDefinition.getParamType() == HttpServletResponse.class) { return resp; } else if (isJavaType (parameterDefinition)) {/ / removed from the url parameter data = the req. The getParameter (parameterDefinition. GetParamName ()); If (data == null) {throw new ParamException(" The server cannot retrieve request data, please check request class "); }} else {/ / parameter enclosed in the request url into Object try {Object obj = parameterDefinition. GetParamClazz (). The newInstance (); ConvertUtils.register(new DateConverter(), Date.class); BeanUtils.populate(obj, req.getParameterMap()); return obj; } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new ParamException (" not found parameters' "+ parameterDefinition. GetParamName () +" 'corresponding value "); } } try { value = objectMapper.readValue(data, parameterDefinition.getParamClazz()); } the catch (JsonProcessingException e) {String errMsg = "parameters'" + parameterDefinition. GetParamName () + "' need '" + ParameterDefinition. GetParamType () + "type"; throw new ParamException(errMsg); } return value; } private void sendResponse(Object result, HttpServletRequest req, HttpServletResponse resp) throws IOException { if (result == null) { return; } resp.setContentType("text/json; charset=utf-8"); objectMapper.writeValue(resp.getWriter(), result); } /** * private Boolean isJavatype (parameterDefinition parameterDefinition) {Object[] javaTypes = MyJavaType.getJavaTypes(); for (Object item : javaTypes) { if (item.equals(parameterDefinition.getParamClazz())) { return true; } } return false; } /** * String getJonstring (HttpServletRequest Req) throws IOException {BufferedReader br = new BufferedReader(new InputStreamReader(req.getInputStream(), "utf-8")); char[] chars = new char[1024]; int len; StringBuilder sb = new StringBuilder(); while ((len = br.read(chars)) ! = -1) { sb.append(chars, 0, len); } return sb.toString(); }}

The ServletContext listener initializes the container

@Override public void contextInitialized(ServletContextEvent servletContextEvent) { RequestPathContainer requestPathContainer = RequestPathContainer.getInstance(); String configClassName = servletContextEvent.getServletContext().getInitParameter("config"); Class appListenerClass = null; try { appListenerClass = Class.forName(configClassName); XxgScanner xxgScanner = (XxgScanner)appListenerClass.getAnnotation(XxgScanner.class); if (xxgScanner ! = null) { try { requestPathContainer.scanner(xxgScanner.value()); / / scanning controller class, initialization List} the catch (UnsupportedEncodingException | ClassNotFoundException e) {e.p rintStackTrace (); } } } catch (ClassNotFoundException e) { e.printStackTrace(); }}

Legacy issues

Static resources are also blocked

Handling static resources

default servlet

If you open the Tomcat conf/web.xml file, you can see that Tomcat has a default servlet with the following configuration:

 <servlet>
 <servlet-name>default</servlet-name>
 <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
 <init-param>
 <param-name>debug</param-name>
 <param-value>0</param-value>
 </init-param>
 <init-param>
 <param-name>listings</param-name>
 <param-value>false</param-value>
 </init-param>
 <load-on-startup>1</load-on-startup>
 </servlet>

But it does not match the servlet-mapping, which is the processing path, so we can do the following configuration in our project’s web.xml to handle the static resource:

<! Change the global interceptor match /* to /. Must be -- -- > <! > <servlet-mapping> <servlet-name> dispatcherServlet </servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <! -- static resource --> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*. HTML </url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.js</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.css</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.jpg</url-pattern> </servlet-mapping>

The last

In fact, this paper mainly does the following two operations

  1. When the server starts, the Controller package is scanned to assemble the classes, methods, and parameters that meet our expectations into the container.
  2. The front-end accesses the server to obtain the method corresponding to the specified path in the container 2.1 assembles the access parameters into the parameter list according to different types 2.2 executes the corresponding method 2.3 to process the data returned by the method

II. Reference Notes

  • Project implementation process

Version 1.0 was thought up and done by the bloggers themselves. Version 2.0 is the blogger Xiaogao teacher to the blogger talked about the train of thought, write out and then see the realization of Xiaogao teacher, and then integrated the perfect.

  • After writing the article, the blogger carried out operations such as decoupling of different classes in the project and reconfigured the code, mainly to cope with the Open Closure Principle and the Single Responsibility Principle, etc.
  • Code: https://github.com/dengbenche…

portal

Next section: Simple implementation used on the Autowired property