springmvc执行原理及自定义mvc框架
springmvc是spring的一部分,也是一个优秀的mvc框架,其执行原理如下:
(1)浏览器提交请求经web容器(比如tomcat)转发到中央调度器dispatcherServlet。
(2)中央调度器调用处理器映射器handerMapping,处理器映射器根据请求的url找到处理该请求对应的处理器hander及相关的拦截器intercepter,将它们封装成一个处理器执行链并返回给中央调度器
(3)中央调度器根据处理器执行链中的处理器找到对应的处理器适配器handerAdaptor
(4)处理适配器调用处理器执行对应的方法并将返回的结果封装为一个对象modelAndView中返回给中央处理器,当然在处理器执行方法前如果方法有拦截器的话会先依次执行拦截器的prehander方法,方法执行结束后会依次执行拦截器的posthander方法。
(5)中央调度器获取到modelAndView对象后,调用视图解析器viewResolver,将modelAndView封装为视图对象
(6)中央调度器获取到视图对象后,进行渲染,生成最后的响应返回给浏览器。
下面就参照springmvc的原理,写一个我们自己的mvc框架,springmvc是依赖于servlet容器的,所以我们也要依赖javax.servlet-api-3.1.0.jar这个包,另外需要在web.xml中配置我们自己定义的中央处理器,以便web容器在启动时初始化mvc上下文。web.xml配置如下
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <servlet> <servlet-name>MySpringMvc</servlet-name> <servlet-class>io.powerx.servlet.MyDispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.properties</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>MySpringMvc</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
简单期间,我们就不去解析xml,使用springmvc.properties来代替,springmvc.properties的配置很简单,就是配置了一下扫描类的路径:basePackage=io.powerx。MyDispatcherServlet代码如下,具体代码里我都有注释
package io.powerx.servlet; import io.powerx.annotation.*; import io.powerx.util.ParseUtil; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class MyDispatcherServlet extends HttpServlet{ private static final long serialVersionUID = -2661065041652722638L; //存自动扫描包下的所有类全名 private List<String> classNames = new ArrayList<>(); //存controller和 service 实例,key:实例名 valu:实例对象 private Map<String,Object> instanceMap = new HashMap<>(); //存 url 和对应的method private Map<String,Method> handerMaps = new HashMap<>(); //存controller 名字和对象 private Map<String,Object> controllerMap = new HashMap<>(); @Override public void init(ServletConfig config) throws ServletException { try { //1、读取配置文件,获取扫描包信息 String mvcConfig = config.getInitParameter("contextConfigLocation").replace("classpath:",""); String packageName = ParseUtil.getBasePackageName(mvcConfig); //2、自动扫描包下所有类,获取所有的类全名 scanBasePackage(packageName); //3、利用反射机制,获取实例,存储在instanceMapzhing reflectBeanInstance(); //4、依赖注入 doIoc(); //5、初始化handerMapping,建立url和method的映射关系 initHanderMapping(); } catch (Exception e) { e.printStackTrace(); } } private void initHanderMapping() throws Exception{ if(instanceMap.isEmpty()){ throw new Exception("实例对象为空"); } for (Map.Entry<String,Object> entry : instanceMap.entrySet()) { Class<?> aclazz = entry.getValue().getClass(); if(aclazz.isAnnotationPresent(MyController.class)){ String classUrl =""; if(aclazz.isAnnotationPresent(MyRequestMapping.class)){ classUrl = aclazz.getAnnotation(MyRequestMapping.class).value(); } Method[] methods = aclazz.getMethods(); for (Method method : methods) { if(method.isAnnotationPresent(MyRequestMapping.class)){ String methodUrl = method.getAnnotation(MyRequestMapping.class).value(); String url = classUrl + methodUrl; handerMaps.put(url,method); controllerMap.put(url,entry.getValue()); } } } } } private void doIoc() throws Exception{ if(instanceMap.isEmpty()) { throw new Exception("无可注入的实例"); } for (Map.Entry<String, Object> entry : instanceMap.entrySet()) { Field[] fields = entry.getValue().getClass().getDeclaredFields(); for (Field field : fields) { if(field.isAnnotationPresent(MyAotuwired.class)){ String insKey = field.getAnnotation(MyAotuwired.class).value(); if("".equals(insKey)){ insKey = ParseUtil.toLowerFirstName(field.getType().getSimpleName()); } field.setAccessible(true); //注入实例 field.set(entry.getValue(),instanceMap.get(insKey)); } } } } private void reflectBeanInstance() throws Exception{ if(classNames.isEmpty()){ return; } for(String className: classNames){ Class<?> aclazz = Class.forName(className); if(aclazz.isAnnotationPresent(MyController.class)){ MyController myController = aclazz.getAnnotation(MyController.class); String beanName = myController.value(); if("".equals(beanName)){ beanName = ParseUtil.toLowerFirstName(aclazz.getSimpleName()); } instanceMap.put(beanName,aclazz.newInstance()); }else if(aclazz.isAnnotationPresent(MyService.class)){ MyService myService = aclazz.getAnnotation(MyService.class); String beanName = myService.value(); if("".equals(beanName)){ beanName = ParseUtil.toLowerFirstName(aclazz.getSimpleName()); } instanceMap.put(beanName,aclazz.newInstance()); } } } private void scanBasePackage(String basePackName) throws Exception{ System.out.println(basePackName); String path = "/" + ParseUtil.tranferQualifiedToPath(basePackName); System.out.println(path); URL url = this.getClass().getClassLoader().getResource(path); System.out.println(url); File dir = new File(url.getFile()); File[] files = dir.listFiles(); for (File file: files) { if(file.isDirectory()){ scanBasePackage(basePackName +"."+file.getName()); }else if(file.isFile()){ classNames.add(basePackName +"." + file.getName().replace(".class","")); System.out.println("扫描到的类有" + basePackName +"." + file.getName().replace(".class","")); } } } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req,resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { doDispatch(req,resp); } catch (Exception e) { e.printStackTrace(); } } /** * 处理请求 * @param req * @param resp */ private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{ resp.setContentType("text/html;charset=UTF-8"); String uri = req.getRequestURI(); System.out.println(uri); String contextPath = req.getContextPath(); System.out.println(contextPath); uri = uri.replace(contextPath,""); Method method = handerMaps.get(uri); PrintWriter pw = resp.getWriter(); if(method == null){ pw.print("404,请求路径不存在"); return; } Parameter[] parameters = method.getParameters(); Object[] paramValues = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { if(ServletRequest.class.isAssignableFrom(parameters[i].getType())){ paramValues[i] = req; }else if (ServletResponse.class.isAssignableFrom(parameters[i].getType())){ paramValues[i] = resp; }else{ String bindingParam = parameters[i].getName(); if(parameters[i].isAnnotationPresent(MyRequestParam.class)){ bindingParam = parameters[i].getAnnotation(MyRequestParam.class).value(); } String paramValue = req.getParameter(bindingParam); paramValues[i] = paramValue; if(Integer.class.isAssignableFrom(parameters[i].getType())){ paramValues[i] = Integer.parseInt(paramValue); }else if(Float.class.isAssignableFrom(parameters[i].getType())){ paramValues[i] = Float.parseFloat(paramValue); }else if(Double.class.isAssignableFrom(parameters[i].getType())){ paramValues[i] = Double.parseDouble(paramValue); } } } Object result = method.invoke(controllerMap.get(uri),paramValues); pw.print(result); } }
MyDispatcherServlet中依赖了我们自定义的注解类,和ParseUtil工具类,代码如下
package io.powerx.annotation; import java.lang.annotation.*; /** * Created by Administrator on 2019/1/2. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @Documented public @interface MyAotuwired { String value() default ""; }
package io.powerx.annotation; import java.lang.annotation.*; /** * Created by Administrator on 2019/1/2. */ @Target(ElementType.TYPE ) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyController { String value() default ""; }
package io.powerx.annotation; import java.lang.annotation.*; /** * Created by Administrator on 2019/1/2. */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE,ElementType.METHOD}) @Documented public @interface MyRequestMapping { String value() default ""; }
package io.powerx.annotation; import java.lang.annotation.*; /** * Created by Administrator on 2019/1/2. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @Documented public @interface MyRequestParam { String value() default ""; }
package io.powerx.annotation; import java.lang.annotation.*; /** * Created by Administrator on 2019/1/2. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented public @interface MyService { String value() default ""; }
package io.powerx.util; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * Created by Administrator on 2019/1/2. */ public class ParseUtil { /** * 获取配置文件的扫描包 * @param contextConfigLocation * @return */ public static String getBasePackageName(String contextConfigLocation){ InputStream in = ParseUtil.class.getClassLoader().getResourceAsStream(contextConfigLocation); Properties pp = new Properties(); try { pp.load(in); } catch (IOException e) { e.printStackTrace(); } return pp.getProperty("basePackage"); } /** * 将限定名转换为路径,如pers.hdh -> pers/hdh * @param qualifiedName * @return */ public static String tranferQualifiedToPath(String qualifiedName)throws Exception{ if(qualifiedName==null){ throw new Exception("空字符串不能转换"); } return qualifiedName.replace(".","/"); } /** * 转换第一个字母为小写 * @param name * @return */ public static String toLowerFirstName(String name){ char[] charArray = name.toCharArray(); charArray[0] += 32; return String.valueOf(charArray); } public static void main(String[] args) throws Exception { System.out.println(tranferQualifiedToPath("io.powerx")); } }
测试的controller和service
package io.powerx.controller; import io.powerx.annotation.MyAotuwired; import io.powerx.annotation.MyController; import io.powerx.annotation.MyRequestMapping; import io.powerx.annotation.MyRequestParam; import io.powerx.service.TestService; /** * Created by Administrator on 2019/1/3. */ @MyController @MyRequestMapping("/test") public class TestController { @MyAotuwired private TestService testService; @MyRequestMapping("/a01") public void test01(){ testService.mvcTest(); } @MyRequestMapping("/a02") public String test02(@MyRequestParam("age") Integer age){ return testService.mvcTest2(age); } }
package io.powerx.service; import io.powerx.annotation.MyService; /** * Created by Administrator on 2019/1/3. */ @MyService public class TestService { public void mvcTest(){ System.out.println("执行了测试方法"); } public String mvcTest2(Integer a){ System.out.println(a); return "我的响应"; } }
编译打包,发布到tomcat中,可以看到控制台打印了我们扫描类和初始化mvc容器的过程
打开浏览器,访问http://localhost:8888/myMvc/test/a02?age=12,可以看到后台收到浏览器的请求并执行,返回了结果