【Spring】手写Spring MVC

Spring MVC原理

Spring的MVC框架主要由DispatcherServlet、处理器映射、处理器(控制器)、视图解析器、视图组成。

完整的Spring MVC处理 流程如下:

SpringMVC接口解释

DispatcherServlet接口:

Spring提供的前端控制器,所有的请求都有经过它来统一分发。在DispatcherServlet将请求分发给Spring Controller之前,需要借助于Spring提供的HandlerMapping定位到具体的Controller。

HandlerMapping接口:

能够完成客户请求到Controller映射。

Controller接口:

需要为并发用户处理上述请求,因此实现Controller接口时,必须保证线程安全并且可重用。

Controller将处理用户请求,这和Struts Action扮演的角色是一致的。一旦Controller处理完用户请求,则返回ModelAndView对象给DispatcherServlet前端控制器,ModelAndView中包含了模型(Model)和视图(View)。

从宏观角度考虑,DispatcherServlet是整个Web应用的控制器;从微观考虑,Controller是单个Http请求处理过程中的控制器,而ModelAndView是Http请求过程中返回的模型(Model)和视图(View)。

ViewResolver接口:

Spring提供的视图解析器(ViewResolver)在Web应用中查找View对象,从而将相应结果渲染给客户。

手写Spring MVC

本次实现没有视图解析内容。主要包括,自动扫描class类、通过解析注解实现bean的实例化、bean之间的依赖注入、通过注解映射路径返回正确的处理方法。

Spring MVC框架主要依赖于Java的反射机制实现。实现原理与上面描述一致。

核心Servlet

工程名MySpringMVC
代码存放servlet包。
DispatcherServlet

package zqq.servlet;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import zqq.annotations.EnjoyAuthowired;
import zqq.annotations.EnjoyController;
import zqq.annotations.EnjoyRequestMapping;
import zqq.annotations.EnjoyRequestParam;
import zqq.annotations.EnjoyService;
import zqq.controller.ZqqController;

/**
 * Servlet implementation class DispatcherServlet
 */
public class DispatcherServlet extends HttpServlet
{
	private static final long serialVersionUID = 1L;

	// 扫描得到的类名集合
	List<String> classNames = new ArrayList<String>();

	// 存放所有Spring实例的Map
	Map<String, Object> beans = new HashMap<String, Object>();

	// 存放所有路径映射
	Map<String, Object> handlerMap = new HashMap<String, Object>();

	/**
	 * @see HttpServlet#HttpServlet()
	 */
	public DispatcherServlet()
	{
	}

	/**
	 * @see Servlet#init(ServletConfig)
	 */
	public void init(ServletConfig config) throws ServletException
	{
		// 1、扫描工程有多少class
		doScanPackage("zqq");
		// 打印所有class
		for (String name : classNames)
		{
			System.out.println(name);
		}
		// 2、实例化
		doInstance();
		for (Map.Entry<String, Object> entry : beans.entrySet())
		{
			System.out.println(entry.getKey() + ":" + entry.getValue());
		}
		// 3、注入
		doIoC();

		// 4、请求映射
		buildMapping();

		for (Map.Entry<String, Object> entry : handlerMap.entrySet())
		{
			System.out.println(entry.getKey() + ":" + entry.getValue());
		}
	}

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		doPost(request, response);
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
	{
		// springmvc /zqq/query
		String uri = request.getRequestURI();

		String context = request.getContextPath(); // springmvc

		String path = uri.replace(context, ""); // /zqq/query

		// 获取映射对应的method
		Method method = (Method) handlerMap.get(path);

		ZqqController instance = (ZqqController) beans.get("/" + path.split("/")[1]);
		
		Object args[] = this.hand(request, response, method);
		
		try
		{
			method.invoke(instance, args);
		} catch (IllegalAccessException e)
		{
			e.printStackTrace();
		} catch (IllegalArgumentException e)
		{
			e.printStackTrace();
		} catch (InvocationTargetException e)
		{
			e.printStackTrace();
		}
		
	}

	/**
	 * @author qqz
	 * @date 2018年7月12日 上午1:02:44 扫描当前路径下有多少个class类
	 * @param string
	 */
	private void doScanPackage(String basePackage)
	{
//		URL url = this.getClass().getClassLoader().getResource(basePackage.replaceAll("\\.", "/"));
		String filepath = this.getClass().getClassLoader().getResource(basePackage.replaceAll("\\.", "/")).getFile();
		try
		{
			filepath= java.net.URLDecoder.decode(filepath,"utf-8");
		} catch (UnsupportedEncodingException e)
		{
			e.printStackTrace();
		}  
//		String fileStr = url.getFile();
		
		// 目录对象
		File file = new File(filepath);
		String[] filesStr = file.list();

		// 递归处理路径basepackage下的类文件
		for (String path : filesStr)
		{
			File filePath = new File(filepath + path);
			if (filePath.isDirectory())
			{
				doScanPackage(basePackage + "." + path);
			} else
			{
				// 得到class 全类名路径 zqq.controller.ZqqController
				classNames.add(basePackage + "." + filePath.getName());
			}
		}
	}

	/**
	 * @author qqz
	 * @date 2018年7月12日 上午1:11:04 TODO
	 */
	private void doInstance()
	{
		if (classNames.size() <= 0)
		{
			System.out.println("scan classes failed!");
		}

		for (String className : classNames)
		{
			// 去掉.class后缀
			String cn = className.replace(".class", "");

			try
			{
				Class<?> clazz = Class.forName(cn);
				// 处理带有EnjoyController注解的类
				if (clazz.isAnnotationPresent(EnjoyController.class))
				{
					// 实例化对象
					Object instance = clazz.newInstance();
					EnjoyRequestMapping reqMapping = clazz.getAnnotation(EnjoyRequestMapping.class);
					String key = reqMapping.value();
					beans.put(key, instance);
				} else if (clazz.isAnnotationPresent(EnjoyService.class))
				{
					// 实例化对象
					Object instance = clazz.newInstance();
					EnjoyService service = clazz.getAnnotation(EnjoyService.class);
					String key = service.value();
					beans.put(key, instance);
				} else
				{
					continue;
				}
			} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e)
			{
				e.printStackTrace();
			}
		}
	}

	/**
	 * @author qqz 属性注入
	 * @date 2018年7月12日 上午1:21:10 TODO
	 */
	private void doIoC()
	{
		if (beans.entrySet().size() <= 0)
		{
			System.out.println("instance bean failed.");
			return;
		}

		for (Map.Entry<String, Object> entry : beans.entrySet())
		{
			Object instance = entry.getValue();
			Class<?> clazz = instance.getClass();

			if (clazz.isAnnotationPresent(EnjoyController.class))
			{
				// 获取类中所有属性
				Field[] fields = clazz.getDeclaredFields();
				for (Field field : fields)
				{
					// 获取声明注入的属性
					if (field.isAnnotationPresent(EnjoyAuthowired.class))
					{
						EnjoyAuthowired authowired = field.getAnnotation(EnjoyAuthowired.class);
						// 获取注解EnjoyAutowired中命名的值
						String value = authowired.value();
						// 放开权限设置属性值
						field.setAccessible(true);
						try
						{
							field.set(instance, beans.get(value));
						} catch (IllegalArgumentException e)
						{
							e.printStackTrace();
						} catch (IllegalAccessException e)
						{
							e.printStackTrace();
						}
					} else
					{
						continue;
					}
				}
			}

		}
	}

	/**
	 * @author qqz
	 * @date 2018年7月12日 上午1:32:25 TODO
	 */
	private void buildMapping()
	{
		if (beans.entrySet().size() <= 0)
		{
			System.out.println("instance bean failed.");
			return;
		}

		for (Map.Entry<String, Object> entry : beans.entrySet())
		{
			Object instance = entry.getValue();
			Class<?> clazz = instance.getClass();
			// 映射是在Controller层
			if (clazz.isAnnotationPresent(EnjoyController.class))
			{
				// 获取类映射
				EnjoyRequestMapping requestMapping = clazz.getAnnotation(EnjoyRequestMapping.class);
				String classPath = requestMapping.value();

				// 获取方法上的映射
				Method[] methods = clazz.getMethods();

				for (Method method : methods)
				{
					if (method.isAnnotationPresent(EnjoyRequestMapping.class))
					{
						EnjoyRequestMapping requestMapping1 = method.getAnnotation(EnjoyRequestMapping.class);

						String methodPath = requestMapping1.value();

						// 构建方法路径与方法的映射
						handlerMap.put(classPath + methodPath, method);

					} else
					{
						continue;
					}
				}

			}
		}
	}

	/**
	 * @author qqz
	 * @date 2018年7月12日 上午1:59:48
	 * 方法参数注解解析
	 * @param request
	 * @param response
	 * @param method
	 * @return
	 */
	private static Object[] hand(HttpServletRequest request, HttpServletResponse response, Method method)
	{
		// 拿到当前执行的方法有哪些参数
		Class<?>[] paramClazzs = method.getParameterTypes();

		// 根据参数的个数,new 一个参数的数组, 将方法里所有参数赋值到args来
		Object[] args = new Object[paramClazzs.length];

		int arg_i = 0;
		int index = 0;
		for (Class<?> paramClazz : paramClazzs)
		{
			if (ServletRequest.class.isAssignableFrom(paramClazz))
			{
				args[arg_i++] = request;
			}

			if (ServletResponse.class.isAssignableFrom(paramClazz))
			{
				args[arg_i++] = response;
			}

			// 从0-3判断有没有RequestParam注解,很明显paramClazz为0和1时,不是,当为2和3时为@RequestParam,需要
			// 解析[@zqq.annotation.EnjoyRequestParam(value=name)]
			Annotation[] paramAns = method.getParameterAnnotations()[index];
			if (paramAns.length > 0)
			{
				for (Annotation paramAn : paramAns)
				{
					if (EnjoyRequestParam.class.isAssignableFrom(paramAn.getClass()))
					{
						EnjoyRequestParam rp = (EnjoyRequestParam)paramAn;
						//找到注解里的name和age
						args[arg_i++] = request.getParameter(rp.value());
					}
				}
			}
			index ++;
		}
		return args;
	}
}

注解定义

代码存放的包annotations中。包括如下几个

EnjoyAuthowired.java
EnjoyController.java
EnjoyRequestMapping.java
EnjoyRequestParam.java
EnjoyService.java

属性注解EnjoyAuthowired.java

package zqq.annotations;

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;

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

Controller注解EnjoyController.java

package zqq.annotations;

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;

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

映射注解EnjoyRequestMapping.java

package zqq.annotations;

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;

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

参数注解EnjoyRequestParam.java

package zqq.annotations;

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;

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

Service注解EnjoyService.java


package zqq.annotations;


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;

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

控制层

代码存放controller包。

/**
 * ZqqController.java
 */
package zqq.controller;

import java.io.IOException;
import java.io.PrintWriter;

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

import zqq.annotations.EnjoyAuthowired;
import zqq.annotations.EnjoyController;
import zqq.annotations.EnjoyRequestMapping;
import zqq.annotations.EnjoyRequestParam;
import zqq.service.ZqqService;

@EnjoyController
@EnjoyRequestMapping("/zqq")
public class ZqqController
{
	@EnjoyAuthowired("ZqqServiceImpl")
	private ZqqService zqqService;

	@EnjoyRequestMapping("/query")
	public void query(HttpServletRequest req, HttpServletResponse resp, @EnjoyRequestParam("name") String name,
			@EnjoyRequestParam("age") String age)
	{
		PrintWriter pw;
		try
		{
			pw = resp.getWriter();
			String result = zqqService.query(name, age);
			pw.write(result);
		} catch (IOException e)
		{
			e.printStackTrace();
		}

	}
}

Service层

代码存放service包

/**
 * ZqqService.java
 */
package zqq.service;
public interface ZqqService
{
	String query(String name,String age);
}

Service实现类存放service/impl

/**
 * ZqqServiceImpl.java
 */
package zqq.service.impl;

import zqq.annotations.EnjoyService;
import zqq.service.ZqqService;

@EnjoyService("ZqqServiceImpl")
public class ZqqServiceImpl implements ZqqService
{

	/*
	 * (non-Javadoc)
	 * 
	 * @see zqq.service.ZqqService#query(java.lang.String, java.lang.String)
	 */
	@Override
	public String query(String name, String age)
	{
		return "{name:" + name + ",age:" + age + "}";
	}

}

web层

src/main/webapp/WEB-INF/web.xml

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

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
  	<servlet-name>DispatcherServlet</servlet-name>
  	<display-name>DispatcherServlet</display-name>
  	<description></description>
  	<servlet-class>zqq.servlet.DispatcherServlet</servlet-class>
  	<load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
  	<servlet-name>DispatcherServlet</servlet-name>
  	<url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>

使用

部署后访问localhost:8080/MySpringMVC/zqq/query?name=zqq&age=18
可以在页面上看到请求中的name和age。

项目码云路径

MySpringMVC

参考资料:
SpringMVC框架介绍

本博客用于技术学习,所有资源都来源于网络,部分是转发,部分是个人总结。欢迎共同学习和转载,转载请在醒目位置标明原文。如有侵权,请留言告知,及时撤除。
posted @ 2018-07-22 16:01  风动静泉  阅读(310)  评论(0编辑  收藏  举报