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)
blank

相关推荐

  • 人工势场法matlab讲解_【机器人路径规划】人工势场法

    人工势场法matlab讲解_【机器人路径规划】人工势场法阅读本文需要的基础知识为:理解机器人的构型空间。建议阅读:机器人运动规划中的Cspace怎样理解?为什么不直接在笛卡尔坐标系下运算呢?本文的实现程序与使用说明见我的学习工具箱:小明工坊:【个人开源】机器人运动规划学习工具箱使用说明基本原理1.概述我们打两个比方来说明人工势场法的作用机理。首先,我们把构型空间比作一个电势场平面,机器人(的当前构型)比作空间中一点。如果让机器人的起点和障碍物带正…

  • 【mybatis系列】自定义实现拦截器插件Interceptor

    【mybatis系列】自定义实现拦截器插件Interceptor目录类型规则介绍intercept(Invocationinvocation)plugin(Objecttarget)setProperties(Propertiesproperties)实战首先熟悉一下Mybatis的执行过程,如下图:拦截器应用场景:类型先说明Mybatis中可以被拦截的类型具体有以下四种:1.Executor:拦截执行器的方法。2.ParameterHandler:拦截参数的处理。3.ResultHandler:拦截结果集的处理。4.StatementHandl

    2022年10月25日
  • oracle client 环境不满足最低要求_oracle11g安装环境不满足最低要求

    oracle client 环境不满足最低要求_oracle11g安装环境不满足最低要求为什么需要oinstall,dba两个组一个是控制软件安装,补丁安装等的;另一个是控制数据库创建,数据库管理等的。你可以将两个权限都授权给dba组,只创建dba一个组就可以了。角色细化而已,另外oracle还要求redo日志、控制文件分不同盘放,数据安全要求而已2、usr/sbin/useradd-m-goinstall-Gdbaoracle什么意思??创建了一个新的UNIX/LINUX用户,-m表示如果已经有这个用户不报错,-g是组,-G是其他组,最后是用户名m表示为用

  • java检测tomcat宕机_Tomcat意外宕机分析

    java检测tomcat宕机_Tomcat意外宕机分析之前在网上看过一篇文章,是讲Tomcat进程意外退出的,我看完感觉好奇,自己也测试了下,果然是有这种问题,所以自己也借此总结一下。先简单说下测试过程,先创建一个web服务启动test.sh,内容如下:#!/bin/bashcd/usr/software/tomcat/apache-tomcat-7.0.81/bin/./catalina.shstarttail-f/usr/software/…

  • elasticsearch集群搭建_Linux如何关闭kafka集群

    elasticsearch集群搭建_Linux如何关闭kafka集群安装包自己自行准备,或者用我百度网盘的安装包,ElasticSearchLogstashKibanaFileBeat:**链接:**https://pan.baidu.com/s/1_Iv2R8pmYaHkoaOk_m8OuQ提取码:5ezt复制这段内容:后打开百度网盘手机App,操作更方便哦创建普通用户ES不能使用root用户来启动,必须使用普通用户来安装启动。这里我们创建一个普通用户以及定义一些常规目录用于存放我们的数据文件以及安装包等。创建一个es专门的用户(必须)使

    2022年10月13日
  • SqlDataSource WEB控件:当DeleteCommandType=”storedProcedure”时「建议收藏」

    SqlDataSource WEB控件:当DeleteCommandType=”storedProcedure”时「建议收藏」设计Users表:UserID,NameArticles表:ArticleID,UserID,ArticleTitle生成视图:SELECT     dbo.Articles.ArticleID, dbo.Articles.ArticleTitle, dbo.Users.NameFROM         dbo.Articles INNER JOIN                      db…

发表回复

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

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