SpringMVC-手写一个SpringMVC框架

前言:

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

1、编写代码的规范

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

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

4、知识的查漏补缺

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

github地址:源码地址

一、了解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;
	}

}

 测试结果:

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值