Spring MVC-05循序渐进之数据绑定和form标签库(下) 实战从0到1

2021-08-17 10:35:41 浏览数 (1)

  • 概述
  • 功能概述
  • 搭建SpringMVC Maven工程
    • pom.xml
    • 部署描述符web.xml
    • 配置Spring MVC配置文件
    • 日志配置文件
  • Domain类
  • Controller类
  • Service类
  • 视图
  • artisan_list测试
  • artisan_add
    • 编写超链接标签中对应的uri
    • Controller映射方法
    • AddArtisan.jsp
    • 测试结果
  • Edit Artisan
    • 编写uri
    • 编写映射方法
    • 编写EditArtisan.jsp
    • update映射方法
    • 测试
  • 总结
  • 源码

概述

Spring MVC-05循序渐进之数据绑定和form标签库(上) 博文中我们学习了数据绑定和form标签库,那我们来写一个小demo练习下吧。


功能概述

假设有个Artisan管理页面,先抛开花里胡哨的前端,我们用最丑最简单的方式实现,来体会下Spring MVC数据绑定及表单的操作过程 。如下图


搭建SpringMVC Maven工程

pom.xml

添加Maven依赖,主要的依赖包是spring-webmvc-${version},这里我们采用4.3.9版本,同时使用JDK7来编译

代码语言:javascript复制
<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0modelVersion>
    <groupId>com.artisangroupId>
    <artifactId>chapter05aartifactId>
    <packaging>warpackaging>
    <version>0.0.1-SNAPSHOTversion>
    <name>chapter05a Maven Webappname>
    <url>http://maven.apache.orgurl>
    <dependencies>
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>3.8.1version>
            <scope>testscope>
        dependency>

        <dependency>
            <groupId>javax.servletgroupId>
            <artifactId>javax.servlet-apiartifactId>
            <version>3.1.0version>
            <scope>providedscope>

        dependency>

        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-webmvcartifactId>
            <version>4.3.9.RELEASEversion>
        dependency>


        <dependency>
            <groupId>log4jgroupId>
            <artifactId>log4jartifactId>
            <version>1.2.17version>
        dependency>

        <dependency>
            <groupId>jstlgroupId>
            <artifactId>jstlartifactId>
            <version>1.2version>
        dependency>


    dependencies>
    <build>
        <finalName>chapter05afinalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <version>2.5.1version>
                <configuration>
                    <source>1.7source>
                    <target>1.7target>
                    <compilerArgument>-Xlint:allcompilerArgument>
                    <showWarnings>trueshowWarnings>
                    <showDeprecation>trueshowDeprecation>
                configuration>
            plugin>
        plugins>
    build>
project>

部署描述符web.xml

配置DispatcherServlet,指定SpringMVC配置文件的路径,同时为避免中文乱码配置filter ,指定CharacterEncodingFilter为UTF-8。

代码语言:javascript复制
<web-app version="3.0" 
        xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <servlet>
        <servlet-name>springmvcservlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        servlet-class>
        <init-param>
            <param-name>contextConfigLocationparam-name>
            <param-value>/WEB-INF/config/springmvc-config.xmlparam-value>
        init-param>
        <load-on-startup>1load-on-startup>    
    servlet>

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

     
    <filter>  
        <filter-name>characterEncodingFilterfilter-name>  
        <filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>  
        <init-param>  
            <param-name>encodingparam-name>  
            <param-value>UTF-8param-value>  
        init-param>  
        <init-param>  
            <param-name>forceEncodingparam-name>  
            <param-value>trueparam-value>  
        init-param>  
    filter>  
    <filter-mapping>  
        <filter-name>characterEncodingFilterfilter-name>  
        <url-pattern>/*url-pattern>  
    filter-mapping> 



web-app>

配置Spring MVC配置文件

通过context:component-scan 结合注解,扫描bean 。 同时配置静态资源文件过滤,以及视图解析器。

代码语言:javascript复制
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    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/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd     
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    
    <context:component-scan base-package="com.artisan.springmvc.controller"/>
    
    <context:component-scan base-package="com.artisan.springmvc.service"/>


    
    <mvc:annotation-driven/>
    <mvc:resources mapping="/css/**" location="/css/"/>
    <mvc:resources mapping="/*.jsp" location="/"/>

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

beans>

日志配置文件

先简单配置下 ,启动Spring容器的时候不报错即可。

代码语言:javascript复制
log4j.rootLogger=INFO,A1
log4j.logger.org.springframework=info
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d %5p [%t] (%F:%L) - %m%n

Domain类

根据我们的构想及页面原型,这个Demo中的domain类Artisan,应该有如下几个属性

代码语言:javascript复制
    private long id;
    private String name;
    private String code;
    private String sex;
    private Org org;

有个类型为Org 的org属性,其中 Org有如下2个属性

代码语言:javascript复制
    private int orgId;
    private String orgName;
代码语言:javascript复制
package com.artisan.springmvc.domian;

public class Artisan {

    private long id;
    private String name;
    private String code;
    private String sex;
    private Org org;




    /**
     * 
     * 创建一个新的实例 Artisan.
     * 
     * @param id
     * @param name
     * @param code
     * @param sex
     * @param org
     */
    public Artisan(long id, String name, String code, String sex, Org org) {
        super();
        this.id = id;
        this.name = name;
        this.code = code;
        this.sex = sex;
        this.org = org;
    }

    /**
     * 
    * 默认构造函数
    *
     */
    public Artisan() {
        super();
    }


    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Org getOrg() {
        return org;
    }

    public void setOrg(Org org) {
        this.org = org;
    }

}
代码语言:javascript复制
package com.artisan.springmvc.domian;

public class Org {

    private int orgId;
    private String orgName;

    /**
     * 
     * 创建一个新的实例 Org. 默认的构造函数需要有,否则 org.apache.jasper.JasperException:
     * org.springframework.beans.NullValueInNestedPathException: Invalid
     * property 'org' of bean class [com.artisan.springmvc.domian.Artisan]:
     * Could not instantiate property type [com.artisan.springmvc.domian.Org] to
     * auto-grow nested property path; nested exception is
     * org.springframework.beans.BeanInstantiationException: Failed to
     * instantiate [com.artisan.springmvc.domian.Org]: Is it an abstract class?;
     * nested exception is java.lang.InstantiationException:
     * com.artisan.springmvc.domian.Org
     *
     * 
     */
    public Org() {
        super();
    }

    /**
     * 
     * 创建一个新的实例 Org.
     * 
     * @param orgId
     * @param orgName
     */
    public Org(int orgId, String orgName) {
        super();
        this.orgId = orgId;
        this.orgName = orgName;
    }

    public int getOrgId() {
        return orgId;
    }

    public void setOrgId(int orgId) {
        this.orgId = orgId;
    }

    public String getOrgName() {
        return orgName;
    }

    public void setOrgName(String orgName) {
        this.orgName = orgName;
    }

}

Domain域的类,没什么好说的,提供默认构造函数,和前台一致即可。


Controller类

第一步,首先获取一个Artisan列表, 个人习惯先开发Controller

按照设计输入http://ip:port/context/artisan/artisanList 可获取全部的Artisan数据

代码语言:javascript复制
@Controller
@RequestMapping("/artisan")
public class ArtisanController {

    private static final Logger logger  = Logger.getLogger(ArtisanController.class);

    private ArtisanService artisanService;

    public ArtisanService getArtisanService() {
        return artisanService;
    }

    /**
     * 
    * @Title: setArtisanService  
    * @Description: 通过 @Autowired注入ArtisanService
    * @param @param artisanService    参数  
    * @return void    返回类型  
    * @throws
     */
    @Autowired
    public void setArtisanService(ArtisanService artisanService) {
        this.artisanService = artisanService;
    }



    @RequestMapping(value="/artisanList",method=RequestMethod.GET)
    public String getAllArtisans(Model model){
        logger.info("getAllArtisans called....");

        List artisanList = artisanService.getArtisans();
        // 添加到Model中,以便前台能访问到
        model.addAttribute("artisanList", artisanList);

        return "ArtisanList";
    }


}

通过在类上标注注解@Controller ,配合component-scan扫描,使其成为一个控制器,然后标注了@RequestMapping(“/artisan”),在类层级上标注了请求路径,这个控制器中所有的方法都基于/artisan。

通过@Autowired自动注入service,然后通过artisanService.getArtisans()获取模拟的artisanList

紧接着将数据添加到Model中,以便前台能访问到 model.addAttribute(“artisanList”, artisanList);

最后返回了一个视图ArtisanList,结合SpringMVC配置文件中的视图解析器,会转发到/WEB-INF/jsp/目录下的ArtisanList.jsp


Service类

目前只有一个获取全部数据的接口,后续根据功能逐个增加

代码语言:javascript复制
package com.artisan.springmvc.service;

import java.util.List;

import com.artisan.springmvc.domian.Artisan;

public interface ArtisanService {
    // 获取所有的Artisan
    List getArtisans();
}

接口实现类

代码语言:javascript复制
package com.artisan.springmvc.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Service;

import com.artisan.springmvc.domian.Artisan;
import com.artisan.springmvc.domian.Org;

/**
 * 
 * @ClassName: ArtisanServiceImpl
 * @Description: 通过@Service标注的服务层 ,
 * @author Mr.Yang
 * @date 2018年1月30日
 *
 */

@Service
public class ArtisanServiceImpl implements ArtisanService {

    /*
     * this implementation is not thread-safe
     */

    List artisanList = null;
    String sex = null;
    /**
     * 
     * 创建一个新的实例 ArtisanServiceImpl的同时初始化模拟数据
     *
     */
    public ArtisanServiceImpl() {
        super();
        // 初始化模式数据
        artisanList = new ArrayList();
        for (int i = 0; i < 10; i  ) {
            if (i%2 == 0) {
                sex = "男";
            }else {
                sex="女";
            }
            artisanList.add(new Artisan(i, "Artisan"   i, "ATSCode"   i, sex, new Org(i, "org"   i)));
        }

    }

    @Override
    public List getArtisans() {
        return artisanList;
    }

}

视图

引入c标签,然后对后台Model中的artisanList进行遍历显示数据。 有CSS修饰样式。

ArtisanList.jsp

代码语言:javascript复制
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<html>
<head>
<title>Artisan Listtitle>
<style type="text/css">@import url(""/>");style>
head>
<body>

<div id="global">
<h1>Artisan Listh1>

<p>
    <a href=''>Add Artisana>
p>


<table border="1" cellspacing="0">
<tr>
    <th align="center">OrgNameth>
    <th align="center">ArtisanNameth>
    <th align="center">Codeth>
    <th align="center">Sexth>
    <th align="center" colspan="2">Operationth>
tr>


<c:forEach items="${artisanList}" var="artisan" varStatus="status">
    <tr <c:if test="${status.count%2==0}">bgcolor="#7CCD7C"c:if> align="center">
        <td>${artisan.org.orgName}td>
        <td>${artisan.name}td>
        <td>${artisan.code}td>
        <td>${artisan.sex}td>
        <td><a href>Edita>td>
    tr>
c:forEach>
table>
div>
body>
html>

artisan_list测试

启动tomcat,然后访问 http://localhost:8080/chapter05a/artisan/artisanList 即可获取全部的ArtisanList


artisan_add

我们来分析一下artisan_add的逻辑

1. 通过点击ArtisanList.jsp页面上的Add Artisan 超链接标签,使用JSTL标记的URL解决路径访问的问题,跳转到添加页面

2. 再添加页面中加载Org下拉列表,输入信息后,提交触发保存Artisan的操作

3. 后台保存完成后 ,重定向到ArtisanList,展示数据。

编写超链接标签中对应的uri

代码语言:javascript复制
<a href=''>Add Artisana>

使用JSTL标记的URL解决路径访问的问题, 因为我们在web.xml中配置拦截所有的请求,因此这个请求会被DispatcherServlet拦截,映射到如下的方法中


Controller映射方法

代码语言:javascript复制
/**
     * 
    * @Title: inputArtisan  
    * @Description: 进入inputArtisan的页面 
    * @param @return    参数  
    * @return String    返回类型  
    * @throws
     */
    @RequestMapping(value="/artisan_input")
    public String inputArtisan(Model model){
        // 获取全部的org
        List orgs = artisanService.getAllOrgs();
        // 加载org到Model中以便前台展示
        model.addAttribute("orgs", orgs);
        // 前台form  commandName为artisan,因此必须保证model中存在一个artisan
        model.addAttribute("artisan",new Artisan());
        return "AddArtisan";

    }

因为添加页面需要展示org列表,所以必须从后台加载全部的org,放到model中,确保前台页面可以通过表达式获取到对应的数据。 同时,前台添加Artisan的form ,打算加入commandName属性方便识别, 如下 form:form commandName="artisan" commandName 为artisan,如果该属性存在,则必须在返回包含该表单的视图的请求处理方法中添加对应的模型属性.

返回的字符串 AddArtisan,SpringMVC会根据视图解析器的配置规则

代码语言:javascript复制
id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>

映射到/WEB-INF/jsp/AddArtisan.jsp


AddArtisan.jsp

代码语言:javascript复制
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<html>
<head>
<title>Add Artisan Formtitle>
<style type="text/css">@import url(""/>");style>
head>
<body>

<div id="global">
<form:form commandName="artisan" action="artisan_add" method="post">
    <fieldset>
        <legend>Add an Artisanlegend>
        <p>
            <label for="orgs">orgName: label>
            <form:select id="org" path="org.orgId" 
                items="${orgs}"  
                itemValue="orgId" 
                itemLabel="orgName"/>
        p>
        <p>
            <label for="name">name: label>
            <form:input id="name" path="name"/>
        p>
        <p>
            <label for="code">code: label>
            <form:input id="code" path="code"/>
        p>
        <p>
            <label for="sex">sex: label>
            <form:input id="sex" path="sex"/>
        p>

        <p id="buttons">
            <input id="reset" type="reset" tabindex="4">
            <input id="submit" type="submit" tabindex="5" 
                value="Add Artisan">
        p>
    fieldset>
form:form>
div>
body>
html>

Org的下拉列表采用form的select标签,点击超链接跳转页面的方法中,调用后端的方法获取全部的orgList,同时存放到model中,便于前端展示。 同时绑定了path=”org.orgId” ,后端提供根据页面传入的orgId获取Org的接口及实现类

实现类如下

代码语言:javascript复制
@Override
    public Org getOrg(int orgId) {
        for (Org org:orgList) {
            if (orgId == org.getOrgId()) {
                return org;
            }
        }
        return null;
    }

根据前端选择orgId, 返回对应的org实体类。

然后设置给artisan, 最后调用服务层的方法保存artisan到list中,最后重定向到list列表

代码如下

代码语言:javascript复制
@RequestMapping(value="/artisan_add",method=RequestMethod.POST)
    public String addArtisan(@ModelAttribute Artisan artisan){
        logger.info("addArtisan called...");

        // 获取页面的数据
        logger.info("orgId:"   artisan.getOrg().getOrgId());
        logger.info("Name:"   artisan.getName());
        logger.info("Code:"   artisan.getCode());
        logger.info("Sex:"   artisan.getSex());

        //根据前台传入绑定的orgId,获取Org
        Org org = artisanService.getOrg(artisan.getOrg().getOrgId());
        // 设置org
        artisan.setOrg(org);
        // 保存artisan
        artisanService.addArtisan(artisan);

        // 跳转到list页面
        return "redirect:/artisan/artisan_list";
    }

测试结果


Edit Artisan

下面我们来梳理一下编辑的逻辑

1. 点击Edit按钮,进入编辑页面,这个页面需要将对应的数据加载显示,然后提供用户编辑

2. 用户点击UPDATE按钮后,提交到后端更新数据,然后重定向到list页面

编写uri

第一步展示list的时候,我们已经从后端加载了artisan的id ,所以编辑的时候根据artisan#id去编辑,这样href如下

代码语言:javascript复制
<a href="artisan_edit/${artisan.id}">Edita>

编写映射方法

根据artisan_edit/${artisan.id} 映射到如下方法

代码语言:javascript复制
/**
     * 
    * @Title: editArtisan  
    * @Description: 跳转到编辑Artisan页面  
    * @param @param model
    * @param @param id
    * @param @return    参数  
    * @return String    返回类型  
    * @throws
     */
    @RequestMapping(value="/artisan_edit/{id}")
    public String editArtisan(Model model,@PathVariable long  id){
        logger.info("Artisan ID:"   id);
        // 加载Org全部数据 用于选择
        List orgList = artisanService.getAllOrgs();
        // 添加到model,以便前台访问
        model.addAttribute("orgList", orgList);

        // 根据传入的id,获取对应的artisan信息 用于编辑页面展示Artisan信息
        Artisan artisan = artisanService.getArtisanById(id);
        // 添加到model,以便前台访问
        model.addAttribute("artisan", artisan);

        return "EditArtisan";
    }

编写EditArtisan.jsp

代码语言:javascript复制
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<%
String path = request.getContextPath();
//获得本项目的地址(例如: http://localhost:8080/domain/)赋值给basePath变量 
String basePath = request.getScheme() "://" request.getServerName() ":" request.getServerPort() path "/";
// 将 "项目路径basePath" 放入pageContext中,待以后用EL表达式读出。 
pageContext.setAttribute("basePath",basePath); 
%>

<html>
<head>
<title>Edit Artisan Formtitle>
<style type="text/css">@import url(""/>");style>
head>
<body>

<div id="global">
<form:form commandName="artisan" action="${pageScope.basePath}/artisan/artisan_update" method="post">
    <fieldset>
        <legend>Edit Artisanlegend>

        <form:hidden path="id"/>

        <p>
            <label for="orgs">orgName: label>
            <form:select id="org" path="org.orgId" 
                items="${orgList}"  
                itemValue="orgId" 
                itemLabel="orgName"/>
        p>


        <p>
            <label for="name">name: label>
            <form:input id="name" path="name"/>
        p>
        <p>
            <label for="code">code: label>
            <form:input id="code" path="code"/>
        p>
        <p>
            <label for="sex">sex: label>
            <form:input id="sex" path="sex"/>
        p>

        <p id="buttons">
            <input id="reset" type="reset" tabindex="4">
            <input id="submit" type="submit" tabindex="5" 
                value="Update Artisan">
        p>
    fieldset>
form:form>
div>
body>
html>

update映射方法

点击提交后,action=”${pageScope.basePath}/artisan/artisan_update” ,映射

代码语言:javascript复制
    @RequestMapping(value="/artisan_update",method=RequestMethod.POST)
    public String artisanUpdate(@ModelAttribute Artisan artisan){
        logger.info("artisanUpdate called");

        logger.info("artisan orgId:"   artisan.getOrg().getOrgId());
        logger.info("artisan Id:"   artisan.getId());
        logger.info("artisan Name:"   artisan.getName());
        logger.info("artisan Sex:"   artisan.getSex());
        logger.info("artisan Code:"   artisan.getCode());

        // 根据orgId获取org
        Org org = artisanService.getOrg(artisan.getOrg().getOrgId());
        logger.info("Org Name :"   org.getOrgName());
        artisan.setOrg(org);
        // 更新数据
        artisanService.updateArtisan(artisan);

        return "redirect:/artisan/artisan_list";
    }

测试

修改一条数据,如下


总结

至此,一个简单的实例已经编写完毕,重点是体会思路及spring mvc 及form的应用。


源码

代码已提交到github

https://github.com/yangshangwei/SpringMvcTutorialArtisan

0 人点赞