重复造轮子没有意义,但是通过现已存在的轮子,模仿着思路去实现一套,还是比较cool的。花了三天,终于深夜搞定!收益都在代码里,我干了,您随意!

一、简单思路

简单介绍:

1、所有的请求交给TyDispatcher处理,TyDispatcher主要负责读取配置、分发请求、初始化handlerMapping等功能;

2、根据handlerMapping与请求url,找到对应的处理方法,并由该处理方法处理业务逻辑;

二、建项目

1、新建maven项目

选择webapp结尾的。然后继续创建,创建完成后,有个设置,因为本次项目是基于tomcat的,右击项目properties-->project facets-->Dynamic Web Module

project facets就是指项目特性的意思,如果不选择这个,eclipse会把项目当做是普通的javase项目,那自然也就无法在tomcat等应用服务器上部署。

进去之后设置路径为src/main/webapp,对应maven的工程目录结构。

2、配置tomcat

新建server这种就不多说了,将项目添加到tomcat中,若是添加不了,说明不是web项目,按照上步操作,其他基本没啥问题了。

项目结构如图:

三、代码

1、注解类

模仿着springMVC实现了@TyController、@TyRequestMapping以及@TyRequestParam三个注解。

a、@TyController

package com.ty.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* @author Taoyong
* @date 2018年6月6日
* 天下没有难敲的代码!
*/
//只能在类上使用该注解
@Target(ElementType.TYPE)
//表示在运行时使用
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TyController { String value() default "";
}

b、@TyRequestMapping

package com.ty.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* @author Taoyong
* @date 2018年6月6日
* 天下没有难敲的代码!
*/
//该注解只能在方法上使用
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TyRequestMapping { String value() default "";
}

c、@TyRequestParam

package com.ty.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* @author Taoyong
* @date 2018年6月6日
* 天下没有难敲的代码!
*/
//该注解只能在参数上注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TyRequestParam { String value() default "";
}

2、TyDispatcher(初始化相关basePackage下的controller、分发请求、处理客户端请求参数等等)

package com.ty.dispatcher;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties; import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import com.ty.annotation.TyController;
import com.ty.annotation.TyRequestMapping;
import com.ty.annotation.TyRequestParam; /**
* @author Taoyong
* @date 2018年6月5日
* 天下没有难敲的代码!
* 此类的主要作用为初始化相关配置以及分发请求
* 首先要将此servlet配置在web.xml中,在容器启动的时候,TyDispatcher初始化,并且在整个容器的生命周期中,只会创建一个TyDispatcher实例
* TyDispatcher在web.xml中的配置信息将会包装成ServletConfig类
*/ public class TyDispatcher extends HttpServlet { private static final long serialVersionUID = 9003485862460547072L; //自动扫描的basePackage
private Properties property = new Properties(); //保存所有需要自动创建实例对象的类的名字
private List<String> classNameList = new ArrayList<>(); //IOC容器,是一个键值对(数据格式:{"TestController": Object})
public Map<String, Object> ioc = new HashMap<>(); /*
* 用于存放url与method对象映射关系的容器,数据格式为{"/base/TySpringMVCTest": Method}
*/
private Map<String, Method> handlerMappingMap = new HashMap<>(); //用于将请求url与controller实例对象建立映射关系(数据格式:{"": Controller})
public Map<String, Object> controllerContainer = new HashMap<>(); /*
* init方法主要用于初始化相关配置,比如basePackage
*
*/
@Override
public void init(ServletConfig config) throws ServletException {
//配置basePackage,该包下所有的controller全部自动创建实例 initBaseScan(config); //扫描basePackage下所有应该被实例化的类
scanBasePackage(property.getProperty("scanPackage")); //根据classNameList中的文件去创建实例对象(即实例化basePackage下的controller)
createInstance(classNameList); //初始化handlerMapping
initHandlerMapping(ioc);
} private void initBaseScan(ServletConfig config) {
/*
* servlet会将web.xml中的配置封装成ServletConfig对象。
* 从该对象获取<init-param>所配置的初始化参数
*/
String location = config.getInitParameter("contextConfigLocation");
//通过类加载器去读取该文件中的数据,并封装成流,然后通过property去加载流
InputStream input = this.getClass().getClassLoader().getResourceAsStream(location);
try {
property.load(input);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(input != null) {
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
} private void scanBasePackage(String basePackage) {
/*
* 找到basePackage下所有的resource(resource即为图片、声音、文本等等数据)
* 另外需要将com.ty.controller转换成/com/ty/controller/这种相对路径形式
* \\.是为了防止转义
*/
URL url = this.getClass().getClassLoader().getResource("/" + basePackage.replaceAll("\\.", "/"));
//此时file相当于一个总文件夹
File totalFolder = new File(url.getFile());
/*
* 列出总文件夹下的文件夹以及文件
*/
for(File file: totalFolder.listFiles()) {
if(file.isDirectory()) {
//通过递归去找到最后一层级的文件,其实也就是.class文件(编译后)
scanBasePackage(basePackage + file.getName());
} else {
//要将编译后文件的.class后缀去掉
classNameList.add(basePackage + "." + file.getName().replaceAll(".class", ""));
}
}
} private void createInstance(List<String> classNameList) {
if(classNameList == null || classNameList.size() == 0) {
return;
} for(String className: classNameList) {
try {
//根据className获取Class对象,通过反射去实例化对象
Class<?> clazz = Class.forName(className); //只需要将basePackage下的controller实例化即可
if(clazz.isAnnotationPresent(TyController.class)) {
Object obj = clazz.newInstance();
//这点跟spring容器稍有不同的是就是key值虽为类名,但是首字母并没有小写
ioc.put(clazz.getSimpleName(), obj);
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
} /*
* controller现已实例化,但是还要对其方法的url(controller上注解的url+方法上的url)与方法对象建立起映射关系
* ioc数据格式:{"TestController": Object}
*/
private void initHandlerMapping(Map<String, Object> ioc) {
if(ioc.isEmpty()) {
return;
} for(Entry<String, Object> entry: ioc.entrySet()) {
Class<?> clazz = entry.getValue().getClass();
//如果ioc容器中的对象不是controller对象,不进行处理
if(!clazz.isAnnotationPresent(TyController.class)) {
continue;
} /*
* controller上配置的@TyRequestMapping的值。因为controller类上使用@TyRequestMapping注解
* 是类维度的,所以通过clazz.getAnnotation获取value值
*/
String baseURL = clazz.getAnnotation(TyController.class).value(); //通过clazz获取到该类下所有的方法数组
Method[] methods = clazz.getMethods();
for(Method method: methods) {
//判断Method对象上是否存在@TyRequestMapping的注解,若有,取其value值
if(!method.isAnnotationPresent(TyRequestMapping.class)) {
continue;
} String methodURL = method.getAnnotation(TyRequestMapping.class).value();
//数据格式:{"/controller/methodURL": Method}
handlerMappingMap.put(baseURL + methodURL, method);
//并且需要将url对应controller的映射关系保存
controllerContainer.put(baseURL + methodURL, entry.getValue());
}
}
} @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
} /*
* 此类用于处理客户端的请求,所有核心的分发逻辑由doPost控制,包括解析客户端请求参数等等
*
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
/*
* 根据JDK的说明,若请求url为http://localhost:8080/TySpringMVC/controller/testMethod
* 则requestURL为/TySpringMVC/controller/testMethod。所以应该将项目名去掉
*/
String requestURL = req.getRequestURI();
//contextPath的路径为/TySpringMVC
String contextPath = req.getContextPath();
//requestMappingURL为/controller/testMethod
String requestMappingURL = requestURL.replaceAll(contextPath, "");
Method method = handlerMappingMap.get(requestMappingURL); if(method == null) {
/*
* 通过Writer.write向与客户端连接的I/O通道写数据。并且这地方注意设置编码,要保证客户端解析编码与
* 代码中编码格式一致。这也是出现乱码的根本原因所在
*/
resp.setCharacterEncoding("UTF-8");
String errorMessage = "404 请求URL不存在!";
resp.getWriter().write(errorMessage);
return;
} /*
* 获取前端入参,例如url为http://localhost:8080/TySpringMVC/sss?name=ty
* map:{"name": ["ty","\s"}
*/
Map<String, String[]> paramMap = req.getParameterMap(); //获取该method的所有参数对象数组。注:这地方找了好久api,才找到此方法。。。
Parameter[] params = method.getParameters();
//用于将前端参数封装,并且供method执行
Object[] paramArr = new Object[params.length];
for(int i = 0; i < params.length; i++) {
//这两个if是用于解决method中的参数类型为HttpServletRequest或HttpServletResponse
if("HttpServletRequest".equals(params[i].getType().getSimpleName())) {
paramArr[i] = req;
continue;
} if("HttpServletResponse".equals(params[i].getType().getSimpleName())) {
paramArr[i] = resp;
continue;
} if(params[i].isAnnotationPresent(TyRequestParam.class)) {
String paramKey = params[i].getAnnotation(TyRequestParam.class).value();
//客户端传过来的参数会是[小可爱]这种形式,因此需要去除[以及],并且需要使用转义符
String value = Arrays.toString(paramMap.get(paramKey)).replaceAll("\\[", "").replaceAll("\\]", "");
paramArr[i] = value;
}
} //开始调用反射来执行method
try {
method.invoke(controllerContainer.get(requestMappingURL), paramArr);
} catch (Exception e) {
e.printStackTrace();
}
} @Override
public void destroy() {
super.destroy();
} }

3、tySpringMVC.properties(主要用来配置basePackage)

scanPackage=com.ty.controller

4、BaseCntroller(用来测试的controller)

package com.ty.controller;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import com.ty.annotation.TyController;
import com.ty.annotation.TyRequestMapping;
import com.ty.annotation.TyRequestParam; /**
* @author Taoyong
* @date 2018年6月7日
* 天下没有难敲的代码!
*/
@TyController("/baseController")
public class BaseController { @TyRequestMapping("/firstMethod")
public void firstMethod(HttpServletRequest req, HttpServletResponse resp, @TyRequestParam("name") String name) {
try {
resp.setCharacterEncoding("UTF-8");
resp.getWriter().write("我不管,我最帅,我是你们的" + name);
} catch (IOException e) {
e.printStackTrace();
}
}
}

四、测试

1、成功场景(控制层中通过@TyRequestParam注解拿到前台的入参,并打印)

2、请求不存在

当然,水平有限,有什么错误的地方

所有源代码已经上传到github中:https://github.com/ali-mayun/springMVC

实现一套山寨springMVC的更多相关文章

  1. 解决springmvc报No converter found for return value of type: class java.util.ArrayList问题

    一.背景 最近闲来无事,想自己搭建一套Spring+SpringMVC+Mybatis+Mysql的环境(搭建步骤会在以后博客中给出),结果运行程序时,适用@ResponseBody注解进行返回Lis ...

  2. 前馈网络求导概论(一)·Softmax篇

    Softmax是啥? Hopfield网络的能量观点 1982年的Hopfiled网络首次将统计物理学的能量观点引入到神经网络中, 将神经网络的全局最小值求解,近似认为是求解热力学系统的能量最低点(最 ...

  3. 使用Netty实现HTTP服务器

    使用Netty实现HTTP服务器,使用Netty实现httpserver,Netty Http Netty是一个异步事件驱动的网络应用程序框架用于快速开发可维护的高性能协议服务器和客户端.Netty经 ...

  4. 解决java.lang.IllegalArgumentException: No converter found for return value of type: class java.util.ArrayList的问题

    一.背景 最近闲来无事,想自己搭建一套Spring+SpringMVC+Mybatis+Mysql的环境(搭建步骤会在以后博客中给出),结果运行程序时,适用@ResponseBody注解进行返回Lis ...

  5. Netty入门——客户端与服务端通信

    Netty简介Netty是一个基于JAVA NIO 类库的异步通信框架,它的架构特点是:异步非阻塞.基于事件驱动.高性能.高可靠性和高可定制性.换句话说,Netty是一个NIO框架,使用它可以简单快速 ...

  6. 解决Spring MVC报No converter found for return value of type:class java.util.ArrayList问题

    一.背景 在搭建一套Spring+SpringMVC+Mybatis(SSM)的环境(搭建步骤会在以后博客中给出),结果运行 程序时,适用@ResponseBody注解进行返回List<对象&g ...

  7. SSH框架集成Activiti Modeler在线设计器页面出现问号及乱码的解决办法

    文·原创/朱季谦 工作流是一个针对企业用户.开发人员.系统管理员的轻量级工作流业务管理平台,其核心是使用Java开发的快速.稳定的BPMN2.0流程引擎.在我们日常开发当中,例如oa系统里的请假功能, ...

  8. 如何自定义JSTL标签与SpringMVC 标签的属性中套JSTL标签报错的解决方法

    如何自定义JSTL标签 1.创建一个类,从SimpleTagSupport继承 A) 通过继承可以获得当前JSP页面上的对象,如JspContext I) 实际上可以强转为PageContext II ...

  9. Spring+SpringMVC+MyBatis+easyUI整合进阶篇(一)设计一套好的RESTful API

    写在前面的话 看了一下博客目录,距离上次更新这个系列的博文已经有两个多月,并不是因为不想继续写博客,由于中间这段时间更新了几篇其他系列的文章就暂时停止了,如今已经讲述的差不多,也就继续抽时间更新< ...

随机推荐

  1. 如何禁止chrome自动跳转https

    请在chrome的地址栏输入: chrome://net-internals/#hsts 在打开的页面中, Delete domain 栏的输入框中输入:xx.xx.com(注意这里是二级域名),然后 ...

  2. swift4.2 打印所有系统字体

    func showAllFonts(){ let familyNames = UIFont.familyNames var index:Int = 0 for familyName in family ...

  3. [leetcode]300. Longest Increasing Subsequence最长递增子序列

    Given an unsorted array of integers, find the length of longest increasing subsequence. Example: Inp ...

  4. 浅谈前端三大框架Angular、react、vue

    每个框架都不可避免会有自己的一些特点,从而会对使用者有一定的要求,这些要求就是主张,主张有强有弱,它的强势程度会影响在业务开发中的使用方式. 一.Angular,它两个版本都是强主张的,如果你用它,必 ...

  5. errror:[test_rig3.launch] is neither a launch file in package [svo_ros] nor is [svo_ros] a launch file name The traceback for the exception was written to the log file

    1. 打开一个终端,运行roscore 2. 打开另一个终端,运行 roslaunch svo_ros test_rig3.launch 出现errror: 忘记关键步骤了 $ cd <path ...

  6. Python中的 __all__和__path__ 解析

    https://blog.csdn.net/u012450329/article/details/53001071

  7. SQL查询有两门以上不及格的学生及查询出全部及格的学生

    1.表结构: /*学生*/ create table student( sno int not null primary key, sname ) ); /*课程*/ create table cen ...

  8. JS部分

    前端三剑客(HTML,CSS,JavaScript) Html:负责一个页面的结构 Css:负责一个页面的样式 JavaScript:负责与用户进行交互 JS概念 JS是JavaScript的简称,是 ...

  9. 探索未知种族之osg类生物---器官初始化三

    当判断到viewer中没有一个graphicContext可用时,osg就会默认的进行一次对viewer的实现操作,这样可以保证osg以后可以安心的在屏幕上进行作画.那我们就来看看这个osgViewe ...

  10. iOS设置图片名称、启动图片、防止TabBar图片和文字渲染

    设置App的名称 设置App的启动图片 需要注意点是,App要杀掉重启才能显示出启动图片 2种方法防止图片被渲染 1. vc02.tabBarItem.image = [UIImage imageNa ...