【转】手写一个破产版springmvc框架

原文链接:
https://mp.weixin.qq.com/s/36F_fFbGKkRL20DJgX4ahg

跟着大佬的推送做了一遍,自己加了一点注释。
基本思路是实现一个自己的DispatcherServlet,在容器启动时初始化,进行包扫描,获得controller注解的实例,并将url和各个方法进行对应。

比较简陋,只有controller,requestmapping,requestparam三个注解。
啊其实我挺希望大佬能加个autowired这类的注解的实现的。。。这样才像spring嘛。

直接上代码:
pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.wmx</groupId>
  <artifactId>myspringmvc</artifactId>
  <packaging>war</packaging>
  <version>0.0.1-SNAPSHOT</version>
  <name>myspringmvc Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
	<dependency>
		<groupId>javax.servlet</groupId>
		<artifactId>javax.servlet-api</artifactId>
		<version>3.0.1</version>
		<!-- 只在编译和测试时运行 -->
		<scope>provided</scope>
	</dependency>
  </dependencies>
  <build>
    <finalName>myspringmvc</finalName>
    <plugins>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-compiler-plugin</artifactId>
			<version>3.1</version>
			<configuration>
				<source>1.8</source>
				<target>1.8</target>
			</configuration>
		</plugin>
	</plugins>
  </build>
</project>

application.properties:

scanPackage=com.wmx.core

web.xml:

<?xml version="1.0" encoding="UTF-8"?>

<web-app version="3.0"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <servlet>
    	<servlet-name>MySpringServlet</servlet-name>
    	<servlet-class>com.wmx.servlet.MySpringServlet</servlet-class>
    	<init-param>
    		<param-name>contextConfigLocation</param-name>
    		<param-value>application.properties</param-value>
    	</init-param>
    	<load-on-startup>1</load-on-startup>
    </servlet>
    
    <servlet-mapping>
    	<servlet-name>MySpringServlet</servlet-name>
    	<url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

实现的三个注解:

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

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {

	/**
	 * 默认值为""
	 * @return
	 */
	String value() default "";
}
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE , ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {

	String value() default "";
}
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {

	String value();
}

servlet实现:

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.wmx.annotation.MyController;
import com.wmx.annotation.MyRequestMapping;

public class MySpringServlet extends HttpServlet{

	private Properties properties = new Properties();
	
	private List<String> classNames = new ArrayList<>();
	
	private Map<String ,Object> ioc = new HashMap<>();//bean名称,实例对象的map
	
	private Map<String , Method> handlerMapping = new HashMap<>();
	
	private Map<String , Object> controllerMap = new HashMap<>();
	
	@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) {
			resp.getWriter().write("500 server exception");
			e.printStackTrace();
		}
	}
	
	/**
	 * 通过请求来查询对应的方法,并通过反射来进行调用
	 * @param req
	 * @param resp
	 * @throws Exception
	 */
	private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{
		if(handlerMapping.isEmpty())
			return;
		
		String uri = req.getRequestURI();
		String contextPath = req.getContextPath();
		
		//将项目路径替换为空
		String url = uri.replace(contextPath, "");
		
		//如果没有对应方法,返回404
		if(!this.handlerMapping.containsKey(url)) {
			resp.getWriter().write("404 not found!");
			return;
		}
		
		//获得方法参数
		Method m = this.handlerMapping.get(url);
		Class<?>[] params = m.getParameterTypes();
		
		//获取请求参数
		Map<String , String[]> paramMap = req.getParameterMap();
		
		//保存参数值
		Object[] paramValues = new Object[params.length];
		
		for(int i = 0 ; i < params.length ; i++) {
			String paramName = params[i].getSimpleName();
			
			if (paramName.equals("HttpServletRequest")){  
	              //参数类型已明确,这边强转类型  
	              paramValues[i]=req;
	              continue;  
	          }  
	          if (paramName.equals("HttpServletResponse")){  
	              paramValues[i]=resp;
	              continue;  
	          }
	          if(paramName.equals("String")){
	             for (Entry<String, String[]> param : paramMap.entrySet()) {
	            	//去除中括号和逗号后的空白符
	            	//字符串数组转换为字符串时会加上中括号和逗号
	                String value =Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
	                paramValues[i]=value;
	             }
	          }
	          
	          //其他参数类型可自行扩展
		}
		
		//反射调用方法
		m.invoke(this.controllerMap.get(url), paramValues);
	}

	@Override
	public void init(ServletConfig config) throws ServletException {
		//加载配置文件,从web.xml里读出contextConfigLocation参数的值
		doLoadConfig(config.getInitParameter("contextConfigLocation"));
		
		//扫描类,获得类名,为下一步用反射实例化类做准备
		doScanner(properties.getProperty("scanPackage"));
		
		//实例化bean
		doInstance();
		
		//初始化handlermapping
		initHandlerMapping();
		
	}
	
	
	/**
	 * 加载配置文件
	 * @param location
	 */
	private void doLoadConfig(String location) {
		//从web.xml里获得properties文件的路径,然后通过类加载器从路径里加载配置文件的输入流
		InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(location);
		
		try {
			//加载application.properties里的内容
			properties.load(inputStream);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			if(inputStream != null) {
				try {
					inputStream.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
	
	/**
	 * 根据配置文件里的扫描包属性,扫描该目录下的所有类的全名并放入list中
	 * @param packageName
	 */
	private void doScanner(String packageName) {
		//读取扫描包的路径,把.替换成/,然后通过file去读取
		URL url = this.getClass().getClassLoader().getResource("/"+packageName.replace(".", "/"));
		File dir = new File(url.getFile());
		
		//递归遍历所有java文件信息
		for(File f : dir.listFiles()) {
			if(f.isDirectory()) {
				//递归遍历目录
				doScanner(packageName + "."+f.getName());
			}else {
				String className = packageName + "."+f.getName().replace(".class", "");
				classNames.add(className);
			}
		}
	}
	
	
	/**
	 * 通过反射实例化类
	 */
	private void doInstance() {
		if(classNames.isEmpty()) {
			return;
		}
		
		for(String s : classNames) {
			try {
				Class<?> clazz = Class.forName(s);
				//只把具有@MyController注解的类进行实例化
				if(clazz.isAnnotationPresent(MyController.class)) {
					Object o = clazz.newInstance();
					String classShortName = clazz.getSimpleName();
					String beanName = toLowerFirstWord(classShortName);//beanName默认首字母小写
					//将bean名称和实例放入map中
					ioc.put(beanName, o);
				}else {
					continue;
				}
			}catch(Exception e) {
				e.printStackTrace();
				//继续循环
				continue;
			}
		}
		
	}
	
	/**
	 * 初始化各种controller及其中方法与具体url的对应关系
	 */
	private void initHandlerMapping() {
		if(ioc.isEmpty()) {
			return;
		}
		
		for(Entry<String, Object> e : ioc.entrySet()) {
			Class<? extends Object> clazz = e.getValue().getClass();
			if(!clazz.isAnnotationPresent(MyController.class))
				continue;
			
			//先拼接controller的url
			String baseUrl = "";
			if(clazz.isAnnotationPresent(MyRequestMapping.class)) {
				MyRequestMapping m = clazz.getAnnotation(MyRequestMapping.class);
				baseUrl = m.value();
			}
			
			//遍历controller中方法,获得方法上的url值,和controller的值拼接后放入map中保存
			Method[] methods = clazz.getMethods();
			for(Method m : methods) {
				if(!m.isAnnotationPresent(MyRequestMapping.class))
					continue;
				MyRequestMapping mrm = m.getAnnotation(MyRequestMapping.class);
				String url = baseUrl + mrm.value();
				handlerMapping.put(url, m);
				controllerMap.put(url, e.getValue());
			}
		}
	}
	
	
	/**
	 * 把字符串的首字母小写
	 * @param name
	 * @return
	 */
	private String toLowerFirstWord(String name){
	  char[] charArray = name.toCharArray();
	  charArray[0] += 32;
	  return String.valueOf(charArray);
	}

	
}

测试用controller:

import java.io.IOException;

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

import com.wmx.annotation.MyController;
import com.wmx.annotation.MyRequestMapping;
import com.wmx.annotation.MyRequestParam;

@MyController
@MyRequestMapping("/test")
public class TestController {
 
  @MyRequestMapping("/doTest")
   public void test1(HttpServletRequest request, HttpServletResponse response,
       @MyRequestParam("param") String param){
    System.out.println(param);
     try {
           response.getWriter().write( "doTest method success! param:"+param);
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
  
  
  @MyRequestMapping("/doTest2")
   public void test2(HttpServletRequest request, HttpServletResponse response){
       try {
           response.getWriter().println("doTest2 method success!");
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
}

将项目部署到tomcat上,启动访问

http://localhost:8080/myspringmvc/test/doTest?param=[xxx,qqq]

在这里插入图片描述

如果访问一个不存在的链接:
在这里插入图片描述

这个手写框架里大量使用了反射及注解相关的API,spring真正的实现应该也是大量地用到了这些吧。
还是很感谢大佬地推送的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值