开发环境:windows10、idea、jdk1.8、apache-tomcat-9.0.0.M3
SpringMVC框架是基于Servlet设计的,所以如果你知道SpringMVC,但是没听过道Servlet,那你就应该先去学习下Servlet的知识点了。以下我所描述的SpringMVC框架都是简易版本SpringMVC框架。该框架主要是以请求为驱动,其核心是DispatcherServlet类,它实际上就是一个Servlet,底层实现的也是Servlet。在SpringMVC框架中DispatcherServlet主要做两件事,
第一 :处理请求路径到各个Handler之间的映射,这里的Handler就类似我们平时使用的Controller,也就是struts里边的action,这时候需要使用一个HandlerMapping的东西,也就是一个map来保存映射关系,并且需要在初始化的时候就保存,这就使用到了Servlet生命周期中初始化阶段所调用的init()方法;
第二 :将用户请求分发到各个具体的Handler,并且将请求交给Handler中的具体方法去处理,然后就接收返回回来的ModelAndView,查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图,视图负责将结果显示到客户端。
首先我们先新建一个Java Enterprise项目,也就是一个java web项目
项目建立好之后,在web.xml中我们先做简单的配置,将客户端所有的请求交给DispatcherServlet这个类去处理。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>com.zhainan.springmvc.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
接下来就是最为重要的DispatcherServlet实现:
public class DispatcherServlet extends HttpServlet {
private Map<String, HandlerBean> handlerBeanMap;
@Override
public void init() throws ServletException {
handlerBeanMap = new HashMap<String, HandlerBean>();
for (String name : ClassUtils.getAllClassesFromPackage("com.zhainan.springmvc.handler")) {
try {
Class clz = Class.forName(name);
for (Method method : clz.getDeclaredMethods()) {
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
if (requestMapping != null) {
String path = requestMapping.value();
handlerBeanMap.put(path, new HandlerBean(name, method.getName()));
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doDispatch(req, resp);
}
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException
{
String path = req.getPathInfo();
HandlerBean handlerBean = handlerBeanMap.get(path);
if (handlerBean == null)
handlerBean = new HandlerBean("com.zhainan.springmvc.handler.DefaultHandler", "doService");
try {
Class clz = Class.forName(handlerBean.getBeanName());
for (Method method : clz.getDeclaredMethods()) {
if (handlerBean.getMethodName().equals(method.getName())) {
Parameter[] parameters = method.getParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
if (requestParam != null) {
String parameterName = requestParam.value();
args[i] = req.getParameter(parameterName);
}
if (parameters[i].getType() == HttpServletResponse.class) {
args[i] = resp;
}
}
ModelAndView view = (ModelAndView) method.invoke(clz.newInstance(), args);
new ViewResolver().render(view, req, resp);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
可以看到简易SpringMVC框架中的DispatcherServlet直接继承自HttpServlet,重写了它的init()方法和doGet()方法,init()方法先保存请求路径到各个Handler之间的映射,当然这里边就涉及了类ClassUtils中的一个重要方法和一个类HandlerBean,以及一些关键注解
ClassUtils类:根据包名定位到文件夹,然后根据读取该包下所有class文件,最后根据包名和文件名,构造返回的完整类名.
public class ClassUtils {
public static List<String> getAllClassesFromPackage(String packageName){
List<String> classNames = new ArrayList<String>();
String pkg = "com.zhainan.springmvc.handler";
String relPath = pkg.replace('.', '/');
URL resource = Thread.currentThread().getContextClassLoader().getResource(relPath);
if (resource == null) {
throw new RuntimeException("Unexpected problem: No resource for "
+ relPath);
}
File f = new File(resource.getPath());
String[] files = f.list();
for (int i = 0; i < files.length; i++) {
String fileName = files[i];
String className = null;
String fileNm = null;
if (fileName.endsWith(".class")) {
fileNm = fileName.substring(0, fileName.length() - 6);
className = pkg + '.' + fileNm;
}
if (className != null) {
classNames.add(className);
}
}
return classNames;
}
}
HandlerBean类:具体Handler和其中处理方法的对应关系
public class HandlerBean {
private String beanName;
private String methodName;
public HandlerBean(String beanName, String methodName) {
this.beanName = beanName;
this.methodName = methodName;
}
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
}
注解RequestMapping:和Controller里边的RequestMapping是一样的道理,将请求路径映射到某个具体方法
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "";
}
注解RequestParam:和Controller里边的RequestParam一样,请求路径中的请求参数接收
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
String value() default "";
}
注解PathVariable:和Controller里边的PathVariable一样,请求路径中的路径变量接收
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {
String value() default "";
}
doGet()方法直接调用类里边的doDispatch()方法用于将用户请求分发到各个具体的Handler去处理。接收返回回来的ModelAndView,查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图,视图负责将结果显示到客户端。
首先来看看我们的Handler实现,在Demo中我定义了两个Handler
DefaultHandler:用于处理默认请求
public class DefaultHandler {
public void doService(HttpServletRequest request,
HttpServletResponse response) throws IOException {
response.getWriter().write("this is index page !");
}
}
DemoHandler:用于处理指定路径的请求,可以在这编写相关的API
public class DemoHandler {
@RequestMapping(value = "/test")
public void test(HttpServletRequest request,
HttpServletResponse response) throws IOException {
response.getWriter().write("<h1>test</h1>");
}
@RequestMapping(value = "/demo")
public ModelAndView demo(@RequestParam(value = "name") String name,
@RequestParam(value = "age") String age) {
Map<String, String> model = new HashMap<String, String>();
model.put("name", name);
model.put("age", age);
return new ModelAndView(model, "my.view");
}
}
除此之外,还可以去编写其他的Handler,如同Controller一样去处理各类请求。
接下来就是关于视图的处理了,先来看下ModelAndView
ModelAndView:ModelAndView中包含了模型(Model)和视图(View),Handler处理完具体的业务逻辑后可以返回对应的模型和需要返回的视图
public class ModelAndView {
private Map<String, String> model;
private String viewName;
public ModelAndView(Map<String, String> model, String viewName) {
this.model = model;
this.viewName = viewName;
}
public Map<String, String> getModel() {
return model;
}
public void setModel(Map<String, String> model) {
this.model = model;
}
public String getViewName() {
return viewName;
}
public void setViewName(String viewName) {
this.viewName = viewName;
}
}
然后再编写一个小型的试图解析功能
先定义一个接口View
public interface View {
public void render(ModelAndView view,
HttpServletRequest request,
HttpServletResponse response) throws IOException;
}
然后定义一个泪ViewResolver去实现View的render()方法
public class ViewResolver implements View {
@Override
public void render(ModelAndView view, HttpServletRequest request, HttpServletResponse response) throws IOException {
PrintWriter writer = response.getWriter();
String viewName = view.getViewName();
String content = loadTemplate(request.getServletContext().getResourceAsStream("/WEB-INF/" + viewName));
content = parseTemplate(view.getModel(), content);
writer.write(content);
}
private String loadTemplate(InputStream is) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder content = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
content.append(line);
}
return content.toString();
}
private String parseTemplate(Map<String, String> model, String template) {
for (Map.Entry<String, String> entry : model.entrySet()) {
String key = entry.getKey();
String matchedKey = String.format("\\$\\{%s\\}", key);
template = template.replaceAll(matchedKey, entry.getValue());
}
return template;
}
}
最后就是视图文件了,demo中定义了一个简单的视图文件my.view
<html>
<head>
<title>test</title>
</head>
<body>
<h3>hello,my name is ${name},${age} years old.</h3>
</body>
</html>
最终还是要展示下效果
附上项目基本结构图和别人家的SpringMVC实现原理图帮助理解
项目基本结构图:
别人家的SpringMVC实现原理图:
项目Git地址:https://gitee.com/lvchang/springmvc_demo.git
--END--
个人微信公众号“IT集装箱”目前正在吸粉阶段,欢迎搜索加关注,获取更多精彩IT技术知识内容分享!