SpringMVC介绍

  • SpringMVC基于Servlet去实现的。
  • Servlet是单例的,所以也是存在线程安全问题的。线程不安全。
  • SpringMVC基于Servlet实现,所以也是线程不安全的。
  • 判断是单例还是多例就看其构造函数执行几次,创建多个对象,构造函数调用了一次就是单例

什么是Servlet

  • Java Servlet是运行在web服务器或应用服务器上的程序,他是作为来自web浏览器或者其他Http客户端的请求和HTTP服务器上的数据库或者应用程序之间的中间层。
  • 使用Servlet,可以收集来自网页表单的用户输入,呈现来自数据库或者其他资源的记录,还可以动态创建网页
  • Servlet是独立于平台的,用Java编写的
  • Java类库的全部功能堆Servlet来说都是可用的。

Servlet的生命周期

  • Servlet加载------>实例化 ------->服务 ------->销毁
  • init()

在Servlet的生命周期中,仅仅执行一次init()方法,他是在服务器装入Servlet的时候进行执行的,负责初始化Servlet对象。可以配置服务器,让其在启动服务器或者客户机首次访问Servlet的时候装入Servlet。无论多少个客户机访问Servlet,都不会重复执行init()

  • service()

他是Servlet的核心,负责响应客户的请求。每当一个客户请求一个HttpServlet对象,还对象的Service方法就会被调用,而且传递给这个方法一个请求(ServletRequest)对象和一个响应(ServletResponse)对象作为参数。在HttpServlet中存在Service()方法。默认的服务功能与调用HTTP请求的方法相应的doXXX功能。

  • destory()

仅仅执行一次,在服务器端停止且卸载Servlet时执行该方法。当Servlet对象退出生命周期的时候,负责释放占用的资源。一个Servlet在运行service()方法的时候会产生其他的线程,因此需要确认在调用destory()方法的时候,这些线程已经终止或者执行完成了。

SpringMVC的运行原理

  • 核心就是DispatcherServlet

  • 原理图如下所示:
    在这里插入图片描述
    (1)用户发送请求到前端控制器DispatcherServlet
    (2)DispatcherServlet收到请求调用HandlerMapping处理器映射器(就是通过请求地址找到对应的方法。)
    (3)处理器映射器根据请求url找到具体的处理器,生成处理器对象以及处理器拦截器(如果有作为生成)一并返回给DisPatcherServlet。
    (4)DispatcherServlet通过HandlerAdapter处理器适配器调用处理器
    (5)执行处理器(Controller)
    (6)Controlelr执行完成返回ModelAndView
    (7)HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet
    (8)DispatcherServlet将ModelAndView传给viewResolver视图解析器
    (9)viewResolver解析后返回具体的view
    (10)DispatcherServlet对View进行渲染
    (11)DispatcherServlet响应用户

  • SpringMVC就是基于Servlet去实现的。

手写SpringMVC框架

  • 思路:
    (1)创建一个前端控制器,拦截所有的请求(SpringMVC基于Servlet实现)
    (2)初始化操作,重写Servlet的init()方法
    ----(2.1)扫描包。找寻哪些类上有指定注解,加载到SpringIOC容器中,存放到map中,key是类名小写,value是该类对象
    ----(2.2)将url映射和方法进行关联
    ----------(2.2.1)判断类上是否有注解。使用java反射机制遍历方法,判断方法上是否有注解,有注解取出方法的url地址,存入map。key是url,value是对应方法(类名+方法)。
    (3)处理请求(重写get和post方法)
    ---- (3.1)获取请求的url,获取类的实例结果,获取成功后,调用key获取方法名的集合,使用反射机制执行
  • 这里我们可以建立三个map,一个key是类名的id(类名小写,value是对象);第二个是存储url和类之间的关系,key是请求地址,value是类的反射对象;最后一个存放url和方法之间的关系,key是url,value是方法的反射对象。
  • java代码设计:
    (1)代码结构图:
    在这里插入图片描述
    (2)index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>这是我第一次手写springmvc.....</h1>
	<h1>每特教育!!!!!</h1>
</body>
</html>

(3)web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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/web-app_2_4.xsd"
	version="2.4">


	<!-- Spring MVC 核心控制器 DispatcherServlet 配置 -->
	<servlet>
		<servlet-name>dispatcher</servlet-name>
		<servlet-class>com.xiyou.ext.ExtDispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>dispatcher</servlet-name>
		<!-- 拦截所有/* 的请求,交给DispatcherServlet处理,性能最好 -->
		<url-pattern>/</url-pattern>
	</servlet-mapping>

</web-app>

(4)自定义注解

  • ExtController:
package com.xiyou.annotation;

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

/**
 * 自定义注解 功能类似@Controller
 * @author zyf
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtController {
}

  • ExtRequestMapping
package com.xiyou.annotation;

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

/**
 * 自定义注解,类似于@RequestMapping
 * @Author
 */
// 表示可以放在类上或者方法上
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtRequestMapping {
    /**
     * 定义value属性,默认值是""
     * @return
     */
    String value() default "";
}

(5)Controller层:

package com.xiyou.controller;


import com.xiyou.annotation.ExtController;
import com.xiyou.annotation.ExtRequestMapping;

@ExtController
@ExtRequestMapping("/ext")
public class ExtIndexController {
	@ExtRequestMapping("/test")
	public String test() {
		System.out.println("手写springmvc框架...");
		return "index";
	}

}

(6)工具类(反射用的,主要是用来处理找到指定包下所有的类)

package com.xiyou.utils;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class ClassUtil {

	/**
	 * 取得某个接口下所有实现这个接口的类
	 */
	public static List<Class> getAllClassByInterface(Class c) {
		List<Class> returnClassList = null;

		if (c.isInterface()) {
			// 获取当前的包名
			String packageName = c.getPackage().getName();
			// 获取当前包下以及子包下所以的类
			List<Class<?>> allClass = getClasses(packageName);
			if (allClass != null) {
				returnClassList = new ArrayList<Class>();
				for (Class classes : allClass) {
					// 判断是否是同一个接口
					if (c.isAssignableFrom(classes)) {
						// 本身不加入进去
						if (!c.equals(classes)) {
							returnClassList.add(classes);
						}
					}
				}
			}
		}

		return returnClassList;
	}

	/*
	 * 取得某一类所在包的所有类名 不含迭代
	 */
	public static String[] getPackageAllClassName(String classLocation, String packageName) {
		// 将packageName分解
		String[] packagePathSplit = packageName.split("[.]");
		String realClassLocation = classLocation;
		int packageLength = packagePathSplit.length;
		for (int i = 0; i < packageLength; i++) {
			realClassLocation = realClassLocation + File.separator + packagePathSplit[i];
		}
		File packeageDir = new File(realClassLocation);
		if (packeageDir.isDirectory()) {
			String[] allClassName = packeageDir.list();
			return allClassName;
		}
		return null;
	}

	/**
	 * 从包package中获取所有的Class
	 * 
	 * @param packageName
	 * @return
	 */
	public static List<Class<?>> getClasses(String packageName) {

		// 第一个class类的集合
		List<Class<?>> classes = new ArrayList<Class<?>>();
		// 是否循环迭代
		boolean recursive = true;
		// 获取包的名字 并进行替换
		String packageDirName = packageName.replace('.', '/');
		// 定义一个枚举的集合 并进行循环来处理这个目录下的things
		Enumeration<URL> dirs;
		try {
			dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
			// 循环迭代下去
			while (dirs.hasMoreElements()) {
				// 获取下一个元素
				URL url = dirs.nextElement();
				// 得到协议的名称
				String protocol = url.getProtocol();
				// 如果是以文件的形式保存在服务器上
				if ("file".equals(protocol)) {
					// 获取包的物理路径
					String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
					// 以文件的方式扫描整个包下的文件 并添加到集合中
					findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
				} else if ("jar".equals(protocol)) {
					// 如果是jar包文件
					// 定义一个JarFile
					JarFile jar;
					try {
						// 获取jar
						jar = ((JarURLConnection) url.openConnection()).getJarFile();
						// 从此jar包 得到一个枚举类
						Enumeration<JarEntry> entries = jar.entries();
						// 同样的进行循环迭代
						while (entries.hasMoreElements()) {
							// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
							JarEntry entry = entries.nextElement();
							String name = entry.getName();
							// 如果是以/开头的
							if (name.charAt(0) == '/') {
								// 获取后面的字符串
								name = name.substring(1);
							}
							// 如果前半部分和定义的包名相同
							if (name.startsWith(packageDirName)) {
								int idx = name.lastIndexOf('/');
								// 如果以"/"结尾 是一个包
								if (idx != -1) {
									// 获取包名 把"/"替换成"."
									packageName = name.substring(0, idx).replace('/', '.');
								}
								// 如果可以迭代下去 并且是一个包
								if ((idx != -1) || recursive) {
									// 如果是一个.class文件 而且不是目录
									if (name.endsWith(".class") && !entry.isDirectory()) {
										// 去掉后面的".class" 获取真正的类名
										String className = name.substring(packageName.length() + 1, name.length() - 6);
										try {
											// 添加到classes
											classes.add(Class.forName(packageName + '.' + className));
										} catch (ClassNotFoundException e) {
											e.printStackTrace();
										}
									}
								}
							}
						}
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		return classes;
	}

	/**
	 * 以文件的形式来获取包下的所有Class
	 * 
	 * @param packageName
	 * @param packagePath
	 * @param recursive
	 * @param classes
	 */
	public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive,
			List<Class<?>> classes) {
		// 获取此包的目录 建立一个File
		File dir = new File(packagePath);
		// 如果不存在或者 也不是目录就直接返回
		if (!dir.exists() || !dir.isDirectory()) {
			return;
		}
		// 如果存在 就获取包下的所有文件 包括目录
		File[] dirfiles = dir.listFiles(new FileFilter() {
			// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
			public boolean accept(File file) {
				return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
			}
		});
		// 循环所有文件
		for (File file : dirfiles) {
			// 如果是目录 则继续扫描
			if (file.isDirectory()) {
				findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive,
						classes);
			} else {
				// 如果是java类文件 去掉后面的.class 只留下类名
				String className = file.getName().substring(0, file.getName().length() - 6);
				try {
					// 添加到集合中去
					classes.add(Class.forName(packageName + '.' + className));
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				}
			}
		}
	}

	// 首字母转小写
	public static String toLowerCaseFirstOne(String s) {
		if (Character.isLowerCase(s.charAt(0))) {
            return s;
		}
		else{
            return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
        }

	}

	// 初始化对象
	public static Object newInstance(Class<?> classInfo)
			throws ClassNotFoundException, InstantiationException, IllegalAccessException {
		return classInfo.newInstance();
	}
}

(7)重要,自定义实现逻辑,实际上依靠servlet完成,解析注解的过程在Servlet的init()方法中

package com.xiyou.ext;

import com.xiyou.annotation.ExtController;
import com.xiyou.annotation.ExtRequestMapping;
import com.xiyou.utils.ClassUtil;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

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

import org.apache.commons.lang.StringUtils;
/**
 * 自定义一个Servlet
 * 该Servlet用来解析自定义注解,完成注解功能
 * @Author zyf
 */
public class ExtDispatcherServlet extends HttpServlet{
    /**
     * 该Map用来存储上面有指定注解的类的信息,key是类的首字母小写(默认的beanId)
     */
    private ConcurrentHashMap<String, Object> springMvcBeans = new ConcurrentHashMap<>();

    /**
     * 定义map,key是映射的url(宽窄路径),value是当前类的对象
     */
    private ConcurrentHashMap<String, Object> urlBeans = new ConcurrentHashMap<String, Object>();

    /**
     * 定义Map,key是映射的url(宽窄路径),value是当前的方法名
     */
    private ConcurrentHashMap<String, String> urlMethods = new ConcurrentHashMap<String, String>();


    /**
     * Servlet初始化的时候会调用该方法,该方法只会调用一次
     * @throws ServletException
     */
    @Override
    public void init() throws ServletException {
        // 获取指定包下面的所有的类信息
        List<Class<?>> classes = ClassUtil.getClasses("com.xiyou.controller");
        try {
            // 1. 判断哪些类是上面有ExtController注解的,有注解的存入Map
            findClassMVCAnnotation(classes);
        }  catch (Exception e) {
            e.printStackTrace();
        }
        // 2. 将宽窄路径和具体的方法和类映射起来,存入两个map中
        handlerMapping();
    }


    /**
     * 将宽窄路径进行和类以及方法的绑定,将其保存在两个定义好的map中
     */
    public void handlerMapping() {
        // 遍历上面有指定注解的所有的类
        for (Map.Entry<String, Object> mvcBean : springMvcBeans.entrySet()) {
            // 得到类的对象
            Object object = mvcBean.getValue();
            // 判断该对象的上面是否有@ExtRequestMapping
            Class<?> classInfo = object.getClass();
            ExtRequestMapping declaredAnnotation = classInfo.getDeclaredAnnotation(ExtRequestMapping.class);
            // 用来保存类上面的路径
            String baseUrl = "";
            if (declaredAnnotation != null) {
                baseUrl = declaredAnnotation.value();
            }
            // 判断方法上有没有指定的注解
            Method[] declaredMethods = classInfo.getDeclaredMethods();
            for (Method method : declaredMethods) {
                // 判断方法上有无注解
                ExtRequestMapping methodExtRequestMapping = method.getDeclaredAnnotation(ExtRequestMapping.class);
                if (methodExtRequestMapping != null) {
                    // 方法上的路径
                    String methodUrl = methodExtRequestMapping.value();
                    if (StringUtils.isNotEmpty(baseUrl)) {
                        methodUrl = baseUrl + methodUrl;
                    }
                    // 保存两个map(一个保存类信息,一个保存方法信息)
                    urlBeans.put(methodUrl, object);
                    urlMethods.put(methodUrl, method.getName());
                }
            }
        }
    }


    /**
     * 找到当前类上有指定注解的类,将其保存到map中,key是类的首字母小写,value是类对象
     * @param classes 传进来的是扫描指定包下的所有的类
     */
    public void findClassMVCAnnotation(List<Class<?>> classes) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        // 遍历判断是否有指定的注解在类的上面
        for (Class<?> classInfo : classes) {
            // 判断类上是否有注解
            ExtController annotation = classInfo.getDeclaredAnnotation(ExtController.class);
            if (annotation != null) {
                // 不为空表示的是该类上有ExtController注解
                String beanId = ClassUtil.toLowerCaseFirstOne(classInfo.getSimpleName());
                // 实例化对象
                Object object = ClassUtil.newInstance(classInfo);
                // 存入map中
                springMvcBeans.put(beanId, object);
            }
        }
    }


    /**
     * 根据返回结果映射到指定的jsp上
     * @param pageName
     * @param req
     * @param res
     */
    public void extResourceViewResolver(String pageName, HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        String prefix = "/";
        String suffix = ".jsp";
        // 默认是转发请求
        req.getRequestDispatcher(prefix + pageName + suffix).forward(req, res);
    }


    /**
     * 反射调用获取方法返回结果
     * @param object 类的对象
     * @param methodName 方法名称
     * @return
     */
    public Object methodInvoke(Object object, String methodName){
        try {
            Class<?> classInfo = object.getClass();
            // 得到对应方法名称的方法
            Method method = classInfo.getMethod(methodName);
            // 执行方法
            Object result = method.invoke(object);
            return result;
        } catch (Exception e){
            return null;
        }
    }

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

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 获取请求的url地址
        String requestURI = req.getRequestURI();
        if (StringUtils.isEmpty(requestURI)) {
            return;
        }
        // 2. 从map中获取控制的对象
        Object object = urlBeans.get(requestURI);
        if (object == null) {
            resp.getWriter().println(" not found 404  url");
            return;
        }
        // 3. 从url中获取方法
        String methodName = urlMethods.get(requestURI);
        if (StringUtils.isEmpty(methodName)) {
            resp.getWriter().println(" not found method");
        }
        // 4. 使用java的反射机制调用方法
        String result = (String) methodInvoke(object, methodName);
        // 5. 视图解析
        extResourceViewResolver(result, req, resp);
    }
}

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐