Напишите фреймворк SpringMVC самостоятельно

задняя часть Spring

Фреймворк Spring хорошо знаком Java-программистам, раньше я знал только, что он реализуется посредством рефлексии, но разобравшись с ним, я понял, что в нем много гениальных замыслов. Если вы не посмотрите исходный код Spring, вы потеряете возможность учиться у мастера: его спецификации кода и идеи дизайна заслуживают изучения. Большинство из нас, программистов, дикие и не знают, что такое спецификация кода. После написания кода в течение месяца другим старым драйверам приходится тратить на рефакторинг дня 3. Я считаю, что у большинства старых драйверов голова болит, чтобы прочитать код новичка.

Без лишних слов, давайте перейдем к сегодняшней теме, в дизайне веб-приложений широко используется шаблон MVC. SpringMVC использует DispatcherServlet в качестве ядра, которое отвечает за координацию и организацию различных компонентов для выполнения работы по обработке запросов и возврату ответа, а также реализует шаблон MVC. Чтобы реализовать собственный фреймворк SpringMVC, нужно начать со следующих моментов:

1. Понять работающий процесс SpringMVC и его девять основных компонентов.

2. Самостоятельно реализовать функциональный анализ SpringMVC

3. Рукописный фреймворк SpringMVC

1. Понимание рабочего процесса SpringMVC и девяти основных компонентов.

1. Работающий процесс SpringMVC

             

  • Пользователь отправляет запрос на фронт-контроллер DispatcherServlet
  • DispatcherServlet получает запрос и вызывает обработчик отображения HandlerMapping.
  • Преобразователь процессора находит конкретный процессор в соответствии с URL-адресом запроса, генерирует объект процессора и перехватчик процессора (если есть) и возвращает его DispatcherServlet.
  • DispatcherServlet вызывает обработчик через адаптер обработчика HandlerAdapter
  • Запустите процессор (Контроллер, также называемый внутренним контроллером).
  • Выполнение контроллера завершается и возвращается в ModelAndView
  • HandlerAdapter возвращает результат выполнения контроллера ModelAndView в DispatcherServlet.
  • DispatcherServlet передает ModelAndView преобразователю представления ViewReslover.
  • ViewReslover возвращается к конкретному представлению после синтаксического анализа.
  • DispatcherServlet визуализирует представление (то есть заполняет представление данными модели).
  • DispatcherServlet отвечает пользователю.

Как видно из вышеизложенного, DispatcherServlet имеет функции приема запросов, ответа на результаты и переадресации. DispatcherServlet позволяет уменьшить связь между компонентами.

2. Девять компонентов SpringMVC (ссылка:[SpringMVC] Обзор 9 основных компонентов)

protected void initStrategies(ApplicationContext context) {
	//用于处理上传请求。处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取File.
	initMultipartResolver(context);
	//SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候。
	initLocaleResolver(context); 
	//用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源、
	//如图片、css样式等。SpringMVC的主题也支持国际化, 
	initThemeResolver(context);
	//用来查找Handler的。
	initHandlerMappings(context);
	//从名字上看,它就是一个适配器。Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。
	//如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情
	initHandlerAdapters(context);
	//其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢?
	//这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是HandlerExceptionResolver。
	initHandlerExceptionResolvers(context);
	//有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,
	//如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。
	initRequestToViewNameTranslator(context);
	//ViewResolver用来将String类型的视图名和Locale解析为View类型的视图。
	//View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。
	initViewResolvers(context);
	//用来管理FlashMap的,FlashMap主要用在redirect重定向中传递参数。
	initFlashMapManager(context); 
}

2. Самостоятельно реализовать функциональный анализ SpringMVC

В этой статье реализованы только функции загрузки конфигурации SpringMVC, создания экземпляров отсканированных пакетов, сопоставления URL-адреса HandlerMapping с соответствующим методом контроллера, перехвата исключения и динамического вызова и вывода результата в браузер. Читатели других функций SpringMVC могут попытаться реализовать их самостоятельно.

1. Прочтите конфигурацию

         

Как видно из рисунка, SpringMVC по сути является сервлетом, который наследуется от HttpServlet. FrameworkServlet отвечает за инициализацию контейнера SpringMVC и установку контейнера Spring в качестве родительского контейнера. Поскольку в этой статье реализуется только SpringMVC, я не буду слишком много рассказывать о контейнере Spring (заинтересованные студенты могут посмотреть статью другого блоггера:Поклонитесь весеннему боссу - большое количество анализа оттока исходного кода).

Чтобы прочитать конфигурацию в web.xml, мы используем класс ServletConfig, который представляет информацию о конфигурации текущего сервлета в web.xml. Загрузите наш собственный MyDispatcherServlet и прочитайте файл конфигурации через web.xml.

2. Этап инициализации

Из вышеизложенного мы знаем, что метод initStrategies DispatcherServlet будет инициализировать 9 основных компонентов, но в этой статье вместо всех будут реализованы некоторые из самых основных компонентов SpringMVC, в том числе по порядку:

  • загрузить файл конфигурации
  • Инициализировать все связанные классы, сканировать все классы в пакете, установленном пользователем
  • Получите отсканированный класс и создайте его экземпляр с помощью механизма отражения. И поместите его в контейнер ioc (пара ключ-значение карты beanName-bean) beanName по умолчанию в нижнем регистре
  • Инициализация HandlerMapping на самом деле заключается в том, чтобы поместить пару ключ-значение карты, Ключ — это URL-адрес, а значение — это вызываемый в нем метод (соответствующий URL-адресу и методу).

3. Этап эксплуатации

Каждый запрос будет вызывать метод doGet или doPost, поэтому унифицированная фаза выполнения обрабатывается в методе doDispatch. Он будет соответствовать соответствующему методу в HandlerMapping в соответствии с запросом URL, а затем использовать механизм отражения для вызова метода в контроллере. и получить результат. Включите следующие функции по порядку:

  • перехват исключений
  • Получить параметры, переданные в запросе, и обработать параметры
  • Извлекая имя метода, соответствующее URL-адресу, из инициализированного контейнера карты handlerMapping, вызов отражения

Три, рукописный фреймворк SpringMVC

Файлы и каталоги проекта:

                            

Сначала создайте новый проект maven и импортируйте следующие зависимости в pom.xml. Для удобства блогер напрямую импортировал веб-пакет Springboot, который содержит все необходимые нам вещи для веб-разработки:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.liugh</groupId>
  <artifactId>liughMVC</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  
 <dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-dependencies</artifactId>
				<version>1.4.3.RELEASE</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
		<java.version>1.8</java.version>
	</properties>
	
	<dependencies>
  		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

Затем мы создаем файл web.xml в WEB-INF со следующей конфигурацией:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
	version="3.0">
	<servlet>
		<servlet-name>MySpringMVC</servlet-name>
		<servlet-class>com.liugh.servlet.MyDispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>application.properties</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>MySpringMVC</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>

</web-app>

Файл application.properties просто настраивает пакеты для сканирования в контейнер SpringMVC.

scanPackage=com.liugh.core

Создайте свою собственную аннотацию контроллера, которую можно аннотировать только в классе:

package com.liugh.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {
	/**
     * 表示给controller注册别名
     * @return
     */
    String value() default "";

}

Аннотации RequestMapping, которые можно использовать в классах и методах:

package com.liugh.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {
	/**
     * 表示访问该方法的url
     * @return
     */
    String value() default "";

}

Аннотация RequestParam, может быть аннотирована только для параметров

package com.liugh.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {
	/**
     * 表示参数的别名,必填
     * @return
     */
    String value();

}

Затем создайте класс MyDispatcherServlet для наследования HttpServlet, перепишите метод init, методы doGet, doPost и добавьте функции, которые будут реализованы на втором этапе анализа:

package com.liugh.servlet;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.liugh.annotation.MyController;
import com.liugh.annotation.MyRequestMapping;

public class MyDispatcherServlet extends HttpServlet{
	
	private Properties properties = new Properties();
	
	private List<String> classNames = new ArrayList<>();
	
	private Map<String, Object> ioc = new HashMap<>();
	
	private Map<String, Method> handlerMapping = new  HashMap<>();
	
	private Map<String, Object> controllerMap  =new HashMap<>();
	

	@Override
	public void init(ServletConfig config) throws ServletException {
		
		//1.加载配置文件
		doLoadConfig(config.getInitParameter("contextConfigLocation"));
		
		//2.初始化所有相关联的类,扫描用户设定的包下面所有的类
		doScanner(properties.getProperty("scanPackage"));
		
		//3.拿到扫描到的类,通过反射机制,实例化,并且放到ioc容器中(k-v  beanName-bean) beanName默认是首字母小写
		doInstance();
		
		//4.初始化HandlerMapping(将url和method对应上)
		initHandlerMapping();
		
		
	}
	
	

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		this.doPost(req,resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		try {
			//处理请求
			doDispatch(req,resp);
		} catch (Exception e) {
			resp.getWriter().write("500!! Server Exception");
		}

	}
	
	
	private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
		if(handlerMapping.isEmpty()){
			return;
		}
		
		String url =req.getRequestURI();
		String contextPath = req.getContextPath();
		
		url=url.replace(contextPath, "").replaceAll("/+", "/");
		
		if(!this.handlerMapping.containsKey(url)){
			resp.getWriter().write("404 NOT FOUND!");
			return;
		}
		
		Method method =this.handlerMapping.get(url);
		
		//获取方法的参数列表
		Class<?>[] parameterTypes = method.getParameterTypes();
	
		//获取请求的参数
		Map<String, String[]> parameterMap = req.getParameterMap();
		
		//保存参数值
		Object [] paramValues= new Object[parameterTypes.length];
		
		//方法的参数列表
        for (int i = 0; i<parameterTypes.length; i++){  
            //根据参数名称,做某些处理  
            String requestParam = parameterTypes[i].getSimpleName();  
            
            
            if (requestParam.equals("HttpServletRequest")){  
                //参数类型已明确,这边强转类型  
            	paramValues[i]=req;
                continue;  
            }  
            if (requestParam.equals("HttpServletResponse")){  
            	paramValues[i]=resp;
                continue;  
            }
            if(requestParam.equals("String")){
            	for (Entry<String, String[]> param : parameterMap.entrySet()) {
         			String value =Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
         			paramValues[i]=value;
         		}
            }
        }  
		//利用反射机制来调用
		try {
			method.invoke(this.controllerMap.get(url), paramValues);//obj是method所对应的实例 在ioc容器中
		} catch (Exception e) {
			e.printStackTrace();
		}
	}



	private void  doLoadConfig(String location){
		//把web.xml中的contextConfigLocation对应value值的文件加载到留里面
		InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(location);
		try {
			//用Properties文件加载文件里的内容
			properties.load(resourceAsStream);
		} catch (IOException e) {
			e.printStackTrace();
		}finally {
			//关流
			if(null!=resourceAsStream){
				try {
					resourceAsStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		
	}
	
	private void doScanner(String packageName) {
		//把所有的.替换成/
		URL url  =this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.", "/"));
		File dir = new File(url.getFile());
		for (File file : dir.listFiles()) {
			if(file.isDirectory()){
				//递归读取包
				doScanner(packageName+"."+file.getName());
			}else{
				String className =packageName +"." +file.getName().replace(".class", "");
				classNames.add(className);
			}
		}
	}
	
	
	
	private void doInstance() {
		if (classNames.isEmpty()) {
			return;
		}	
		for (String className : classNames) {
			try {
				//把类搞出来,反射来实例化(只有加@MyController需要实例化)
				Class<?> clazz =Class.forName(className);
			   if(clazz.isAnnotationPresent(MyController.class)){
					ioc.put(toLowerFirstWord(clazz.getSimpleName()),clazz.newInstance());
				}else{
					continue;
				}
				
				
			} catch (Exception e) {
				e.printStackTrace();
				continue;
			}
		}
	}


	private void initHandlerMapping(){
		if(ioc.isEmpty()){
			return;
		}
		try {
			for (Entry<String, Object> entry: ioc.entrySet()) {
				Class<? extends Object> clazz = entry.getValue().getClass();
				if(!clazz.isAnnotationPresent(MyController.class)){
					continue;
				}
				
				//拼url时,是controller头的url拼上方法上的url
				String baseUrl ="";
				if(clazz.isAnnotationPresent(MyRequestMapping.class)){
					MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);
					baseUrl=annotation.value();
				}
				Method[] methods = clazz.getMethods();
				for (Method method : methods) {
					if(!method.isAnnotationPresent(MyRequestMapping.class)){
						continue;
					}
					MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
					String url = annotation.value();
					
					url =(baseUrl+"/"+url).replaceAll("/+", "/");
					handlerMapping.put(url,method);
					controllerMap.put(url,clazz.newInstance());
					System.out.println(url+","+method);
				}
				
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}


	/**
	 * 把字符串的首字母小写
	 * @param name
	 * @return
	 */
	private String toLowerFirstWord(String name){
		char[] charArray = name.toCharArray();
		charArray[0] += 32;
		return String.valueOf(charArray);
	}
	
		
}

Вот мы и разработали собственный SpringMVC, теперь давайте его протестируем:

package com.liugh.core.controller;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.liugh.annotation.MyController;
import com.liugh.annotation.MyRequestMapping;
import com.liugh.annotation.MyRequestParam;

@MyController
@MyRequestMapping("/test")
public class TestController {
	

	
	 @MyRequestMapping("/doTest")
    public void test1(HttpServletRequest request, HttpServletResponse response,
    		@MyRequestParam("param") String param){
 		System.out.println(param);
	    try {
            response.getWriter().write( "doTest method success! param:"+param);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
	 
	 
	 @MyRequestMapping("/doTest2")
    public void test2(HttpServletRequest request, HttpServletResponse response){
        try {
            response.getWriter().println("doTest2 method success!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Посетите http://localhost:8080/liughMVC/test/doTest?param=liugh следующим образом:

Чтобы получить доступ к несуществующей попытке:

Вот и все!

Блогер - обычный программист с ограниченным уровнем, а статья неизбежно ошибочна. Читатели, жертвующие своим драгоценным временем, могут высказать свое мнение непосредственно по содержанию этой статьи. Цель блогера - только помочь читателям. Адрес источника:GitHub.com/QQ53182347/…