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

概述

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


功能概述

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

这里写图片描述


搭建SpringMVC Maven工程

这里写图片描述

pom.xml

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

<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.0</modelVersion>
    <groupId>com.artisan</groupId>
    <artifactId>chapter05a</artifactId>
    <packaging>war</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <name>chapter05a Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>

        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.3.9.RELEASE</version>
        </dependency>


        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>


    </dependencies>
    <build>
        <finalName>chapter05a</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

部署描述符web.xml

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

<?xml version="1.0" encoding="UTF-8"?>
<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>springmvc</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/config/springmvc-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>    
    </servlet>

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

     <!-- 避免中文乱码 -->
    <filter>  
        <filter-name>characterEncodingFilter</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>  
        <init-param>  
            <param-name>forceEncoding</param-name>  
            <param-value>true</param-value>  
        </init-param>  
    </filter>  
    <filter-mapping>  
        <filter-name>characterEncodingFilter</filter-name>  
        <url-pattern>/*</url-pattern>  
    </filter-mapping> 



</web-app>

配置Spring MVC配置文件

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

<?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: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">

    <!-- 扫描Controller -->
    <context:component-scan base-package="com.artisan.springmvc.controller"/>
    <!-- 扫描Service -->
    <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容器的时候不报错即可。

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,应该有如下几个属性

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

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

    private int orgId;
    private String orgName;
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;
    }

}

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数据

@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<Artisan> 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类

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

package com.artisan.springmvc.service;

import java.util.List;

import com.artisan.springmvc.domian.Artisan;

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

接口实现类

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<Artisan> artisanList = null;
    String sex = null;
    /**
     * 
     * 创建一个新的实例 ArtisanServiceImpl的同时初始化模拟数据
     *
     */
    public ArtisanServiceImpl() {
        super();
        // 初始化模式数据
        artisanList = new ArrayList<Artisan>();
        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<Artisan> getArtisans() {
        return artisanList;
    }

}

视图

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

ArtisanList.jsp

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML>
<html>
<head>
<title>Artisan List</title>
<style type="text/css">@import url("<c:url value="/css/main.css"/>");</style>
</head>
<body>

<div id="global">
<h1>Artisan List</h1>

<p>
    <a href='<c:url value="/artisan/artisan_input"/>'>Add Artisan</a>
</p>


<table border="1" cellspacing="0">
<tr>
    <th align="center">OrgName</th>
    <th align="center">ArtisanName</th>
    <th align="center">Code</th>
    <th align="center">Sex</th>
    <th align="center" colspan="2">Operation</th>
</tr>

<!-- var要循环集合的别名 -->
<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>Edit</a></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

<a href='<c:url value="/artisan/artisan_input"/>'>Add Artisan</a>

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


Controller映射方法

/**
     * 
    * @Title: inputArtisan  
    * @Description: 进入inputArtisan的页面 
    * @param @return    参数  
    * @return String    返回类型  
    * @throws
     */
    @RequestMapping(value="/artisan_input")
    public String inputArtisan(Model model){
        // 获取全部的org
        List<Org> 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会根据视图解析器的配置规则

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

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


AddArtisan.jsp

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML>
<html>
<head>
<title>Add Artisan Form</title>
<style type="text/css">@import url("<c:url value="/css/main.css"/>");</style>
</head>
<body>

<div id="global">
<form:form commandName="artisan" action="artisan_add" method="post">
    <fieldset>
        <legend>Add an Artisan</legend>
        <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的接口及实现类

实现类如下

@Override
    public Org getOrg(int orgId) {
        for (Org org:orgList) {
            if (orgId == org.getOrgId()) {
                return org;
            }
        }
        return null;
    }

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

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

代码如下

@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如下

<a href="artisan_edit/${artisan.id}">Edit</a>

编写映射方法

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

/**
     * 
    * @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<Org> 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

<%@ 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); 
%>
<!DOCTYPE HTML>
<html>
<head>
<title>Edit Artisan Form</title>
<style type="text/css">@import url("<c:url value="/css/main.css"/>");</style>
</head>
<body>

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

        <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” ,映射

    @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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小工匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值