spring对于java程序员来说,无疑就是吃饭到筷子。在每次编程工作到时候,我们几乎都离不开它,相信无论过去,还是现在或是未来到一段时间,它仍会扮演着重要到角色。自己对spring有一定的自我见解,所以参考网上的视频和文章,整理出一套简单的SpirngMVC。

  项目地址先贴出来,接下来大概讲下流程。

  手写简单的SpringMvc框架。

  主要分为几个步骤:

  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框架的更多相关文章

  1. 动手写一个简单的Web框架(Werkzeug路由问题)

    动手写一个简单的Web框架(Werkzeug路由问题) 继承上一篇博客,实现了HelloWorld,但是这并不是一个Web框架,只是自己手写的一个程序,别人是无法通过自己定义路由和返回文本,来使用的, ...

  2. 用Python写一个简单的Web框架

    一.概述 二.从demo_app开始 三.WSGI中的application 四.区分URL 五.重构 1.正则匹配URL 2.DRY 3.抽象出框架 六.参考 一.概述 在Python中,WSGI( ...

  3. 利用SpringBoot+Logback手写一个简单的链路追踪

    目录 一.实现原理 二.代码实战 三.测试 最近线上排查问题时候,发现请求太多导致日志错综复杂,没办法把用户在一次或多次请求的日志关联在一起,所以就利用SpringBoot+Logback手写了一个简 ...

  4. 动手写一个简单的Web框架(模板渲染)

    动手写一个简单的Web框架(模板渲染) 在百度上搜索jinja2,显示的大部分内容都是jinja2的渲染语法,这个不是Web框架需要做的事,最终,居然在Werkzeug的官方文档里找到模板渲染的代码. ...

  5. 动手写一个简单的Web框架(HelloWorld的实现)

    动手写一个简单的Web框架(HelloWorld的实现) 关于python的wsgi问题可以看这篇博客 我就不具体阐述了,简单来说,wsgi标准需要我们提供一个可以被调用的python程序,可以实函数 ...

  6. 手写一个简单的ElasticSearch SQL转换器(一)

    一.前言 之前有个需求,是使ElasticSearch支持使用SQL进行简单查询,较新版本的ES已经支持该特性(不过貌似还是实验性质的?) ,而且git上也有elasticsearch-sql 插件, ...

  7. 手写一个简单的starter组件

    spring-boot中有很多第三方包,都封装成starter组件,在maven中引用后,启动springBoot项目时会自动装配到spring ioc容器中. 思考: 为什么我们springBoot ...

  8. 自己动手写一个简单的MVC框架(第一版)

    一.MVC概念回顾 路由(Route).控制器(Controller).行为(Action).模型(Model).视图(View) 用一句简单地话来描述以上关键点: 路由(Route)就相当于一个公司 ...

  9. 手写一个简单的HashMap

    HashMap简介 HashMap是Java中一中非常常用的数据结构,也基本是面试中的"必考题".它实现了基于"K-V"形式的键值对的高效存取.JDK1.7之前 ...

随机推荐

  1. 12-SSMS图形化工具中不允许保存修改的解决办法

    1.报出的警告 2.解决办法 工具-->选项-->设计器--->表设计和数据库设计器-->阻止保存要求重新创建表的更改  的勾去掉就OK 了

  2. c++使用初始化列表来初始化字段

    #include<iostream> using namespace std; class Student1 { private: int _a; int _b; public: void ...

  3. SQL CASE Syntax

    CASE case_value WHEN when_value THEN statement_list [WHEN when_value THEN statement_list] ... [ELSE ...

  4. Nowcoder的JS计时器题分析

    题目描述 实现一个打点计时器,要求1.从 start 到 end(包含 start 和 end),每隔 100 毫秒 console.log 一个数字,每次数字增幅为 12.返回的对象中需要包含一个 ...

  5. Java多态性详解——父类引用子类对象

    来源:http://blog.csdn.net/hikvision_java_gyh/article/details/8957456 面向对象编程有三个特征,即封装.继承和多态. 封装隐藏了类的内部实 ...

  6. Confluence 6 上传一个附加文件的新版本

    有下面 2 种方法来上传一个附加文件的新版本,你可以: 上传与已有附件具有相同文件名的版本. 使用 上传一个新版本(Upload a new version)   按钮来进行上传(这个在文件预览界面中 ...

  7. 「HEOI2016/TJOI2016」 排序

    题目链接 戳我 \(Solution\) 这道题在线的做法不会,所以这里就只讲离线的做法. 因为直接排序的话复杂度显然不对.但是如果数列为\(01\)串的话就可以让复杂度变成对的了 那么\(01\)串 ...

  8. C++入门经典-例7.1-对象之访问类成员

    1:建立一个类CPerson. (1)在person.h文件中代码: class CPerson { public: //数据成员 int m_iIndex; ]; short m_shAge; do ...

  9. (一)C语言的四大数据类型

  10. 安装ubuntu是所需的引导

    title Install Ubuntu root (hd0,0) kernel (hd0,0)/vmlinuz boot=casper iso-scan/filename=/ubuntu-16.04 ...