Handwritten Mini spring MVC core code

Possible knowledge blind spots in the process

The role of init param in web.xml

Customize initialization parameters: you can customize the initialization parameters of servlet, JSP and Context, and then obtain these parameter values from servlet, JSP and Context. Let's take servlet as an example:

  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
  </servlet>

After the above configuration, getServletConfig().getInitParameter("contextConfigLocation") can be called in the servlet to obtain the value corresponding to the parameter name.

The function and basic configuration of web.xml file

Get the value defined by init param in web.xml

tomcat and servlet quick start tutorial!!!

Annotation @ Retention

Annotation @ Retention can be used to modify annotation. It is an annotation of annotation, which is called meta annotation.

The Retention annotation has an attribute value, which is of type RetentionPolicy, and Enum RetentionPolicy is of type enumeration,

This enumeration determines how the Retention annotation should be maintained. It can also be understood as the use of Retention with rentionpolicy.

RetentionPolicy has three values: CLASS RUNTIME SOURCE

According to the life cycle, it can be divided into three categories:

1. RetentionPolicy.SOURCE: annotations are only retained in source files. When Java files are compiled into class files, annotations are discarded;

2. RetentionPolicy.CLASS: annotations are retained in the class file, but are discarded when the jvm loads the class file. This is the default life cycle;

3. RetentionPolicy.RUNTIME: the annotation is not only saved to the class file, but still exists after the jvm loads the class file;

These three life cycles correspond to: java source file (. java file) - >. Class file - > bytecode in memory.

How to choose the appropriate annotation life cycle?
First of all, the life cycle length source < class < runtime must be clear, so where the former can work, the latter must also work.

Generally, if you need to dynamically obtain annotation information at RUNTIME, you can only use RUNTIME annotation, such as @ Deprecated using RUNTIME annotation

If you want to perform some preprocessing operations during compilation, such as generating some auxiliary code (such as ButterKnife), use CLASS annotation;

If you only do some checking operations, such as @ Override and @ SuppressWarnings, use the SOURCE annotation.

The @ Override annotation is used on methods. When we want to Override a method, we add @ Override to the method. When the name of our method is wrong, the compiler will report an error

Annotation @ Deprecated is used to indicate that when a class or property or method is outdated and does not want to be reused by others, it is modified with @ Deprecated on the property and method

The @ SuppressWarnings annotation is used to suppress warnings in programs, such as when generics are not used or methods are outdated

response.getwriter().println() and response.getwriter.write()

The API of response.getwriter.write() is as follows:

The API of response.getwriter().println() is as follows:

As can be seen from the API, both of them can print and output various types of data: the function of response.getwriter().println() is more powerful than that of response.getwriter.write().

response.getwriter().println() can output html type tags and an object.
response.getwriter.write() can also output html type tags, but it cannot output an object.

request.getRequestURI() is different from the method of obtaining the relevant path

Suppose your web application name (project name) is news, and you enter the request path in the browser:

http://localhost:8080/news/main/list.jsp

Then execute the following line of code and print the following results:

request.getRequestURL() returns the full path, i.e http://localhost:8080/news/main/list.jsp

1, System.out.println(request.getContextPath());

Print results: / news

Return the project name part. If the project is mapped to /, it will be null here

2,System.out.println(request.getServletPath());

Print results: / main/list.JSP

Returns the path excluding the host and project name parts

3, System.out.println(request.getRequestURI());

Print results: / news/main/list.JSp

Returns the path excluding the host (domain name or ip) part

4, System.out.println(request.getRealPath("/"));

Print result: F:\tomcat 6.0\webapps\news\test

Returns the absolute path of the current web page

request.getParameterMap()

According to the Java specification: request.getParameterMap() returns a value of Map type, which records the mapping relationship between the request parameters and the request parameter values in the request submitted by the front end. This return value has a special feature - it can only be read. Unlike ordinary Map type data, it can be modified. This is because of the restrictions made by the server in order to achieve certain security specifications.

When using generics for the return value of request.getParameterMap(), it should be in the form of Map < string, string [] > because sometimes components such as checkbox have a name corresponding to a value, so the key value pair in the Map is the implementation of < string – > string [] >.

For example, it is easy to get the parameters submitted by the jsp page on the server side, but the parameters and values in the request can be turned into a Map through request.getParameterMap().

The following is the map structure formed by printing the obtained parameters and values: Map(key,value []), that is, key is a String type and value is a String type array.

For example, the map structure formed by the parameter T1 = 1 & T1 = 2 & T2 = 3 in the request:

key=t1;value[0]=1,value[1]=2

key=t2;value[0]=3

If you directly use map.get("t1"), you will get: Ljava.lang.String; value is only in the form of an array to prevent the same parameter names.

Key points of reflection related knowledge

  1. The class name. class.getName() is used to get the full class name of this class
  2. Method. Getdeclarangclass() gets the Class to which the current method object belongs
  3. class.getDeclaredxxx get all xxx of the current class
  4. class.getPackage().getName() gets the package name of the current class

Java reflection 08: member Method learning example

Java reflection 06: learning example of member variable Field

java URL class interface and simple application

java URL get local URL new URL("file:\d:\hehe.html")

java URL class interface and simple application

getFile() and getPath() methods of URL

getFile() method:

public class URLTest {  
    public static void main(String[] args) {  
          
        URL url1 = URLTest.class.getResource("te st.txt");  
        URL url2 = URLTest.class.getResource("chinese.txt");  
          
        System.out.println("te st.txt => " + url1.getFile() + ", exist => " + new File(url1.getFile()).exists());  
        System.out.println("chinese.txt => " + url2.getFile() + ", exist => " + new File(url2.getFile()).exists());  
    }   
}  
te st.txt => /E:/CODE/Test/bin/url/te st.txt, exist => true  
chinese.txt => /E:/CODE/Test/bin/url/chinese.txt, exist => true  

getFile()

getPath() method

The return path obtained by Java through the getpath method of URL is a garbled solution

The difference between getFile() and getPath() methods of URL

The difference between getClassLoader().getResource() and getResource() in Java

The difference between getClassLoader().getResource() and getResource() in Java

trim() method in String

Detailed description of trim() method in java.lang.String

Java regular expression Pattern and Matcher classes

Detailed explanation of Java regular expression Pattern and Matcher classes

thinking

1. Configuration file

  1. Configure the application.properties file

For the convenience of parsing, use. properties instead of spring's native application.xml

The specific configuration contents are as follows:

scanPackage=com.dhy.demo
  1. Configure the web.xml file

All projects that depend on the web container start from reading the web.xml file. Let's configure the contents of the web.xml file first

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
         xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <display-name>Dhy Web Application</display-name>
    <!--    to configure DispatcherServlet-->
    <!--servlet-name label Servlet The program has an alias(Usually the class name)-->
    <servlet>
        <servlet-name>dhy_mvc</servlet-name>
        <servlet-class>com.dhy.demo.DispatcherServlet</servlet-class>
<!--         Initialization parameters-->
        <init-param>
             <param-name>contextConfigLocation</param-name>
             <param-value>application.properties</param-value>
         </init-param>
<!--        Server startup creation servlet-->

        <!--  appoint Servlet Time to create
         1.When first accessed, create
          <load-on-startup>The value of is negative
          2.When the server starts, create
           <load-on-startup>The value of is 0 or a positive integer
    -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <!--to servlet Program configuration access address-->
<servlet-mapping>
    <!--Tell the server to which request path I currently configure servlet Program use-->
    <servlet-name>dhy_mvc</servlet-name>
<!--Intercepted path,/*Block all requests,Include all static resources,and.jsp-->
<!--    /Intercept all requests, including all static resources,barring.jsp-->
    <url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
        

DispatcherServlet is a core function class that simulates the implementation of Spring

2. User defined annotation

//Only works on classes
@Target({ElementType.TYPE})
//Reserved bytecode file
@Retention(RetentionPolicy.RUNTIME)
@Documented//Extract to api document
public @interface Service
{
    //The value attribute holds the alias of the current class in the IOC container
    //The default is empty
    String value() default "";
}

//Act on member variables
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired
{
    String value() default "";
}


//Act on class
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller
{
    //The value attribute holds the alias of the current class in the IOC container
    //The default is empty
    String value() default "";
}



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


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

3. Configuration notes

The jar package of servlet api needs to be introduced so that we can use the relevant api functions in the servlet

     <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>

Configure business implementation class

public interface IDemoService
{
    String get(String name);
}

@Service
public class DemoService implements IDemoService
{
    @Override
    public String get(String name) {
        return "my name is "+name;
    }
}

Configuration request class

@Controller
@RequestMapping("/demo")
public class DemoController
{
    @Autowired
    private IDemoService demoService;
    @RequestMapping("/query")
    public void query(HttpServletRequest req, HttpServletResponse resp
                      , @RequestParam("name")String name)
    {
        String ret = demoService.get(name);
         try{
             //Output content to page
             resp.getWriter().write(ret);
         }catch(Exception e)
         {
             e.printStackTrace();
         }
    }

    @RequestMapping("/add")
    public void add(HttpServletRequest req,HttpServletResponse resp,
                    @RequestParam("a")Integer a,@RequestParam("b")Integer b)
    {
        try {
            resp.getWriter().write(a+"+"+b+"="+(a+b));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @RequestMapping("/remove")
    public void remove(HttpServletRequest req,HttpServletResponse resp,
                       @RequestParam("id")Integer id)
    {}
}

Container initialization

Implement version 1.0

All core method logic is written in init() method, and the code is as follows:

public class DispatcherServlet extends HttpServlet
{
    //The controller container that holds the mapping request
    private Map<String,Object> mapping=new HashMap<>();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       //The implementation logic of the doGet method is the same as that of doPost, so the doPost method is called directly here
        this.doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }

    private void doDispatch(HttpServletRequest req,HttpServletResponse resp) throws IOException, InvocationTargetException, IllegalAccessException {
        //Gets the URI of the request
        //Project name + virtual request path
        String url=req.getRequestURI();
        //Returns the current project name section
        String contextPath = req.getContextPath();
        //Remove the project name from the url path
        url=url.replace(contextPath,"").replaceAll("/+","/");
        //If there is no controller for the currently requested mapping in the container
        if(!this.mapping.containsKey(url))
        {
            resp.getWriter().write("404 Not Found!!");
        }
        //Gets the method corresponding to processing the current request mapping
        Method method=(Method)this.mapping.get(url);
        //Get the request parameters and encapsulate them into a map set
        Map<String, String[]> parameterMap = req.getParameterMap();
        //Reflection call method
        //Method. Getdeclarangclass() gets the Class to which the current method object belongs
        //Method. Getdeclarangclass(). Getname() gets the name of the current class (full class name)
        method.invoke(this.mapping.get(method.getDeclaringClass().getName())
                //There are multiple method parameters
                ,new Object[]{
                        req,resp,parameterMap.get("name")[0]
                });
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        InputStream is=null;
        try {
            //Read out the package path to be scanned from the configuration file
            Properties configContext=new Properties();
            //Read the specified properties file from the classpath
            is = this.getClass().getClassLoader().
                    //config.getInitParameter gets the value of the initialization parameter
                            getResourceAsStream(config.getInitParameter("contextConfigLocation"));
            configContext.load(is);
            //Gets the package path to scan
            String scanPackage = configContext.getProperty("scanPackage");
            //Perform packet scan logic
            doScanner(scanPackage);
            //Traverse the collection of key values in the container
            //The key store is the full class name
            for (String className : this.mapping.keySet())
            {
                //If the full class name does not contain., it indicates that it is not the full class name
                if(!className.contains(".")){continue;}
                //Obtain the bytecode file object according to the full class name
                Class<?> clazz=Class.forName(className);
               //Judge whether the controller annotation is marked on the current class
                if(clazz.isAnnotationPresent(Controller.class))
                {
                    //If the annotation is marked on the current class, you need to create a unique instance and put it into the container
                    //Default class name as id
                    mapping.put(className,clazz.newInstance());
                    //Virtual request path
                    String baseUrl="";
                    //Judge whether the requestMapping annotation is marked on the current class
                    if(clazz.isAnnotationPresent(RequestMapping.class))
                    {
                        //Get this annotation object
                        RequestMapping requestMapping=clazz.getAnnotation(RequestMapping.class);
                        //If the class is annotated and the path is given, it is spliced
                        baseUrl=requestMapping.value();
                    }
                    //Get all the methods on the current class
                    Method[] methods = clazz.getMethods();
                    for (Method method : methods) {
                        //If there is no annotation on the method, skip it directly
                        if(!method.isAnnotationPresent(RequestMapping.class)){continue;}
                        //Gets the annotation object on the method
                        RequestMapping requestMapping=method.getAnnotation(RequestMapping.class);
                        //Splice request path
                        String url=(baseUrl+"/"+requestMapping.value().replace("/+","/"));
                        //Put the current method into the container
                        mapping.put(url,method);
                        System.out.println("Mapped "+url+" , "+method);
                    }
                }
                else if(clazz.isAnnotationPresent(Service.class))
                {
                    //Gets the annotation object annotated on the class
                    Service service=clazz.getAnnotation(Service.class);
                    //value is the name of the bean in the container
                    String beanName=service.value();
                    //If the name of the bean is not specified manually, the full class name is used as the name by default
                    if("".equals(beanName)){beanName=clazz.getName();}
                    //Create instance of reflection
                    Object instance = clazz.newInstance();
                    //Put in container
                    mapping.put(beanName,instance);
                    //Gets all interfaces implemented by the current class
                    for (Class<?> Interface : clazz.getInterfaces()) {
                             mapping.put(Interface.getName(),instance);
                    }
                }
                else
                {
                    continue;
                }
            }
            //Traverses all values in the container
            for(Object object:mapping.values())
            {
                if(object==null)continue;
                Class<?> clazz= object.getClass();
                //Judge whether the controller annotation exists on the current class
                if(clazz.isAnnotationPresent(Controller.class))
                {
                    //Get all member variables defined in the current class, including private properties
                    Field[] fields = clazz.getDeclaredFields();
                    for (Field field : fields) {
                        //Judge whether there is autowired annotation on the attribute
                        if(!field.isAnnotationPresent(Autowired.class))continue;
                        //Get annotation object
                        Autowired autowired = field.getAnnotation(Autowired.class);
                        //Get the name of the injected bean, that is, take it out of the container according to the name and assign it to the corresponding attribute
                        String beanName=autowired.value();
                        //If beanName is not specified manually
                        //By default, beanName is the full class name of the current attribute type
                        //Because autowired can only inject reference attributes, what you get here is the full class name of the class
                        if("".equals(beanName)){beanName=field.getType().getName();}
                        //Mandatory private properties can also be accessed and manipulated
                        field.setAccessible(true);
                        //Inject values into the current attribute
                        //The first parameter is the instance object of the current class
                        //The second parameter is the value to assign to the attribute
                        field.set(mapping.get(clazz.getName()),mapping.get(beanName));
                    }
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }finally {
            //Close flow
            if(is!=null)
            {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
     //Packet scan logic
    //Scan all. Class files under the specified package and put them into the container. The default class name is id
    private void doScanner(String scanPackage)
    {
        //url saves information about the resource file
        URL url=this.getClass().getClassLoader().getResource("/"+scanPackage.replace(".","/"));
        //Return URL file name section
        File classDir = new File(url.getFile());
        //Traverse all files and directories in the current directory
        for (File file : classDir.listFiles())
        {
            //If the current directory
            if(file.isDirectory())
            {
                //Recursively scan the files inside
                doScanner(scanPackage+"."+file.getName());
            }
            else
            {
                //If it is a file, and it is a file ending in. class
                if(!file.getName().endsWith(".class"))continue;
                //Get the full class name of the class under the current package
                String clazzName=(scanPackage+"."+file.getName().replace(".class",""));
                //During package scanning, only the full class names of all classes under the current package are saved in the container
                mapping.put(clazzName,null);
            }
        }
    }
}

Implement version 2.0

It is optimized in version 1.0 and encapsulates the code in init method by using common design patterns (factory mode, singleton mode, delegation mode and policy mode).

public class DispatchServlet2 extends HttpServlet
{
    //Save the contents of the application.properties configuration file
    private Properties contextConfig=new Properties();
    //Save all class names obtained from package scanning
    private  List<String> classNames=new ArrayList<>();
    //IOC container. Concurrent operations are not considered here, that is, concurrent HashMap is not used
    private Map<String,Object> ioc=new HashMap<>();
    //Save the mapping relationship between url and method
    private Map<String, Method> handlerMapping=new HashMap<>();
    //Initialization phase
    @Override
    public void init(ServletConfig config)
    {
        //1. Load configuration file
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
       //2. Packet scanning
        doScanner(contextConfig.getProperty("scanPackage"));
       //3. Initialize the scanned classes and put them into the IOC container
        doInstance();
        //4. Complete dependency injection
         doAutowired();
         //5. Initialize the mapping relationship between url and method
        initHandlerMapping();
        //IOC container initialization completed
        System.out.println("-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
        System.out.println("Spring frameWork Initialization complete");
        System.out.println("-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
    }

    //Load profile
    private void doLoadConfig(String contextConfigLocation)
    {
        //Idea: find the path where the spring main configuration file is located through the classpath
        //Read it out and save it in the properties object
        //In fact, this is to read the package scanning path from the configuration file and save it through the properties object
        InputStream fis = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try
        {
            contextConfig.load(fis);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        finally
        {
            //Don't forget to turn off the stream
          if(null!=fis)
          {
              try
              {
                  fis.close();
              }
              catch (IOException e)
              {
                  e.printStackTrace();
              }
          }
        }
    }

    //Packet scanning
    private void doScanner(String scanPackage)
    {
       //Idea: here, scanPackage stores the path of the package to be scanned
       //What we need to do here is to convert it into a file path, and then traverse all. class files under the current directory
      //Get the class name of each. Class file and put it into the collection that saves the class name
        URL url = this.getClass().getClassLoader().getResource("/"+scanPackage.replace(".","/"));
        //Get all. class files under the current classpath
        //url.getFile() gets the absolute path of the current package directory
        File classpath = new File(url.getFile());
        //Recursively traverses the current package directory
        for (File file : classpath.listFiles()) {
            if(file.isDirectory())
            {
                //The name of the current directory
                doScanner(scanPackage+"."+file.getName());
            }
            else{
                if(!file.getName().endsWith(".class")){continue;}
                //Save full class name
                String className=scanPackage+"."+file.getName().replace(".class","");
                classNames.add(className);
            }
        }
    }

    //Initialize the scanned classes and put them into the IOC container
    //Concrete embodiment of factory mode
    private void doInstance()
    {
        //Initialization to prepare for di (dependency injection)
        if(classNames.isEmpty()){return;}

            try {

                for (String className : classNames) {
                    Class<?> clazz = Class.forName(className);

                    //Here, we only need to initialize the classes annotated with controller and service
                    if(clazz.isAnnotationPresent(Controller.class))
                    {
                        //instantiation 
                        Object Instance = clazz.newInstance();
                        //First, judge whether the corresponding id of the current class instance in the container is manually specified
                        Controller controller = clazz.getAnnotation(Controller.class);
                        String beanName = controller.value().trim();
                        //beanName was not specified manually
                        if(beanName.equals(""))
                        {
                            //The default class name of Spring is lowercase and serves as the corresponding id in the IOC container
                            //clazz.getName() gets the full class name
                            //clazz.getSimpleName() gets the class name
                            beanName=toLowerFirstCase(clazz.getSimpleName());
                        }
                         //Put in ioc container
                        ioc.put(beanName,Instance);
                    }
                    else if(clazz.isAnnotationPresent(Service.class))
                    {
                        //1. Custom beanName
                        Service service=clazz.getAnnotation(Service.class);
                        String beanName = service.value().trim();
                        //2. The default class name is lowercase
                        if("".equals(beanName)){
                            beanName=toLowerFirstCase(clazz.getSimpleName());
                        }
                        //Initialize instance
                        Object instance = clazz.newInstance();
                        //Put in ioc container
                        ioc.put(beanName,instance);

                        //3. Automatic assignment according to type
                        //For example, when we use service annotation, autowired is generally a service interface class
                        //So what we get here is the full class name of the interface. How can we get its corresponding subclass instance?
                        //That is, the full class names of all interfaces correspond to their subclasses in the container
                        for (Class<?> i : clazz.getInterfaces()) {
                            if(ioc.containsKey(i.getName()))
                            {
                                throw new Exception("The ""+i.getName()+" "is exists!!");
                            }
                            //Take the type of the interface as the key
                            ioc.put(i.getName(),instance);
                        }
                    }
                    else{continue;}
                }

            } catch (Exception e) {
                e.printStackTrace();
            }


    }

    //Self defined dependency injection
    private void doAutowired()
    {
        if(ioc.isEmpty()){return;}
        //Traverse the ioc container - currently only the classes annotated with controller and service under the specified package are stored
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            //Gets all fields of the current class, including private fields
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            //Judge whether autowired annotation is marked on the field
            for (Field field : fields) {
                if(!field.isAnnotationPresent(Autowired.class)){continue;}
                //Get annotation object
                Autowired autowired = field.getAnnotation(Autowired.class);

                //If the user does not have a custom beanName, it will be injected according to the type by default
                //That is, the class name of the user-defined type is injected in lowercase
                String beanName=autowired.value().trim();
                //If the beanName is customized, use the customized beanName as a key to get values in the container
                //Only the service annotation is injected here, and when the service annotation is used, it is added to the corresponding service interface
                if("".equals(beanName))
                {
                    //Get the full class name of the interface type as the key and get the value from the ioc container
                    beanName=field.getType().getName();
                }

                //If it is a type other than public, mandatory assignment is required as long as the autpwired annotation is added
                //Reflection is called violent access
                field.setAccessible(true);

                //Dynamic field assignment using reflection mechanism
                try {
                    //The first parameter is the instance of the class where the current field is located
                    //The second parameter is the value to assign to the current field
                    field.set(entry.getValue(),ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

     //Implement the lowercase initial of the class name
    private String toLowerFirstCase(String simpleName)
    {
        char[] chars=simpleName.toCharArray();
        //The ascii codes of large and small letters differ by 32
        //And the ascii of uppercase letters is less than that of lowercase letters
        //In Java, arithmetic operation on char is actually arithmetic operation on ascii code
        chars[0]+=32;
        return String.valueOf(chars);
    }



    //Implement the initHandlerMapping method. HandlerMapping is an application case of policy mode
    //Implement the one-to-one relationship between url and method
    private void initHandlerMapping()
    {
        if(ioc.isEmpty()){return;}
         //Traverse ioc container
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();

            //If there is no controller annotation on the class, there will certainly be no RequestMapping annotation
            if(!clazz.isAnnotationPresent(Controller.class)){continue;}

            //Save the RequestMapping("/demo1") annotation on the class
            String baseUrl="";
            if(clazz.isAnnotationPresent(RequestMapping.class))
            {
                RequestMapping requestMapping=clazz.getAnnotation(RequestMapping.class);
                baseUrl=requestMapping.value().trim();
            }

            //Get all public type methods by default
            for (Method method : clazz.getMethods()) {
                if(!method.isAnnotationPresent(RequestMapping.class)){continue;}

                RequestMapping requestMapping=method.getAnnotation(RequestMapping.class);

                //url path of splicing virtual request resource
                String url="/"+baseUrl+"/"+requestMapping.value();

                //Save the mapping relationship between the current url and method
                handlerMapping.put(url,method);

                System.out.println("Mapped: "+url+ " , "+method);
            }
        }
    }

    //So far, the initialization has been completed
    //Implement running logic
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    //The delegation mode is used in the doPost() method, which is embodied in the doDispatch method
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req,resp);
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
             resp.getWriter().write("500 Excetion,Detail: "+Arrays.toString(e.getStackTrace()));
        }
    }

    private void doDispatch(HttpServletRequest req,HttpServletResponse resq) throws IOException, InvocationTargetException, IllegalAccessException {
        //Uri = = = > project name + virtual path = = = > / Demo1 / getallusers
        String url=req.getRequestURI();
        //Project name = = > / Demo1
        String contextPath = req.getContextPath();
        //Save only virtual path = = > / getallusers
        url= url.replace(contextPath, "");
        //Determine whether the current url has a method that can handle it
        if(!handlerMapping.containsKey(url))
        {
            resq.getWriter().write("404 Not Found");
            return;
        }
        //Get the method instance object that can handle the current url
        Method method=handlerMapping.get(url);

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

        //For the class annotated with controller, the corresponding id in the IOC container can be either manually specified or the class name is lowercase
        //Method. Getdeclarangclass() gets the Class to which the current method object belongs
        Controller annotation = method.getDeclaringClass().getAnnotation(Controller.class);
        String beanName=annotation.value().trim();
        //beanName was not specified manually
        if("".equals(beanName))
        {
            //The default is lowercase class name
            beanName=toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        }
        //Trigger method
        method.invoke(ioc.get(beanName),new Object[]{req,resq,parameterMap.get("name")[0]});
    }
}

In the above code, although doDispatch() completes dynamic delegation and makes reflection calls, the processing of url parameters is still static. It is still cumbersome to obtain url parameters dynamically, but we can still optimize the above doDispatch method:

 private void doDispatch(HttpServletRequest req,HttpServletResponse resq) throws IOException, InvocationTargetException, IllegalAccessException {
        //Uri = = = > project name + virtual path = = = > / Demo1 / getallusers
        String url=req.getRequestURI();
        //Project name = = > / Demo1
        String contextPath = req.getContextPath();
        //Save only virtual path = = > / getallusers
        url= url.replace(contextPath, "");
        //Determine whether the current url has a method that can handle it
        if(!handlerMapping.containsKey(url))
        {
            resq.getWriter().write("404 Not Found");
            return;
        }
        //Get the method instance object that can handle the current url
        Method method=handlerMapping.get(url);

        //Get request parameters
        Map<String, String[]> parameterMap = req.getParameterMap();
        //Gets an array of parameter types for the method parameter list
        Class<?>[] parameterTypes = method.getParameterTypes();
        //The location where the assignment parameter is saved
        Object[] paramValues=new Object[parameterTypes.length];
        //Dynamic assignment according to parameter position
        for(int i=0;i<parameterTypes.length;i++)
        {
            //For example, if the formal parameter is string name = = >, the parameterType here is String.class
            Class<?> parameterType = parameterTypes[i];
            if(parameterType==HttpServletRequest.class)
            {
                paramValues[i]=req;
            }
            else if(parameterType==HttpServletResponse.class)
            {
                paramValues[i]=resq;
            }
            else if(parameterType==String.class)
            {
                //Gets the annotated parameter in the method
                //The first dimension identifies all annotations of the i-th parameter
                //The second dimension identifies the j-th annotation of the i-th parameter
                Annotation[][] pa = method.getParameterAnnotations();
                for(int j=0;j<pa.length;j++)
                {
                    //Here, each parameter can be preceded by at most one annotation
                    for (Annotation a : pa[i])
                    {
                     //You have specified the name of the receive request parameter
                        if(a instanceof RequestParam)
                       {
                        String paramName=((RequestParam) a).value();
                          if(!"".equals(paramName.trim()))
                          {
                              //Get all the values corresponding to the current request parameters
                              String value = Arrays.toString(parameterMap.get(paramName));
                              //Save parameter values
                              paramValues[i]=value;
                          }
                       }
                    }
                }
            }
        }
        //For the class annotated with controller, the corresponding id in the IOC container can be either manually specified or the class name is lowercase
        //Method. Getdeclarangclass() gets the Class to which the current method object belongs
        Controller annotation = method.getDeclaringClass().getAnnotation(Controller.class);
        String beanName=annotation.value().trim();
        //beanName was not specified manually
        if("".equals(beanName))
        {
            //The default is lowercase class name
            beanName=toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        }
        //Trigger method
        method.invoke(ioc.get(beanName),new Object[]{req,resq,parameterMap.get("name")[0]});
    }

Version 3.0

In version 2.0, the basic functions have been realized, but the elegance of the code is still not very high. For example, HandlerMapping does not support regular expressions like Spring MVC, and url parameters do not support forced type conversion. Before reflection calls, we need to re obtain beanName. In version 3.0, we continue to optimize

First, transform HandlerMapping. In the real spring source code, HandlerMapping is actually a List rather than a map. The elements in the List are user-defined types. Now let's simulate and write a piece of code. First define an internal class Handler:

    //The Handler records the correspondence between RequestMapping and Mehtod in the Controller
    //The mapping relationship between the internal class -- > URL and the corresponding method is saved
    private  class Handler{
        protected Object controller;//Save the instance corresponding to the method
        protected Method method;//How to save the mapping
        protected Pattern pattern;//Support regular
        protected Map<String,Integer> paramIndexMapping;//Parameter order

        protected  Handler(Pattern pattern,Object controller,Method method)
        {
            this.controller=controller;
            this.method=method;
            this.pattern=pattern;
            paramIndexMapping=new HashMap<>();
             //Process the parameters of the current method method and save the properties of the parameters
            putParamIndexMapping(method);
         }
        //The order in which parameters are processed and saved
        private void putParamIndexMapping(Method method)
        {
            //Annotated parameters in the extraction method
            Annotation[][] pa = method.getParameterAnnotations();
            //The first dimension identifies all annotations of the i-th parameter
            //The second dimension identifies the j-th annotation of the i-th parameter
            for(int i=0;i<pa.length;i++)
            {
                for (Annotation a : pa[i]) {
                    if(a instanceof RequestParam)
                    {
                        String paramName = ((RequestParam) a).value();
                        if(!"".equals(paramName.trim()))
                        {
                            paramIndexMapping.put(paramName,i);
                        }
                    }
                }
            }
            //Extract the request and response parameters in the method
            Class<?>[] parameterTypes = method.getParameterTypes();
            int i=0;
            for (Class<?> parameterType : parameterTypes) {
                if(parameterType==HttpServletRequest.class||parameterType==HttpServletResponse.class)
                {
                    //The full class name of the class name
                    paramIndexMapping.put(parameterType.getName(),i);
                }
                i++;
            }
        }
    }

Then, optimize the structure of HandlerMapping. The code is as follows:

public class DispatchServlet2 extends HttpServlet
{
    //Save the contents of the application.properties configuration file
    private Properties contextConfig=new Properties();
    //Save all class names obtained from package scanning
    private  List<String> classNames=new ArrayList<>();
    //IOC container. Concurrent operations are not considered here, that is, concurrent HashMap is not used
    private Map<String,Object> ioc=new HashMap<>();
    //Save the mapping relationship between url and method
    private List<Handler> handlerMapping=new ArrayList<>();
    //Initialization phase
    @Override
    public void init(ServletConfig config)
    {
        //1. Load configuration file
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
       //2. Packet scanning
        doScanner(contextConfig.getProperty("scanPackage"));
       //3. Initialize the scanned classes and put them into the IOC container
        doInstance();
        //4. Complete dependency injection
         doAutowired();
         //5. Initialize the mapping relationship between url and method
        initHandlerMapping();
        //IOC container initialization completed
        System.out.println("-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
        System.out.println("Spring frameWork Initialization complete");
        System.out.println("-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
    }

    //Load profile
    private void doLoadConfig(String contextConfigLocation)
    {
        //Idea: find the path where the spring main configuration file is located through the classpath
        //Read it out and save it in the properties object
        //In fact, this is to read the package scanning path from the configuration file and save it through the properties object
        InputStream fis = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try
        {
            contextConfig.load(fis);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        finally
        {
            //Don't forget to turn off the stream
          if(null!=fis)
          {
              try
              {
                  fis.close();
              }
              catch (IOException e)
              {
                  e.printStackTrace();
              }
          }
        }
    }

    //Packet scanning
    private void doScanner(String scanPackage)
    {
       //Idea: here, scanPackage stores the path of the package to be scanned
       //What we need to do here is to convert it into a file path, and then traverse all. class files under the current directory
      //Get the class name of each. Class file and put it into the collection that saves the class name
        URL url = this.getClass().getClassLoader().getResource("/"+scanPackage.replace(".","/"));
        //Get all. class files under the current classpath
        //url.getFile() gets the absolute path of the current package directory
        File classpath = new File(url.getFile());
        //Recursively traverses the current package directory
        for (File file : classpath.listFiles()) {
            if(file.isDirectory())
            {
                //The name of the current directory
                doScanner(scanPackage+"."+file.getName());
            }
            else{
                if(!file.getName().endsWith(".class")){continue;}
                //Save full class name
                String className=scanPackage+"."+file.getName().replace(".class","");
                classNames.add(className);
            }
        }
    }

    //Initialize the scanned classes and put them into the IOC container
    //Concrete embodiment of factory mode
    private void doInstance()
    {
        //Initialization to prepare for di (dependency injection)
        if(classNames.isEmpty()){return;}

            try {

                for (String className : classNames) {
                    Class<?> clazz = Class.forName(className);

                    //Here, we only need to initialize the classes annotated with controller and service
                    if(clazz.isAnnotationPresent(Controller.class))
                    {
                        //instantiation 
                        Object Instance = clazz.newInstance();
                        //First, judge whether the corresponding id of the current class instance in the container is manually specified
                        Controller controller = clazz.getAnnotation(Controller.class);
                        String beanName = controller.value().trim();
                        //beanName was not specified manually
                        if(beanName.equals(""))
                        {
                            //The default class name of Spring is lowercase and serves as the corresponding id in the IOC container
                            //clazz.getName() gets the full class name
                            //clazz.getSimpleName() gets the class name
                            beanName=toLowerFirstCase(clazz.getSimpleName());
                        }
                         //Put in ioc container
                        ioc.put(beanName,Instance);
                    }
                    else if(clazz.isAnnotationPresent(Service.class))
                    {
                        //1. Custom beanName
                        Service service=clazz.getAnnotation(Service.class);
                        String beanName = service.value().trim();
                        //2. The default class name is lowercase
                        if("".equals(beanName)){
                            beanName=toLowerFirstCase(clazz.getSimpleName());
                        }
                        //Initialize instance
                        Object instance = clazz.newInstance();
                        //Put in ioc container
                        ioc.put(beanName,instance);

                        //3. Automatic assignment according to type
                        //For example, when we use service annotation, autowired is generally a service interface class
                        //So what we get here is the full class name of the interface. How can we get its corresponding subclass instance?
                        //That is, the full class names of all interfaces correspond to their subclasses in the container
                        for (Class<?> i : clazz.getInterfaces()) {
                            if(ioc.containsKey(i.getName()))
                            {
                                throw new Exception("The ""+i.getName()+" "is exists!!");
                            }
                            //Take the type of the interface as the key
                            ioc.put(i.getName(),instance);
                        }
                    }
                    else{continue;}
                }

            } catch (Exception e) {
                e.printStackTrace();
            }


    }

    //Self defined dependency injection
    private void doAutowired()
    {
        if(ioc.isEmpty()){return;}
        //Traverse the ioc container - currently only the classes annotated with controller and service under the specified package are stored
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            //Gets all fields of the current class, including private fields
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            //Judge whether autowired annotation is marked on the field
            for (Field field : fields) {
                if(!field.isAnnotationPresent(Autowired.class)){continue;}
                //Get annotation object
                Autowired autowired = field.getAnnotation(Autowired.class);

                //If the user does not have a custom beanName, it will be injected according to the type by default
                //That is, the class name of the user-defined type is injected in lowercase
                String beanName=autowired.value().trim();
                //If the beanName is customized, use the customized beanName as a key to get values in the container
                //Only the service annotation is injected here, and when the service annotation is used, it is added to the corresponding service interface
                if("".equals(beanName))
                {
                    //Get the full class name of the interface type as the key and get the value from the ioc container
                    beanName=field.getType().getName();
                }

                //If it is a type other than public, mandatory assignment is required as long as the autpwired annotation is added
                //Reflection is called violent access
                field.setAccessible(true);

                //Dynamic field assignment using reflection mechanism
                try {
                    //The first parameter is the instance of the class where the current field is located
                    //The second parameter is the value to assign to the current field
                    field.set(entry.getValue(),ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

     //Implement the lowercase initial of the class name
    private String toLowerFirstCase(String simpleName)
    {
        char[] chars=simpleName.toCharArray();
        //The ascii codes of large and small letters differ by 32
        //And the ascii of uppercase letters is less than that of lowercase letters
        //In Java, arithmetic operation on char is actually arithmetic operation on ascii code
        chars[0]+=32;
        return String.valueOf(chars);
    }

    //Implement the initHandlerMapping method. HandlerMapping is an application case of policy mode
    //Implement the one-to-one relationship between url and method
    private void initHandlerMapping()
    {
        if(ioc.isEmpty()){return;}
         //Traverse ioc container
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();

            //If there is no controller annotation on the class, there will certainly be no RequestMapping annotation
            if(!clazz.isAnnotationPresent(Controller.class)){continue;}

            //Save the RequestMapping("/demo1") annotation on the class
            String baseUrl="";
            if(clazz.isAnnotationPresent(RequestMapping.class))
            {
                RequestMapping requestMapping=clazz.getAnnotation(RequestMapping.class);
                baseUrl=requestMapping.value().trim();
            }

            //Get all public type methods by default
            for (Method method : clazz.getMethods()) {
                if(!method.isAnnotationPresent(RequestMapping.class)){continue;}
                RequestMapping requestMapping=method.getAnnotation(RequestMapping.class);
                //url path of splicing virtual request resource
                String regex="/"+baseUrl+"/"+requestMapping.value();
                 //Compile the given regular expression and give it to the Pattern class
                Pattern pattern=Pattern.compile(regex);
                //Save the mapping relationship between the current url and method
                //The requests that the current handler can handle must meet the above regular verification
                handlerMapping.add(new Handler(pattern,entry.getValue(),method));
                System.out.println("Mapped: "+regex+ " , "+method);
            }
        }
    }

    //So far, the initialization has been completed
    //Implement running logic
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    //The delegation mode is used in the doPost() method, which is embodied in the doDispatch method
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req,resp);
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
             resp.getWriter().write("500 Excetion,Detail: "+Arrays.toString(e.getStackTrace()));
        }
    }

    private void doDispatch(HttpServletRequest req,HttpServletResponse resq) throws IOException, InvocationTargetException, IllegalAccessException
    {
        Handler handler = getHandler(req);
        if(handler==null)
        {
          resq.getWriter().write("404 Not Found!!!");
         return;
        }
        //Get request parameters
        Map<String, String[]> parameterMap = req.getParameterMap();
        //Gets an array of parameter types for the method parameter list
        Class<?>[] parameterTypes = handler.method.getParameterTypes();
        //An array that holds the values of the request parameters
        Object[] paramValues=new Object[parameterTypes.length];
        //Traverse the map of request parameters
        //By default, all request parameters have corresponding formal parameters to receive
        for(Map.Entry<String,String[]> parm:parameterMap.entrySet())
        {
            //Gets the value array corresponding to the key of the current request parameter
            String value=Arrays.toString(parm.getValue());
            //Gets the index of the formal parameter on the corresponding method that processes the current request parameter
            Integer index = handler.paramIndexMapping.get(parm.getKey());
            //Save -- request parameters to formal parameters, and forced type conversion is required
            paramValues[index]=convert(parameterTypes[index],value);
        }
        //It is not in the request parameters, but the user may also need it
        //If the full class name of request exists in the parameter attribute mapping set, it indicates that the parameter appears on the formal parameter and the user needs it
        if(handler.paramIndexMapping.containsKey(HttpServletRequest.class.getName()))
        {
            int reqIndex=handler.paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[reqIndex]=req;
        }

        if(handler.paramIndexMapping.containsKey(HttpServletResponse.class.getName()))
        {
            int reqIndex=handler.paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[reqIndex]=resq;
        }
        
        //Return call method
        Object returnValue = handler.method.invoke(handler.controller, paramValues);
        //If the return value is empty, nothing will be done, otherwise it will be output to the page
        if(returnValue==null||returnValue instanceof Void)
        {
            return;
        }
        resq.getWriter().write(returnValue.toString());
    }

    //The Handler records the correspondence between RequestMapping and Mehtod in the Controller
    //The mapping relationship between the internal class -- > URL and the corresponding method is saved
    private  class Handler{
        protected Object controller;//Save the instance corresponding to the method
        protected Method method;//How to save the mapping
        protected Pattern pattern;//Support regular
        protected Map<String,Integer> paramIndexMapping;//Parameter order

        protected  Handler(Pattern pattern,Object controller,Method method)
        {
            this.controller=controller;
            this.method=method;
            this.pattern=pattern;
            paramIndexMapping=new HashMap<>();
             //Process the parameters of the current method method and save the properties of the parameters
            putParamIndexMapping(method);
         }
        //The order in which parameters are processed and saved
        private void putParamIndexMapping(Method method)
        {
            //Annotated parameters in the extraction method
            Annotation[][] pa = method.getParameterAnnotations();
            //The first dimension identifies all annotations of the i-th parameter
            //The second dimension identifies the j-th annotation of the i-th parameter
            for(int i=0;i<pa.length;i++)
            {
                for (Annotation a : pa[i]) {
                    if(a instanceof RequestParam)
                    {
                        String paramName = ((RequestParam) a).value();
                        if(!"".equals(paramName.trim()))
                        {
                            paramIndexMapping.put(paramName,i);
                        }
                    }
                }
            }
            //Extract the request and response parameters in the method
            Class<?>[] parameterTypes = method.getParameterTypes();
            int i=0;
            for (Class<?> parameterType : parameterTypes) {
                if(parameterType==HttpServletRequest.class||parameterType==HttpServletResponse.class)
                {
                    //The full class name of the class name
                    paramIndexMapping.put(parameterType.getName(),i);
                }
                i++;
            }
        }
    }

    private  Handler getHandler(HttpServletRequest req)
    {
        if(handlerMapping.isEmpty()){return null;}
        //Uri = = = > project name + virtual path = = = > / Demo1 / getallusers
        String url=req.getRequestURI();
        //Project name = = > / Demo1
        String contextPath = req.getContextPath();
        //Save only virtual path = = > / getallusers
        url= url.replace(contextPath, "");
        for (Handler handler : handlerMapping) {
            //Find a handler that can handle the current url request
            Matcher matcher = handler.pattern.matcher(url);
            //If there is no match, continue to match the next one
            if(!matcher.matches()){continue;}
            return handler;
        }
        return null;
    }
    
    //Cast request parameter to formal parameter assignment
    //The parameters passed from the url are of String type, because HTPP is based on String protocol
    //You just need to convert String to any type
    private Object convert(Class<?> type,String value)
    {
        if(Integer.class==type)
        {
            return Integer.valueOf(value);
        }
        //If there are double or other types, you can use the policy mode
        //There is no implementation here for the time being
        return value;
    }
}

Two methods are added to the above code. One is the getHandler method, which is mainly responsible for handling the mapping relationship between url and method and the regular matching of url. The other is the convert method, which is mainly responsible for the forced type conversion of url parameters

summary

So far, the handwritten Mini Spring MVC framework has been completed

Tags: Java Spring

Posted on Sat, 09 Oct 2021 08:47:25 -0400 by ljzxtww