SpringMVC-手写一个SpringMVC框架

SpringMVC-手写一个SpringMVC框架

前言:

spring框架是目前互联网应用开发最流行的框架之一,作为一个后台的开发人员应当不能错过向大佬学习学习的机会,所以阅读spring的源码还是非常有价值的。总的来说阅读源码基本可以获得以下好处

1、编写代码的规范

2、学习如何编写健壮性代码

3、框架的设计、设计模式、思想等

4、知识的查漏补缺

当然,肯定不仅仅是上面的优点,不管怎么样,有时间多看源码、多思考、对于程序员来说还是好处大大滴,好了废话不多说了,直接看今天分享的内容,手写一个简单的springmvc框架

github地址:源码地址

一、了解SpringMVC运行的流程

SpringMVC-手写一个SpringMVC框架

 

(1)用户发送请求至前端控制器DispatcherServlet

(2)DispatcherServlet收到请求调用HandlerMapping处理器映射器。

(3)处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。

(4)DispatcherServlet通过HandlerAdapter处理器适配器调用处理器

(5)执行处理器(Controller,也叫后端控制器)。

(6)Controller执行完成返回ModelAndView

(7)HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet

(8)DispatcherServlet将ModelAndView传给ViewReslover视图解析器

(9)ViewReslover解析后返回具体View

(10)DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。

(11)DispatcherServlet响应用户。

二、手写SpringMVC思路

(1)编写注解

如@controller、@service、@requestParam、@requestMapping、@autowired

这些注解作用在类、属性、参数上

(2)实例化bean

这一步没有在springmvc的图中展现出来,这是因为由spring ioc支持

(3)依赖注入

依赖注入主要是针对@autowired,这也是spring ioc支持

(4)uri映射到对应的instance和method

这里主要就是上面图中的部分逻辑,也是关键

(5)参数处理

主要处理被@requestparam修饰的参数

(6)在web.xml中配置拦截的servlet

主要配置自己手写的dispatcherServlet拦截

三、关键部分代码

1、web.xml配置拦截器

<servlet>
  	<servlet-name>DispatcherServlet</servlet-name>
  	<display-name>DispatcherServlet</display-name>
  	<description></description>
  	<servlet-class>com.taolong.mymvc.servlet.DispatcherServlet</servlet-class>
  	<load-on-startup>0</load-on-startup>
  </servlet>
  <servlet-mapping>
  	<servlet-name>DispatcherServlet</servlet-name>
  	<url-pattern>/</url-pattern>
  </servlet-mapping>

2、 编写注释类,简单看一个@MyController注释类,其他的都差不多

package com.taolong.mymvc.annotatioin;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({java.lang.annotation.ElementType.TYPE})//作用范围
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {
	String value() default "";
}

 

3、编写核心DispatcherServlet类,基本上所有的逻辑都在这个类里面,分开解析

(1)扫描所有的类名,就是为实例化类做准备

private void scanPackage(String basePackage) {
	URL url = this.getClass().getClassLoader().getResource("/"+replaceTo(basePackage));
	System.out.println("url = "+url);
	String parentPath = url.getFile();
	File file = new File(parentPath);
	String[] fileList = file.list();
	if (null == fileList || fileList.length == 0) return;
	for (String fileName : fileList) {
		File filePath = new File(parentPath+fileName);
		//如果是文件夹,则继续递归扫描
		if (filePath.isDirectory()) {
			scanPackage(basePackage+"."+fileName);
		}else {//将class文件加入到集合中,方便下次实例化
			classNames.add(basePackage+"."+filePath.getName());
		}
	}
 }

//路径
private String replaceTo(String basePackage) {
	return basePackage.replaceAll("\\.", "/");
}

 代码还是比较简单,主要就是扫描指定基础包下面的所有的类名称

(2)实例化类,其实就是根据(1)步中类名通过反射实例化

/**
* 将扫描的类,通过反射实例化,然后加入到map中
 */
private void instanceScanedBeans() {
	if (classNames.isEmpty()) {
		System.out.println("未扫描到任何的类!");
		return;
	}
	for (String className : classNames) {
		//去掉.class,方便反射处理
		String cn = className.replace(".class", "");
		try {
			Class<?> clazz = Class.forName(cn);
			//判断类是否被mycontroller注释过,目前做简单处理,只实例化被mycontroller和myservice注释过的类
			if (clazz.isAnnotationPresent(MyController.class)) {
				MyController annotation = clazz.getAnnotation(MyController.class);
				Object instance = clazz.newInstance();//实例化
				String instanceKey = annotation.value();//获取annotation的value
				if ("".equals(instanceKey)) {//如果注释中没有值,则将类的名字当作key(简单处理)
					instanceKey = toLowerFirstWord(clazz.getSimpleName());
				}
				beans.put(instanceKey, instance);
			}else if(clazz.isAnnotationPresent(MyService.class)) {//判断是否被myservice注释
				MyService annotation = clazz.getAnnotation(MyService.class);
				Object instance = clazz.newInstance();
				String instanceKey = annotation.value();
				if ("".equals(instanceKey)) {
					//这里做了简单处理,直接获取serviceimpl的接口名当作key
					instanceKey = toLowerFirstWord(clazz.getInterfaces()[0].getSimpleName());
				}
				beans.put(instanceKey, instance);
			}else {
				continue;
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
	}
}

 这里只实例化了被@mycontroller和@myservice注释的bean

(3)依赖注入,主要是解决controller中通过@autowired注入的service属性

/**
* 依赖注入,如controller中使用autowired注入service
*/
private void iocDI() {
	if (beans.isEmpty() || beans.entrySet().isEmpty()) {
		System.out.println("没有实例化的类!");
		return;
	}
	for (Map.Entry<String, Object> entry : beans.entrySet()) {
		Object instance = entry.getValue();
		Class<?> clazz = instance.getClass();
		//通过反射获取类的属性,为简化代码,这里只在controller中注入,service中无注入
		if (clazz.isAnnotationPresent(MyController.class)) {
			Field[] fields = clazz.getDeclaredFields();//获取所有属性
			for (Field field : fields) {
				//其实就是判断属性是否被autowired修饰
				if (field.isAnnotationPresent(MyAutoWired.class)) {
					MyAutoWired annotation = field.getAnnotation(MyAutoWired.class);
					String value = annotation.value();
					field.setAccessible(true);//增加权限
					if ("".equals(value)) {
						//这里也是简单处理,因为是面向接口编程的,所以为了找到前面实例化的service,应该拿到它的接口名
						//value=com.taolong.service.UserService
						value = field.getType().getName();
						//value=userService
						value = toLowerFirstWord(value.substring(value.lastIndexOf(".")+1));
					}
					try {
						//注入到属性中
						field.set(instance, beans.get(value));
					} catch (IllegalArgumentException e) {
						e.printStackTrace();
					} catch (IllegalAccessException e) {
						e.printStackTrace();
					}
				}else {
					continue;
				}
			}
		}else {
			continue;
		}
	}
}

 (4)uri和controller以及method绑定

/**
* 将requestMapping中的url和对应的方法进行绑定
*/
private void handlerMapping() {
	if (beans.isEmpty() || beans.entrySet().isEmpty()) {
		System.out.println("没有实例化类!");
		return;
	}
	for (Map.Entry<String, Object> entry : beans.entrySet()) {
		Object instance = entry.getValue();
		Class<?> clazz = instance.getClass();
		//首先读取类中的requestmapping的url
		if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
			MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);
			String classPathUrl = annotation.value();
			//保存url关联的controller,方便dopost时找到对应的类
			//简单处理,默认是controller类中存在mapping值,可优化
			clazzHandlerMap.put(classPathUrl.replace("/", ""), instance);
			Method[] methods = clazz.getMethods();
			if (methods == null || methods.length == 0) {
				System.out.println("没有对应的方法!");
				return;
			}
			for (Method method : methods) {
				if (method.isAnnotationPresent(MyRequestMapping.class)) {
					MyRequestMapping annotationMethod = method.getAnnotation(MyRequestMapping.class);
					String methodPathUrl = annotationMethod.value();
					//key为对应的url,value为映射的方法
					handlerMap.put(classPathUrl+methodPathUrl, method);
				}else {
					continue;
				}
			}
		}else {
			continue;
		}
	}
}

(5)处理请求参数,主要看被@requestParam注释的参数,这里是使用策略模式分别处理request参数response参数@requestparam修饰的参数

//参数解析,并获取注解的值
public Object argumentResolver(HttpServletRequest request,
            HttpServletResponse response, Class<?> type, int paramIndex,
            Method method) {
        
    Annotation[][] an = method.getParameterAnnotations();
        
    Annotation[] paramAns = an[paramIndex];
        
    for (Annotation paramAn : paramAns) {
       if (MyRequestParam.class.isAssignableFrom(paramAn.getClass())) {
         MyRequestParam rp = (MyRequestParam)paramAn;
                
            String value = rp.value();
            return request.getParameter(value);
        }
    }
        
    return null;
}

(6)最后看下dopost方法处理请求

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
			throws ServletException, IOException {
	String uri = req.getRequestURI();
	String context = req.getContextPath();
	System.out.println("uri="+uri+" context="+context);
	//去掉uri前面的context
	String path = uri.replace(context, "");
	if (!handlerMap.containsKey(path)) {
		resp.getWriter().write("404 NOT FOUNT");
		return;
	}
	//根据uri找到需要调用的方法
	Method method = (Method)handlerMap.get(path);
	//简单处理,默认controller类的requestmapping存在且有值
	String clzzUrl = path.split("/")[1];
	Object instance = clazzHandlerMap.get(clzzUrl);
	//处理器
	HandlerAdapterService ha = (HandlerAdapterService) beans.get(HANDLERADAPTER);
	//使用策略模式处理method中的参数
	Object[] args = ha.hand(req, resp, method, beans);
	try {
		method.invoke(instance, args);		
	} catch (IllegalAccessException e) {
		e.printStackTrace();
	} catch (IllegalArgumentException e) {
		e.printStackTrace();
	} catch (InvocationTargetException e) {
		e.printStackTrace();
	}
		
}

/**
* 把字符串的首字母小写
* @param name
* @return
* eg.UserService->userService
*/
private String toLowerFirstWord(String name){
    if (null == name || "".equals(name)) return null;
	char[] charArray = name.toCharArray();
	charArray[0] += 32;
	return String.valueOf(charArray);
}

(7)简单的贴下业务代码吧

UserController.java

package com.taolong.mymvc.controller;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.taolong.mymvc.annotatioin.MyAutoWired;
import com.taolong.mymvc.annotatioin.MyController;
import com.taolong.mymvc.annotatioin.MyRequestMapping;
import com.taolong.mymvc.annotatioin.MyRequestParam;
import com.taolong.mymvc.service.UserService;

@MyController("UserController")
@MyRequestMapping("/user")
public class UserController {

	@MyAutoWired
	private UserService userServiceabc;
	
	@MyRequestMapping("/saveUser")
	public void saveUser(HttpServletRequest request,HttpServletResponse response,
					@MyRequestParam("userName")String userName,
					@MyRequestParam("passwd") String password) {
		try {
			PrintWriter printWriter = response.getWriter();
			String result = userServiceabc.saveUser(userName, password);
			printWriter.write(result);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

UserServiceImpl.java

package com.taolong.mymvc.service.impl;

import com.taolong.mymvc.annotatioin.MyService;
import com.taolong.mymvc.service.UserService;

@MyService
public class UserServiceImpl implements UserService {

	@Override
	public String saveUser(String name, String passwd) {
		System.out.println("name="+name+" passwd="+passwd);
		return "save user successful..."+"name="+name+" passwd="+passwd;
	}

}

 测试结果:

SpringMVC-手写一个SpringMVC框架

 

 

 

 

 

 

 

 

 

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/111228.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • win10下使用anaconda安装pytorch和torchvision

    win10下使用anaconda安装pytorch和torchvision一、系统配置以及软件版本1、操作系统:win102、显卡:NVIDIAGeForceGTX1070Ti3、cuda_9.0.176_win10链接:https://pan.baidu.com/s/1f9MowahErE9u60LO1MOcPw提取码:5k2c4、cudnn-9.0-windows10-x64-v7.1链接:https://pan.baidu.com/s/1MGe…

  • 治疗治愈埃博拉病毒(非科幻)

    治疗治愈埃博拉病毒(非科幻)

  • A记录、CNAME和URL转发区别[通俗易懂]

    A记录、CNAME和URL转发区别[通俗易懂]我们在做域名解析时,尤其是很多虚拟主机,大都会使用到CNAME解析,独立主机、VPS则用A记录较多,而URL转发则会在更换域名时用到,从设置效果来看,都是“解析”到一个“其它”URL地址,而实际上它们之间还是有些区别的,尤其是URL转发和其它两个之间区别很大的,首先A记录和CNAME属于标准的DNS记录,而URL转发则实际上只是个简单的重定向。另外,我们还常遇到别名ALIAS这个词,ALIAS对解

  • 详解HTML超链接

    详解HTML超链接超链接是互联网提供的最令人兴奋的创新之一,它们从一开始就一直是互联网的一个特性,使互联网成为互联的网络。HTML超链接也是各个网站网页之间实现相互连接的一个手段之一,被广泛应用在各大网站。HTML超

  • pascal voc数据集下载_目标检测分类

    pascal voc数据集下载_目标检测分类一、简介PASCALVOC挑战赛主要有ObjectClassification、ObjectDetection、ObjectSegmentation、HumanLayout、ActionClassification这几类子任务PASCAL主页与排行榜PASCALVOC2007挑战赛主页、PASCALVOC2012挑战赛主页、PASC…

  • PHPmyadmin安装教程+遇到问题「建议收藏」

    PHPmyadmin安装教程+遇到问题「建议收藏」安装PHPMyAdmin遇到些问题,我的PHP版本是5.6的。开始安装了5.0的phpMyAdmin,报错了。,之后将phpMyAdmin减低到4.4.12版本,成功安装。安装过程遇到个问题【注意】localhost/phpmyadmin,这个访问的时候,localhost后面要加:端口号,不然无法访问一、下载PHPmyadmin打开PHP中文网中的PHPmyadmi…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号