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