手写一个简单到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框架。