1. The introduction of the servlet

Servlet is a Java program running in the Web server environment. It is commonly used in the form of defining a Java class that inherits the HttpServlet class and overrides the Init () and doGet/doPost methods of HttpServlet. Then define the servlet response path of this class in web. XML, and put the compiled class file of the Java class into the /webapps/ROOT/WEB-INF/classes folder of the Tomcat server. Put the web. XML file into the /webapps/ROOT/ web-INF folder, start Tomcat, access the mapping path, and obtain the result, that is, access the servlet successfully.

Workflow of servlets

To understand the workflow process of servlets, it is necessary to understand the flow logic of servlets in the Web server. Here, take Tomcat as an example, the Tomcat server mainly realizes three functions:

  1. Establish a channel to listen for request data on the port

  2. Creates a servlet container, the servlet execution code (init – > service – > (doGet | | doPost))

  3. Build a mapping between the port request object and the servlet response address

So a complete request flow is:

  1. The user initiates a request for information in the Web environment
  2. After the Tomcat port listener captures the request, it creates request and Response objects and encapsulates the request packets into reqUSET
  3. The request object finds the corresponding servlet object through the request URL mapping. If the servlet is accessed for the first time, the init method is executed
  4. The Service method is then executed to further perform request methods like doGet or doPost, depending on the request type in resquest
  5. After processing the logic in the doGet or doPost methods, the data is encapsulated in a Response object and returned to the user to complete the request flow
A servlet instance

Springboot is used here to build the Web project environment

Define a servlet class:

public class SimpleServlet extends HttpServlet {

    @Override
    public void init(a) throws ServletException {
        System.out.println("The servlet execution");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("DoGet execution");
        doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("The doPost execution");
        resp.setContentType("text/html; charset=UTF-8");
        resp.getWriter().append("Hello");
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.service(req, resp);
        System.out.println("The service execution"); }}Copy the code

Create the Servlet registration class

@Configuration
public class ServletConfig  {
    @Bean
    public ServletRegistrationBean servletRegistrationBean(a) {
        ServletRegistrationBean servletRegistrationBean =
                new ServletRegistrationBean(new SimpleServlet(),"/v4/*");
        returnservletRegistrationBean; }}Copy the code

Start the Springboot project, visit http://localhost/v4, the page displays’ Hello ‘, and the project control side prints:

Servlet performs doGet performs doPost performs Service executionCopy the code

2. Application of Servlet in Spring

In the above servlet instance, we implemented the request interception for **/ V4 ** URL. If we want to block other URL requests, we need to create another servlet. Can we create only one servlet to complete the interception for all requested addresses? The answer is yes, just change the servlet’s blocking address to **/***. Can we make some extensions on this basis: Spring MVC module does this. It involves DispatcherServlet, @Controller, @requestMapping and other things we commonly use. Let’s briefly implement the basic functionality of springMVC.

A custom MVC mapping servlet

Let’s start by simplifying the code to implement springMVC:

The structure of the project file is as follows:

1. Define common annotations in MVC

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PWBAutowired {
    String value(a) default "";
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PWBController {
    String value(a) default "";
}

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PWBRequestMapping {
    String value(a) default "";
}

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PWBRequestParam {
    String value(a) default "";
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PWBService {
    String value(a) default  "";
}
Copy the code

2. Define a generic servlet

@WebServlet(name = "PwbDisparcherServlet", urlPatterns = {
        "/ *"
})
public class PwbDisparcherServlet extends HttpServlet {
    // Store the class path name of the scanned bean
    private  List<String> classNameList = new ArrayList<>();
    / / store the bean
    private Map<String,Object> iocMap = new HashMap<>();
    // Store URL mappings
    private Map<String, Method> handlerMap = new HashMap<>();

    @Override
    public void init(ServletConfig cfg) throws ServletException {
        // Read the bean to be instantiated according to the configuration
        doScanner("com.demo");
        // Instantiate and store the annotated bean
        doInstance();
        // Inject the called object
        doAutowire();
        // Store all mapped access paths
        doHandlerMapping();
        System.out.println("--------------- initialization completed !!!!");
    }


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        doDisparcher(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) { doDisparcher(req, resp); }}Copy the code

This serlvet initialization method contains the following logic:

  1. Scan the package path for bean object information and store it in classNameList (doScanner method)

    private void doScanner(String scanPackage){
        URL url = this.getClass().getClassLoader().getResource(scanPackage.replaceAll("\ \."."/"));
        File classFile = new File(url.getFile());
        if(classFile == null) {throw new RuntimeException(classFile.toString()+"Wrong file path!");
        }
        if(classFile.listFiles() == null) {throw new RuntimeException("Not available"+classFile.toString()+"List data in file path!");
        }
        for(File file: classFile.listFiles()){
            if(file.isDirectory()){
                / / recursion
                doScanner(scanPackage+"."+file.getName());
            }else{
                // Get the class absolute path
                if(file.getName().endsWith(".class") &&! file.getName().contains("PWB") &&! file.getName().contains("Application") &&! file.getName().contains("Servlet")){
                    String className = scanPackage+"."+ file.getName().replaceAll(".class".""); classNameList.add(className); }}}}Copy the code
  2. Instantiate the bean from the classNameList list and store it in the iocMap (doInstance method)

    private void doInstance(a){
        try {
            for(String className: classNameList){
                / / filter
                if(! className.contains(".")) {
                    continue;
                }
                // Class instantiationiocMap.put(className, Class.forName(className).newInstance()); }}catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch(IllegalAccessException e) { e.printStackTrace(); }}Copy the code
  3. Complete the injection of a class into the bean based on the @PWbAutoWired annotation information in the bean (doAutowire method)

    private void doAutowire(a) {
        System.out.println("--------------- start injection !!!!");
        try {
            // Handle dependency injection for classes
            for(Object object: iocMap.values()){
                if(object == null) {continue;
                }
                Class clazz = object.getClass();
                // Loop through the class properties to find the class to inject
                for(Field field : clazz.getDeclaredFields()){
                    if(field.isAnnotationPresent(PWBAutowired.class)){
                        String beanName = field.getAnnotation(PWBAutowired.class).value();
                        if("".equals(beanName)){
                            beanName = field.getType().getName();
                        }
                        // Remove the restriction
                        field.setAccessible(true); field.set(iocMap.get(clazz.getName()), iocMap.get(beanName)); }}}}catch(IllegalAccessException e) { e.printStackTrace(); }}Copy the code
  4. Build the mapping between URL and method according to @pwbController and @PwBrequestMapping annotation information in bean, and store it in handlerMap (doHandlerMapping method).

    private void doHandlerMapping(a) {
        for(Map.Entry bean: iocMap.entrySet()){ Class<? > clazz = bean.getValue().getClass();// Whether there is a PWBController annotation
            if (clazz.isAnnotationPresent(PWBController.class)) {
                // Whether there is a PWBRequestMapping annotation
                String mappingUrl = "";
                if (clazz.isAnnotationPresent(PWBRequestMapping.class)) {
                    // Get the annotation content
                    mappingUrl = clazz.getAnnotation(PWBRequestMapping.class).value();
                }
                // Loop the class method to record the request URL and the corresponding method
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if (method.isAnnotationPresent(PWBRequestMapping.class)) {
                        String url = method.getAnnotation(PWBRequestMapping.class).value();
                        url = (mappingUrl + url).replaceAll("/ +"."/");
                        handlerMap.put(url, method);
                    }
                }
            }
        }
    }
    Copy the code
  5. Match method to the request URL in request and execute the target method (doDisparcher method) by reflection

    private void doDisparcher(HttpServletRequest req, HttpServletResponse resp) {
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replace(contextPath,"").replaceAll("/ +"."/");
        try {
            if(!this.handlerMap.containsKey(url)){
                resp.getWriter().append("-------------404 NOT FOUND!");
                return;
            }
            // Call the corresponding method
            Method method = (Method) handlerMap.get(url);
            // Get the actual request parameters
            Map<String, String[]> params = req.getParameterMap();
            // Request method input type arrayClass<? > [] paramsTypes = method.getParameterTypes();// The processed parameter container
            Object[] resultArray = new Object[paramsTypes.length];
            // Array of loop parameter types
            for (int i=0; i < paramsTypes.length; i++){ Class<? > paramType = paramsTypes[i];// Return request directly
                if(paramType == HttpServletRequest.class){
                    resultArray[i] = req;
                    continue;
                // Response is returned directly
                }else if (paramType == HttpServletResponse.class){
                    resultArray[i] = resp;
                    continue;
                // The parameter is a string
                } else if (paramType == String.class) {
                    // Get the parameter object
                    Parameter parameter = method.getParameters()[i];
                    // Whether the parameter has a PWBRequestParam annotation
                    if(parameter.isAnnotationPresent(PWBRequestParam.class)){
                        String annoParamName = parameter.getAnnotation(PWBRequestParam.class).value();
                        resultArray[i] = Arrays.toString(params.get(annoParamName));
                    }else{ String paramName = method.getParameters()[i].getName(); resultArray[i] = Arrays.toString(params.get(paramName)); }}}// Get the class object corresponding to the method
            Object obj = (iocMap.get(method.getDeclaringClass().getName()));
            / / reflection
            method.invoke(obj, resultArray);
        }  catch (IOException e) {
            e.printStackTrace();
        }catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch(InvocationTargetException e) { e.printStackTrace(); }}Copy the code

Create a corresponding request Controller

@PWBController
@PWBRequestMapping("/v1")
public class IndexController {
    @PWBAutowired
    private IndexService indexService;

    @PWBRequestMapping("/query")
    public void queryString(HttpServletRequest request, HttpServletResponse response, String name){
        String msg = indexService.queryString(name);
        try {
            response.setContentType("text/html; charset=UTF-8");
            response.getWriter().write(msg);
        } catch(IOException e) { e.printStackTrace(); }}}@PWBService
public class IndexService {
    public String queryString(String msg){
        return "Service returns message :"+msg+"= = = = ="; }}Copy the code

Start the project, visit http://localhost/v1/query