Handwritten Mini spring MVC core code

Possible knowledge blind spots in the process ...
The role of init param in web.xml
Annotation @ Retention
response.getwriter().println() and response.getwriter.write()
request.getRequestURI() is different from the method of obtaining the relevant path
request.getParameterMap()
Key points of reflection related knowledge
java URL class interface and simple application
getFile() and getPath() methods of URL
The difference between getClassLoader().getResource() and getResource() in Java
trim() method in String
Java regular expression Pattern and Matcher classes
1. Configuration file
2. User defined annotation
3. Configuration notes
Implement version 1.0
Implement version 2.0
Version 3.0

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

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() //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() @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Autowired { String value() default ""; } //Act on class @Target() @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() @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestMapping { String value() default ""; } @Target() @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(".")) //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)) //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)) //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)) //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")) //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()) 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 } } catch (Exception e) { e.printStackTrace(); } } //Self defined dependency injection private void doAutowired() { if(ioc.isEmpty()) //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)) //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()) //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)) //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)) 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[]); } }

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[]); }

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")) //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()) 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 } } catch (Exception e) { e.printStackTrace(); } } //Self defined dependency injection private void doAutowired() { if(ioc.isEmpty()) //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)) //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()) //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)) //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)) 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()) //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()) 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

9 October 2021, 08:47 | Views: 6113

Add new comment

For adding a comment, please log in
or create account

0 comments