重复造轮子没有意义,但是通过现已存在的轮子,模仿着思路去实现一套,还是比较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. stable

    stable - 必应词典 美['steɪb(ə)l]英['steɪb(ə)l] n.马厩:马房:(养马作特定用途的)养马场 adj.稳定的:稳固的:牢固的:稳重的 v.使(马)入厩:把(马)拴在马厩 ...

  2. f5创建VS

    1.1) 2) 3) 4) 5) 2.Availability 1)Pool 中的monitor保障服务高可用 2)Pool 失败机制一 Fallback Host 最后的host( 使用于HTTP ...

  3. 【centos】centos中添加一个新用户,并授权

    前言 有时候给root用户不太方便,新建一个用于并赋予权限这个做法相对好些 创建新用户 创建一个用户名为:cmj [root@localhost ~]# adduser cmj 为这个用户初始化密码, ...

  4. sql2000三个表的级联删除

    sql2000中三个表级联删除 create table a(    id int primary key,    Content varchar(50)) create table b(    id ...

  5. swift - 正则表达式

    import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoa ...

  6. Linux配置Nginx负载均衡

    nginx配置负载均衡其实很简单,一直还以为负载均衡是个很高端人士玩的 首先先了解下负载均衡,假设一个场景,如果有1000个客户同时访问你服务器时,而你只有一台服务器的Nginx,且只有一个MySQL ...

  7. PHP系统编程--PHP进程信号处理(转)

    原地址:https://www.cnblogs.com/linzhenjie/p/5485436.html PHP的pcntl扩展提供了信号处理的功能,利用它可以让PHP来接管信号的处理,在开发服务器 ...

  8. Struts2把数据封装到集合中之封装到map中

    struts框架封装数据可以封装到集合中也可以封装到map中,该篇博客主要讲解将数据封装到map中. 1. 封装复杂类型的参数(集合类型 Collection .Map接口等) 2. 需求:页面中有可 ...

  9. Redhat(RHEL)配置静态IP

    vim /etc/sysconfig/network NETWORKING=yes NETWORKING_IPV6=no HOSTNAME=itcc.dev GATEWAY=192.168.0.1 v ...

  10. nginx与php-fpm原理

    一.正向代理与反向代理 1.正向代理:访问google.com google.com   vpn需要FQ才能访问 vpn 对于我们来说是可以感知到的(我们连接vpn),但对于google服务器是不知道 ...