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。
项目码云路径
参考资料:
SpringMVC框架介绍