简单谈谈对SpringMVC的理解,以及手写简单的mvc框架

 

1:什么是MVC?

model-view-controller(即模型-视图-控制层),一种设计思想,个人简单理解其实就是对应的数据库-浏览器-服务器提出的设计模式。

2:为什么要使用SpringMVC?

老师说:将WEB编程,业务功能(controller),界面显示(modelandview),进行拆分。达到解耦,方便维护,方便开发的目的。

整体思路如下:

如果web.xml中一个url配置对应一个servlet,会大大降低性能,造成资源浪费,就像这样:


    HelloServlet
    HelloServlet


    HelloServlet
    /hello

怎么办?还好url-pattern支持通配符,就像这样:


    DispatcherServlet
    mvc.DispatcherServlet


    DispatcherServlet
    *.do

这样一个servlet就可以处理多个请求,就需要解析*.do中的*具体对应的controller。

手写框架思路:一个dispatcherServlet处理所有请求,解析url后调用对应的RequestMapping注解的controller,实现功能。

一:注解类(RequestMapping)

package mvc;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

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

@Retention(RUNTIME)
@Target(METHOD)
/** 
 * 标注在控制器方法上
 * 用途:将请求url地址映射到当前的方法上
 * 英文翻译: Request 请求  Mapping 映射
 */
public @interface RequestMapping {
	//为注解定义参数
	public String value();
}

二、处理器

package mvc;

import java.lang.reflect.Method;

/**
 * Handler 处理器
 * 请求处理器,用处理用户请求
 * 封装控制器对象和控制器上的业务方法 
 */
public class RequestHandler {
	private Object controller;
	private Method method;

	public RequestHandler() {
	}

	public RequestHandler(
			Object controller, Method method) {
		super();
		this.controller = controller;
		this.method = method;
	}

	public Object getController() {
		return controller;
	}

	public Method getMethod() {
		return method;
	}

	@Override
	public String toString() {
		return "RequestHandler [controller=" + controller + ", method=" + method + "]";
	}
	
}

三、HandlerMapping

       用于解析 控制器 类将请求URL和控制器方法的对应关系缓存到Map中,并且提供根据url找到对应控制器方法,并且执行控制方法的功能。

package mvc;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

public class HandlerMapping {
	/**
	 * map 用于映射 请求 path 到对应的请求处理器
	 * 如:将 /emp/list.do 映射到 bizColtroller.list()方法
	 */
	private Map map=new HashMap<>();

	/**
	 * 用于根据 path 执行对应控制器方法
	 * @param path 请求路径
	 * @param request 执行控制器时候的参数
	 * @return 控制器执行以后的返回值
	 */
	public String execute(String path, 
			HttpServletRequest request)
		throws Exception {
		RequestHandler handler = get(path);
		Method method = handler.getMethod();
		Object controller = handler.getController();
		Object val = method.invoke(
				controller, request);
		String value = (String)val;
		return value;
	}
	
	public RequestHandler get(String path) {
		return map.get(path);
	}
	
	/**
	 * 初始化方法:解析控制器中的注解,将注解和注解
	 * 标注的方法添加到 map中
	 * @param className 控制器类名
	 */
	public void init(String className) 
		throws Exception{
		Class cls = Class.forName(className);
		Method[] methods = cls.getDeclaredMethods();
		Object controller = cls.newInstance();
		for(Method method:methods) {
			RequestMapping ann = method
				.getAnnotation(RequestMapping.class);
			if(ann!=null) {
				//找到注解上标注的路径
				String path=ann.value();
				//创建请求处理器对象,封装控制器对象和方法
				RequestHandler handler=
						new RequestHandler(
						controller, method);
				//请求路径和对应的“请求处理器”添加到map
				map.put(path, handler);
				System.out.println(path+":"+handler); 
			}
		}
	}
}






四、增加 ContextConfigListener 加载配置文件

将配置控制器类名登记到XML文件中,这样可以灵活配置控制器的类名,以及控制器数量等

①编写配置文件 resources/beans.xml:



    

②ContextConfigListener

/**
 * 初始化 URL 到 控制器 的映射表 
 */
public class ContextConfigListener implements ServletContextListener {

    /**
     * 读取 beans.xml 配置文件,
     * 解析配置文件中定义的控制器类
     * 将控制器类的url映射到对应的控制器方法
     * @param xml
     * @return 包含url到控制器方法映射关系的
     *         HandlerMapping对象
     */
    public HandlerMapping loadControlers(String xml)
        throws Exception {
        SAXReader reader = new SAXReader();
        InputStream in = ContextConfigListener.class
            .getClassLoader().getResourceAsStream(xml);
        System.out.println(in); 
        Document doc = reader.read(in);
        in.close();
        Element root = doc.getRootElement();//beans
        List list=root.elements("bean"); 
        HandlerMapping mapping = new HandlerMapping();
        for(Element e:list) {
            //在bean元素上获取class属性的值作为类名
            String className=e.attributeValue("class");
            System.out.println(className);
            //利用类名初始化 HandlerMapping 中的map
            mapping.init(className);
        }
        return mapping;
    }

    public void contextInitialized(
            ServletContextEvent e)  {
        try {
            ServletContext ctx = e.getServletContext();
            //HandlerMapping mapping=new HandlerMapping();
            //mapping.init("mvc.BizController"); 
            HandlerMapping mapping=loadControlers(
                    "beans.xml");

            String path = ctx.getContextPath();
            ctx.setAttribute("root", path); 

            ctx.setAttribute("handlerMapping", mapping);
            System.out.println("初始化了handlerMapping"); 
        }catch(Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex);
        }
    }
    public void contextDestroyed(ServletContextEvent arg0)  { 
    }

}

五、DispatcherServlet

package mvc;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 单一前端控制器
 * 负责接收请求,处理与HTTP协议有关逻辑
 * 同时处理 get 和 post请求
 * 为了增加广泛的实用性, 可以处理任何的*.do 请求
 * 将 请求URL设置为 *.do
 */
public class DispatcherServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(
			HttpServletRequest request, 
			HttpServletResponse response) throws ServletException, IOException {
		try {
			//创建 HandlerMapping 对象 
			//找到对应的业务方法,执行业务方法
			//HandlerMapping mapping = new HandlerMapping();
			//mapping.init("mvc.BizController"); 
			
			//从ServletContext获取已经创建并初始化完成的
			//HandlerMapping 对象,这样可以避免每次都初始化
			//可以提高软件的性能
			HandlerMapping mapping=(HandlerMapping)
				getServletContext().getAttribute("handlerMapping");
			
			//获取用户发起的请求
			String pth = request.getServletPath();
			System.out.println(pth); 
			
			//处理编码问题
			request.setCharacterEncoding("UTF-8");
			
			//执行 URL 路径对应的业务方法
			String target=mapping.execute(pth, request);
			//target代表需要显示是目标网页,
			//约定target是以 redirect:为前缀
			//则进行重定向,如果控制器返回的字符串
			//以 redirect: 为开头,则重定向到 
			//redirect: 以后的URL地址
			if(target.startsWith("redirect:")) {
				String path = target.substring(9);
				response.sendRedirect(path); 
			}else {
				String path = "/WEB-INF/jsp/"+target+".jsp";
				request.getRequestDispatcher(path)
					.forward(request, response); 
			}
		}catch(Exception e) {
			e.printStackTrace();
			//抛出一个 ServletException,这个异常是
			//作用是,将异常e抛给Web容器,Web容器会
			//显示 500 错误页面到浏览器
			throw new ServletException(e);
		}
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

}

六、Controller

package mvc;

import javax.servlet.http.HttpServletRequest;

/**
 * 控制器类, 用于封装业务功能, 
 */
public class BizController {
	
	/**
	 * 第一个业务功能,Hello World! 
	 * @return 目标页面名称
	 */
	public String execute(HttpServletRequest request) {
		System.out.println("Hello World!");
		request.setAttribute("msg", "Hello"); 
		return "hello"; 
	}
	/**
	 * 负责处理 /emp/list.do 显示员工列表
	 * 在Web程序运行期间, 将/emp/list.do请求映射到
	 * list() 方法上,也就是请求/emp/list.do时候,
	 * 执行list()方法
	 */
	@RequestMapping("/test/list.do")
	public String list(HttpServletRequest request) {
		return "list";
	}
	/**
	 * 负责处理 /emp/add.do 显示添加员工界面
	 */
	@RequestMapping("/test/add.do")
	public String add(HttpServletRequest request) {
		return "add";
	}
	
	@RequestMapping("/test/hello.do")
	public String hello(HttpServletRequest request) {
		request.setAttribute("msg", "HI"); 
		return "hello";
	}
	// ...
	
	@RequestMapping("/test/test.do")
	public String test(HttpServletRequest request) {
		//测试重定向功能
		String path=request.getContextPath()+
				"/emp/list.do";
		return "redirect:"+path;
	}
	
}

3:总结:

一个DispatcherServlet处理多个请求,请求进来先调用HandlerMapping的初始化方法,将url和对应的处理器requestHandler存到map中,执行map中url对应的处理器。处理器调用controller和method方法。

你可能感兴趣的:(简单谈谈对SpringMVC的理解,以及手写简单的mvc框架)