SpringMVC框架

了解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框架优点:

  1. 多个视图共享一个模型,大大提高代码的可重用性

  2. 三个模块相互独立,改变其中一个不会影响其他俩,所以依据这种设计模式能构建良好的松耦合性的组件。

  3. 控制器提高了应用程序的灵活性和可控制性。控制器可以用来连接不同的模型和视图去完成用户的需求,这样控制器可以为构造应用程序提高强有力的手段。

这就和我们Java很符合,代码可重用性、维护独立的组件,哪里bug找哪里!效率调高太多了。。

这三层是紧密联系在一起的,但又是互相独立的,每一层内部的变化不影响其他层。每一层都对外提供接口(Interface),供上面一层调用。这样一来,软件就可以实现模块化,修改外观或者变更数据都不用修改其他层,大大方便了维护和升级。

有优点就肯定有缺点!接下来我们了解一下缺点吧。。

MVC框架缺点:

  1. 增加了系统结构和实现的复杂性。
    对于简单页面,严格遵循mvc,使模型、视图与控制器分离,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率。

  2. 视图与控制器过于紧密的连接。
    视图与控制器是相互分离,但确实联系紧密的部件,视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了他们的独立重用。

  3. 视图对模型数据的低效率访问。
    依据模型操作接口的不同,视图可能需要多次调用才能获得足够的显示数据。对未变化数据的不必要的频繁访问,也将损害操作性能。

  4. 目前,一些高级的界面工具或构造器不支持mvc。

缺点也有,优点也有,,但是博主个人认为对于开发存在大量用户界面,并且业务逻辑复杂的大型应用程序,MVC将会使你的软件在健壮性、代码重用和结构方面上一个新的台阶。尽管在最初构建MVC框架时会花费一定的工作量,但从长远角度看,它会大大提高后期软件开发的效率。总的来说MVC框架会让你的项目更加好维护,而不能局限在开发的时间更长上!!!

接下来就是本文的重点了!!

2、SpringMVC框架构成

2.1、MVC框架流程图

在这里插入图片描述
执行过程中的组件的含义:

  1. 用户发送请求至前端控制器DispatcherServlet。
  2. DispatcherServlet收到请求调用HandlerMapping处理器映射器
  3. 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
  4. DispatcherServlet通过HandlerAdapter处理器适配器调用处理器。
  5. 执行处理器(Controller,也叫后端控制器)。
  6. Controller执行完成返回ModelAndView。
  7. HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
  8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
  9. ViewReslover解析后返回具体View。
  10. DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
  11. 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框架了哦!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值