• 【Spring】手写Spring MVC


    Spring MVC原理

    Spring的MVC框架主要由DispatcherServlet、处理器映射、处理器(控制器)、视图解析器、视图组成。

    完整的Spring MVC处理 流程如下:

    SpringMVC接口解释

    DispatcherServlet接口:

    Spring提供的前端控制器,所有的请求都有经过它来统一分发。在DispatcherServlet将请求分发给Spring Controller之前,需要借助于Spring提供的HandlerMapping定位到具体的Controller。

    HandlerMapping接口:

    能够完成客户请求到Controller映射。

    Controller接口:

    需要为并发用户处理上述请求,因此实现Controller接口时,必须保证线程安全并且可重用。

    Controller将处理用户请求,这和Struts Action扮演的角色是一致的。一旦Controller处理完用户请求,则返回ModelAndView对象给DispatcherServlet前端控制器,ModelAndView中包含了模型(Model)和视图(View)。

    从宏观角度考虑,DispatcherServlet是整个Web应用的控制器;从微观考虑,Controller是单个Http请求处理过程中的控制器,而ModelAndView是Http请求过程中返回的模型(Model)和视图(View)。

    ViewResolver接口:

    Spring提供的视图解析器(ViewResolver)在Web应用中查找View对象,从而将相应结果渲染给客户。

    手写Spring MVC

    本次实现没有视图解析内容。主要包括,自动扫描class类、通过解析注解实现bean的实例化、bean之间的依赖注入、通过注解映射路径返回正确的处理方法。

    Spring MVC框架主要依赖于Java的反射机制实现。实现原理与上面描述一致。

    核心Servlet

    工程名MySpringMVC
    代码存放servlet包。
    DispatcherServlet

    package zqq.servlet;
    
    import java.io.File;
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.net.URL;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import javax.servlet.Servlet;
    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 zqq.annotations.EnjoyAuthowired;
    import zqq.annotations.EnjoyController;
    import zqq.annotations.EnjoyRequestMapping;
    import zqq.annotations.EnjoyRequestParam;
    import zqq.annotations.EnjoyService;
    import zqq.controller.ZqqController;
    
    /**
     * Servlet implementation class DispatcherServlet
     */
    public class DispatcherServlet extends HttpServlet
    {
    	private static final long serialVersionUID = 1L;
    
    	// 扫描得到的类名集合
    	List<String> classNames = new ArrayList<String>();
    
    	// 存放所有Spring实例的Map
    	Map<String, Object> beans = new HashMap<String, Object>();
    
    	// 存放所有路径映射
    	Map<String, Object> handlerMap = new HashMap<String, Object>();
    
    	/**
    	 * @see HttpServlet#HttpServlet()
    	 */
    	public DispatcherServlet()
    	{
    	}
    
    	/**
    	 * @see Servlet#init(ServletConfig)
    	 */
    	public void init(ServletConfig config) throws ServletException
    	{
    		// 1、扫描工程有多少class
    		doScanPackage("zqq");
    		// 打印所有class
    		for (String name : classNames)
    		{
    			System.out.println(name);
    		}
    		// 2、实例化
    		doInstance();
    		for (Map.Entry<String, Object> entry : beans.entrySet())
    		{
    			System.out.println(entry.getKey() + ":" + entry.getValue());
    		}
    		// 3、注入
    		doIoC();
    
    		// 4、请求映射
    		buildMapping();
    
    		for (Map.Entry<String, Object> entry : handlerMap.entrySet())
    		{
    			System.out.println(entry.getKey() + ":" + entry.getValue());
    		}
    	}
    
    	/**
    	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
    	 *      response)
    	 */
    	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    	{
    		doPost(request, response);
    	}
    
    	/**
    	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
    	 *      response)
    	 */
    	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    	{
    		// springmvc /zqq/query
    		String uri = request.getRequestURI();
    
    		String context = request.getContextPath(); // springmvc
    
    		String path = uri.replace(context, ""); // /zqq/query
    
    		// 获取映射对应的method
    		Method method = (Method) handlerMap.get(path);
    
    		ZqqController instance = (ZqqController) beans.get("/" + path.split("/")[1]);
    		
    		Object args[] = this.hand(request, response, method);
    		
    		try
    		{
    			method.invoke(instance, args);
    		} catch (IllegalAccessException e)
    		{
    			e.printStackTrace();
    		} catch (IllegalArgumentException e)
    		{
    			e.printStackTrace();
    		} catch (InvocationTargetException e)
    		{
    			e.printStackTrace();
    		}
    		
    	}
    
    	/**
    	 * @author qqz
    	 * @date 2018年7月12日 上午1:02:44 扫描当前路径下有多少个class类
    	 * @param string
    	 */
    	private void doScanPackage(String basePackage)
    	{
    //		URL url = this.getClass().getClassLoader().getResource(basePackage.replaceAll("\.", "/"));
    		String filepath = this.getClass().getClassLoader().getResource(basePackage.replaceAll("\.", "/")).getFile();
    		try
    		{
    			filepath= java.net.URLDecoder.decode(filepath,"utf-8");
    		} catch (UnsupportedEncodingException e)
    		{
    			e.printStackTrace();
    		}  
    //		String fileStr = url.getFile();
    		
    		// 目录对象
    		File file = new File(filepath);
    		String[] filesStr = file.list();
    
    		// 递归处理路径basepackage下的类文件
    		for (String path : filesStr)
    		{
    			File filePath = new File(filepath + path);
    			if (filePath.isDirectory())
    			{
    				doScanPackage(basePackage + "." + path);
    			} else
    			{
    				// 得到class 全类名路径 zqq.controller.ZqqController
    				classNames.add(basePackage + "." + filePath.getName());
    			}
    		}
    	}
    
    	/**
    	 * @author qqz
    	 * @date 2018年7月12日 上午1:11:04 TODO
    	 */
    	private void doInstance()
    	{
    		if (classNames.size() <= 0)
    		{
    			System.out.println("scan classes failed!");
    		}
    
    		for (String className : classNames)
    		{
    			// 去掉.class后缀
    			String cn = className.replace(".class", "");
    
    			try
    			{
    				Class<?> clazz = Class.forName(cn);
    				// 处理带有EnjoyController注解的类
    				if (clazz.isAnnotationPresent(EnjoyController.class))
    				{
    					// 实例化对象
    					Object instance = clazz.newInstance();
    					EnjoyRequestMapping reqMapping = clazz.getAnnotation(EnjoyRequestMapping.class);
    					String key = reqMapping.value();
    					beans.put(key, instance);
    				} else if (clazz.isAnnotationPresent(EnjoyService.class))
    				{
    					// 实例化对象
    					Object instance = clazz.newInstance();
    					EnjoyService service = clazz.getAnnotation(EnjoyService.class);
    					String key = service.value();
    					beans.put(key, instance);
    				} else
    				{
    					continue;
    				}
    			} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e)
    			{
    				e.printStackTrace();
    			}
    		}
    	}
    
    	/**
    	 * @author qqz 属性注入
    	 * @date 2018年7月12日 上午1:21:10 TODO
    	 */
    	private void doIoC()
    	{
    		if (beans.entrySet().size() <= 0)
    		{
    			System.out.println("instance bean failed.");
    			return;
    		}
    
    		for (Map.Entry<String, Object> entry : beans.entrySet())
    		{
    			Object instance = entry.getValue();
    			Class<?> clazz = instance.getClass();
    
    			if (clazz.isAnnotationPresent(EnjoyController.class))
    			{
    				// 获取类中所有属性
    				Field[] fields = clazz.getDeclaredFields();
    				for (Field field : fields)
    				{
    					// 获取声明注入的属性
    					if (field.isAnnotationPresent(EnjoyAuthowired.class))
    					{
    						EnjoyAuthowired authowired = field.getAnnotation(EnjoyAuthowired.class);
    						// 获取注解EnjoyAutowired中命名的值
    						String value = authowired.value();
    						// 放开权限设置属性值
    						field.setAccessible(true);
    						try
    						{
    							field.set(instance, beans.get(value));
    						} catch (IllegalArgumentException e)
    						{
    							e.printStackTrace();
    						} catch (IllegalAccessException e)
    						{
    							e.printStackTrace();
    						}
    					} else
    					{
    						continue;
    					}
    				}
    			}
    
    		}
    	}
    
    	/**
    	 * @author qqz
    	 * @date 2018年7月12日 上午1:32:25 TODO
    	 */
    	private void buildMapping()
    	{
    		if (beans.entrySet().size() <= 0)
    		{
    			System.out.println("instance bean failed.");
    			return;
    		}
    
    		for (Map.Entry<String, Object> entry : beans.entrySet())
    		{
    			Object instance = entry.getValue();
    			Class<?> clazz = instance.getClass();
    			// 映射是在Controller层
    			if (clazz.isAnnotationPresent(EnjoyController.class))
    			{
    				// 获取类映射
    				EnjoyRequestMapping requestMapping = clazz.getAnnotation(EnjoyRequestMapping.class);
    				String classPath = requestMapping.value();
    
    				// 获取方法上的映射
    				Method[] methods = clazz.getMethods();
    
    				for (Method method : methods)
    				{
    					if (method.isAnnotationPresent(EnjoyRequestMapping.class))
    					{
    						EnjoyRequestMapping requestMapping1 = method.getAnnotation(EnjoyRequestMapping.class);
    
    						String methodPath = requestMapping1.value();
    
    						// 构建方法路径与方法的映射
    						handlerMap.put(classPath + methodPath, method);
    
    					} else
    					{
    						continue;
    					}
    				}
    
    			}
    		}
    	}
    
    	/**
    	 * @author qqz
    	 * @date 2018年7月12日 上午1:59:48
    	 * 方法参数注解解析
    	 * @param request
    	 * @param response
    	 * @param method
    	 * @return
    	 */
    	private static Object[] hand(HttpServletRequest request, HttpServletResponse response, Method method)
    	{
    		// 拿到当前执行的方法有哪些参数
    		Class<?>[] paramClazzs = method.getParameterTypes();
    
    		// 根据参数的个数,new 一个参数的数组, 将方法里所有参数赋值到args来
    		Object[] args = new Object[paramClazzs.length];
    
    		int arg_i = 0;
    		int index = 0;
    		for (Class<?> paramClazz : paramClazzs)
    		{
    			if (ServletRequest.class.isAssignableFrom(paramClazz))
    			{
    				args[arg_i++] = request;
    			}
    
    			if (ServletResponse.class.isAssignableFrom(paramClazz))
    			{
    				args[arg_i++] = response;
    			}
    
    			// 从0-3判断有没有RequestParam注解,很明显paramClazz为0和1时,不是,当为2和3时为@RequestParam,需要
    			// 解析[@zqq.annotation.EnjoyRequestParam(value=name)]
    			Annotation[] paramAns = method.getParameterAnnotations()[index];
    			if (paramAns.length > 0)
    			{
    				for (Annotation paramAn : paramAns)
    				{
    					if (EnjoyRequestParam.class.isAssignableFrom(paramAn.getClass()))
    					{
    						EnjoyRequestParam rp = (EnjoyRequestParam)paramAn;
    						//找到注解里的name和age
    						args[arg_i++] = request.getParameter(rp.value());
    					}
    				}
    			}
    			index ++;
    		}
    		return args;
    	}
    }
    
    

    注解定义

    代码存放的包annotations中。包括如下几个

    EnjoyAuthowired.java
    EnjoyController.java
    EnjoyRequestMapping.java
    EnjoyRequestParam.java
    EnjoyService.java

    属性注解EnjoyAuthowired.java

    package zqq.annotations;
    
    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;
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface EnjoyAuthowired
    {
    	String value() default "";
    }
    
    

    Controller注解EnjoyController.java

    package zqq.annotations;
    
    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;
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface EnjoyController
    {
    	String value() default "";
    }
    
    

    映射注解EnjoyRequestMapping.java

    package zqq.annotations;
    
    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;
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD,ElementType.TYPE})
    public @interface EnjoyRequestMapping
    {
    	String value() default "";
    }
    
    

    参数注解EnjoyRequestParam.java

    package zqq.annotations;
    
    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;
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.PARAMETER)
    public @interface EnjoyRequestParam
    {
    	String value() default "";
    }
    
    

    Service注解EnjoyService.java

    
    package zqq.annotations;
    
    
    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;
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface EnjoyService
    {
    	String value() default "";
    }
    
    

    控制层

    代码存放controller包。

    /**
     * ZqqController.java
     */
    package zqq.controller;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import zqq.annotations.EnjoyAuthowired;
    import zqq.annotations.EnjoyController;
    import zqq.annotations.EnjoyRequestMapping;
    import zqq.annotations.EnjoyRequestParam;
    import zqq.service.ZqqService;
    
    @EnjoyController
    @EnjoyRequestMapping("/zqq")
    public class ZqqController
    {
    	@EnjoyAuthowired("ZqqServiceImpl")
    	private ZqqService zqqService;
    
    	@EnjoyRequestMapping("/query")
    	public void query(HttpServletRequest req, HttpServletResponse resp, @EnjoyRequestParam("name") String name,
    			@EnjoyRequestParam("age") String age)
    	{
    		PrintWriter pw;
    		try
    		{
    			pw = resp.getWriter();
    			String result = zqqService.query(name, age);
    			pw.write(result);
    		} catch (IOException e)
    		{
    			e.printStackTrace();
    		}
    
    	}
    }
    
    

    Service层

    代码存放service包

    /**
     * ZqqService.java
     */
    package zqq.service;
    public interface ZqqService
    {
    	String query(String name,String age);
    }
    
    

    Service实现类存放service/impl

    /**
     * ZqqServiceImpl.java
     */
    package zqq.service.impl;
    
    import zqq.annotations.EnjoyService;
    import zqq.service.ZqqService;
    
    @EnjoyService("ZqqServiceImpl")
    public class ZqqServiceImpl implements ZqqService
    {
    
    	/*
    	 * (non-Javadoc)
    	 * 
    	 * @see zqq.service.ZqqService#query(java.lang.String, java.lang.String)
    	 */
    	@Override
    	public String query(String name, String age)
    	{
    		return "{name:" + name + ",age:" + age + "}";
    	}
    
    }
    
    

    web层

    src/main/webapp/WEB-INF/web.xml

    <!DOCTYPE web-app PUBLIC
     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
      <display-name>Archetype Created Web Application</display-name>
      <servlet>
      	<servlet-name>DispatcherServlet</servlet-name>
      	<display-name>DispatcherServlet</display-name>
      	<description></description>
      	<servlet-class>zqq.servlet.DispatcherServlet</servlet-class>
      	<load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
      	<servlet-name>DispatcherServlet</servlet-name>
      	<url-pattern>/*</url-pattern>
      </servlet-mapping>
    </web-app>
    
    

    使用

    部署后访问localhost:8080/MySpringMVC/zqq/query?name=zqq&age=18
    可以在页面上看到请求中的name和age。

    项目码云路径

    MySpringMVC

    参考资料:
    SpringMVC框架介绍

  • 相关阅读:
    在前后端分离的SpringBoot项目中集成Shiro权限框架
    过账销售订单装箱单报错:用库存单位数量表示的实际剩余数量不能为零
    外部系统调用AX服务
    InventSum Closed and ClosedQty
    固定资产日志过账报错
    AX批处理相关
    AX2012打开报表报错
    有折扣的销售订单过账
    AX版本查询
    AX2012 SSRS Report 相关
  • 原文地址:https://www.cnblogs.com/z00377750/p/9301508.html
Copyright © 2020-2023  润新知