转载

学习笔记---SpringMVC入门

MVC 概述

在早期Java Web的开发中,统一把显示层、控制层、数据层的操作全部较高JSP或者JavaBean来处理,称之为Model1:

学习笔记---SpringMVC入门

  • 弊端

    • JSP和Java Bean之间严重耦合,Java代码和HTML代码也耦合在一起
    • 要求开发者不仅掌握Java后端设计能力,还需要较高水平的前端设计与开发能力
    • 前端和后端相互依赖,前端需要等待后端完成,后端也依赖前端完成,才能进行有效的测试
    • 代码难以复用

正因为Model1的种种弊端,所以很快这种方式就被 Servlet + JSP + Java Bean 所替代了,早期的 MVC 模型 (Model2) 就像下图这样:

学习笔记---SpringMVC入门

首先用户的请求会到达Servlet,然后根据请求调用相应的Java Bean,并把所有的显示结果交给JSP去完成,这样的模式我们成为MVC模式,其中

  • M代表模型(Model)

模型就是数据,dao、bean、service等

  • V代表视图(View)

网页,JSP,用来展示模型中的数据

  • C代表控制器(Controller)

    控制器的作用就是把不同的数据(Model),显示在不同的视图(View)上,==Servlet 扮演的就是这样的角色==。

Spring MVC 架构

为解决持久层中一直未处理好的数据库事务的编程,同时迎合NoSQL的强势崛起,SpringMVC给出了如下解决方案:

学习笔记---SpringMVC入门

传统的模型层被拆分了为业务层(Service)和数据访问层(Dao,Data Access Object)。在Service层中可以通过Spring的声明式事务操作数据访问层,而在业务层上还允许我们访问NoSQL,这样就能够满足NoSQL的使用了,也可以大大提高互联网系统的性能。

  • 特点
  • 结构松散,几乎可以在Spring MVC中使用各类视图
  • 降低耦合,各个模块分离
  • 与Spring无缝集成

Spring MVC实践操作

Step1:在IDEA中新建Spring MVC项目

如图,创建SpringMVC项目,IDEA会自动帮我们下载好必要的jar包,并且为我们创建好一些默认的目录和文件:

学习笔记---SpringMVC入门

学习笔记---SpringMVC入门

最好设置一下项目的路径,路径下如果无此文件夹,会自动为我们创建

学习笔记---SpringMVC入门

学习笔记---SpringMVC入门

Step2:修改web.xml文件

把<url-pattern>元素的值改为 / ,表示要拦截所有的请求,并交由Spring MVC的后台控制器来处理,改完之后:

<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

Step3:编辑 dispatcher-servlet.xml

这个文件名的开头 dispatcher 与上面 web.xml 中的 <servlet-name>元素配置的 dispatcher 对应,这是 Spring MVC 的映射配置文件(xxx-servlet.xml),我们编辑如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="simpleUrlHandlerMapping"
          class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <!-- /hello 路径的请求交给 id 为 helloController 的控制器处理-->
                <prop key="/hello">helloController</prop>
            </props>
        </property>
    </bean>
    <bean id="helloController" class="controller.HelloController"></bean>
</beans>

Step4:编写HelloController.java

在 Package【controller】下创建 【HelloController】类,并实现 org.springframework.web.servlet.mvc.Controller 接口:

package controller;


import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class HelloController implements Controller {

    @Override
    public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {
        return null;
    }
}
  • 出现了问题: javax.servlet 包找不到
  • 解决: 将本地 Tomcat 服务器的目录下【lib】文件夹下的 servlet-api.jar 包拷贝到工程【lib】文件夹下,添加依赖(我没有找到本地的这个jar包,从网上下载该jar包,并粘贴到lib中,然后add 就行了)
package controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class HelloController implements Controller {

    @Override
    public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {
        ModelAndView mav = new ModelAndView("index.jsp");
        mav.addObject("message", "Hello Spring MVC");
        return mav;
    }
}

注释: Spring MVC 通过 ModelAndView 对象把模型和视图结合在一起

**这里表示视图的是index.jsp

模型数据的是 message,内容是 “Hello Spring MVC”**

Step5:准备index.jsp文件

将index.jsp的内容修改为

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" isELIgnored="false"%>
 
<h1>${message}</h1>

内容很简单,用EI表达式显示message的内容

Step6:部署Tomcat及相关环境

在【Run】菜单项下找到【Edit Configurations】

配置Tomcat环境

学习笔记---SpringMVC入门

选择好本地的Tomcat服务器,并改好名字:

学习笔记---SpringMVC入门

在 Deployment 标签页下完成如下操作:添加待启动的项目,可以在添加结束后,选中,修改项目名称

学习笔记---SpringMVC入门

点击 OK 就好了,我们点击右上角的三角形将 Tomcat 服务器运行起来。

学习笔记---SpringMVC入门

  • 出现的问题: Tomcat 服务器无法正常启动
  • 原因: Tomcat 服务器找不到相关的 jar 包
  • 解决方法: 将【lib】文件夹整个剪贴到【WEB-INF】下,并重新建立依赖:

学习笔记---SpringMVC入门

Step7:重启服务器

重启服务器,输入地址:localhost/hello

我实际输入的是:localhost:8080/SpringMVC_war_exploded/hello

不知道什么原因,可能是没有配置好

解析Spring MVC的请求流程

下图为Spring MVC的请求流程:

学习笔记---SpringMVC入门

第一站:DispatcherServlet

从请求离开浏览器以后,第一站到达的就是 DispatcherServlet ,DispatchServlet会拦截所有请求,并将这些请求发送给Spring MVC控制器

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <!-- 拦截所有的请求 -->
    <url-pattern>/</url-pattern>
</servlet-mapping>

DispatcherServlet 的任务就是拦截请求发送给 Spring MVC 控制器。

第二站:处理映射器(HandlerMapping)

  • 问题: 典型的应用程序中可能会有多个控制器,这些请求到底应该发给哪一个控制器呢?

所以 DispatcherServlet 会查询一个或多个处理器映射来确定请求的下一站在哪里,处理器映射会 根据请求所携带的 URL 信息来进行决策 ,例如本项目中,我们通过配置 simpleUrlHandlerMapping 来将 /hello 地址交给 helloController 处理:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="controller"/>
    <!--配置handle,映射“hello”请求 -->
    <bean name="/hello" class="controller.HelloController"/>
    <!-- 处理映射器将bean的name作为URl进行查找,需要在配置Handle时指定name(即url—)-->
    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
    <!-- SimpleControllerHandlerAdapter是一个处理器适配器,所有处理器适配器都要实现HandleAdapter接口-->
    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"/>

</beans>

第三站:控制器

一旦选择了合适的控制器,DispatcherServlet会将请求发送给选中的控制器,请求会卸下其负载(用户提交的请求)等待控制器处理完这些信息:

public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {
     //处理逻辑
        logger.info("handleRequest被调用");
        ModelAndView mav = new ModelAndView("index.jsp");
        mav.addObject("message", "Hello Spring MVC");
        mav.setViewName("/WEB-INF/content/welcome.jsp");
        return mav;
    }

第四站:返回DispatcherServlet

当控制器在完成逻辑处理后,通常会产生一些信息,这些信息就是需要返回给用户并在浏览器上显示的信息,他们被称为 模型(Model) 。仅仅返回原始信息是不够的,这些信息需要以用户友好的方式进行格式化,一般会是HTML,所以,信息需要发送一个 视图(View) ,通常会是JSP。

控制器所要做的最后一件事就是将模型数据打包,并且表示出用于渲染输出的视图名 (逻辑视图名) 。它接下来会将请求连同模型和视图名发送回 DispatcherServlet

public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {
    // 处理逻辑
    ....
    // 返回给 DispatcherServlet
    return mav;
}

第五站:视图解析器

这样一来,控制器就不会和特定的视图想耦合,传递给DispatcherServlet的视图名并不直接表示某个特定的JSP。(实际上,他甚至都不能确定视图就是JSP)。相反,它传递的仅仅是一个逻辑名称,这个名称将会用来查找产生结果的真正视图。

DispatcherServlet将会使用 视图解析器(view resolver) 来将逻辑视图匹配为一个特定的视图实现,它可能是也可能不是JSP。

本项目就是直接绑定到了index.jsp视图

第六站:视图

DispatcherServlet已经确定由哪个视图渲染结果了,请求的任务就基本完成了。

流程的最后一站是视图的实现,在这里交付模型数据,请求的任务也就完成了。视图使用模型数据渲染出结果,这个输出结果会通过响应对象传递给客户端。

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8" isELIgnored="false"%>

<h1>${message}</h1>

使用注解配置Spring MVC

以上的项目是通过xml配置的方式创建了第一个Spring MVC程序,接下来,测试 基于注解 完成程序的配置:

第一步:新建WorldController并添加注解

package controller;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Component
@Controller
public class WorldController {

    @RequestMapping(value = "/world")
    public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {
        ModelAndView mav = new ModelAndView("index.jsp");
        mav.addObject("message", "Spring MVC World!");
        mav.setViewName("/WEB-INF/content/welcome.jsp");
        return mav;
    }
}
  • 注解

    • @controller注解:

      这个注解是用来声明控制器的,但实际上这个注解对 Spring MVC 本身的影响并不大。(Spring 实战说它仅仅是辅助实现组件扫描,可以用 @Component 注解代替,但我自己尝试了一下并不行,因为上述例子没有配置 JSP 视图解析器我还自己配了一个仍没有成功...

    • @RequestMapping注解

      表示路径'/world'会映射到该方法上

第二步:取消之前的XML注释

在 dispatcher-servlet.xml 文件中,注释掉之前的配置,然后增加一句组件扫描: <context:component-scan base-package="controller"/>

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="controller"/>
    <!--配置handle,映射“hello”请求 -->
<!--    <bean name="/hello" class="controller.HelloController"/>-->
<!--    <!– 处理映射器将bean的name作为URl进行查找,需要在配置Handle时指定name(即url—)–>-->
<!--    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>-->
<!--    <!– SimpleControllerHandlerAdapter是一个处理器适配器,所有处理器适配器都要实现HandleAdapter接口–>-->
<!--    <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>-->
<!--    <!– 视图解析器 –>-->
<!--    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"/>-->
</beans>

第三步:重启服务器

当配置完成后,重启服务器,在localhost后输入/world,能够看到如下结果:

学习笔记---SpringMVC入门

@RequestMapping注解细节

如果 @RequestMapping 作用在类上,那么就相当于是给该类所有配置的映射地址前加上了一个地址:

@Controller
@RequestMapping("/mvc")
public class HelloController {
    @RequestMapping("/hello")
    public ModelAndView handleRequest(....) throws Exception {
        ....
    }
}

则访问地址: localhost/mvc/hello

配置视图解析器

根据Spring MVC的请求流程,视图解析器负责定位视图,它接受一个由DispatcherServlet传递过来的逻辑视图名来匹配特定的视图。

  • 需求:

有些页面我们不希望被用户直接访问到,例如有重要数据的页面、有模型数据支撑的页面。

  • 问题:

我们可以在【web】根目录下放置一个【test.jsp】模拟一个重要数据的页面,我们什么都不用做,重新启动服务器,网页中输入 localhost/test.jsp 就能够直接访问到了,这会造成 数据泄露 ...

另外我们可以直接输入 localhost/index.jsp 试试,根据我们上面的程序,这会是一个空白的页面,因为并没有获取到 ${message} 参数就直接访问了,这会 影响用户体验

  • 解决方案

我们将 JSP 文件配置在【WEB-INF】文件夹中的【page】文件夹下,【WEB-INF】是 Java Web 中默认的安全目录,是不允许用户直接访问的 (也就是你说你通过 localhost/WEB-INF/ 这样的方式是永远访问不到的)

同时我们需要将这些信息告诉视图解析器,我们在 dispatcher-servlet.xml 文件中做如下配置:

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

这里配置了一个 Spring MVC 内置的一个视图解析器,该解析器是遵循着一种约定:会 在视图名上添加前缀和后缀,进而确定一个 Web 应用中视图资源的物理路径的。 让我们实际来看看效果:

第一步:修改WorldController代码

package controller;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Component
@Controller
public class WorldController {

    @RequestMapping(value = "/world")
    public ModelAndView handleRequest(javax.servlet.http.HttpServletRequest httpServletRequest, javax.servlet.http.HttpServletResponse httpServletResponse) throws Exception {
        ModelAndView mav = new ModelAndView("index");//原来是index.jsp
        mav.addObject("message", "Spring MVC World!");
        mav.setViewName("/WEB-INF/content/welcome.jsp");
        return mav;
    }
}

第二步:配置视图解析器

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--<bean id="simpleUrlHandlerMapping"-->
                                        <!--class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">-->
    <!--<property name="mappings">-->
            <!--<props>-->
                <!--<!– /hello 路径的请求交给 id 为 helloController 的控制器处理–>-->
                <!--<prop key="/hello">helloController</prop>-->
            <!--</props>-->
        <!--</property>-->
    <!--</bean>-->
    <!--<bean id="helloController" class="controller.HelloController"></bean>-->

    <!-- 扫描controller下的组件 -->
    <context:component-scan base-package="controller"/>
    <bean id="viewResolver"
          class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/page/" />
        <property name="suffix" value=".jsp" />
    </bean>
</beans>

第三步:剪贴index.jsp文件

在【WEB-INF】文件夹下新建一个【page】文件夹,并将【index.jsp】文件剪贴到里面:

学习笔记---SpringMVC入门

第四步:更新资源重启服务器

访问 localhost/world 路径,看到正确效果:

学习笔记---SpringMVC入门

原理

学习笔记---SpringMVC入门

我们传入的逻辑视图名为 index ,再加上 “ /WEB-INF/page/ ” 前缀和 “ .jsp ” 后缀,就能确定物理视图的路径了,这样我们以后就可以将所有的视图放入【page】文件夹下了!

  • 注意: 此时的配置仅是 dispatcher-servlet.xml 下的

控制器接收请求数据

使用控制器接收参数往往是 Spring MVC 开发业务逻辑的第一步,浏览器提交数据是非常常见的场景,现在我们来演示一下Spring MVC如何接受数据的。

结构目录

学习笔记---SpringMVC入门

第一步:在pojo包下建立实体类Product

package pojo;

public class Product {

    private int id;
    private String name;
    private float price;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public float getPrice() {
        return price;
    }
    public void setPrice(float price) {
        this.price = price;
    }

}

第二步:addProduct.jsp

web目录下 (不是在WEB-INF下)增加商品的页面addProduct.jsp

注意:产品名称input的name要使用 name而不是 product.name

<%--
  Created by IntelliJ IDEA.
  User: 10224683
  Date: 2019/12/3
  Time: 16:56
  To change this template use File | Settings | File Templates.
--%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8" import="java.util.*" isELIgnored="false"%>

<form action="addProduct">

    产品名称 :<input type="text" name="name" value=""><br />
    产品价格: <input type="text" name="price" value=""><br />

    <input type="submit" value="增加商品">
</form>

第三步:在controller包下建立ProductController

控制器ProductController,准备一个add方法映射/addProduct路径

为add方法准备一个Product 参数,用于接收注入

最后跳转到showProduct页面显示用户提交的数据

注: addProduct.jsp 提交的name和price会自动注入到参数 product里

注:参数product会默认被当做值加入到ModelAndView 中,相当于: mav.addObject("product",product);​

package controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import pojo.Product;

@Controller
public class ProductController {

    @RequestMapping("/addProduct")
    public ModelAndView add(Product product) throws Exception {
        ModelAndView mav = new ModelAndView("showProduct");
        return mav;
    }
}

第四步:showProduct.jsp

WEB-INF/page 目录下创建 showProduct.jsp

用 EL 表达式显示用户提交的名称和价格

<%--
  Created by IntelliJ IDEA.
  User: 10224683
  Date: 2019/12/3
  Time: 16:58
  To change this template use File | Settings | File Templates.
--%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8" isELIgnored="false"%>

产品名称: ${product.name}<br>
产品价格: ${product.price}

第五步:重启服务器

学习笔记---SpringMVC入门

客户端跳转

在前面的例子中,无论是/index跳转到index.jsp 还是/addProduct 跳转到showProduct.jsp,都是服务端跳转。

现在开始讲解如何进行客户端跳转。

第一步:修改WorldController

首先映射/jump到jump()方法

在jump()中编写如下代码

ModelAndView mav = new ModelAndView("redirect:/index");

redirect:/index

即表示客户端跳转的意思

第二步:使用jump方法,实现跳转

访问页面:

http://localhost/springmvc/jump

结果客户端跳转到了:

http://localhost/springmvc/index

原文  https://segmentfault.com/a/1190000021188933
正文到此结束
Loading...