­

手写Springmvc

  • 2019 年 11 月 7 日
  • 笔记

一、手写springmvc所用技术

1、java反射

2、自定义注解

二、手写思路:

init:

通过解析xml获取扫包范围,通过扫包范围工具类,找打类上是否有controller注解,并将其装入容器中。再看类上是否有requestMapping ,创建类与url的关系,再检查该类方法中是否有requestMapping是否存在注解,并且创建方法与url的关系。

Dispatservlet:

解析请求的路径,通过路径去寻找对应的方法,通过反射执行方法。并且转发到相对应的视图。

现在开始我们的手写springmvc之旅 !!!

首先我们创建一个web项目并且创建一下目录结构:

第一步:引入依赖

 <dependencies>    	<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->  		<dependency>  			<groupId>javax.servlet</groupId>  			<artifactId>javax.servlet-api</artifactId>  			<version>3.1.0</version>  			<scope>provided</scope>  		</dependency>    </dependencies>

第二部:自定义工具类

ClassUtil

package com.siyuan.extspringmvc.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 pack  	 * @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();  	}  }

第三步:自定义注解类 ExtController 与 ExtyRequstMapping

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)  public @interface ExtController {    }

ExtyRequstMapping:

//自定义RequestMapping  @Target({ ElementType.METHOD, ElementType.TYPE })  @Retention(RetentionPolicy.RUNTIME)  public @interface ExtyRequstMapping {    	String value() default "";  }

第四步:创建dispatrue类

package com.siyuan.extspringmvc.dispatur;    import java.io.IOException;  import java.lang.reflect.Method;  import java.util.HashMap;  import java.util.List;  import java.util.Map;    import javax.servlet.ServletException;  import javax.servlet.http.HttpServlet;  import javax.servlet.http.HttpServletRequest;  import javax.servlet.http.HttpServletResponse;    import com.siyuan.extspringmvc.annotation.ExtController;  import com.siyuan.extspringmvc.annotation.ExtyRequstMapping;  import com.siyuan.extspringmvc.utils.ClassUtil;    public class DispactrueServlet extends HttpServlet{    	/**  	 *  一、设计容器:  	 *    1、定义controller容器  	 *    2、定义controllerRequestMapping容器  	 *    3、定义methodRequstMapping容器  	 *  二、设计dispatcherServlet  	 *    1、重写servlet init方法  	 *      1) 通过解析xml获取package路径  	 *      2) 扫描包下所有类,将带有ExtController注解的类全部添加到mvcBeans容器中   ==== init  	 *      3) 通过遍历mvcBeans容器 通过java反射初始化mvcControllerUrl,mvcMethodUrl ==== handelMaping  	 *    2、重写 get,post方法  	 *      1)通过反射得到路径去寻找对应的方法。  	 *      2)通过转发不改变访问路径 找到对应的页面   ===视图  	 */    	private  Map<String, Object>  mvcBeans = new HashMap<String, Object>();  	private  Map<String, Object>  mvcControllerUrl = new HashMap<String, Object>();  	private  Map<String, String>  mvcMethodUrl = new HashMap<String, String>();    	@Override  	public void init() throws ServletException {  		try {  			//1、扫包  			List<Class<?>>  classes =  ClassUtil.getClasses("com.siyuan.extcontroller");  			//2、遍历包下所有类,找到含有ExtController类并装配到 mvcBeans容器中  			initBeans(classes);  			//3、处理路径与类的关系,和路径与方法的关系。处理映射关系  			handlerMapping();    		} catch (Exception e) {  			System.out.println("springmbvc初始化异常:"+e);  		}    	}    	/**  	 * 处理映射关系  	 */  	public void handlerMapping() {    		for (Map.Entry<String, Object> entry : mvcBeans.entrySet()) {  			// 1、获取对象  以及反射对象  			Object object = entry.getValue();  			Class<? extends Object> classInfo =  object.getClass();  			// 2、判断类上有没有requestMapping  			String obectUrl = null;  			ExtyRequstMapping extController = classInfo.getDeclaredAnnotation(ExtyRequstMapping.class);  			if(extController != null ) {  				obectUrl = extController.value();  			}  			//3、遍历该类所有方法并且判断方法上面是否有requestMapping 并且设置对应关系。  			Method []  declareMthods = classInfo.getDeclaredMethods();  			for (Method method : declareMthods) {  				 //1、判断该方法上是否有这个 注解  				ExtyRequstMapping methdoRequestMapping  = method.getDeclaredAnnotation(ExtyRequstMapping.class);  				//2、判断  				if(methdoRequestMapping != null ) {  					//获取 value值  					String  methdUrl = methdoRequestMapping.value();  					//存储类  					mvcControllerUrl.put(obectUrl+methdUrl, object);  					//存储方法名称对应关系  					mvcMethodUrl.put(obectUrl+methdUrl, method.getName());    				}    			}  		}    	}    	/**  	 * 遍历包下所有类,找到含有ExtController类并装配到 mvcBeans容器中  	 * @param classes  	 * @throws IllegalAccessException  	 * @throws InstantiationException  	 * @throws ClassNotFoundException  	 */  	public  void initBeans(List<Class<?>>  classes) throws ClassNotFoundException, InstantiationException, IllegalAccessException {  		for (Class<?> classinfo : classes) {  			//1、找到含有ExtController  			ExtController extController = classinfo.getDeclaredAnnotation(ExtController.class);  		    //2、判断  			if(extController != null) {  				//首字母小写  				String  beanId =  ClassUtil.toLowerCaseFirstOne(classinfo.getSimpleName());  				//通过反射创建对象  				mvcBeans.put(beanId, ClassUtil.newInstance(classinfo));    			}    		}    	}    	@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 {  		try {  			dipact(req,resp);  		} catch (Exception e) {  			e.printStackTrace();  		}    	}        /**       * 根据路径找到对应的方法并且执行 返回对应的视图       *       * @param req       * @param resp       * @throws IOException       * @throws ServletException       */  	public void dipact(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {  		//1、得到 请求路径  		String reqUrl = req.getRequestURI();  		//2、去mvcControllerUrl找对应的类  		Object  object = mvcControllerUrl.get(reqUrl);  	    if(object == null){  	    	resp.getWriter().println("not fund controller ");  	    	System.out.println("没有找到对应的 controller");  	    	return ;  	    }  		//3、找方法  	    String  method = mvcMethodUrl.get(reqUrl);  	    if(method.isEmpty()) {  	    	resp.getWriter().println("not fund requstMapping ");  	    	System.out.println("没有找到对应的 requstMapping");  	    	return ;  	    }  	   // 4、执行方法  	    String retPage = methodInvok(object,method);    	    //5、返回视图处理  	    if(!retPage.isEmpty()) {  	    	viewdisplay(retPage,req,resp);  	    }    	}  	/**  	 * 执行方法  	 * @param object  	 * @param method  	 * @return  	 */  	public String methodInvok(Object  object, String  method) {  		  try {  			  //4、执行方法  				Class<? extends Object> classInfo =  object.getClass();  				//5、得到方法对象  				Method actMenthod = classInfo.getMethod(method);  				//6、执行方法  				return (String) actMenthod.invoke(object);      			} catch (Exception e) {  			    System.out.println("执行方法报错:"+e);    			}  		return null;  	}  	 /**       * 视图展示       * @param pageName       * @param req       * @param res       * @throws ServletException       * @throws IOException       */   	public void viewdisplay(String pageName, HttpServletRequest req, HttpServletResponse res)   			throws ServletException, IOException {   		// 获取后缀信息   		String suffix = ".jsp";   		// 页面目录地址   		String prefix = "/";   		req.getRequestDispatcher(prefix + pageName + suffix).forward(req, res);   	}    }

第五步:web.xml中配置dispatruservlet

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

第六步:创建controller类

package com.siyuan.extcontroller;    import com.siyuan.extspringmvc.annotation.ExtyRequstMapping;    @ExtController  @ExtyRequstMapping("/extIndex")   //       1、/extIndex/test  public class ExtController {    	@ExtyRequstMapping("/test")  	public String test() {  		System.out.println("手写springmvc");    		return "test";  	}    }

第七步:写好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>页面展示</title>  </head>  <body>    	<h1>我是手写SpringMVC框架....</h1>    </body>  </html>

执行效果:

这里有一个bug就是需要配置一下项目路径为 / 因为我没做处理只是简单的写一下。