百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

美团总监知乎3000赞的SpringMVC整体框架理解宝典,助你成为大佬

ccwgpt 2024-09-13 16:18 100 浏览 0 评论

今天被公司派到别的公司谈项目,刚去就先被面试了一波(原来是把我外包到别的公司做项目了 -。-),面试时候问了我一个问题,很简单,就是问我java开发web项目为什么要用spring,springmvc?

好吧,当时我人直接懵逼了,什么鬼问我这个!!不就是可以省去很多功夫让我们踏踏实实写业务代码嘛?

当时就随便回答了一些,回到公司仔细想想,发现还有挺多可以想,可以讲的。我想起了之前项目的控制层从struts2转到springmvc,我就在想为什么我们现在做javaweb开发,要用struts2或者springMVC这样的框架,而不是使用servlet加jsp这样的技术呢?特别是现在我们web的前端页面都是使用freemaker这样的模板语言进行开发,抛弃了jsp,这样的选择又会给我们javaweb开发带来什么样的好处,延着这个问题的思路,我又发现新的疑问,为什么现在很多java企业级开发都会去选择spring框架,spring框架给我们开发的应用带来了什么?这么一想我人更加糊涂了,很难找带让自己完全信服的答案。

最终我发现,这些我认为“用”的很熟悉技术,其实还有很多让我陌生不解的地方,这些陌生和不解的地方也正是我是否能更高层次使用它们的关键。

说白点Spring MVC 相当于一辆手动挡汽车,Spring Boot 相当于把汽车变成自动挡,然后还加装了无钥匙进入、自动启停等功能,让你开车更省心。但是车的主体功能不变,你还是要用到 Spring MVC。

SpringMVC入门程序

(1)web.xml

<web-app>
  <servlet>
  <!-- 加载前端控制器 -->
  <servlet-name>springmvc</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <!-- 
       加载配置文件
       默认加载规范:
       * 文件命名:servlet-name-servlet.xml====springmvc-servlet.xml
       * 路径规范:必须在WEB-INF目录下面
       修改加载路径:
   -->
   <init-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>classpath:springmvc.xml</param-value>   
   </init-param>
  </servlet>
  
  <servlet-mapping>
  <servlet-name>springmvc</servlet-name>
  <url-pattern>*.do</url-pattern>
  </servlet-mapping>
</web-app>

(2)springmvc.xml

<beans>
    <!-- 配置映射处理器:根据bean(自定义Controller)的name属性的url去寻找handler;springmvc默认的映射处理器是
    BeanNameUrlHandlerMapping
     -->
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
    
    
    <!-- 配置处理器适配器来执行Controlelr ,springmvc默认的是
    SimpleControllerHandlerAdapter
    -->
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
    
    <!-- 配置自定义Controller -->
    <bean id="myController" name="/hello.do" class="org.controller.MyController"></bean>
    
    <!-- 配置sprigmvc视图解析器:解析逻辑试图; 
        后台返回逻辑试图:index
        视图解析器解析出真正物理视图:前缀+逻辑试图+后缀====/WEB-INF/jsps/index.jsp
    -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsps/"></property>
        <property name="suffix" value=".jsp"></property>        
    </bean>
</beans>

(3)自定义处理器

public class MyController implements Controller{

    public ModelAndView handleRequest(HttpServletRequest arg0,
            HttpServletResponse arg1) throws Exception {
        ModelAndView mv = new ModelAndView();
        //设置页面回显数据
        mv.addObject("hello", "欢迎学习springmvc!");
        
        //返回物理视图
        //mv.setViewName("/WEB-INF/jsps/index.jsp");
        
        //返回逻辑视图
        mv.setViewName("index");
        return mv;
    }
}

(4)index页面

<html>
<body>
<h1>${hello}</h1>
</body>
</html>

(5)测试地址

http://localhost:8080/springmvc/hello.do

MVC设计模式

MVC设计模式的任务是将包含业务数据的模块与显示模块的视图解耦。这是怎样发生的?在模型和视图之间引入重定向层可以解决问题。此重定向层是控制器,控制器将接收请求,执行更新模型的操作,然后通知视图关于模型更改的消息。

SpringMVC架构

SpringMVC是Spring的一部分,如图:

SpringMVC的核心架构:

具体流程:

(1)首先浏览器发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;

(2)DispatcherServlet——>HandlerMapping,处理器映射器将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器对象、多个HandlerInterceptor拦截器)对象;

(3)DispatcherServlet——>HandlerAdapter,处理器适配器将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;

(4)HandlerAdapter——>调用处理器相应功能处理方法,并返回一个ModelAndView对象(包含模型数据、逻辑视图名);

(5)ModelAndView对象(Model部分是业务对象返回的模型数据,View部分为逻辑视图名)——> ViewResolver, 视图解析器将把逻辑视图名解析为具体的View;

(6)View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构;

(7)返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。

HandlerAdapter

处理器适配器有两种,可以共存,分别是SimpleControllerHandlerAdapter和HttpRequestHandlerAdapter。

SimpleControllerHandlerAdapter SimpleControllerHandlerAdapter是默认的适配器,所有实现了org.springframework.web.servlet.mvc.Controller 接口的处理器都是通过此适配器适配, 执行的。

HttpRequestHandlerAdapter 该适配器将http请求封装成HttpServletResquest 和HttpServletResponse对象. 所有实现了 org.springframework.web.HttpRequestHandler 接口的处理器都是通过此适配器适配, 执行的. 实例如下所示:

(1)配置HttpRequestHandlerAdapter适配器

<!-- 配置HttpRequestHandlerAdapter适配器 -->
<bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"></bean>

(2)编写处理器

public class HttpController implements HttpRequestHandler{

    public void handleRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //给Request设置值,在页面进行回显
        request.setAttribute("hello", "这是HttpRequestHandler!");
        //跳转页面
        request.getRequestDispatcher("/WEB-INF/jsps/index.jsp").forward(request, response);
    }
}

(3)index页面

<html>
<body>
<h1>${hello}</h1>
</body>
</html>

Adapter源码分析 模拟场景:前端控制器(DispatcherServlet)接收到Handler对象后,传递给对应的处理器适配器(HandlerAdapter),处理器适配器调用相应的Handler方法。

(1)模拟处理器

//以下是Controller接口和它的是三种实现 
public interface Controller {
}

public class SimpleController implements Controller{
    public void doSimpleHandler() {
        System.out.println("Simple...");
    }
}

public class HttpController implements Controller{
    public void doHttpHandler() {
        System.out.println("Http...");
    }
}

public class AnnotationController implements Controller{
    public void doAnnotationHandler() {
        System.out.println("Annotation..");
    }
} 

(2)模拟处理器适配器

//以下是HandlerAdapter接口和它的三种实现
public interface HandlerAdapter {
    public boolean supports(Object handler);
    public void handle(Object handler);
}

public class SimpleHandlerAdapter implements HandlerAdapter{
    public boolean supports(Object handler) {
        return (handler instanceof SimpleController);
    }

    public void handle(Object handler) {
        ((SimpleController)handler).doSimpleHandler();
    }
}

public class HttpHandlerAdapter implements HandlerAdapter{
    public boolean supports(Object handler) {
        return (handler instanceof HttpController);
    }

    public void handle(Object handler) {
        ((HttpController)handler).doHttpHandler();
    }
}

public class AnnotationHandlerAdapter implements HandlerAdapter{
    public boolean supports(Object handler) {
        return (handler instanceof AnnotationController);
    }

    public void handle(Object handler) {
        ((AnnotationController)handler).doAnnotationHandler();
    }
}

(3)模拟DispatcherServlet

public class Dispatcher {
    public static List<HandlerAdapter> handlerAdapter = new ArrayList<HandlerAdapter>();
    
    public Dispatcher(){
        handlerAdapter.add(new SimpleHandlerAdapter());
        handlerAdapter.add(new HttpHandlerAdapter());
        handlerAdapter.add(new AnnotationHandlerAdapter());
    }
    
    //核心功能
    public void doDispatch() {
        //前端控制器(DispatcherServlet)接收到Handler对象后
        //SimpleController handler = new SimpleController();
        //HttpController handler = new HttpController();
        AnnotationController handler = new AnnotationController();
        
        //传递给对应的处理器适配器(HandlerAdapter)
        HandlerAdapter handlerAdapter = getHandlerAdapter(handler);
        
        //处理器适配器调用相应的Handler方法
        handlerAdapter.handle(handler);
    }
    
    //通过Handler找到对应的处理器适配器(HandlerAdapter)
    public HandlerAdapter getHandlerAdapter(Controller handler) {
        for(HandlerAdapter adapter : handlerAdapter){
            if(adapter.supports(handler)){
                return adapter;
            }
        }
        return null;
    }
}

(4)测试

public class Test {
    public static void main(String[] args) {
        Dispatcher dispather = new Dispatcher();
        dispather.doDispatch();
    }
}

HandlerMapping

处理器映射器将会把请求映射为 HandlerExecutionChain 对象(包含一个 Handler 处理器对象、多个 HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新的映射策略。

处理器映射器有三种,三种可以共存,相互不影响,分别是BeanNameUrlHandlerMapping、SimpleUrlHandlerMapping和ControllerClassNameHandlerMapping;

BeanNameUrlHandlerMapping

默认映射器,即使不配置,默认就使用这个来映射请求。

<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
//映射器把hello.do请求映射到该处理器
<bean id="testController" name="/hello.do" class="org.controller.TestController"></bean>

SimpleUrlHandlerMapping

该处理器映射器可以配置多个映射对应一个处理器.

<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
        <props>
            <prop key="/ss.do">testController</prop>
            <prop key="/abc.do">testController</prop>
        </props>
    </property>
</bean>
//上面的这个映射配置表示多个*.do文件可以访问同一个Controller。 
<bean id="testController" name="/hello.do" class="org.controller.TestController"></bean>

ControllerClassNameHandlerMapping

该处理器映射器可以不用手动配置映射, 通过[类名.do]来访问对应的处理器.

//这个Mapping一配置, 我们就可以使用Controller的 [类名.do]来访问这个Controller.
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"></bean>


Handler

这里只介绍上文提到的两种处理器, 除此之外还有很多适用于各种应用场景的处理器, 尤其是Controller接口还有很多实现类, 大家可以自行去了解.

Controller

org.springframework.web.servlet.mvc.Controller, 该处理器对应的适配器是 SimpleControllerHandlerAdapter.

public interface Controller {

    /**
     * Process the request and return a ModelAndView object which the DispatcherServlet
     * will render. A {@code null} return value is not an error: it indicates that
     * this object completed request processing itself and that there is therefore no
     * ModelAndView to render.
     * @param request current HTTP request
     * @param response current HTTP response
     * @return a ModelAndView to render, or {@code null} if handled directly
     * @throws Exception in case of errors
     */
    ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;

}

该处理器方法用于处理用户提交的请求, 通过调用service层代码, 实现对用户请求的计算响应, 并最终将计算所得数据及要响应的页面封装为一个ModelAndView 对象, 返回给前端控制器(DispatcherServlet).

Controller接口的实现类:

HttpRequestHandler

org.springframework.web.HttpRequestHandler, 该处理器对应的适配器是 HttpRequestHandlerAdapter.

public interface HttpRequestHandler {
    void handleRequest(HttpServletRequest var1, HttpServletResponse var2) throws ServletException, IOException;
}

该处理器方法没有返回值, 不能像 ModelAndView 一样, 将数据及目标视图封装为一个对象, 但可以将数据放入Request, Session等域属性中, 并由Request 或 Response完成目标页面的跳转.

中文乱码解决

Get请求乱码

Tomcat8已经解决了Get请求乱码, 如果是Tomcat8以下的版本, 可以使用以下两种方法:

  • 更改Tomcat的配置文件server.xml
  • 对参数进行重新编码String userName =new String(request.getParamter(“userName”).getBytes(“ISO-8859-1”),“UTF-8”); //ISO-8859-1是Tomcat8以下版本的默认编码

Post请求乱码 在web.xml中加入:

<filter>
    <filter-name>characterEncoding</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>characterEncoding</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

ViewResolver

视图解析器负责将处理结果生成View视图. 这里介绍两种常用的视图解析器:

InternalResourceViewResolver

该视图解析器用于完成对当前Web应用内部资源的封装和跳转. 而对于内部资源的查找规则是, 将ModelAndView中指定的视图名称与视图解析器配置的前缀与后缀想结合, 拼接成一个Web应用内部资源路径. 内部资源路径 = 前缀 + 视图名称 + 后缀.

InternalResourceViewResolver解析器会把处理器方法返回的模型属性都存放到对应的request中, 然后将请求转发到目标URL.

(1) 处理器

public class MyController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ModelAndView mv = new ModelAndView();
        mv.addObject("hello", "hello world!");
        mv.setViewName("index");
        return mv;
    }
}

(2) 视图解析器配置

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <property name="suffix" value=".jsp"></property>
   </bean>

当然, 若不指定前缀与后缀, 直接将内部资源路径写到 setViewName()中也可以, 相当于前缀与后缀均为空串.

mv.setViewName("/WEB-INF/jsp/index.jsp");

BeanNameViewResolver

InternalResourceViewResolver视图解析器存在一个问题, 就是只可以完成将内部资源封装后的跳转, 无法跳转向外部资源, 如外部网页.

BeanNameViewResolver 视图解析器将资源(内部资源和外部资源)封装为bean实例, 然后在 ModelAndView 中通过设置bean实例的id值来指定资源. 在配置文件中可以同时配置多个资源bean.

(1) 处理器

public class MyController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {

        return new ModelAndView("myInternalView");
//        return new ModelAndView("baidu");
    }
}

(2) 视图解析器配置

<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
<!--内部资源view-->
<bean id="myInternalView" class="org.springframework.web.servlet.view.JstlView">
    <property name="url" value="/jsp/show.jsp"/>
</bean>
<!--外部资源view-->
<bean id="baidu" class="org.springframework.web.servlet.view.RedirectView">
    <property name="url" value="https://www.baidu.com/"/>
</bean>

了解完这两种视图解析器后是不是有种熟悉感, 是的, 就是请求转发和重定向.

Spring Boot 还是 Spring MVC

既然使用 Spring Boot 可以简化 Spring MVC 的配置,开发起来更加快捷方便,那就用它就好了,为什么要学 Spring MVC ,放着简单的东西不用,非要去用复杂的东西呢?


这个问题需要因人而异,如果你是一个开发经验丰富、对 Spring 框架体系产品原理都非常了解的老司机,那不用说,肯定推荐你使用 Spring Boot。但是如果你是一个经验尚浅,对 Spring 框架体系不是很了解的开发者,过于简化的东西对你来说不见得是一件好事,简单的背后其实是隐藏了其中的学习曲线,在不需要了解 Spring MVC 原理的情况下就使用其进行开发,这叫知其然而不知其所以然,不是正确的学习方式。


Spring Boot 的优点是框架帮你屏蔽了很多底层操作,可以完成快速开发,但任何事情都有两面性,它屏蔽了底层操作的同时也屏蔽掉了你对于底层原理的理解和学习,假如只会简单的使用框架,一旦遇到较为复杂的问题,一定是一脸懵逼。

若不懂原理,是无法解决问题的,你只知道 Spring Boot 自动完成了一些操作,但是对于它究竟完成了哪些操作浑然不知,想想看,这样的方式真的有利于自我提高吗?除非你想一辈子搬砖,不考虑做一些底层架构或者更深层次的工作。


就好比一个赛车爱好者,如果仅仅是驾驶技术好,那永远只能是个票友;如果想成为真正的高手,一定是需要自己对赛车进行不断地调试改装,直至性能达到车子的极限。那如果连汽车的结构都不了解,只会开车,又怎么能完成车辆的性能优化和改装呢,因此,不但要驾驶技术一流,还要懂得赛车的内部原理,才能成为真正的老司机。


写代码也是一样,如果仅仅停留在使用快速开发框架完成项目,而不去钻研探究底层原理的话,永远也不会有质地提高,只会调方法堆逻辑。在没有夯实底层体系的情况下,一味追求敏捷高效,欲速则不达。

最后

小伙伴们,帮忙一键三连呀

题外话,我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在Java学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升

故此将并将重要的Java进阶资料包括并发编程、JVM调优、SSM、设计模式、spring等知识技术、阿里面试题精编汇总、常见源码分析等录播视频免费分享出来,私信【555】自行下载

Java进阶视频资料

Java面试题精编汇总

JAVA核心知识点整理

相关推荐

团队管理“布阵术”:3招让你的团队战斗力爆表!

为何古代军队能够以一当十?为何现代企业有的团队高效似“特种部队”,有的却松散若“游击队”?**答案正隐匿于“布阵术”之中!**今时今日,让我们从古代兵法里萃取3个核心要义,助您塑造一支战斗力爆棚的...

知情人士回应字节大模型团队架构调整

【知情人士回应字节大模型团队架构调整】财联社2月21日电,针对原谷歌DeepMind副总裁吴永辉加入字节跳动后引发的团队调整问题,知情人士回应称:吴永辉博士主要负责AI基础研究探索工作,偏基础研究;A...

豆包大模型团队开源RLHF框架,训练吞吐量最高提升20倍

强化学习(RL)对大模型复杂推理能力提升有关键作用,但其复杂的计算流程对训练和部署也带来了巨大挑战。近日,字节跳动豆包大模型团队与香港大学联合提出HybridFlow。这是一个灵活高效的RL/RL...

创业团队如何设计股权架构及分配(创业团队如何设计股权架构及分配方案)

创业团队的股权架构设计,决定了公司在随后发展中呈现出的股权布局。如果最初的股权架构就存在先天不足,公司就很难顺利、稳定地成长起来。因此,创业之初,对股权设计应慎之又慎,避免留下巨大隐患和风险。两个人如...

消息称吴永辉入职后引发字节大模型团队架构大调整

2月21日,有消息称前谷歌大佬吴永辉加入字节跳动,并担任大模型团队Seed基础研究负责人后,引发了字节跳动大模型团队架构大调整。多名原本向朱文佳汇报的算法和技术负责人开始转向吴永辉汇报。简单来说,就是...

31页组织效能提升模型,经营管理团队搭建框架与权责定位

分享职场干货,提升能力!为职场精英打造个人知识体系,升职加薪!31页组织效能提升模型如何拿到分享的源文件:请您关注本头条号,然后私信本头条号“文米”2个字,按照操作流程,专人负责发送源文件给您。...

异形柱结构(异形柱结构技术规程)

下列关于混凝土异形柱结构设计的说法,其中何项正确?(A)混凝土异形柱框架结构可用于所有非抗震和抗震设防地区的一般居住建筑。(B)抗震设防烈度为6度时,对标准设防类(丙类)采用异形柱结构的建筑可不进行地...

职场干货:金字塔原理(金字塔原理实战篇)

金字塔原理的适用范围:金字塔原理适用于所有需要构建清晰逻辑框架的文章。第一篇:表达的逻辑。如何利用金字塔原理构建基本的金字塔结构受众(包括读者、听众、观众或学员)最容易理解的顺序:先了解主要的、抽象的...

底部剪力法(底部剪力法的基本原理)

某四层钢筋混凝土框架结构,计算简图如图1所示。抗震设防类别为丙类,抗震设防烈度为8度(0.2g),Ⅱ类场地,设计地震分组为第一组,第一自振周期T1=0.55s。一至四层的楼层侧向刚度依次为:K1=1...

结构等效重力荷载代表值(等效重力荷载系数)

某五层钢筋混凝土框架结构办公楼,房屋高度25.45m。抗震设防烈度8度,设防类别丙类,设计基本地震加速度0.2g,设计地震分组第二组,场地类别为Ⅱ类,混凝土强度等级C30。该结构平面和竖向均规则。假定...

体系结构已成昭告后世善莫大焉(体系构架是什么意思)

实践先行也理论已初步完成框架结构留余后人后世子孙俗话说前人栽树后人乘凉在夏商周大明大清民国共和前人栽树下吾之辈已完成结构体系又俗话说青出于蓝而胜于蓝各个时期任务不同吾辈探索框架结构体系经历有限肯定发展...

框架柱抗震构造要求(框架柱抗震设计)

某现浇钢筋混凝土框架-剪力墙结构高层办公楼,抗震设防烈度为8度(0.2g),场地类别为Ⅱ类,抗震等级:框架二级,剪力墙一级,混凝土强度等级:框架柱及剪力墙C50,框架梁及楼板C35,纵向钢筋及箍筋均采...

梁的刚度、挠度控制(钢梁挠度过大会引起什么原因)

某办公楼为现浇钢筋混凝土框架结构,r0=1.0,混凝土强度等级C35,纵向钢筋采用HRB400,箍筋采用HPB300。其二层(中间楼层)的局部平面图和次梁L-1的计算简图如图1~3(Z)所示,其中,K...

死要面子!有钱做大玻璃窗,却没有钱做“柱和梁”,不怕房塌吗?

活久见,有钱做2层落地大玻璃窗,却没有钱做“柱子和圈梁”,这样的农村自建房,安全吗?最近刷到个魔幻施工现场,如下图,这栋5开间的农村自建房,居然做了2个全景落地窗仔细观察,这2个落地窗还是飘窗,为了追...

不是承重墙,物业也不让拆?话说装修就一定要拆墙才行么

最近发现好多朋友装修时总想拆墙“爆改”空间,别以为只要避开承重墙就能随便砸!我家楼上邻居去年装修,拆了阳台矮墙想扩客厅,结果物业直接上门叫停。后来才知道,这种配重墙拆了会让阳台承重失衡,整栋楼都可能变...

取消回复欢迎 发表评论: