手写一个简单到SpirngMVC框架
spring对于java程序员来说,无疑就是吃饭到筷子。在每次编程工作到时候,我们几乎都离不开它,相信无论过去,还是现在或是未来到一段时间,它仍会扮演着重要到角色。自己对spring有一定的自我见解,所以参考网上的视频和文章,整理出一套简单的SpirngMVC。
项目地址先贴出来,接下来大概讲下流程。
主要分为几个步骤:
1. 扫描包下面的文件。
2. 根据扫描到到文件,初始化bean工厂。
3. 根据@Controller @RequestMapping 注解,处理映射关系。
4. 启动tomcat。
1. 项目基于maven,framework模块是主要对SpringMVC框架,test只是为了方便测试,单独引用framework框架进行测试 。
2.接下来讲下主要framework模块。图如下:
(1)MiniApplication为框架的入口类。其主要有以下四个功能。
package com.chan.starter; import com.chan.beans.BeanFactory;
import com.chan.core.ClassScanner;
import com.chan.web.handler.HandlerManage;
import com.chan.web.server.TomcatServer;
import org.apache.catalina.LifecycleException; import java.io.IOException;
import java.util.List; /**
* @description: 项目的入口
* @author: Chen
* @create: 2019-06-23 14:22
**/
public class MiniApplication { public static void run(Class<?> clz,String[] agrs){
try {
//根据传进来的clz所在的包取扫描包下的数据
List<Class<?>> classList = ClassScanner.scanClasses(clz.getPackage().getName());
try {
//根据扫描到到文件,初始化bean工厂。
BeanFactory.init(classList);
//根据@Controller @RequestMapping 注解,处理映射关系。
HandlerManage.resolveMappingHandleList(classList);
} catch (Exception e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try {
//启动tomcat
TomcatServer tomcatServer = new TomcatServer(agrs);
tomcatServer.startServer();
} catch (LifecycleException e) {
e.printStackTrace();
}
} }
(2)功能一 扫描包下的文件。
package com.chan.core; import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile; /**
* @description:类扫描器
* @author: Chen
* @create: 2019-07-02 22:29
**/
public class ClassScanner { /**
* 扫描包下的文件并返回。
* 如果是jar包,则取jar的文件。如果是文件夹,这递归取
* @param packegeName
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
public static List<Class<?>> scanClasses(String packegeName) throws IOException, ClassNotFoundException { List<Class<?>> classList = new ArrayList<>();
String path = packegeName.replace(".","/");
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> resources = classLoader.getResources(path);
while (resources.hasMoreElements()){ URL resource = resources.nextElement();
//如果资源是jar包,那么遍历jar里面到文件
if (resource.getProtocol().contains("jar")){
JarURLConnection jarURLConnection = (JarURLConnection) resource.openConnection();
String jarFilePath = jarURLConnection.getJarFile().getName();
classList.addAll(getClassesFromJar(path,jarFilePath));
}
//是文件的话,递归取的文件
else if (resource.getProtocol().contains("file")){
File dir = new File(resource.getFile());
for (File file:dir.listFiles()){
if (file.isDirectory()){
classList.addAll(scanClasses(packegeName + "." + file.getName()));
}else {
String className =packegeName +"." +file.getName().replace(".class", "");
classList.add(Class.forName(className));
}
} } }
return classList; } private static List<Class<?>> getClassesFromJar(String path, String jarFilePath) throws IOException, ClassNotFoundException { List<Class<?>> classList = new ArrayList<>();
JarFile jarFile = new JarFile(jarFilePath);
Enumeration<JarEntry> jarEntrys = jarFile.entries();
while (jarEntrys.hasMoreElements()){
JarEntry jarEntry = jarEntrys.nextElement();
String name = jarEntry.getName();
if (name.startsWith(path)&&name.endsWith(".class")){
String classFullName = name.replace("/", ".").substring(0, name.length() - 6);
classList.add(Class.forName(classFullName));
}
} return classList; } }
(3) 根据扫描到到文件,初始化bean工厂。
package com.chan.beans; import com.chan.web.mvc.Controller; import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; /**
* @description: bean 工厂
* @author: Chen
* @create: 2019-07-03 00:03
**/
public class BeanFactory { /**
* 配置bean容器 存放bean
*/
private static Map<Class<?>,Object> beanMap = new ConcurrentHashMap<>(); /**
* 根据类 获取bean
* @param clz
* @return
*/
public static Object getBean(Class<?> clz){return beanMap.get(clz);} /**
* 根据扫描到到类 依次按照规则注入到bean容器中
* @param classList
* @throws Exception
*/
public static void init(List<Class<?>> classList) throws Exception { List<Class<?>> toCreate = new ArrayList<>(classList); /**
* 将满足注入条件循环注入bean容器
*/
while (toCreate.size()!=0){ int remainSize = toCreate.size(); for (int i=0;i<toCreate.size();i++){
if (finishCreate(toCreate.get(i))){
toCreate.remove(i);
}
} if (remainSize==toCreate.size()){
throw new Exception("cycle dependency");
} } } /**
* 判断是否是需要注入到bean
* 如果不是 直接返回true 并且添加到bean容器
* 如果是,但是当时不满足注入条件 返回false 等到下次循环再调用
* 直至满足条件,添加到bean容器中
* @param clz
* @return
* @throws IllegalAccessException
* @throws InstantiationException
*/
private static boolean finishCreate(Class<?> clz) throws IllegalAccessException, InstantiationException { if (!clz.isAnnotationPresent(Controller.class)&&!clz.isAnnotationPresent(Bean.class)){
return true;
} Object bean = clz.newInstance();
for (Field field : clz.getDeclaredFields()){
if (field.isAnnotationPresent(AutoWired.class)){
Class<?> fieldType = field.getType();
Object filedTypeObj = BeanFactory.getBean(fieldType);
if (filedTypeObj==null){
return false;
} field.setAccessible(true);
field.set(bean,filedTypeObj);
}
} beanMap.put(clz,bean);
return true;
} }
(4)根据@Controller @RequestMapping 注解,处理映射关系。
package com.chan.web.handler; import com.chan.web.mvc.Controller;
import com.chan.web.mvc.RequestMapping;
import com.chan.web.mvc.RequestParam;
import com.sun.glass.events.mac.NpapiEvent; import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List; /**
* @description: 映射处理管理中心
* @author: Chen
* @create: 2019-07-03 00:34
**/
public class HandlerManage { /**
* 保存需要映射的列表
*/
public static List<MappingHandle> mappingHandleList = new ArrayList<>(); public static void resolveMappingHandleList(List<Class<?>> classList){ for (Class<?> clz :classList){ if (clz.isAnnotationPresent(Controller.class)){
parseHandlerFromController(clz);
} } } private static void parseHandlerFromController(Class<?> clz) { Method[] methods = clz.getMethods(); for (Method method:methods){ if (!method.isAnnotationPresent(RequestMapping.class)){
continue;
} String uri = method.getDeclaredAnnotation(RequestMapping.class).value();
List<String> paramNameList = new ArrayList<>(); for (Parameter parameter:method.getParameters()){
if (parameter.isAnnotationPresent(RequestParam.class)){
paramNameList.add(parameter.getDeclaredAnnotation(RequestParam.class).value());
}
}
String[] args = paramNameList.toArray(new String[paramNameList.size()]);
MappingHandle mappingHandle = new MappingHandle(uri,method,clz,args);
HandlerManage.mappingHandleList.add(mappingHandle);
} } }
(5)配置DispatcherServlet启动tomcat。
package com.chan.web.server; import com.chan.util.YmlUtil;
import com.chan.web.servlet.DispatcherServlet;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.startup.Tomcat; import javax.servlet.Servlet; /**
* @description:根据 tomcat 的包进行处理
* @author: Chen
* @create: 2019-06-23 14:58
**/
public class TomcatServer { private Tomcat tomcat;
private String[] args; public TomcatServer(String[] args){
this.args = args;
} public void startServer() throws LifecycleException {
tomcat = new Tomcat();
tomcat.setPort(YmlUtil.get("server.port"));
tomcat.start(); Context context = new StandardContext();
context.setPath("");
context.addLifecycleListener(new Tomcat.FixContextListener()); DispatcherServlet servlet = new DispatcherServlet();
Tomcat.addServlet(context, "dispatcherServlet", servlet).setAsyncSupported(true);
context.addServletMappingDecoded("/", "dispatcherServlet");
tomcat.getHost().addChild(context); Thread awaitThread = new Thread("tomcar-await-thread"){
@Override
public void run() {
TomcatServer.this.tomcat.getServer().await();
}
};
awaitThread.setDaemon(false);
awaitThread.start();
} }
package com.chan.web.servlet; import com.chan.web.handler.HandlerManage;
import com.chan.web.handler.MappingHandle; import javax.servlet.*;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException; /**
* @description: serlet适配器,所有的url都会进入到此处,
* 在此处根据@RequestMaping所映射到方法进行操作。
* @author: Chen
* @create: 2019-06-23 15:15
**/
public class DispatcherServlet implements Servlet {
@Override
public void init(ServletConfig config) throws ServletException { } @Override
public ServletConfig getServletConfig() {
return null;
} @Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { System.out.println("HandlerManage.mappingHandleList:"+HandlerManage.mappingHandleList.size()); for (MappingHandle mappingHandle : HandlerManage.mappingHandleList){
try {
if (mappingHandle.handle(req,res)){
return;
}else {
System.out.println("false");
}
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} } @Override
public String getServletInfo() {
return null;
} @Override
public void destroy() { }
}
package com.chan.web.handler; import com.chan.beans.BeanFactory;
import org.apache.catalina.servlet4preview.http.HttpServletRequest; import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; /**
* @description: 映射处理类
* @author: Chen
* @create: 2019-07-03 00:35
**/
public class MappingHandle { String uri; Method method; Class<?> controller; String[] agrs; public MappingHandle(String uri,Method method,Class<?> controller,String[] agrs){
this.uri = uri;
this.method = method;
this.controller = controller;
this.agrs = agrs;
} /**
* 将扫描到到RequestMapping的路径 进行处理
* 通过反射调用 调用该方法
* @param req
* @param res
* @return
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws IOException
*/
public boolean handle(ServletRequest req, ServletResponse res) throws InvocationTargetException, IllegalAccessException, IOException { String requestURI = ((HttpServletRequest)req).getRequestURI(); System.out.println("uri:"+uri);
System.out.println("requestURI:"+requestURI.substring(1));
if (!uri.equals(requestURI.substring(1))){
return false;
} String[] params = new String[agrs.length];
for (int i =0;i< params.length;i++){
params[i] = req.getParameter(agrs[i]);
} Object obj = BeanFactory.getBean(controller);
System.out.println(obj);
if (obj==null){
return false;
} Object ret = method.invoke(obj,params);
System.out.println("ret:"+ret.toString());
res.getWriter().println(ret.toString());
return true;
} }
3.测试framework模块。
(1) application.yml 配置项目启动的端口号。
(2)Application项目入口。
(3)controller 控制层
(4)service 业务层,在这里注入类bean。
这里和springboot类似,这只是简单Spirng框架的大概思路流程。
在尾巴处再次贴上项目手写简单的SpringMvc框架。
手写一个简单到SpirngMVC框架的更多相关文章
- 动手写一个简单的Web框架(Werkzeug路由问题)
动手写一个简单的Web框架(Werkzeug路由问题) 继承上一篇博客,实现了HelloWorld,但是这并不是一个Web框架,只是自己手写的一个程序,别人是无法通过自己定义路由和返回文本,来使用的, ...
- 用Python写一个简单的Web框架
一.概述 二.从demo_app开始 三.WSGI中的application 四.区分URL 五.重构 1.正则匹配URL 2.DRY 3.抽象出框架 六.参考 一.概述 在Python中,WSGI( ...
- 利用SpringBoot+Logback手写一个简单的链路追踪
目录 一.实现原理 二.代码实战 三.测试 最近线上排查问题时候,发现请求太多导致日志错综复杂,没办法把用户在一次或多次请求的日志关联在一起,所以就利用SpringBoot+Logback手写了一个简 ...
- 动手写一个简单的Web框架(模板渲染)
动手写一个简单的Web框架(模板渲染) 在百度上搜索jinja2,显示的大部分内容都是jinja2的渲染语法,这个不是Web框架需要做的事,最终,居然在Werkzeug的官方文档里找到模板渲染的代码. ...
- 动手写一个简单的Web框架(HelloWorld的实现)
动手写一个简单的Web框架(HelloWorld的实现) 关于python的wsgi问题可以看这篇博客 我就不具体阐述了,简单来说,wsgi标准需要我们提供一个可以被调用的python程序,可以实函数 ...
- 手写一个简单的ElasticSearch SQL转换器(一)
一.前言 之前有个需求,是使ElasticSearch支持使用SQL进行简单查询,较新版本的ES已经支持该特性(不过貌似还是实验性质的?) ,而且git上也有elasticsearch-sql 插件, ...
- 手写一个简单的starter组件
spring-boot中有很多第三方包,都封装成starter组件,在maven中引用后,启动springBoot项目时会自动装配到spring ioc容器中. 思考: 为什么我们springBoot ...
- 自己动手写一个简单的MVC框架(第一版)
一.MVC概念回顾 路由(Route).控制器(Controller).行为(Action).模型(Model).视图(View) 用一句简单地话来描述以上关键点: 路由(Route)就相当于一个公司 ...
- 手写一个简单的HashMap
HashMap简介 HashMap是Java中一中非常常用的数据结构,也基本是面试中的"必考题".它实现了基于"K-V"形式的键值对的高效存取.JDK1.7之前 ...
随机推荐
- 06-char,varchar和nvarchar三者的区别
总结: 1.首先先知道一下SQLServer中数据存储的基本单位是页.每页的大小是8KB: 2.char(n),里面的n用于定义字符串长度,以字节为单位: 3.三者的区别 * char: 是定长的,比 ...
- 解决laydate动态设置初始值的问题
//初始化//注意:我这里是时间范围选择,所以定义了range属性.var timeScope = laydate.render({ elem: '#time_scope', range: '~', ...
- BZOJ 2946 [Poi2000]公共串 (二分+Hash/二分+后缀数组/后缀自动机)
求多串的最长公共字串. 法1: 二分长度+hash 传送门 法2: 二分+后缀数组 传送门 法3: 后缀自动机 拿第一个串建自动机,然后用其他串在上面匹配.每次求出SAM上每个节点的最长匹配长度后,再 ...
- EOS dice移到1.8版本的修改汇总
EOS dice移到1.8版本的修改汇总 1. CORE_SYMBOL 被去掉了,需要自己在文件中声明eg: uint64_t string_to_symbol_c(uint8_t precision ...
- Word:英文从“单词”中间断行
造冰箱的大熊猫@cnblogs 2019/2/1 在Word输入一个比较长的英文内容,比如“D:/software/myapp/bulids/FieldTest/Final_0533/PViewEdi ...
- java中如何根据函数查询引用的jar包
选中函数,按Ctrl+Shift+T,就可以弹出对应的jar包地址 例如:
- 21.Python算术运算符及用法详解
算术运算符是处理四则运算的符号,在数字的处理中应用得最多.Python 支持所有的基本算术运算符,如表 1 所示. 表 1 Python常用算术运算符 运算符 说明 实例 结果 + 加 12.45 + ...
- MySQL_(Java)【连接池】使用DBCP简单模拟银行转账事物
dbcp下载 传送门 Commons Pool下载 传送门 Commons log下载 传送门 MySQL_(Java)[事物操作]使用JDBC模拟银行转账向数据库发起修改请求 传送门 MySQL_( ...
- spring boot 下 mapper接口与xml文件映射问题
1. @MapperScan @MapperScan("com.streamax.ums.business.dao") 注解扫描的包路径是否有问题 2. 目录结构 mapper接口 ...
- 20165213 Exp7 网络欺诈防范
Exp7 网络欺诈防范 一. 实践内容 简单应用SET工具建立冒名网站 1.首先使用sudo vi /etc/apache2/ports.conf 进行查看listen的端口号,若不是80改为80. ...