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,可以看到后台收到浏览器的请求并执行,返回了结果

springmvc执行原理及自定义mvc框架的更多相关文章

  1. Java Web自定义MVC框架详解 (转)

    转自:http://blog.csdn.net/jackfrued/article/details/42774459 最近给学生讲Java Web,希望他们能够在学完这部分内容后自己实现一个MVC框架 ...

  2. 自定义MVC框架

    我们在学习自定义MVC框架的时候常常会听到Model1 ,Model2和MVC.那么什么是Model1 什么是Model2什么又是MVC呢? 什么是Model1? Model1就是一种纯jsp开发技术 ...

  3. Struts2 自定义MVC框架

    一.Model1与Model2: Model1:就是一种纯jsp开发技术,将业务逻辑代码和视图渲染代码杂糅在一起. Model2:Model2是在Model1的基础上,将业务逻辑的代码分离开来,单独形 ...

  4. 第一章 自定义MVC框架

    第一章  自定义MVC框架1.1 MVC模式设计    组成:Model:模型,用于数据和业务的处理          View :视图,用于数据的显示          Controller:控制器 ...

  5. 自定义MVC框架之工具类-模型类

    截止目前已经改造了5个类: ubuntu:通过封装验证码类库一步步安装php的gd扩展 自定义MVC框架之工具类-分页类的封装 自定义MVC框架之工具类-文件上传类 自定义MVC框架之工具类-图像处理 ...

  6. JSP学习笔记(6)—— 自定义MVC框架

    仿照SpringMVC,实现一个轻量级MVC框架,知识涉及到了反射机制.注解的使用和一些第三方工具包的使用 思路 主要的总体流程如下图所示 和之前一样,我们定义了一个DispatchServlet,用 ...

  7. springmvc执行原理

    大家是否遇到过被面试官问了这样一句话:"来聊聊springmvc执行原理".是的,springmvc的执行流程是面试的高频点,今天我就来浅谈它! 一.下面通过一个简单的spring ...

  8. 自定义MVC框架之工具类-图像处理类

    截止目前已经改造了4个类: ubuntu:通过封装验证码类库一步步安装php的gd扩展 自定义MVC框架之工具类-分页类的封装 自定义MVC框架之工具类-文件上传类 图像处理类: 1,图片加水印处理( ...

  9. 自定义MVC框架之工具类-文件上传类

    截止目前已经改造了3个类: ubuntu:通过封装验证码类库一步步安装php的gd扩展 自定义MVC框架之工具类-分页类的封装 该文件上传类功能如下: 1,允许定制上传的文件类型,文件mime信息,文 ...

随机推荐

  1. C语言 void 万能类型

    C中 void类型其实是一种万能类型, 也就是说 我们的 void 可以代表任意一种类型,也就是说他的范围要比具体的一种类型要宽广, 例如: int *p = NULL;void *ptr = p; ...

  2. 微信小程序 tp5上传图片

    test.wxml页面 <view class="title">请选择要反馈的问题</view> <view> <picker bindc ...

  3. redis 工具

    搜索关键词:redis 工具 推荐:http://redisdesktop.com/download 点评:使用顺手,顺畅. ★★★★★ 五星好评 备注:redis key 可以采用某种format ...

  4. 从Objective-C到Swift,你必须会的(二)组合options

    用过Options这个东西的人都知道,几个竖线就把这些值都和到一起了.比如: + (NSStringDrawingOptions)combine{ return NSStringDrawingTrun ...

  5. java中时间

    格式转化 SimpleDateFormat package day1211.common; import java.sql.Date;import java.sql.Timestamp;import ...

  6. 转:css实现强制不换行/自动换行/强制换行

    css实现强制不换行/自动换行/强制换行 [日期:2007-08-22] 来源:  作者: [字体:大 中 小] 强制不换行 div{ white-space:nowrap;} 自动换行 div{  ...

  7. D3 data()

    与datum()的区别 假设要绑定的数组为[3,6,9],那么我们希望第一个p元素绑定3,第二个绑定6,第三个绑定9.这种情况就需要使用data()函数,如果使用datum(),则会将数组本身绑定到各 ...

  8. #测试框架推荐# test4j,数据库测试

    # 背景 后端都是操作DB的,这块的自动化测试校验的话,是需要数据库操作的,当然可以直接封装方法来操作数据,那么有没有开源框架支持数据操作,让我们关注写sql语句?或者帮我们做mysql的断言呢? # ...

  9. ubuntu 16.04.1 nginx彻底删除与重新安装

    1.删除nginx,-purge包括配置文件 sudo apt-get --purge remove nginx 2.移除全部不使用的软件包 sudo apt-get autoremove 3.罗列出 ...

  10. .Net Mvc5Filter与权限认证扩展

    WebForm 在做WebForm的时候,如果我们要实现某页面登陆后才能访问,这个非常容易实现 public partial class IndexForm : Page { protected vo ...