目录
1. 什么是MVC框架?
1.1 MVC编程模式
1.2 MVC处理流程?
?2. 编写一个简单的MVC框架
2.1 注解
2.2 处理器映射器HandlerMapping
2.3 中央控制器DispatcherServlet
2.4 梳理MVC流程
?3. 源码
1. 什么是MVC框架?
MVC开始是存在于桌面程序中的,M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。
1.1 MVC编程模式
- V??即View视图,是指用户看到并与之交互的界面。比如由html元素组成的网页界面,或者软件的客户端界面。MVC的好处之一在于它能为应用程序处理很多不同的视图。在视图中其实没有真正的处理发生,它只是作为一种输出数据并允许用户操作的方式。
- M??即model模型,是指模型表示业务规则。在MVC的三个部件中,模型拥有最多的处理任务。被模型返回的数据是中立的,模型与数据格式无关,这样一个模型能为多个视图提供数据,由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性。
- C??即controller控制器,是指控制器接受用户的输入并调用模型和视图去完成用户的需求,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。
1.2 MVC处理流程?
【流程图说明】
1.用户发送请求至 前端控制器DispatcherServlet。
2.前端控制器DispatcherServlet收到请求后调用处理器映射器HandlerMapping。
3.处理器映射器HandlerMapping根据请求的Url找到具体的处理器,生成处理器对象Handler及处理器拦截器HandlerIntercepter(如果有则生成)一并返回给前端控制器DispatcherServlet。
4.前端控制器DispatcherServlet通过处理器适配器HandlerAdapter调用处理器Controller。
5.执行处理器(Controller,也叫后端控制器)
6.处理器Controller执行完后返回ModelAnView。
7.处理器映射器HandlerAdapter将处理器Controller执行返回的结果ModelAndView返回给前端控制器DispatcherServlet。
8.前端控制器DispatcherServlet将ModelAnView传给视图解析器ViewResolver。
9.视图解析器ViewResolver解析后返回具体的视图View。
10.前端控制器DispatcherServlet对视图View进行渲染视图(即:将模型数据填充至视图中)
11.前端控制器DispatcherServlet响应用户。
【此处摘自CSDN博主「皓月星辰_w」的原创文章:MVC框架详解(资源整理)_皓月星辰-CSDN博客_mvc框架】
?2. 编写一个简单的MVC框架
观察下面这个类中的方法, 这里指定了url和相应的处理器,那么如何才能让用户在浏览器输入url后,后台正确地调用对应的处理器呢?
2.1 注解
?首先每个方法(处理器)是通过注解与url关联的(value):
-
@Target(ElementType.METHOD)表示是方法注解 -
@Retention(RetentionPolicy.RUNTIME)表示注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;一般如果需要在运行时去动态获取注解信息,那只能用?RUNTIME 注解 -
@Documented表示这个注解应该被 javadoc工具记录. 默认情况下,javadoc是不包括注解的. 但如果声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理, 所以注解类型信息也会被包括在生成的文档中,是一个标记注解,没有成员。
ResponseBody:
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
/**
* 被此注解添加的方法,会被用于处理请求
* 方法返回的内容会被以文本的形式返回到客户端
*/
public @interface ResponseBody {
String value();
}
?ResponseView:
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
/**
* 被此注解添加的方法,会被用于处理请求
* 方法返回的内容会直接重定向
*/
public @interface ResponseView {
String value();
}
?2.2 处理器映射器HandlerMapping
处理器映射器类实际上是一个工具类:建立映射、存储映射、获取处理器
(1) 处理器映射器会在物理上建立url与处理器之间的映射:
private static Map<String,MVCMapping> data = new HashMap<>();
?用一个HashMap来保存String类型的url与“处理器对象”之间的映射关系,这个处理器对象我们用一个静态内部类进行封装:
/**
* 映射对象,每一个对象封装一个方法,用于处理请求
*/
public static class MVCMapping{
private Object object;
private Method method;
private ResponseType type;
}
这是因为我们处理请求的类是通过配置文件进行配置的:application.properties
一个类中间可能包含多个处理请求的方法method,注解的类型表示处理器返回的类型type
(2) 处理器映射器向外提供方法:通过url获取处理器
public static MVCMapping get(String uri)
(3) 处理器能够读取指定流中的处理器类,以方法为单位解析成处理器对象MVCMapping,并生成映射:
public static void load(InputStream is) throws IOException {
Properties ppt = new Properties();
ppt.load(is);
//获取配置文件中描述的一个个类
Collection values = ppt.values();
for (Object cla : values) {
String className = (String) cla;
try {
//加载类
Class c = Class.forName(className);
//创建对象
Object object = c.getConstructor().newInstance();
//获取类的所有方法
Method[] methods = c.getMethods();
for (Method m : methods) {
Annotation[] annotations = m.getAnnotations();
if(annotations!=null) {
for (Annotation a : annotations) {
if (a instanceof ResponseBody){
//说明此方法用于返回字符串给客户端
MVCMapping mapping = new MVCMapping(object,m,ResponseType.TEXT);
Object o = data.put(((ResponseBody) a).value(),mapping);
if(o!=null){//存在的重复的请求地址
throw new RuntimeException("请求地址重复:"+((ResponseBody) a).value());
}
}else if(a instanceof ResponseView){
//说明此方法用于返回界面给客户端
MVCMapping mapping = new MVCMapping(object,m,ResponseType.VIEW);
Object o = data.put(((ResponseView) a).value(),mapping);
if(o!=null){//存在的重复的请求地址
throw new RuntimeException("请求地址重复:"+((ResponseBody) a).value());
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
上面代码中出现的ResponseType是自定义的枚举类型:表示响应的类型
public enum ResponseType {
TEXT,VIEW;
}
2.3 中央控制器DispatcherServlet
(1)中央处理器继承了HttpServlet,在web.xml中我们对其进行配置:
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>pers.xls.mvc.DispatcherServlet</servlet-class>
<init-param>
<param-name>contentConfigLocation</param-name>
<param-value>application.properties</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
(2)通过重写Servlet的init方法获取初始化文件:
在这里调用处理器映射类来读取初始化文件建立映射
@Override
public void init(ServletConfig config) throws ServletException {
String path = config.getInitParameter("contentConfigLocation");
InputStream is = DispatcherServlet.class.getClassLoader().getResourceAsStream(path);
try {
HandlerMapping.load(is);
} catch (IOException e) {
e.printStackTrace();
}
}
(3)通过重写Service方法,根据接收的请求,获取对应的处理器,进行方法的调用:
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.获取用户请求的uri
String uri = req.getRequestURI();
HandlerMapping.MVCMapping mapping = HandlerMapping.get(uri);
if (mapping==null){
resp.sendError(404,"自定义MVC:映射地址不存在:"+uri);
return;
}
Object obj = mapping.getObject();
Method method = mapping.getMethod();
Object result = null;
try {
result = method.invoke(obj,req,resp);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
switch (mapping.getType()){
case TEXT:
resp.getWriter().println((String) result);
break;
case VIEW:
resp.sendRedirect((String) result);
break;
}
}
2.4 梳理MVC流程
现在我们来梳理一下这个简单的MVC的处理流程:
- 通过浏览器我们输入特定的?url?比如:localhost:8080/login.do
- 中央控制器?DispatcherServlet?处理请求,如果是第一次访问,会调用 init 方法,加载src文件夹下的application.properties配置文件,将文件输入流传递给处理器映射器类?HandlerMapping 进行加载
- 处理器映射器类 HandlerMapping 加载文件输入流,读取配置文件中的处理器类,分析出类中的含有特定注解的方法,建立url到“处理器对象MVCMapping”的映射,存储在哈希表中
- 中央控制器?DispatcherServlet?继续调用service方法,调用处理器映射器类 HandlerMapping?的 get 方法获取请求uri对应的“处理器对象”。如果获取不到,说明不存在该访问地址到处理器的映射,访问的内容不存在。从获取到的“处理器对象”中提取出相关的处理类对象、方法、响应类型,利用Java的反射机制,调用该方法:
?????????获取响应值,最后根据响应类型的不同对前台进行响应:
?3. 源码
链接:https://pan.baidu.com/s/1kfZAc-Z6DmK0cp1pcEfX7Q? 提取码:g8zx
|