作为java程序员,项目中使用到的主流框架多多少少和spring有关联,在面试的过程难免会问一些spring springmvc spring boot的东西,比如设计模式的使用、 怎么实现springioc 怎么实现springmvc诸如此类的问题,今天我们就来探寻spring mvc的实现,然后自己实现一个简单的spring mvc
一. 了解spring mvc的基本运行流程
ps: 网上一大堆关于springmvc的详细讲解,在这里就不累赘了
小结:spring mvc的核心是DispatcherServlet,DispatcherServlet继承于HttpServlet,可以说spring mvc是基于Servlet的一个实现,DispatcherServlet负责协调和组织不同组件以完成请求处理并返回响应的工作,实现了MVC模式。
二. 梳理简单SpringMVC的设计思路
1. 初始化容器
1.1 读取配置文件
1.1.1.加载配置文件信息到DispatcherServlet
1.2 根据配置扫描包、初始化容器和组件
1.2.1.根据配置信息递归扫描包
1.2.2.把包下的类实例化 并且扫描注解
1.2.3.根据类的方法和注解,初始化HandlerMapping
2. 处理业务请求
2.1 处理请求业务
2.2.1 首先拿到请求URI
2.2.2 根据URI,在HandlerMapping中查找和URI对应的Handler
2.2.3 根据Handler里面的method中的参数名称和http中的请求参数匹配,填充method参数,反射调用
三. 没时间解释了,快上车
ps :环境基于maven idea tomat(端口8080) servlet
1.搭建一个基本web项目,并导入idea配置servlet 和javassist pom依赖 如下
创建命令: mvn archetype:generate -DgroupId=com.adminkk -DartifactId=adminkk-mvc -DpackageName=com.adminkk -Dversion=1.0
pom依赖
4.0.0 com.adminkk adminkk-mvc 1.0-SNAPSHOT org.apache.maven.plugins maven-compiler-plugin 8 war adminkk-mvc http://maven.apache.org UTF-8 junit junit 3.8.1 test javax.servlet javax.servlet-api 3.0.1 provided asm asm 3.3.1 org.javassist javassist 3.23.1-GA
2.创建mvc的注解 Controller RequestMapping 和统一异常处理类、方法参数工具类ParameterNameUtils
package com.adminkk.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface Controller { public String value() default ""; public String description() default ""; }
package com.adminkk.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface RequestMapping { public String value() default ""; public String method() default ""; public String description() default ""; }
package com.adminkk.exception; public final class MvcException extends RuntimeException{ public MvcException() { super(); } public MvcException(String message) { super(message); } }
package com.adminkk.tools;import javassist.*;import javassist.bytecode.CodeAttribute;import javassist.bytecode.LocalVariableAttribute;import javassist.bytecode.MethodInfo;import java.lang.reflect.Method; public final class ParameterNameUtils { public final static String[] getParameterNamesByJavassist(final Class> clazz, final Method method) { ClassPool pool = ClassPool.getDefault(); try { CtClass ctClass = pool.get(clazz.getName()); CtMethod ctMethod = ctClass.getDeclaredMethod(method.getName()); // 使用javassist的反射方法的参数名 MethodInfo methodInfo = ctMethod.getMethodInfo(); CodeAttribute codeAttribute = methodInfo.getCodeAttribute(); LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute .getAttribute(LocalVariableAttribute.tag); if (attr != null) { String[] rtv = new String[ctMethod.getParameterTypes().length]; int len = ctMethod.getParameterTypes().length; // 非静态的成员函数的第一个参数是this int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1; for (int i = 0; i < len; i++) { rtv[i] = attr.variableName(i + pos); } return rtv; } } catch (NotFoundException e) { System.out.println("获取异常"+ e.getMessage()); } return new String[0]; } }
3.创建 HandlerMapping类 主要是两个方法 doInit初始化 doService处理请求 相关代码如下
package com.adminkk.handler;import com.adminkk.scan.FileScaner;import com.adminkk.scan.Scaner;import com.adminkk.scan.XmlScaner;import com.adminkk.tools.ParameterNameUtils;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.PrintWriter;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map; public final class HandlerMapping { private static final MaphandlerMapping = new HashMap (); private static final List scaners = new ArrayList<>(2); static { scaners.add(new XmlScaner()); scaners.add(new FileScaner()); } public static void scanPackage(String scanUrl) throws RuntimeException, IllegalAccessException, InstantiationException, ClassNotFoundException { for (Scaner scaner : scaners) { scaner.doScane(scanUrl); } } public static void doInit(String scanUrl) throws IllegalAccessException, ClassNotFoundException, InstantiationException { scanPackage(scanUrl); } public static void doService(HttpServletRequest request, HttpServletResponse response) { String requestURI = request.getRequestURI(); System.out.println("请求地址是="+ requestURI); Handler handler = handlerMapping.get(requestURI); if(handler == null){ System.out.println("请求地址是="+ requestURI+" 没有配置改路径"); return; } Method method = handler.getMethod(); Object instance = handler.getInstance(); response.setCharacterEncoding("UTF-8"); //response.setContentType("application/json; charset=utf-8"); PrintWriter writer = null; try { //这里是简单的解析 可以像springmvc那样解析处理 Map parameterMap = request.getParameterMap(); String[] parameters = ParameterNameUtils.getParameterNamesByJavassist(instance.getClass(),method); Object[] parameter = new Object[parameters.length]; if(parameters != null && parameters.length > 0){ for (int i = 0; i < parameters.length; i++) { final String simpleName = parameters[i]; StringBuilder parameterSb = new StringBuilder(); final String[] parameterStr = parameterMap.get(simpleName); if(parameterStr != null){ for (int j = 0; j < parameterStr.length; j++) { parameterSb.append(parameterStr[j]); } } parameter[i] = parameterSb.toString(); } } writer = response.getWriter(); String result = (String) method.invoke(instance,parameter); writer.print(result); } catch (Exception e) { e.printStackTrace(); System.out.println("请求地址是="+ requestURI+" 执行异常"); writer.print("业务执行异常"); }finally { writer.flush(); writer.close(); } } public static Handler addHandlerMapping(String url,Handler handler) { return handlerMapping.put(url,handler); } public static Handler getHandlerMapping(String url) { return handlerMapping.get(url); } }
扫描包
package com.adminkk.scan;public interface Scaner { void doScane(String scanUrl) throws IllegalAccessException, InstantiationException, ClassNotFoundException; }
package com.adminkk.scan;import com.adminkk.exception.MvcException;import com.adminkk.factory.BeanPostProcessor;import com.adminkk.factory.MvcBeanPostProcessor;import com.adminkk.factory.ServiceBeanPostProcessor;import com.adminkk.handler.HandlerMapping;import javassist.ClassClassPath;import javassist.ClassPool;import java.io.File;import java.util.ArrayList;import java.util.List;public final class FileScaner implements Scaner{ public FileScaner() { } public static final ListbeanPostProcessorList = new ArrayList<>(); static { beanPostProcessorList.add(new MvcBeanPostProcessor()); beanPostProcessorList.add(new ServiceBeanPostProcessor()); } @Override public void doScane(String scanUrl) throws IllegalAccessException, InstantiationException, ClassNotFoundException { if(scanUrl == null || scanUrl.length() == 0){ throw new MvcException("容器基础扫描路径为空,请检查参数配置"); } String baseUrl = HandlerMapping.class.getResource("/").getPath(); String codeUrl = scanUrl.replaceAll("\\.", "/"); String path = baseUrl + codeUrl; File file = new File(path); if(file == null || !file.exists()){ throw new MvcException("找不到对应扫描路径,请检查参数配置"); } recursionRedFile(scanUrl,file); } //递归读取文件 private void recursionRedFile(String scanUrl,File file) throws MvcException, ClassNotFoundException, IllegalAccessException, InstantiationException { if(!file.exists()){ return; } //读取java文件 if(file.isFile()){ String beanName = scanUrl.replaceAll(".class",""); Class> forName = Class.forName(beanName); //放到Javassist容器里面 ClassPool pool = ClassPool.getDefault(); ClassClassPath classPath = new ClassClassPath(forName); pool.insertClassPath(classPath); if(forName.isAnnotation() || forName.isEnum() || forName.isInterface() ){ return; } Object newInstance = forName.newInstance(); //前置执行 for (int i = 0; i < beanPostProcessorList.size() ; i++) { BeanPostProcessor beanPostProcessor = beanPostProcessorList.get(i); beanPostProcessor.postProcessBeforeInitialization(newInstance,beanName); } //后置执行 for (int i = beanPostProcessorList.size()-1; i > 0 ; i++) { BeanPostProcessor beanPostProcessor = beanPostProcessorList.get(i); beanPostProcessor.postProcessAfterInitialization(newInstance,beanName); } return; } //文件夹下面的文件都递归处理 if(file.isDirectory()){ File[] files = file.listFiles(); if(files != null && files.length >0){ for (int i = 0; i < files.length; i++) { File targetFile = files[i]; recursionRedFile(scanUrl+"."+targetFile.getName(),targetFile); } } } } }
package com.adminkk.scan;public final class XmlScaner implements Scaner{ public XmlScaner() { } @Override public void doScane(String scanUrl) { //可自行扩展 } }
扫描bean
package com.adminkk.factory;import com.adminkk.exception.MvcException; public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object object, String beanName) throws MvcException; Object postProcessAfterInitialization(Object object, String beanName) throws MvcException; }
package com.adminkk.factory;import com.adminkk.annotation.Controller;import com.adminkk.annotation.RequestMapping;import com.adminkk.exception.MvcException;import com.adminkk.handler.Handler;import com.adminkk.handler.HandlerMapping;import java.lang.reflect.Method; public class MvcBeanPostProcessor implements BeanPostProcessor{ //扫描Controller业务 @Override public Object postProcessBeforeInitialization(Object object, String beanName) throws MvcException { Class> objectClass = object.getClass(); if(objectClass.getAnnotation(Controller.class) != null){ RequestMapping cal***equestMappingAnnotation = objectClass.getAnnotation(RequestMapping.class); StringBuilder urlSb = new StringBuilder(); if(cal***equestMappingAnnotation != null){ urlSb.append(cal***equestMappingAnnotation.value()); } Method[] methods = objectClass.getMethods(); if(methods != null && methods.length > 0 ){ for (int i = 0; i < methods.length; i++) { Method method = methods[i]; RequestMapping methodAnnotation = method.getAnnotation(RequestMapping.class); if(methodAnnotation != null){ String methodValue = methodAnnotation.value(); String url = new StringBuilder().append(urlSb).append(methodValue).toString(); Handler handler = HandlerMapping.getHandlerMapping(url); if(handler == null){ handler = new Handler(); handler.setMethod(method); handler.setInstance(object); HandlerMapping.addHandlerMapping(url,handler); }else { throw new MvcException("请求路径"+ url + "已经存在容器中"); } } } } } return object; } @Override public Object postProcessAfterInitialization(Object object, String beanName) throws MvcException { return null; } }
package com.adminkk.factory;import com.adminkk.exception.MvcException;public class ServiceBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object object, String beanName) throws MvcException { //可自行扩展 return null; } @Override public Object postProcessAfterInitialization(Object object, String beanName) throws MvcException { //可自行扩展 return null; } }
5.创建 DispatcherServlet
package com.adminkk.servlet;import com.adminkk.handler.HandlerMapping;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@WebServlet(name = "DispatcherServlet",loadOnStartup=1,urlPatterns={"/"})public final class DispatcherServlet extends HttpServlet { public static final String BASE_SCAN_URL = "com.adminkk"; //初始化容器 @Override public void init() throws ServletException { doInit(); } //处理业务请求 @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doService(req,resp); } private void doService(HttpServletRequest req, HttpServletResponse resp) throws ServletException { try { HandlerMapping.doService(req,resp); }catch (Exception e){ e.printStackTrace(); throw new ServletException(e.getMessage()); } } private void doInit() throws ServletException { try { HandlerMapping.doInit(this.BASE_SCAN_URL); }catch (Exception e){ e.printStackTrace(); throw new ServletException(e.getMessage()); } } }
好了,目前为止我们就写好了简版的springmvc 下面开始测试
package com.adminkk.controller;import com.adminkk.annotation.Controller;import com.adminkk.annotation.RequestMapping;@Controller@RequestMapping("/mvc")public class MvcController { @RequestMapping("/index") public String index(){ return "adminkk-mvc system is running"; } @RequestMapping("/arg") public String parameter(String argOne, String argTwo){ return "argOne = " + argOne + " argTwo = " + argTwo; } }
访问地址 http://localhost:8080/mvc/index
访问地址: http://localhost:8080/mvc/arg?argOne=argOne&argTwo=argTwo
总结 :整体实现简单的springmvc,设计上还可以扩展更多,难点在于method 获取方法上的参数名称,由于jdk1.8以前是不支持的,需要借用第三方工具 比如 asm javassist黑科技工具包来帮助实现,spring-core使用的是LocalVariableTableParameterNameDiscoverer底层是调用asm,我们这里使用的是javassist。延用这套思路还可以和spring项目结合,写一个 基于spring的springmvc项目