手写一个简单到SpirngMVC框架

时间:2022-04-14 08:48:31

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

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

  手写简单的SpringMvc框架。

  主要分为几个步骤:

  1. 扫描包下面的文件。

  2. 根据扫描到到文件,初始化bean工厂。

  3. 根据@Controller @RequestMapping 注解,处理映射关系。

  4.  启动tomcat。

1. 项目基于maven,framework模块是主要对SpringMVC框架,test只是为了方便测试,单独引用framework框架进行测试 。

手写一个简单到SpirngMVC框架

2.接下来讲下主要framework模块。图如下:

手写一个简单到SpirngMVC框架

(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模块。

手写一个简单到SpirngMVC框架

(1) application.yml 配置项目启动的端口号。

(2)Application项目入口。

(3)controller 控制层

(4)service 业务层,在这里注入类bean。

这里和springboot类似,这只是简单Spirng框架的大概思路流程。

在尾巴处再次贴上项目手写简单的SpringMvc框架。