前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手写Springmvc

手写Springmvc

作者头像
斯文的程序
发布2019-11-07 19:23:02
6390
发布2019-11-07 19:23:02
举报
文章被收录于专栏:带你回家带你回家

一、手写springmvc所用技术

1、java反射

2、自定义注解

二、手写思路:

init:

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

Dispatservlet:

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

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

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

第一步:引入依赖

代码语言:javascript
复制
 <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

代码语言:javascript
复制
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

代码语言:javascript
复制
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:

代码语言:javascript
复制
//自定义RequestMapping
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtyRequstMapping {
	
	String value() default "";
}

第四步:创建dispatrue类

代码语言:javascript
复制
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

代码语言:javascript
复制
 <!-- 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类

代码语言:javascript
复制
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

代码语言:javascript
复制
<%@ 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就是需要配置一下项目路径为 / 因为我没做处理只是简单的写一下。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档