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方法。