手写Springmvc

一、手写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就是需要配置一下项目路径为 / 因为我没做处理只是简单的写一下。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 根据当前系统时间获取工作日日期

    斯文的程序
  • java实现HTML转PDF

    windows 自带有这个文件 。但是linux 要自己去下载了 如果需要可以联系博主 (千万不要把windows s上的这个文件直接copy 到linux 系...

    斯文的程序
  • Spring boot (六) 集成Druid

    斯文的程序
  • 代码的演变随记

    1) 使用sizeof操作符替代魔鬼数字 if (s1.substr(0, 10) == s2) 改成: if (s1.substr(0, sizeof(...

    一见
  • 大咖 | IEEE专访李开复:五个问题直面人工智能的危机

    霍金先生前曾三番五次表态,“人工智能可能会毁灭人类”。钢铁侠马斯克曾经提出,人工智能有可能会成为人类文明的最大威胁,呼吁政府快速采取措施,有效监管这项技术。

    大数据文摘
  • ​[linux][pthread]qemu的一次pthread create失败的分析

    前言: qemu发生了crash。这种类型的问题比较少见,这里说一下这个问题的分析过程。 分析: 1,coredump 生成的coredump,一种是配置了...

    皮振伟
  • Flutter入门(一)

    Flutter的sdk地址https://flutter.dev/docs/development/tools/sdk/releases

    用户3112896
  • 毕啸南专栏 | 对话李开复:AI科学家的转型之路

    作者简介:毕啸南,知名青年学者,量子位专栏作家,《中国AI领袖人物访谈》系列制片人、主持人。点击文末阅读原文,关注量子学园的毕啸南专栏,跟随他一起持续深度对话李...

    量子位
  • 新智元重磅发布《2018华人AI青年学术影响力百人名单》,清华学者霸榜

    今年3月,人工智能连续第三年写入总理政府工作报告,在总理的工作报告中,其中“关键核心技术”出现了3次。报告明确指出要提升科技支撑能力,加大基础研究和应用基础研究...

    新智元
  • 解密古老又通杀的路由器攻击手法:从嗅探PPPoE到隐蔽性后门

    随着宽带用户爆发性增长,PPPoE被带进了各家各户,而它的安全缺陷却一直没有受到足够关注,直至今日成为了一种低成本的通杀型路由器攻击手段。 PPPoE(Poi...

    FB客服

扫码关注云+社区

领取腾讯云代金券

玩转腾讯云 有奖征文活动