实现一套山寨springMVC
重复造轮子没有意义,但是通过现已存在的轮子,模仿着思路去实现一套,还是比较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的更多相关文章
- 解决springmvc报No converter found for return value of type: class java.util.ArrayList问题
一.背景 最近闲来无事,想自己搭建一套Spring+SpringMVC+Mybatis+Mysql的环境(搭建步骤会在以后博客中给出),结果运行程序时,适用@ResponseBody注解进行返回Lis ...
- 前馈网络求导概论(一)·Softmax篇
Softmax是啥? Hopfield网络的能量观点 1982年的Hopfiled网络首次将统计物理学的能量观点引入到神经网络中, 将神经网络的全局最小值求解,近似认为是求解热力学系统的能量最低点(最 ...
- 使用Netty实现HTTP服务器
使用Netty实现HTTP服务器,使用Netty实现httpserver,Netty Http Netty是一个异步事件驱动的网络应用程序框架用于快速开发可维护的高性能协议服务器和客户端.Netty经 ...
- 解决java.lang.IllegalArgumentException: No converter found for return value of type: class java.util.ArrayList的问题
一.背景 最近闲来无事,想自己搭建一套Spring+SpringMVC+Mybatis+Mysql的环境(搭建步骤会在以后博客中给出),结果运行程序时,适用@ResponseBody注解进行返回Lis ...
- Netty入门——客户端与服务端通信
Netty简介Netty是一个基于JAVA NIO 类库的异步通信框架,它的架构特点是:异步非阻塞.基于事件驱动.高性能.高可靠性和高可定制性.换句话说,Netty是一个NIO框架,使用它可以简单快速 ...
- 解决Spring MVC报No converter found for return value of type:class java.util.ArrayList问题
一.背景 在搭建一套Spring+SpringMVC+Mybatis(SSM)的环境(搭建步骤会在以后博客中给出),结果运行 程序时,适用@ResponseBody注解进行返回List<对象&g ...
- SSH框架集成Activiti Modeler在线设计器页面出现问号及乱码的解决办法
文·原创/朱季谦 工作流是一个针对企业用户.开发人员.系统管理员的轻量级工作流业务管理平台,其核心是使用Java开发的快速.稳定的BPMN2.0流程引擎.在我们日常开发当中,例如oa系统里的请假功能, ...
- 如何自定义JSTL标签与SpringMVC 标签的属性中套JSTL标签报错的解决方法
如何自定义JSTL标签 1.创建一个类,从SimpleTagSupport继承 A) 通过继承可以获得当前JSP页面上的对象,如JspContext I) 实际上可以强转为PageContext II ...
- Spring+SpringMVC+MyBatis+easyUI整合进阶篇(一)设计一套好的RESTful API
写在前面的话 看了一下博客目录,距离上次更新这个系列的博文已经有两个多月,并不是因为不想继续写博客,由于中间这段时间更新了几篇其他系列的文章就暂时停止了,如今已经讲述的差不多,也就继续抽时间更新< ...
随机推荐
- UVa 548 Tree(二叉树最短路径)
You are to determine the value of the leaf node in a given binary tree that is the terminal node of ...
- js文字展示各种滚动效果
js文字展示各种滚动效果:http://www.dowebok.com/demo/188/
- swift - xcode - pod升级版本和降级版本
1. 查看当前版本 pod --version 2.如果安装过pod,更新命令 新版 sudo gem install -n /usr/local/bin cocoapods --pre 旧版 sud ...
- RPC 框架之 Goole protobuf
Goole 的 protobuf 即 Protocol Buffers 是一个很好的RPC 框架,支持 c++ python java 接下来进行官方文档的解读,然后你会对protobuf 会有 ...
- 用java修改文件的编码
1.将本地的文件转换成另外一种编码输出,主要逻辑代码如下: /** * 将本地文件以哪种编码输出 * @param inputfile 输入文件的路径 * @param outfile 输出文件的路径 ...
- 运行./build.sh出现错误:bash: ./build.sh: Permission denied
原因:文件为只读,或者用户没有权限. 修改方法: 运行命令: chmod 777 build.sh
- 安装doxygen(一个自动文档生成工具)+Graphviz图形可视化软件
参考文章: http://www.fmddlmyy.cn/text21.html http://www.cnblogs.com/duguguiyu/archive/2008/06/29/1231852 ...
- Liunx mv(转)
转竹子—博客:http://www.cnblogs.com/peida/archive/2012/10/27/2743022.html mv命令是move的缩写,可以用来移动文件或者将文件改名(mov ...
- RabbitMQ消息队列(一):详细介绍
1. 历史 RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现.AMQP 的出现其实也是应了广大人民群众的需求,虽然在同步消息通讯的世界里有 ...
- Python pip下载安装库 临时用清华镜像命令
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple C:\Users\mu\pip 新建pip.ini [global] index-url ...