了解MVC框架的底层原理?
1、什么是MVC?
MVC是三个单词的首字母缩写,它们是Model(模型)、View(视图)和Controller(控制)。
1.1、Model(模型)
模型(Model):就是业务流程/状态的处理以及业务规则的制定。业务流程的处理过程对其它层来说是黑箱操作,模型接受视图请求的数据,并返回最终的处理结果。业务模型的设计可以说是MVC最主要的核心。目前流行的EJB模型就是一个典型的应用例子,它从应用技术实现的角度对模型做了进一步的划分,以便充分利用现有的组件,但它不能作为应用设计模型的框架。它仅仅告诉你按这种模型设计就可以利用某些技术组件,从而减少了技术上的困难。对一个开发者来说,就可以专注于业务模型的设计。MVC设计模式告诉我们,把应用的模型按一定的规则抽取出来,抽取的层次很重要,这也是判断开发人员是否优秀的设计依据。抽象与具体不能隔得太远,也不能太近。MVC并没有提供模型的设计方法,而只告诉你应该组织管理这些模型,以便于模型的重构和提高重用性。我们可以用对象编程来做比喻,MVC定义了一个顶级类,告诉它的子类你只能做这些,但没法限制你能做这些。这点对编程的开发人员非常重要。
1.2、View(视图)
视图(View)代表用户交互界面,对于Web应用来说,可以概括为HTML界面,但有可能为XHTML、XML和Applet。随着应用的复杂性和规模性,界面的处理也变得具有挑战性。一个应用可能有很多不同的视图,MVC设计模式对于视图的处理仅限于视图上数据的采集和处理,以及用户的请求,而不包括在视图上的业务流程的处理。业务流程的处理交予模型(Model)处理。比如一个订单的视图只接受来自模型的数据并显示给用户,以及将用户界面的输入数据和请求传递给控制和模型。
1.3、Controller(控制)
控制(Controller)可以理解为从用户接收请求, 将模型与视图匹配在一起,共同完成用户的请求。划分控制层的作用也很明显,它清楚地告诉你,它就是一个分发器,选择什么样的模型,选择什么样的视图,可以完成什么样的用户请求。控制层并不做任何的数据处理。例如,用户点击一个连接,控制层接受请求后, 并不处理业务信息,它只把用户的信息传递给模型,告诉模型做什么,选择符合要求的视图返回给用户。因此,一个模型可能对应多个视图,一个视图可能对应多个模型。
通过使用mvc框架采用了封装(分层)的思想,来降低耦合度,从而使我们的系统更灵活,扩展性更好。
那么小伙伴们肯定会问有什么优点呢?
MVC框架优点:
-
多个视图共享一个模型,大大提高代码的可重用性。
-
三个模块相互独立,改变其中一个不会影响其他俩,所以依据这种设计模式能构建良好的松耦合性的组件。
-
控制器提高了应用程序的灵活性和可控制性。控制器可以用来连接不同的模型和视图去完成用户的需求,这样控制器可以为构造应用程序提高强有力的手段。
这就和我们Java很符合,代码可重用性、维护独立的组件,哪里bug找哪里!效率调高太多了。。
这三层是紧密联系在一起的,但又是互相独立的,每一层内部的变化不影响其他层。每一层都对外提供接口(Interface),供上面一层调用。这样一来,软件就可以实现模块化,修改外观或者变更数据都不用修改其他层,大大方便了维护和升级。
有优点就肯定有缺点!接下来我们了解一下缺点吧。。
MVC框架缺点:
-
增加了系统结构和实现的复杂性。
对于简单页面,严格遵循mvc,使模型、视图与控制器分离,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率。 -
视图与控制器过于紧密的连接。
视图与控制器是相互分离,但确实联系紧密的部件,视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了他们的独立重用。 -
视图对模型数据的低效率访问。
依据模型操作接口的不同,视图可能需要多次调用才能获得足够的显示数据。对未变化数据的不必要的频繁访问,也将损害操作性能。 -
目前,一些高级的界面工具或构造器不支持mvc。
缺点也有,优点也有,,但是博主个人认为对于开发存在大量用户界面,并且业务逻辑复杂的大型应用程序,MVC将会使你的软件在健壮性、代码重用和结构方面上一个新的台阶。尽管在最初构建MVC框架时会花费一定的工作量,但从长远角度看,它会大大提高后期软件开发的效率。总的来说MVC框架会让你的项目更加好维护,而不能局限在开发的时间更长上!!!
接下来就是本文的重点了!!
2、SpringMVC框架构成
2.1、MVC框架流程图
执行过程中的组件的含义:
- 用户发送请求至前端控制器DispatcherServlet。
- DispatcherServlet收到请求调用HandlerMapping处理器映射器
- 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
- DispatcherServlet通过HandlerAdapter处理器适配器调用处理器。
- 执行处理器(Controller,也叫后端控制器)。
- Controller执行完成返回ModelAndView。
- HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
- ViewReslover解析后返回具体View。
- DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
- DispatcherServlet响应用户。
这里可以看出DispatherServlet一个中心组件,调配各组件之间的执行。
上面列举的那么多组件,我相信有很多小伙伴和我一样不太了解他们的具体功能,
我把主要的组件的功能放这里了!!
2.2、SpringMVC组件
HandlerMapping
是用来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行处理,这就是HandlerMapping需要做的事。Handler存放了路径和方法。
HandlerAdapter
从名字上看,它就是一个适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。
小结:Handler是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter是使用工具干活的人。
ViewResolver
ViewResolver用来将String类型的视图名和Locale解析为View类型的视图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是ViewResolver主要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。
2.3、手写简单的MVC框架
先看一下大致的配置文件及各组件!!
先创建一个springboot项目
1、pox.xml
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>smart_mvc</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!--读取XML文件 -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!--servlet的一些api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.7.2</version>
<scope>test</scope>
</dependency>
<!--JSTL标签 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
</project>
web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>cn.wen.smart_mvc.web.servlet.DispatcherServlet</servlet-class>
<!--指定前端控制器的配置文件-->
<init-param>
<param-name>configLocation</param-name>
<param-value>smart_mvc.xml</param-value>
</init-param>
<!--启动服务器自动将这个配置文件加载-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<!--url表示什么样的url可以提交到servlet类中-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
配置完了就可以先创建一个User实体类
2、创建User实体类
package cn.wen.smart_mvc.pojo;
public class User {
private String username;
private String password;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
4、实现DispatcherServlet控制器
package cn.wen.smart_mvc.web.servlet;
import cn.wen.smart_mvc.web.common.HandleMapping;
import cn.wen.smart_mvc.web.common.Handler;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* 表示定义一个前端控制器,请求处理和加载配置映射
*/
public class DispatcherServlet extends HttpServlet {
private HandleMapping handleMapping = new HandleMapping();
/**
* 调用时机在处理请求之前,将编写的smart_mvc.xml配置文件导入
* @throws ServletException
*/
@Override
public void init() throws ServletException {
// bean标签的class属性指向的类加载到这里(反射完成对象的创建)
//1、加载读取smart_mvc.xml的配置文件
//通过类加载源文件获取流
/**
* 采用在web.xml类配置初始化配置
*/
//InputStream is = getClass().getClassLoader().getResourceAsStream("smart_mvc.xml");
//获取web.xml中 的configLocation的属性就可以在web.xml中修改,不用固定配置文件名
String configLocation = getServletConfig().getInitParameter("configLocation");
InputStream is = getClass().getClassLoader().getResourceAsStream(configLocation);
//2、加载其中的数据(树状的结构),dom4j相关类和接口完成
SAXReader saxReader = new SAXReader();
try {
//返回的是一个document 对象,是一个树状的结构
Document document = saxReader.read(is);
//获取xml文件的根路劲
Element rootElement = document.getRootElement();
//通过根路劲获取全部的子节点
List<Element> elements = rootElement.elements();//多个子节点
//这个集合存储dom4j加载出来的bean对象
List<Object> beans = new ArrayList<Object>();
//读取元素的目的是将class实现指向的路径的类加载成对象
for (Element element : elements) {
//获取该元素的对象的属性,element相对于一个bean
String className = element.attributeValue("class");
System.out.println("className"+className);
//根据这个类的路径来创建这个类的对象通过forName来完成
Object bean = Class.forName(className).newInstance();//比如HelloController对象
//bean就是当前的对象,进行保存
beans.add(bean);
}
System.out.println("bean" + beans);
//将beans传递个HandlerMapping来查找
handleMapping.process(beans);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO
//获取url截取路径来拼接前缀后缀
request.setCharacterEncoding("UTF-8");
//url请求路径,提前准备
// String uri = request.getRequestURL();// http://localhost:8080/smart_mvc//login
String uri = request.getRequestURI();// /smart_mvc/login
//需要截取路径
System.out.println("uri"+uri);
//获取当前项目的项目名
String contextPath = request.getContextPath();
System.out.println(contextPath);
String path = uri.substring(contextPath.length());
System.out.println("path"+path);
//获取处理映射器的映射对象(通过url来获取)
Handler handler = handleMapping.getHandler(path);
//通过handler来获取object和method
Object bean = handler.getObject();
Method method = handler.getMethod();
Object resultValue = null;
System.out.println(bean);
System.out.println(method);
try {
//通过反射来执行方法
// resultValue = method.invoke(bean);
//根据是否有参数来选择性使用方法
Class<?>[] parameterTypes = method.getParameterTypes();
if(parameterTypes.length>0){
//含参数、创建临时数组(Object)
Object[] params = new Object[parameterTypes.length];
//注意:目前只考虑请求2个参数:request 和 response
for (int i = 0; i<parameterTypes.length;i++){
if(parameterTypes[i]==HttpServletRequest.class){
params[i] =request;
}
if(parameterTypes[i]==HttpServletResponse.class){
params[i] =response;
}
}
//调用invoke方法
resultValue = method.invoke(bean, params);
}else {
//不含参数
resultValue = method.invoke(bean);
}
System.out.println("resultValue"+resultValue);//方法的login redirect:/toSuccess
String viewName = resultValue.toString();
System.out.println(viewName);
if(viewName.startsWith("redirect:")){//重定向
//将视图的名称拼接成完整的路径进行重定向:/smart_mvc/login
String redirectPath = contextPath +"/"+viewName.substring("redirect:".length());
response.sendRedirect(redirectPath);//重定向操作
}else {//转发
//转发路径:父路径/login/+".jsp"
String jspPath = viewName + ".jsp";
//完成转发操作
request.getRequestDispatcher(jspPath).forward(request,response);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
5、创建配置文件
该配置文件可以将Controller注入bean容器中
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<!--表示注册一个处理器: Controller的一个类,class属性,表示一个具体的路径-->
<bean class="cn.wen.smart_mvc.controller.HelloController"></bean>
<bean class="cn.wen.smart_mvc.controller.LoginController"></bean>
</beans>
先将那些注解先写好等下直接用:
@MyController,@MyService,@MyRequesMapping,@RequestParam,@MyAutowired
//@MyAutowired注解代码:
package cn.edu.whu.MVCByHand.annotation;
import java.lang.annotation.*;
/**
* Description:自定义注解@MyAutoWired实现自动注入
* Author:CXJ
* Date: 2018-06-16 20:33
* Remark:
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAutowired {
String value() default "";
}
//@MyController注解代码:
package cn.edu.whu.MVCByHand.annotation;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {
String value() default "";
}
//@MyRequestMapping注解代码:
package cn.edu.whu.MVCByHand.annotation;
import java.lang.annotation.*;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {
String value();
}
//@MyRequestParam注解代码
package cn.edu.whu.MVCByHand.annotation;
import java.lang.annotation.*;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {
String value();
}
//@MyService注解代码
package cn.edu.whu.MVCByHand.annotation;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyService {
String value() default "";
}
上面的注解我还没有放入我这个简单的框架中
我主要用了下面几个
package cn.wen.smart_mvc.web.annotation;
import java.lang.annotation.*;
/**
* 映射规则:即表示那个请求和哪个对象的处理方法进行映射
*/
//添加元注解进行进一步描述
@Documented
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)//注解一直存在
public @interface RequestMapping {
//value 请求的url地址
String value() default "";
}
6、创建Handler处理器类
package cn.wen.smart_mvc.web.common;
import java.lang.reflect.Method;
/**
* 处理器:用来将请求的方法映射:类(那个类),方法(那个类的方法)
*/
public class Handler {
// 类处理器的对象(HelloController类)
private Object object;
// 请求处理的方法(HelloController.hello)
private Method method;
//构造方法
public Handler(){
}
public Handler(Object object, Method method) {
this.object = object;
this.method = method;
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
}
7、Controller类
package cn.wen.smart_mvc.controller;
import cn.wen.smart_mvc.pojo.User;
import cn.wen.smart_mvc.web.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
public class LoginController {
//登录页面
@RequestMapping("/toLogin")
public String toLogin(){
System.out.println("toLogin");
return "login";
}
//登录我这里目前还没实现参数的注解
@RequestMapping("/login")
public String login(HttpServletRequest request){
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println(username+password);
if(username.equals("tom") && password.equals("123456")){
return "redirect:toSuccess";
}else {
request.setAttribute("msg","用户名或者密码错误!");
}
//System.out.println(user.toString());
System.out.println("LoginController类的login()方法");
// return "success";直接进入success页面
return "login";//重定向success页面
}
//跳转成功页面
@RequestMapping("/toSuccess")
public String toSuccess(){
System.out.println("跳转成功页面");
return "success";
}
}
8、HandleMapping
package cn.wen.smart_mvc.web.common;
import cn.wen.smart_mvc.web.annotation.RequestMapping;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 处理器映射,url地址和唯一的一个Handle进行映射(Controller映射)
*/
public class HandleMapping {
//定义一个Map集合用于存储映射规则
private Map<String ,Handler> mappings = new HashMap<String, Handler>();
/**
* getHandler()用户获取映射中所有的结果内容,接受一个值为url
*
*/
public Handler getHandler(String path){
return mappings.get(path);
}
/**
* 将请求映射全部存到集合中
* 1.url地址
* 2.Controller(bean对象),将这个值以参数的形式传递
*/
public void process(List beans){
//需要一个循环结构来拿到url的list
for (Object bean : beans){
//将这个bean的请求方法获取对应的注解获取到
// TODO
//反射为对应对象的实例对象
Class cls = bean.getClass();
//获取该对象的全部方法
Method[] methods = cls.getDeclaredMethods();
//每个方法对应的url路径
for (Method method : methods) {
//判断当前的method对象所指向的方法是否被@RequestMapping修饰
//value ="hello" 获取注解的参数值,就是url
//这里指定注解类型
RequestMapping requestMapping = method.getDeclaredAnnotation(RequestMapping.class);
//请求url地址
String path = requestMapping.value();
//获取Handler对象
Handler handler = new Handler(bean, method);
//保存Handler对象
mappings.put(path,handler);
}
System.out.println("mappings"+mappings);
}
}
}
这样我们就简单手写了一个简易的SpringMVC框架了哦!!