preface

Thank you before the two bosses, the first is my classmate, the brother cow force, in this paper, the code relies on him to code, another bosses is to write the Spring5 core principles and 30 classes written in actual combat “, the author of because this is where it came from, the code is in the book, just here to make a record, Comb through the process.

The environment

Environmental preview

The first is our environment preparation, which is the environment preparation, which is the creation of the Java Web project that we talked about earlier.

After that, there is nothing, we mainly write a few comments in the SpringMVC section.

The project structure looks like this

Environmental test

OK, project, everything is fine

Project structures,

The next step is to build our project

Mymvcframwork structures,

Package creation and annotation writing

We wrote Version1 and HU represents annotations that we typed ourselves.

The annotation code is as follows

package com.huterox.mvcframework.annotation;

import java.lang.annotation.*;

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

Copy the code
package com.huterox.mvcframework.annotation;

import java.lang.annotation.*;

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

Copy the code
package com.huterox.mvcframework.annotation;

import java.lang.annotation.*;

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

Copy the code
package com.huterox.mvcframework.annotation;

import java.lang.annotation.*;

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

Copy the code
package com.huterox.mvcframework.annotation;

import java.lang.annotation.*;

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

Copy the code

HUDispatcherServlet

This is the class that will help us implement all the functionality, and it contains a complete set of IOC containers.

The web.xml configuration

Our HUDispatcherServlet is based on HttpServletAnd the goal is we’re going to implement that MVC functionality like thisThen we have to get a claim.

<! DOCTYPEweb-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>

  <display-name>Archetype Created Web Application</display-name>


  <servlet>
    <servlet-name>gpmvc</servlet-name>
    <servlet-class>com.huterox.mvcframework.servlet.Version1.HUDispatcherServlet</servlet-class>
    <init-param>
      <param-name>Myproperties</param-name>
      <param-value>application.properties</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
<! -- Init -->
  </servlet>
  <servlet-mapping>
    <servlet-name>gpmvc</servlet-name>
    <url-pattern>/ *</url-pattern>
  </servlet-mapping>
</web-app>

Copy the code

We broker all requests so that the flow of requests can be captured by our HUDispatchServlet before we can do any more processing.

The working process

So that’s about six steps

doLoadConfig

This is essentially the IO stream that gets our configuration.Get the packages we need to process

    private void doLoadConfig(String contextproperties) {
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextproperties);
        try {
            contextConfig.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(is! =null) {try {
                    is.close();
                } catch(IOException e) { e.printStackTrace(); }}}}Copy the code

doScanner

This thing is scanning, to save the path of that class under that package for the next step.

    private void doScanner(String scanPackage){
        // Scan all classes under the package we want to control
        // Save com.huterox.test.xx(class name)
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\ \."."/"));

        asserturl ! =null;
        File classDir = new File(url.getFile());
        for(File file: Objects.requireNonNull(classDir.listFiles())){
            if(file.isDirectory()){
                this.doScanner(scanPackage+"."+file.getName());
            }else {
                if(! file.getName().endsWith(".class")) continue;
                String clazzName = (scanPackage + "." + file.getName().replace(".class"."")); classNames.add(clazzName); }}}Copy the code

doInstance

This is to add the path of the class from the previous step to the IOC container according to our rules.

    private void doInstance(a){
        if(classNames.isEmpty()){
            return;
        }
        for(String className:classNames){
            try{ Class<? > clazz = Class.forName(className);if(clazz.isAnnotationPresent(HUController.class)){

                   Object instance = clazz.newInstance();
                   // The class name starts with a lowercase letter
                   String beanName = toLowerFirstCase(clazz.getSimpleName());
                   IOC.put(beanName,instance);
               } else if (clazz.isAnnotationPresent(HUService.class)) {

                   HUService service = clazz.getAnnotation(HUService.class);
                   String beanName = service.value();
                   if("".equals(beanName.trim())){
                       beanName = toLowerFirstCase(clazz.getSimpleName());
                   }
                   Object instance = clazz.newInstance();
                   IOC.put(beanName,instance);

                   // Add the interface implemented by the object
                   for(Class<? > i:clazz.getInterfaces()){if(IOC.containsKey(i.getName())){
                           throw new Exception("Interface implemented by this class"+i.getName()+"Pre-existing"); } IOC.put(i.getName(),instance); }}else {
                   continue; }}catch(Exception e) { e.printStackTrace(); }}}Copy the code

The other thing to notice here is that there’s another way to do this which is to lower case the first letter and treat the bean name as standard.

    private String toLowerFirstCase(String simpleName){
        // Convert letters to javabean standards
        char[] chars = simpleName.toCharArray();
        if(67< = (int)chars[0] && (int)chars[0] < =92)
            chars[0] + =32;
        return String.valueOf(chars);
    }

Copy the code

doAutowired

So dependency injection, there are rules, there are comments in the code

   private void doAutowired(a){
        // Start doing dependency injection values
        if(IOC.isEmpty()){return; }for (Map.Entry<String, Object> Entry : IOC.entrySet()) {
            Field[] fields = Entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                if(! (field.isAnnotationPresent(HUAutowired.class)))continue;
                HUAutowired autowired = field.getAnnotation(HUAutowired.class);
                String beanName = autowired.value().trim();
                if("".equals(beanName)){
                    beanName = field.getType().getName();
                }
                field.setAccessible(true);

                try {
                    // This is why the first letter should be in lower case
                    field.set(Entry.getValue(),IOC.get(beanName));
                } catch(IllegalAccessException e) { e.printStackTrace(); }}}}Copy the code

doInitHandlerMapping

This thing is used to save urls. This is the

   private void doInitHandlerMapping(a) {

        if(IOC.isEmpty()) return;

        for(Map.Entry<String, Object> Entry : IOC.entrySet()) { Class<? > clazz = Entry.getValue().getClass();if(! clazz.isAnnotationPresent(HUController.class))continue;
            String baseURl="";
            if(clazz.isAnnotationPresent(HURequestMapping.class)){
                HURequestMapping requestMapping = clazz.getAnnotation(HURequestMapping.class);
                baseURl = requestMapping.value();
            }
            for (Method method : clazz.getMethods()) {
                if(! method.isAnnotationPresent(HURequestMapping.class))continue;
                HURequestMapping requestMapping = method.getAnnotation(HURequestMapping.class);
                String url =("/" + baseURl +"/"+ requestMapping.value()).replaceAll("/ +"."/");

                handlerMapping.put(url,method);
                // Put the URL together with the corresponding method}}}Copy the code

doDispatch

So that’s what we’re going to end up reading the URL that the browser sent and then matching the method and executing it.

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        // Do not use the original tomcat
        url = url.replaceAll(contextPath,"").replaceAll("/ +"."/");
        System.out.println(url);
        if(!this.handlerMapping.containsKey(url)){
            resp.getWriter().write("404 Not Found!");
            return;
        }

        // Get the request parameters
        Map<String,String[]> paramsMap = req.getParameterMap();

        Method method = this.handlerMapping.get(url);
// String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
// Object o = IOC.get(beanName);
// // here are some parameters
// method.invoke(o,new Object[]{req,resp,params.get("name")[0]});Class<? >[] parameterTypes = method.getParameterTypes(); Object[] paramValues =new Object[parameterTypes.length];
// Dynamically assign values

        for (int i = 0; i < parameterTypes.length; i++) { Class<? > parameterType = parameterTypes[i];if(parameterType==HttpServletRequest.class){
                paramValues[i] = req;
                continue;
            }else if(parameterType==HttpServletResponse.class){
                paramValues[i] =resp;
                continue;
            }else{
                // There are only four types for now
                Annotation[][] pa = method.getParameterAnnotations();
                //pa[0] represents the first annotation of the first parameter pa[0][0]
                for(int j=0; j<pa.length; j++){for(Annotation a:pa[i]){
                        if(a instanceof HURequestParam){
                            String paramName = ((HURequestParam) a).value();
                            if(!"".equals(paramName)){
                                String strings =Arrays.toString(paramsMap.get(paramName))
                                        .replaceAll("\ \ [| \ \]"."")
                                        .replaceAll("\\s"."");/ / filter
                                if(parameterType==String.class){

                                    paramValues[i] = strings;

                                }else if(parameterType==Integer.TYPE ||parameterType==Integer.class ){

                                    paramValues[i] = Integer.valueOf(strings);
                                }else if(parameterType==Double.TYPE ||parameterType==Double.class ){

                                    paramValues[i] = Double.valueOf(strings);
                                }else if(parameterType==Float.TYPE ||parameterType==Float.class ){
                                    paramValues[i] = Float.valueOf(strings);
                                }

                            }

                        }
                    }
                }

            }
        }
        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        method.invoke(IOC.get(beanName),paramValues);

    }
Copy the code

call

Let’s get the whole code out here

package com.huterox.mvcframework.servlet.Version1;

import com.huterox.mvcframework.annotation.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

public class HUDispatcherServlet extends HttpServlet {

    private Map<String,Object> IOC = new HashMap<>();
    private Properties contextConfig = new Properties();
    private List<String> classNames = new ArrayList<>();
    private Map<String,Method> handlerMapping = new HashMap<>();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);// Ensure that both post and GET can be processed at the same time
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            System.out.println("hello this request will be processed by mymvcframeword ");
            this.doDispatch(req,resp);// Handle accordingly
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("Server exception 500:"+Arrays.toString(e.getStackTrace())); }}@Override
    public void init(ServletConfig config) throws ServletException {
        // Initialize the configuration
        //1. Load the configuration file
        doLoadConfig(config.getInitParameter("Myproperties"));
        //2. Scan related classes
        doScanner(contextConfig.getProperty("scanPackage"));
        //3. Initialize the IOC container and instantiate the related classes
        doInstance();
        //4. Complete the DI injection
        doAutowired();
        //5. Initialize HandlerMapping
        doInitHandlerMapping();
        //6. Complete ~~ to the request dispatcher to execute the corresponding method


    }


    private void doLoadConfig(String contextproperties) {
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextproperties);
        try {
            contextConfig.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(is! =null) {try {
                    is.close();
                } catch(IOException e) { e.printStackTrace(); }}}}private void doScanner(String scanPackage){
        // Scan all classes under the package we want to control
        // Save com.huterox.test.xx(class name)
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\ \."."/"));

        asserturl ! =null;
        File classDir = new File(url.getFile());
        for(File file: Objects.requireNonNull(classDir.listFiles())){
            if(file.isDirectory()){
                this.doScanner(scanPackage+"."+file.getName());
            }else {
                if(! file.getName().endsWith(".class")) continue;
                String clazzName = (scanPackage + "." + file.getName().replace(".class"."")); classNames.add(clazzName); }}}private void doInstance(a){
        if(classNames.isEmpty()){
            return;
        }
        for(String className:classNames){
            try{ Class<? > clazz = Class.forName(className);if(clazz.isAnnotationPresent(HUController.class)){

                   Object instance = clazz.newInstance();
                   // The class name starts with a lowercase letter
                   String beanName = toLowerFirstCase(clazz.getSimpleName());
                   IOC.put(beanName,instance);
               } else if (clazz.isAnnotationPresent(HUService.class)) {

                   HUService service = clazz.getAnnotation(HUService.class);
                   String beanName = service.value();
                   if("".equals(beanName.trim())){
                       beanName = toLowerFirstCase(clazz.getSimpleName());
                   }
                   Object instance = clazz.newInstance();
                   IOC.put(beanName,instance);

                   // Add the interface implemented by the object
                   for(Class<? > i:clazz.getInterfaces()){if(IOC.containsKey(i.getName())){
                           throw new Exception("Interface implemented by this class"+i.getName()+"Pre-existing"); } IOC.put(i.getName(),instance); }}else {
                   continue; }}catch(Exception e) { e.printStackTrace(); }}}private void doAutowired(a){
        // Start doing dependency injection values
        if(IOC.isEmpty()){return; }for (Map.Entry<String, Object> Entry : IOC.entrySet()) {
            Field[] fields = Entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                if(! (field.isAnnotationPresent(HUAutowired.class)))continue;
                HUAutowired autowired = field.getAnnotation(HUAutowired.class);
                String beanName = autowired.value().trim();
                if("".equals(beanName)){
                    beanName = field.getType().getName();
                }
                field.setAccessible(true);

                try {
                    // This is why the first letter should be in lower case
                    field.set(Entry.getValue(),IOC.get(beanName));
                } catch(IllegalAccessException e) { e.printStackTrace(); }}}}private void doInitHandlerMapping(a) {

        if(IOC.isEmpty()) return;

        for(Map.Entry<String, Object> Entry : IOC.entrySet()) { Class<? > clazz = Entry.getValue().getClass();if(! clazz.isAnnotationPresent(HUController.class))continue;
            String baseURl="";
            if(clazz.isAnnotationPresent(HURequestMapping.class)){
                HURequestMapping requestMapping = clazz.getAnnotation(HURequestMapping.class);
                baseURl = requestMapping.value();
            }
            for (Method method : clazz.getMethods()) {
                if(! method.isAnnotationPresent(HURequestMapping.class))continue;
                HURequestMapping requestMapping = method.getAnnotation(HURequestMapping.class);
                String url =("/" + baseURl +"/"+ requestMapping.value()).replaceAll("/ +"."/");

                handlerMapping.put(url,method);
                // Put the URL together with the corresponding method}}}private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        // Do not use the original tomcat
        url = url.replaceAll(contextPath,"").replaceAll("/ +"."/");
        System.out.println(url);
        if(!this.handlerMapping.containsKey(url)){
            resp.getWriter().write("404 Not Found!");
            return;
        }

        // Get the request parameters
        Map<String,String[]> paramsMap = req.getParameterMap();

        Method method = this.handlerMapping.get(url);
// String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
// Object o = IOC.get(beanName);
// // here are some parameters
// method.invoke(o,new Object[]{req,resp,params.get("name")[0]});Class<? >[] parameterTypes = method.getParameterTypes(); Object[] paramValues =new Object[parameterTypes.length];
// Dynamically assign values

        for (int i = 0; i < parameterTypes.length; i++) { Class<? > parameterType = parameterTypes[i];if(parameterType==HttpServletRequest.class){
                paramValues[i] = req;
                continue;
            }else if(parameterType==HttpServletResponse.class){
                paramValues[i] =resp;
                continue;
            }else{
                // For now, only strings
                Annotation[][] pa = method.getParameterAnnotations();
                //pa[0] represents the first annotation of the first parameter pa[0][0]
                for(int j=0; j<pa.length; j++){for(Annotation a:pa[i]){
                        if(a instanceof HURequestParam){
                            String paramName = ((HURequestParam) a).value();
                            if(!"".equals(paramName)){
                                String strings =Arrays.toString(paramsMap.get(paramName))
                                        .replaceAll("\ \ [| \ \]"."")
                                        .replaceAll("\\s"."");/ / filter
                                if(parameterType==String.class){

                                    paramValues[i] = strings;

                                }else if(parameterType==Integer.TYPE ||parameterType==Integer.class ){

                                    paramValues[i] = Integer.valueOf(strings);
                                }else if(parameterType==Double.TYPE ||parameterType==Double.class ){

                                    paramValues[i] = Double.valueOf(strings);
                                }else if(parameterType==Float.TYPE ||parameterType==Float.class ){
                                    paramValues[i] = Float.valueOf(strings);
                                }

                            }

                        }
                    }
                }

            }
        }
        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        method.invoke(IOC.get(beanName),paramValues);

    }
    private String toLowerFirstCase(String simpleName){
        // Convert letters to javabean standards
        char[] chars = simpleName.toCharArray();
        if(67< = (int)chars[0] && (int)chars[0] < =92)
            chars[0] + =32;
        returnString.valueOf(chars); }}Copy the code

test

The IOC test

We’ve got the code, but we need to test every feature. The first is our test class

package com.huterox.test;


import com.huterox.mvcframework.annotation.HUController;
import com.huterox.mvcframework.annotation.HURequestMapping;
import com.huterox.mvcframework.annotation.HURequestParam;


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@HUController
public class TestHello {

    @HURequestMapping("/test/hello")
    public String sayHello(HttpServletRequest req, HttpServletResponse resp,
                           @HURequestParam("name") String name,
                           @HURequestParam("id") Integer id
                           ) throws IOException {

        String res = "Hello -- "+name+"--"+id;
        PrintWriter writer = resp.getWriter();
        writer.write(res);
        writer.flush();
        writer.close();
        returnres; }}Copy the code

I only have one class added to our IOC container, so let’s play around.

IOC container normal

HandlerMapping container

Let’s look at this container in actionnormal

Dopatch test

Next let’s take a look at our most important feature page as normalConsole output normal

conclusion

Overall, this is interesting, but obviously there are many points to consider at many levels. On weekends I’ll go to the library and get my books out and have some fun.