Spring MVC 学习总结(一)——MVC概要与环境配置
目录
一、MVC概要
MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范,用一种将业务逻辑、数据、显示分离的方法组织代码,MVC主要作用是降低了视图与业务逻辑间的双向偶合。MVC不是一种设计模式,MVC是一种架构模式。当然不同的MVC存在差异。
在web早期的开发中,通常采用的都是Model1。Model1中,如图所示主要分为两层,视图层和模型层。Model2把一个项目分成三部分,包括视图、控制、模型。这样不仅提高的代码的复用率与项目的扩展性,且大大降低了项目的维护成本。Model 1模式的实现比较简单,适用于快速开发小规模项目,Model1中JSP页面身兼View和Controller两种角色,将控制逻辑和表现逻辑混杂在一起,从而导致代码的重用性非常低,增加了应用的扩展性和维护的难度。Model2消除了Model1的缺点。
Model1
Model2
常见的服务器端MVC框架有:Struts、Spring MVC、ASP.NET MVC、Zend Framework、JSF;常见前端MVC框架:angularjs、reactjs、backbone;由MVC演化出了另外一些模式如:MVP、MVVM。
二、Spring MVC介绍
Spring MVC是Spring Framework的一部分,是基于Java实现MVC的轻量级Web框架。Spring MVC的特点:
1、轻量
2、高效
3、与Spring兼容性好
4、功能强大
RESTful、数据验证、格式化、绑定机制、本地化、主题等
5、简洁灵活
Spring的web框架围绕DispatcherServlet设计。 DispatcherServlet的作用是将请求分发到不同的处理器。从Spring 2.5开始,使用Java 5或者以上版本的用户可以采用基于注解的controller声明方式。官网上说Spring的web模块提供了大量独特的功能,包括:
清晰的角色划分:控制器(controller)、验证器(validator)、 命令对象(command object)、表单对象(form object)、模型对象(model object)、 Servlet分发器(DispatcherServlet)、 处理器映射(handler mapping)、视图解析器(view resolver)等等。 每一个角色都可以由一个专门的对象来实现。
强大而直接的配置方式:将框架类和应用程序类都能作为JavaBean配置,支持跨多个context的引用,例如,在web控制器中对业务对象和验证器(validator)的引用。
可适配、非侵入:可以根据不同的应用场景,选择合适的控制器子类 (simple型、command型、form型、wizard型、multi-action型或者自定义),而不是从单一控制器 (比如Action/ActionForm)继承。
可重用的业务代码:可以使用现有的业务对象作为命令或表单对象,而不需要去扩展某个特定框架的基类。
可定制的绑定(binding)和验证(validation):比如将类型不匹配作为应用级的验证错误, 这可以保存错误的值。再比如本地化的日期和数字绑定等等。在其他某些框架中,你只能使用字符串表单对象, 需要手动解析它并转换到业务对象。
可定制的handler mapping和view resolution:Spring提供从最简单的URL映射, 到复杂的、专用的定制策略。与某些web MVC框架强制开发人员使用单一特定技术相比,Spring显得更加灵活。
灵活的model转换:在Springweb框架中,使用基于Map的 键/值对来达到轻易地与各种视图技术的集成。
可定制的本地化和主题(theme)解析:支持在JSP中可选择地使用Spring标签库、支持JSTL、支持Velocity(不需要额外的中间层)等等。
简单而强大的JSP标签库(Spring Tag Library):支持包括诸如数据绑定和主题(theme) 之类的许多功能。它提供在标记方面的最大灵活性。
JSP表单标签库:在Spring2.0中引入的表单标签库,使得在JSP中编写 表单更加容易。
Spring Bean的生命周期可以被限制在当前的HTTP Request或者HTTP Session。 准确的说,这并非Spring MVC框架本身特性,而应归属于Sping MVC使用的WebApplicationContext容器。
三、第一个Spring MVC 项目:Hello World
3.1、通过Maven新建一个Web项目
在Eclipse中新建Maven项目,选择“Create a simple project”,创建一个简单项目,不选择模板。
修改层面信息,更加详细的内容请参考前面写过的文章:
将webcontent中的所有内容复制到webapp目录下,并删除webContent目录,删除后的结果如下:
修改项目的部署信息,删除测试文件夹,添加webapp为项目根目录:
如果不打算在pom.xml中添加对Server runtime的依赖,则这里必须手动添加依赖,如下图所示:
另外如果pom.xml报错,修改任意位置保存。
3.2、添加依赖的jar包
1、修改pom.xml文件、添加jar包的依赖,主要有:Spring框架核心库、Spring MVC、JSTL等,具体信息如下:
<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>com.zhangguo</groupId>
<artifactId>SpringMVC01</artifactId>
<version>0.0.1</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.0.RELEASE</spring.version>
</properties>
<dependencies>
<!--Spring框架核心库 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Spring MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- JSTL -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
</project>
当依赖成功时,会加载的jar包如下:
3.3、修改web.xml注册中心控制器DispatcherServlet
Spring MVC框架像许多其他MVC框架一样, 请求驱动,围绕一个中心Servlet分派请求及提供其他功能,DispatcherServlet是一个实际的Servlet (它继承自HttpServlet 基类)。如下图所示当发起请求时被前置的控制器拦截到请求,根据请求参数生成代理请求,找到请求对应的实际控制器,控制器处理请求,创建数据模型,访问数据库,将模型响应给中心控制器,控制器使用模型与视图渲染视图结果,将结果返回给中心控制器,再将结果返回给请求者。
修改web.xml文件注册该Servlet,修改后的web.xml文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<servlet>
<!--名称 -->
<servlet-name>springmvc</servlet-name>
<!-- Servlet类 -->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 启动顺序,数字越小,启动越早 -->
<load-on-startup>1</load-on-startup>
<init-param>
<!--SpringMVC配置参数文件的位置 -->
<param-name>contextConfigLocation</param-name>
<!--默认名称为ServletName-servlet.xml -->
<param-value>classpath*:springmvc-servlet.xml</param-value>
</init-param>
</servlet>
<!--所有请求都会被springmvc拦截 -->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
3.4、添加Spring MVC配置文件
在src/main/java源代码目录下添加springmvc-servlet.xml配置文件,配置的形式与Spring容器配置基本类似,为了支持基于注解的IOC,设置了自动扫描包的功能,具体配置信息如下:
<?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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">
<!-- 自动扫描包,实现支持注解的IOC -->
<context:component-scan base-package="com.zhangguo.springmvc01" />
<!-- Spring MVC不处理静态资源 -->
<mvc:default-servlet-handler />
<!-- 支持mvc注解驱动 -->
<mvc:annotation-driven />
<!-- 视图解析器 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="internalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/view/" />
<!-- 后缀 -->
<property name="suffix" value=".jsp" />
</bean>
</beans>
在视图解析中我们把所有的视图都存放在/WEB-INF/目录下,这样是为了视图安全,因为这个目录客户端不能直接访问。
3.5、创建HelloWorld控制器
在src/main/java源代码目录下创建包com.zhangguo.springmvc01.controller,在包下创建一个普通的类:HelloWorld,具体代码如下:
package com.zhangguo.springmvc01.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/Hello")
public class HelloWorld {
@RequestMapping("/Sayhi")
public String SayHi(Model model) {
model.addAttribute("message", "Hello Spring MVC!");
return "sayhi";
}
}
注解为@Controller是为了让Spring IOC容器初始化时自动扫描到;@RequestMapping是为了映射请求路径,这里因为类与方法上都有映射所以访问时应该是/Hello/Sayhi;方法中声明Model类型的参数是为了把Action中的数据带到视图中;方法返回的结果是视图的名称sayhi。
3.6、创建视图
在WEB-INF/view目录中创建视图,视图将从Action中带回的信息展示,具体内容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello Spring MVC!</title>
</head>
<body>
<h2>${message}</h2>
</body>
</html>
3.7、测试运行
启动Tomcat运行项目,请注意查看启动信息,如果有异常应该先解决异常信息,运行成功后的结果如下所示:
Spring MVC 学习总结(二)——控制器定义与@RequestMapping详解
目录
一、控制器定义
控制器提供访问应用程序的行为,通常通过服务接口定义或注解定义两种方法实现。 控制器解析用户的请求并将其转换为一个模型。在Spring MVC中一个控制器可以包含多个Action(动作、方法)。
1.1、实现接口Controller定义控制器
Controller是一个接口,处在包org.springframework.web.servlet.mvc下,接口中只有一个未实现的方法,具体的接口如下所示:
package org.springframework.web.servlet.mvc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
//实现该接口的类获得控制器功能与类型, 解析用户的请求并将其转换为一个模型
public interface Controller {
//处理请求且返回一个模型与视图对象
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
在自定义控制器前先创建一个基于maven的web项目,添加包的依赖,pom.xml文件如下:
View Code
如果不配置scope,会把jar包发布,会跟容器里的jar包冲突、scope要设置为provided,由容器提供,不会发布(或者不配这两个依赖,在项目的Java BuildPath的Libraries里添加Server Runtime)目前scope可以使用5个值:
compile:缺省值,适用于所有阶段,会随着项目一起发布。
provided:类似compile,期望JDK、容器或使用者会提供这个依赖。如servlet.jar。
runtime:只在运行时使用,如JDBC驱动,适用运行和测试阶段。test,只在测试时使用,用于编译和运行测试代码。不会随项目发布。
system:类似provided,需要显式提供包含依赖的jar,Maven不会在Repository中查找它。
创建一个名为Foo的类,实现接口Controller,重写handleRequest方法,代码如下:
package com.zhangguo.springmvc02.controllers;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
/*
* 定义控制器
*/
public class FooController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
//返回一个模型视图对象,指定路径,指定模型的名称为message,值为一段字符串
return new ModelAndView("foo/index", "message", "Hello,我是通过实现接口定义的一个控制器");
}
}
在WEB-INF/views/foo目录下创建一个名为index.jsp的视图,内容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Foo</title>
</head>
<body>
${message}
</body>
</html>
修改springmvc-servlet.xml配置文件,增加一个控制器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:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">
<!-- 自动扫描包,实现支持注解的IOC -->
<context:component-scan base-package="com.zhangguo.springmvc02" />
<!-- Spring MVC不处理静态资源 -->
<mvc:default-servlet-handler />
<!-- 支持mvc注解驱动 -->
<mvc:annotation-driven />
<!-- 视图解析器 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="internalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/views/" />
<!-- 后缀 -->
<property name="suffix" value=".jsp" />
</bean>
<bean name="/foo" class="com.zhangguo.springmvc02.controllers.FooController"></bean>
</beans>
基中name是访问路径,class是自定义的控制器的全名称。运行后的结果如下:
小结:实现接口Controller定义控制器是较老的办法,缺点是:一个控制器中只有一个Action,如果要多个Action则需要定义多个Controller;定义的方式比较麻烦;Spring 2.5以后采用注解的方式定义解决这引起问题。
1.2、使用注解@Controller定义控制器
org.springframework.stereotype.Controller注解类型用于声明Spring类的实例是一个控制器(在讲IOC时还提到了另外3个注解);Spring可以使用扫描机制来找到应用程序中所有基于注解的控制器类,为了保证Spring能找到你的控制器,需要在配置文件中声明组件扫描。
创建一个名了Bar的类,定义为一个控制器,类的具体实现如下:
package com.zhangguo.springmvc02.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 定义控制器
*/
//BarController类的实例是一个控制器,会自动添加到Spring上下文中
@Controller
public class BarController {
//映射访问路径
@RequestMapping("/bar")
public String index(Model model){
//Spring MVC会自动实例化一个Model对象用于向视图中传值
model.addAttribute("message", "这是通过注解定义的一个控制器中的Action");
//返回视图位置
return "foo/index";
}
}
还要需要修改Spring mvc配置文件,启用自动组件扫描功能,在beans中增加如下配置:
<!-- 自动扫描包,实现支持注解的IOC -->
<context:component-scan base-package="com.zhangguo.springmvc02" />
base-package属性用于指定扫描的基础包,可以缩小扫描的范围。运行结果如下:
小结:从代码与运行结果可以看出BarController与FooController同时都指定了一个视图foo/index.jsp,但是页面结果的结果是不一样的,从这里可以看出视图是被复用的,而控制器与视图之间是弱偶合关系。
二、@RequestMapping详解
@RequestMapping注释用于映射url到控制器类或一个特定的处理程序方法。可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。该注解共有8个属性,注解源码如下:
package org.springframework.web.bind.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.Callable;
import org.springframework.core.annotation.AliasFor;
/**
* 用于映射url到控制器类或一个特定的处理程序方法.
*/
//该注解只能用于方法或类型上
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
/**
* 指定映射的名称
*/
String name() default "";
/**
* 指定请求的路径映射,指定的地址可以是uri模板,别名为path
*/
@AliasFor("path")
String[] value() default {};
/** 别名为value,使用path更加形象
* 只有用在一个Servlet环境:路径映射URI(例如“/myPath.do”)。
* Ant风格的路径模式,同时也支持(例如,“/myPath/*.do”)。在方法层面,在主要的映射在类型级别表示相对路径(例如,“edit.do”)
* 的支持。路径映射的URI可能包含占位符(例如“/$ {}连接”)
*/
@AliasFor("value")
String[] path() default {};
/**
* 指定请求谓词的类型如GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE. 收窄请求范围 The
* HTTP request methods to map to, narrowing the primary mapping: GET, POST,
* HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
*/
RequestMethod[] method() default {};
/**
* 映射请求的参数,收窄请求范围 The parameters of the mapped request, narrowing the
* primary mapping.
*/
String[]params() default {};
/**
* 映射请求头部,收窄请求范围 The headers of the mapped request, narrowing the primary
* mapping. RequestMapping(value = "/something", headers =
* "content-type=text/*")
*/
String[] headers() default {};
/**
* 指定处理请求的提交内容类型(Content-Type),例如application/json, text/html,收窄请求范围 The
* consumable media types of the mapped request, narrowing the primary
* mapping.
*/
String[] consumes() default {};
/**
* 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回 The producible media types
* of the mapped request, narrowing the primary mapping. produces =
* "text/plain" produces = {"text/plain", "application/*"} produces =
* "application/json; charset=UTF-8"
*/
String[] produces() default {};
}
从上面的源码可以发现除了name基本都是数组类型,在设置时我们可以指定单个值,如@RequestMapping(value="/foo");也可以同时指定多个值如:@RequestMapping(value={"/foo","/bar"})。
2.1、value 属性指定映射路径或URL模板
指定请求的实际地址,指定的地址可以是URL模板,正则表达式或路径占位,该属性与path互为别名关系,@RequestMapping("/foo")} 与 @RequestMapping(path="/foo")相同。该属性是使用最频繁,最重要的一个属性,如果只指定该属性时可以把value略去。Spring Framework 4.2引入了一流的支持声明和查找注释属性的别名。@AliasFor注解可用于声明一双别名属性,来给注解的属性起别名, 让使用注解时, 更加的容易理解(比如给value属性起别名, 更容易让人理解)。先看一个官网的示例:
@Controller
@RequestMapping("/appointments")
public class AppointmentsController {
private final AppointmentBook appointmentBook;
@Autowired
public AppointmentsController(AppointmentBook appointmentBook) {
this.appointmentBook = appointmentBook;
}
@RequestMapping(method = RequestMethod.GET)
public Map<String, Appointment> get() {
return appointmentBook.getAppointmentsForToday();
}
@RequestMapping(value = "/{day}", method = RequestMethod.GET)
public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso = ISO.DATE) Date day, Model model) {
return appointmentBook.getAppointmentsForDay(day);
}
@RequestMapping(value = "/new", method = RequestMethod.GET)
public AppointmentForm getNewForm() {
return new AppointmentForm();
}
@RequestMapping(method = RequestMethod.POST)
public String add(@Valid AppointmentForm appointment, BindingResult result) {
if (result.hasErrors()) {
return "appointments/new";
}
appointmentBook.addAppointment(appointment);
return "redirect:/appointments";
}
}
2.1.1、指定具体路径字符
2.1.1.1 只注解方法
@Controller
public class FooBarController {
@RequestMapping("/action1")
public String action1(){
return "foo/index";
}
}
访问路径:http://localhost:8087/SpringMVC02/action1
2.1.1.2 同时注解类与方法
@Controller
@RequestMapping("/foobar")
public class FooBarController {
@RequestMapping("/action1")
public String action1(){
return "foo/index";
}
}
访问路径:http://localhost:8087/SpringMVC02/foobar/action1
需要先指定类的路径再指定方法的路径
2.1.1.3 当value为空值
注解在方法上时,如果value为空则表示该方法为类下默认的Action。
@Controller
@RequestMapping("/foobar")
public class FooBarController {
@RequestMapping("/action1")
public String action1(Model model){
//在模型中添加属性message值为action1,渲染页面时使用
model.addAttribute("message", "action1");
return "foo/index";
}
@RequestMapping
public String action2(Model model){
//在模型中添加属性message值为action2,渲染页面时使用
model.addAttribute("message", "action2");
return "foo/index";
}
}
访问action2的路径是:http://localhost:8087/SpringMVC02/foobar,如果加上action2就错误了。
注解在类上时,当value为空值则为默认的控制器,可以用于设置项目的起始页。
@Controller
@RequestMapping
public class FooBarController {
@RequestMapping("/action1")
public String action1(Model model){
//在模型中添加属性message值为action1,渲染页面时使用
model.addAttribute("message", "action1");
return "foo/index";
}
@RequestMapping
public String action2(Model model){
//在模型中添加属性message值为action2,渲染页面时使用
model.addAttribute("message", "action2");
return "foo/index";
}
}
访问路径:http://localhost:8087/SpringMVC02/,同时省去了控制器名与Action名称,可用于欢迎页。
访问action1的路径是:http://localhost:8087/SpringMVC02/action1
2.1.2、路径变量占位,URI模板模式
在Spring MVC可以使用@PathVariable 注释方法参数的值绑定到一个URI模板变量。
@RequestMapping("/action3/{p1}/{p2}")
public String action3(@PathVariable int p1,@PathVariable int p2,Model model){
model.addAttribute("message", p1+p2);
return "foo/index";
}
运行结果:
使用路径变量的好处:使路径变得更加简洁;获得参数更加方便,框架会自动进行类型转换。通过路径变量的类型可以约束访问参数,如果类型不一样,则访问不到action,如这里访问是的路径是/action3/1/a,则路径与方法不匹配,而不会是参数转换失败。
2.1.3、正则表达式模式的URI模板
@RequestMapping(value="/action4/{id:\\d{6}}-{name:[a-z]{3}}")
public String action4(@PathVariable int id,@PathVariable String name,Model model){
model.addAttribute("message", "id:"+id+" name:"+name);
return "foo/index";
}
正则要求id必须为6位的数字,而name必须为3位小写字母,访问结果如下:
2.1.4、矩阵变量@MatrixVariable
矩阵变量可以出现在任何路径段,每个矩阵变量用“;”分隔。例如:“/汽车;颜色=红;年=2012”。多个值可以是“,”分隔“颜色=红、绿、蓝”或变量名称可以重复“颜色=红;颜色=绿色;颜色=蓝”,如下所示:
// GET /pets/42;q=11;r=22
@RequestMapping(value = "/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {
// petId == 42
// q == 11
}
// 矩阵变量
@RequestMapping(value = "/action5/{name}")
public String action5(Model model,
@PathVariable String name, //路径变量,用于获得路径中的变量name的值
@MatrixVariable String r,
@MatrixVariable(required = true) String g, //参数g是必须的
@MatrixVariable(defaultValue = "99", required = false) String b) { //参数b不是必须的,默认值是99
model.addAttribute("message", name + " is #" + r + g + b);
return "foo/index";
}
//Get http://localhost:8087/SpringMVC02/action5/the%20book%20color;r=33;g=66
//the book color is #336699
默认是不允许使用矩阵变量的,需要设置配置文中的RequestMappingHandlerMapping的属性removeSemicolonContent为false;在annotation-driven中增加属性enable-matrix-variables="true",修改后的springmvc-servlet.xml文件如下:
<!-- 支持mvc注解驱动 -->
<mvc:annotation-driven enable-matrix-variables="true" />
<!-- 配置映射媒体类型的策略 -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="removeSemicolonContent" value="false" />
</bean>
访问结果如下:
2.1.5、Ant风格路径模式
@RequestMapping注解也支持ant风格的路径模式,如/myPath/*.do,/owners/*/pets/{petId},示例代码如下:
//Ant风格路径模式
@RequestMapping(value = "/action6/*.do")
public String action6(Model model){
model.addAttribute("message","Ant风格路径模式");
return "foo/index";
}
运行结果:
当然还有关于路径匹配的规则,特殊的优先级高过一般的,更多规则可以参考官方帮助。
2.2、method属性指定谓词类型
用于约束请求的谓词类型,可以收窄请求范围。指定请求谓词的类型如GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE,如下代码所示:
//谓词类型
@RequestMapping(value = "/action6",method={RequestMethod.POST,RequestMethod.DELETE})
public String action6(Model model) {
model.addAttribute("message", "请求谓词只能是POST与DELETE");
return "foo/index";
}
要访问action7请求谓词类型必须是POST或者为DELETE,当我们从浏览器的URL栏中直接请求时为一个GET请求,则结果是405,如下所示:
如果将POST修改为GET则正常了,如下所示:
//谓词类型
@RequestMapping(value = "/action6",method=RequestMethod.GET)
public String action6(Model model) {
model.addAttribute("message", "请求谓词只能是GET");
return "foo/index";
}
2.3、consumes属性指定请求的Content-Type
指定处理请求的提交内容类型(Content-Type),例如application/json, text/html,收窄请求范围,如果用户发送的请求内容类型不匹配则方法不会响应请求,具体使用如下代码所示:
package com.zhangguo.springmvc02.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/home")
public class HomeController {
// 请求内容类型必须为text/html,注意浏览器默认没有指定Content-type
@RequestMapping(value = "/action8",consumes="text/html")
public String action8(Model model) {
model.addAttribute("message", "请求的提交内容类型(Content-Type)是text/html");
return "foo/index";
}
}
在action8的注解中约束发送到服务器的Content-Type必须是text/html类型,如果类型不一致则会报错(415),测试结果如下:
从两个图的对比可以看出当内容类型为text/plain时报客户端错误415,当内容类型为text/html时则响应正常,响应的结果如下:
请求的提交内容类型(Content-Type)是text/html
注意:可以使用!号,如consumes="!text/html"
2.4、produces属性指定响应的Content-Type
指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回,方法才处理客户端的请求否则会报406错误,常用设置如下:
produces = "text/plain" //客户端只接收纯文本
produces = {"text/plain", "application/*"} //客户端接收纯文本与application/*类型的内容
produces = "application/json; charset=UTF-8" //客户端接收json且编码为utf-8
//客户端接收json且编码为utf-8,多数浏览器Accept设置的为*/*,接收任意类型
@RequestMapping(value = "/action9",produces="application/json; charset=UTF-8")
public String action9(Model model) {
model.addAttribute("message", "客户端可以接收的类型是application/json; charset=UTF-8");
return "foo/index";
}
运行结果:
注意:可以使用!号,如produces="!text/html"
2.5、params属性指定请求中必须有特定参数与值
映射请求的参数,收窄请求范围。可以限制客户端发送到服务器的请求参数为某些特定值或不为某些值,如下代码所示:
//请求的参数必须包含id=215与name不等于abc
@RequestMapping(value = "/action10",params={"id=215","name!=abc"})
public String action10(Model model) {
model.addAttribute("message", "请求的参数必须包含id=215与name不等于abc");
return "foo/index";
}
运行结果如下:
name的值如没有指定也是通过的;可以使用不等于;
2.6、headers属性指定请求中必须有特定header值
映射请求头部,收窄请求范围。约束客户端发送的请求头部信息中必须包含某个特定的值或不包含某个值,作用范围明显大于前面讲过的几种,示例代码如下:
//请求头部信息中必须包含Host=localhost:8088
@RequestMapping(value = "/action11",headers="Host=localhost:8088")
public String action11(Model model) {
model.addAttribute("message", "请求头部信息中必须包含Host=localhost:8088");
return "foo/index";
}
运行结果:
修改Host为8087时运行就正常了:
这里同样可以使用!号;可以使用通配符如:Content-Type="application/*"
2.7、name属性指定名称
为当前映射指定一个名称,不常用,一般不会指定。
2.8、path属性指定路径
先看源码中的path与value,定义如下:
@AliasFor("path")
String[] value() default {};
@AliasFor("value")
String[] path() default {};
从Spring 4.2开始引入了@AliasFor注解,可以实现属性的别名,如value本身并没有特定的含义,而path会更加具体,能见名知义,通俗说可以认为两者在使用中是一样的如:@RequestMapping("/foo")} 与 @RequestMapping(path="/foo")相同。示例代码如下:
//映射访问路径为/action12或/myaction,指定映射名称为actionTest
@RequestMapping(path ={"/action12","/myaction"},name="actionTest")
public String action12(Model model) {
model.addAttribute("message", "映射访问路径为/action12或/myaction,指定映射名称为actionTest");
return "foo/index";
}
运行结果:
Spring MVC 学习总结(三)——请求处理方法Action详解
目录
Spring MVC中每个控制器中可以定义多个请求处理方法,我们把这种请求处理方法简称为Action,每个请求处理方法可以有多个不同的参数,以及一个多种类型的返回结果。
一、Action参数类型
如果在请求处理方法中需要访问HttpSession对象,则可以添加HttpSession作为参数,Spring会将对象正确的传递给方法,如:public String action(HttpSession session);若需要访问客户端语言环境和HttpServletRequest对象,则可以在方法签名上包含这样的参数,如:public String action(HttpServletRequest request,Locale locale)。可以在请求中出现的参数类型有:
org.springframework.web.context.request.WebRequest
org.springframework.web.context.request.NativeWebRequest
java.util.Locale 当前请求的语言环境
java.util.TimeZone 时区
java.io.InputStream或java.io.Reader
java.io.OutputStream或java.io.Writer
org.springframework.http.HttpMethod
java.security.Principal
HttpEntity <?>参数用于访问Servlet的HTTP请求的标题和内容
java.util.Map / org.springframework.ui.Model / org.springframework.ui.ModelMap 视图隐含模型
org.springframework.web.servlet.mvc.support.RedirectAttributes 重定向
命令或表单对象
基本数据类型,如int,String,double...
复杂数据类型,如自定义的POJO对象
HandlerAdapter
org.springframework.validation.Errors /
org.springframework.validation.BindingResult 验证结果
org.springframework.web.bind.support.SessionStatus 会话状态
org.springframework.web.util.UriComponentsBuilder
@PathVariable 注解参数访问URI模板变量。
@MatrixVariable 注释参数用于访问位于URI路径段键值对对,矩阵变量。
@RequestParam 注解参数访问特定的Servlet请求参数,请求参数绑定。
@RequestHeader 注解参数访问特定的servlet请求HTTP标头,映射请求头。
@RequestBody 注解参数访问HTTP请求主体,注解映射请求体
@RequestPart 注解参数访问“的multipart
/ form-data的”请求部分的内容。处理客户端上传文件,多部分文件上传的支持
@SessionAttribute 注解参数会话属性
@RequestAttribute 注解参数访问请求属性
1.1、自动参数映射
1.1.1、基本数据类型
方法的参数可以是任意基本数据类型,如果方法参数名与http中请求的参数名称相同时会进行自动映射,视图foo目录下的index.jsp与示例代码如下:
// 自动参数映射
@RequestMapping("/action0")
public String action0(Model model, int id, String name) {
model.addAttribute("message", "name=" + name + ",id=" + id);
return "foo/index";
}
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Foo</title>
</head>
<body>
${message}
</body>
</html>
运行结果如下:
包装类型也一样,但如果参数中没有对应名称与类型的数据则会异常。
1.1.2、自定义数据类型
除了基本数据类型,也可以自定义的数据类型,如一个自定义的POJO对象,Spring MVC会通过反射把请中的参数设置到对象中,转换类型,示例代码如下:
View Code
// 自动参数映射自定义数据类型
@RequestMapping("/action01")
public String action01(Model model, Product product) {
model.addAttribute("message", product);
return "foo/index";
}
运行结果如下:
示例中使用的是的URL中的参数,其实也可以是客户端提交的任意参数,特别是表单中的数据。
1.1.3、复杂数据类型
这里指的复杂数据类型指的是一个自定义类型中还包含另外一个对象类型,如用户类型中包含产品对象:
package com.zhangguo.springmvc03.entities;
public class User {
private String username;
private Product product;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
}
示例代码如下:
// 自动参数映射复杂数据类型
@RequestMapping("/action02")
public String action02(Model model, User user) {
model.addAttribute("message", user.getUsername() + "," + user.getProduct().getName());
return "foo/index";
}
测试运行结果:
为了方便这里我使用的是url,这里当然可以是一个表单,如下代码所示:
<form method="post" action="foo/action02">
username:<input name="username" /><br/>
pdctname:<input name="product.name" /><br/>
<button>提交</button>
</form>
1.1.4、List集合类型
不能直接在action的参数中指定List<T>类型,定义一个类型包装List集合在其中,ProductList类如下所示:
package com.zhangguo.springmvc03.entities;
import java.util.List;
//产品集合
public class ProductList {
private List<Product> items;
public List<Product> getItems() {
return items;
}
public void setItems(List<Product> items) {
this.items = items;
}
}
定义的action代码如下所示:
// 集合类型
@RequestMapping("/action03")
public String action03(Model model, ProductList products) {
model.addAttribute("message", products.getItems().get(0) + "<br/>" + products.getItems().get(1));
return "foo/index";
}
在url中模拟表单数据,提交后的结果如下所示:
这里同样可以使用一个表单向服务器提交数据。
1.1.5、Map集合类型
Map与List的实现方式基本一样,这里先定义了一个包装Map的类型ProductMap,代码如下所示:
package com.zhangguo.springmvc03.entities;
import java.util.Map;
/**
* * 产品字典
*/
public class ProductMap {
private Map<String, Product> items;
public Map<String, Product> getItems() {
return items;
}
public void setItems(Map<String, Product> items) {
this.items = items;
}
}
Action的定义如下:
// Map类型
@RequestMapping("/action04")
public String action04(Model model, ProductMap map) {
model.addAttribute("message", map.getItems().get("p1") + "<br/>" + map.getItems().get("p2"));
return "foo/index";
}
测试运行结果如下:
集合类型基本都一样,set也差不多,问题是如果为了获得一个集合需要刻意去包装会很麻烦,可以通过@RequestParam结合@RequestBody等注解完成。
1.2、@RequestParam参数绑定
简单的参数可以使用上一节中讲过的自动参数映射,复杂一些的需使用@RequestParam完成,虽然自动参数映射很方便,但有些细节是不能处理的,如参数是否为必须参数,名称没有办法指定,参数的默认值就没有有办法做到了。如果使用@RequestParam可以实现请求参数绑定,Spring MVC会自动查找请求中的参数转类型并将与参数进行绑定,示例代码如下:
1.2.1、基本数据类型绑定与注解属性
package com.zhangguo.springmvc03.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
@RequestMapping("/foo")
public class FooController {
@RequestMapping("/action1")
public String action1(Model model, @RequestParam(required = false, defaultValue = "99") int id) {
model.addAttribute("message", id);
return "foo/index";
}
}
@RequestParam共有4个注解属性,required属性表示是否为必须,默认值为true,如果请求中没有指定的参数会报异常;defaultValue用于设置参数的默认值,如果不指定值则使用默认值,只能是String类型的。name与value互为别名关系用于指定参数名称。
运行结果:
1.2.2、List与数组绑定基本数据类型
在上一节中我们使用自动参数映射是不能直接完成List与数组绑定的,结合@RequestParam可以轻松实现,示例代码如下所示:
// List集合与数组类型
@RequestMapping("/action05")
public String action05(Model model, @RequestParam("u") List<String> users) {
model.addAttribute("message", users.get(0) + "," + users.get(1));
return "foo/index";
}
运行结果:
直接在URL中输入测试数据可以绑定成功,使用表单同样可行,页面脚本如下:
<form action="bar/action11" method="post">
<p>
<label>爱好:</label>
<input type="checkbox" value="15" name="id" />阅读
<input type="checkbox" value="20" name="id" />上网
<input type="checkbox" value="73" name="id" />电游
</p>
<button>提交</button>
</form>
请求处理方法action代码如下:
// List与数组绑定基本数据类型
@RequestMapping("/action11")
public String action11(Model model, @RequestParam("id") List<Integer> ids) {
model.addAttribute("message", Arrays.deepToString(ids.toArray()));
return "bar/index";
}
运行结果:
@RequestParam("id")是必须的,因为页面中的表单name的名称为id,所有服务器在收集数据时应该使用id页非ids,如果同名则可以省去。
1.2.3、List与数组直接绑定自定义数据类型与AJAX
上一小节中我们绑定的集合中存放的只是基本数据类型,如果需要直接绑定更加复杂的数据类型则需要使用@RequestBody与@ResponseBody注解了,先解释一下他们的作用:
@RequestBody 将HTTP请求正文转换为适合的HttpMessageConverter对象。
@ResponseBody 将内容或对象作为 HTTP 响应正文返回,并调用适合HttpMessageConverter的Adapter转换对象,写入输出流。
AnnotationMethodHandlerAdapter将会初始化7个转换器,可以通过调用AnnotationMethodHandlerAdapter的getMessageConverts()方法来获取转换器的一个集合 List<HttpMessageConverter>,7个转换器类型分别是
ByteArrayHttpMessageConverter
StringHttpMessageConverter
ResourceHttpMessageConverter
SourceHttpMessageConverter
XmlAwareFormHttpMessageConverter
Jaxb2RootElementHttpMessageConverter
MappingJacksonHttpMessageConverter
@RequestBody默认接收的Content-Type是application/json,因此发送POST请求时需要设置请求报文头信息,否则Spring MVC在解析集合请求参数时不会自动的转换成JSON数据再解析成相应的集合,Spring默认的json协议解析由Jackson完成。要完成这个功能还需要修改配置环境,具体要求如下:
a)、修改Spring MVC配置文件,启用mvc注解驱动功能,<mvc:annotation-driven />
b)、pom.xml,添加jackson依赖,添加依赖的配置内容如下:
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.5.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.2</version>
</dependency>
c)、ajax请求时需要设置属性dataType 为 json,contentType 为 'application/json;charse=UTF-8',data 转换成JSON字符串,如果条件不满足有可能会出现415异常。
Action定义的示例代码如下:
// List与数组直接绑定自定义数据类型与AJAX
@RequestMapping("/action21")
public void action21(@RequestBody List<Product> products, HttpServletResponse response) throws IOException {
response.setCharacterEncoding("UTF-8");
System.out.println(Arrays.deepToString(products.toArray()));
response.getWriter().write("添加成功");
}
action21的参数@RequestBody List<Product> products是接收从客户端发送到服务器的产品集合,默认的请求内容并非是application/json,而是:application/x-www-form-urlencoded,在参数前增加@RequestBody的作用是让Spring MVC在收到客户端请求时将选择合适的转换器将参数转换成相应的对象。action22的返回值为List<Product>,且在方法上有一个注解@ResponseBody,系统会使用jackson将该对象自动序列化成json字符;在客户端请求时设置内容类型为application/json,定义一个myform21.jsp页面,页面的脚本如下所示:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>List与数组直接绑定自定义数据类型与AJAX</title>
</head>
<body>
<button type="button" onclick="addPdts_click1();">向服务器发送json</button>
<button type="button" onclick="addPdts_click2();">接收服务器返回的json</button>
<p id="msg"></p>
<script type="text/javascript"
src="<c:url value="/scripts/jQuery1.11.3/jquery-1.11.3.min.js"/>"></script>
<script type="text/javascript">
var products = new Array();
products.push({
id : 1,
name : "iPhone 6 Plus",
price : 4987.5
});
products.push({
id : 2,
name : "iPhone 7 Plus",
price : 5987.5
});
products.push({
id : 3,
name : "iPhone 8 Plus",
price : 6987.5
});
function addPdts_click1() {
$.ajax({
type : "POST",
//请求谓词类型
url : "bar/action21",
data : JSON.stringify(products), //将products对象转换成json字符串
contentType : "application/json;charset=UTF-8",
//发送信息至服务器时内容编码类型,(默认: "application/x-www-form-urlencoded")
dataType : "text", //预期服务器返回的数据类型
success : function(result) {
$("#msg").html(result);
}
});
}
function addPdts_click2() {
$.ajax({
type : "POST",
//请求谓词类型
url : "bar/action22",
data : JSON.stringify(products), //将products对象转换成json字符串
contentType : "application/json;charset=UTF-8",
//发送信息至服务器时内容编码类型,(默认: "application/x-www-form-urlencoded")
dataType : "json", //预期服务器返回的数据类型
success : function(result) {
var str = "";
$.each(result, function(i, obj) {
str += "编号:" + obj.id + ",名称:" + obj.name + ",价格:"+ obj.price + "<br/>";
});
$("#msg").html(str);
}
});
}
</script>
</body>
</html>
页面中有两个方法,第一个方法是实现将一个json集合发送到服务器并映射成一个List集合;第二个方法是实现接收服务器返回的json对象。
点击按钮1时的运行结果如下:
控制台输出:
[编号(id):1,名称(name):iPhone 6 Plus,价格(price):4987.5, 编号(id):2,名称(name):iPhone 7 Plus,价格(price):5987.5, 编号(id):3,名称(name):iPhone 8 Plus,价格(price):6987.5]
点击按钮2时的运行结果如下:
1.3、重定向与Flash属性
在一个请求处理方法Action中如果返回结果为“index”字符则表示转发到视图index,有时候我们需要重定向,则可以在返回的结果前加上一个前缀“redirect:”,可以重定向到一个指定的页面也可以是另一个action,示例代码如下:
// 重定向
@RequestMapping("/action2")
public String action2(Model model) {
return "foo/index";
}
@RequestMapping("/action3")
public String action3(Model model) {
model.addAttribute("message", "action3Message");
return "redirect:action2";
}
当请求http://localhost:8087/SpringMVC02/foo/action3时运行结果如下:
在action3中返回的结果为redirect:action2,则表示重定向到action2这个请求处理方法,所有重定向都是以当前路径为起点的,请注意路径。在action3向model中添加了名称message的数据,因为重定向到action2中会发起2次请求,为了保持action3中的数据Spring MVC自动将数据重写到了url中。为了实现重定向时传递复杂数据,可以使用Flash属性,示例代码如下:
// 接收重定向参数
@RequestMapping("/action2")
public String action2(Model model, Product product) {
model.addAttribute("message", product);
System.out.println(model.containsAttribute("product")); // true
return "foo/index";
}
//重定向属性
@RequestMapping("/action3")
public String action3(Model model, RedirectAttributes redirectAttributes) {
Product product = new Product(2, "iPhone7 Plus", 6989.5);
redirectAttributes.addFlashAttribute("product", product);
return "redirect:action2";
}
当访问action3时,首先创建了一个product产口对象,将该对象添加到了Flash属性中,在重定向后取出,个人猜测应该暂时将对象存入了Session中。当请求foo/action3时运行结果如下:
url地址已经发生了变化,product对象其实也已经被存入了model中,在action的视图中可以直接拿到。
1.4、@ModelAttribute模型特性
@ModelAttribute可以应用在方法参数上或方法上,他的作用主要是当注解在方法中时会将注解的参数对象添加到Model中;当注解在请求处理方法Action上时会将该方法变成一个非请求处理的方法,但其它Action被调用时会首先调用该方法。
1.4.1、注解在参数上
当注解在参数上时会将被注解的参数添加到Model中,并默认完成自动数据绑定,示例代码如下:
@RequestMapping("/action6")
public String action6(Model model, @ModelAttribute(name = "product", binding = true) Product entity) {
model.addAttribute("message", model.containsAttribute("product") + "<br/>" + entity);
return "foo/index";
}
运行结果:
其实不使用@ModelAttribute我也样可以完成参数与对象间的自支映射,但使用注解可以设置更多详细内容,如名称,是否绑定等。
1.4.2、注解在方法上
用于标注一个非请求处理方法,通俗说就是一个非Action,普通方法。如果一个控制器类有多个请求处理方法,以及一个有@ModelAttribute注解的方法,则在调用其它Action时会先调用非请求处理的Action,示例代码如下:
@RequestMapping("/action7")
public String action7(Model model) {
Map<String, Object> map = model.asMap();
for (String key : map.keySet()) {
System.out.println(key + ":" + map.get(key));
}
return "foo/index";
}
@ModelAttribute
public String noaction() {
System.out.println("noaction 方法被调用!");
String message = "来自noaction方法的信息";
return message;
}
当访问http://localhost:8087/SpringMVC03/foo/action7时,控制台显示结果如下:
非请求处理方法可以返回void,也可以返回一个任意对象,该对象会被自动添加到每一个要被访问的Action的Model中,key从示例中可以看出为类型名称。
二、Action返回值类型
ModelAndView
Model
Map 包含模型的属性
View
String 视图名称
void
HttpServletResponse
HttpEntity<?>或ResponseEntity<?>
HttpHeaders
Callable<?>
DeferredResult<?>
ListenableFuture<?>
ResponseBodyEmitter
SseEmitter
StreamingResponseBody
其它任意类型,Spring将其视作输出给View的对象模型
2.1、视图中url问题
新增一个action5,代码如下:
@RequestMapping("/action5")
public String action5(Model model) {
return "foo/action5";
}
在foo目录下添加视图action5.jsp,内容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>action5的视图</title>
</head>
<body>
<img alt="风景" src="../../images/3.jpg">
</body>
</html>
目标结构如下:
访问结果:
这里图片访问不到的原因是因为:action5.jsp视图此时并非以它所在的目录为实际路径,他是以当前action所在的控制器为起始目录的,当前控制器的url为:http://localhost:8087/SpringMVC02/foo/,而图片的src为:../../images/3.jpg,向上2级后变成:http://localhost:8087/images/3.jpg,但我们的项目实际路径中并没有存放3.jpg这张图片,解决的办法是在视图中使用“绝对”路径;另外一个问题是我们将静态资源存放到WEB-INF下不太合理,因为该目录禁止客户端访问,修改后的视图如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>action5的视图</title>
</head>
<body>
<img alt="风景" src="<c:url value="/images/3.jpg"></c:url>">
</body>
</html>
目录结构变化后如下所示:
运行结果:
小结:主要是借助了标签<c:url value="/images/3.jpg"></c:url>,将路径转换成“绝对路径”;建议在引用外部资源如js、css、图片信息时都使用该标签解析路径。
2.2、返回值为String
2.2.1、String作为视图名称
默认如果action返回String,此时的String为视图名称,会去视图解析器的设定的目录下查找,查找的规则是:URL= prefix前缀+视图名称 +suffix后缀组成,示例代码如下:
//返回值为String
@RequestMapping("/action31")
public String action31(Model model)
{
model.addAttribute("message","action31");
return "bar/action31";
}
Spring MVC的配置文件内容如下:
<!-- 视图解析器 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="internalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/views/" />
<!-- 后缀 -->
<property name="suffix" value=".jsp" />
</bean>
实际url=/WEB-INF/views/bar/action31.jsp
2.2.2、String作为内容输出
如果方法声明了注解@ResponseBody ,将内容或对象作为 HTTP 响应正文返回,并调用适合HttpMessageConverter的Adapter转换对象,写入输出流。些时的String不再是路径而是内容,示例脚本如下:
@RequestMapping("/action32")
@ResponseBody
public String action32()
{
return "not <b>path</b>,but <b>content</b>";
}
测试运行结果:
2.3、返回值为void
void在普通方法中是没有返回值的意思,但作为请求处理方法并非这样,存在如下两种情况:
2.3.1、方法名默认作为视图名
当方法没有返回值时,方法中并未指定视图的名称,则默认视图的名称为方法名,如下代码所示:
@RequestMapping("/action33")
public void action33()
{
}
直接会去访问的路径是:url=/WEB-INF/views/bar/action33.jsp,bar是当前控制器映射的路径,action33是方法名,上面的代码等同于:
@RequestMapping("/action33")
public String action33()
{
return "bar/action33"; //bar是控制器的路径
}
可见URL= prefix前缀+控制器路径+方法名称 +suffix后缀组成。
2.3.2、直接响应输出结果
当方法的返回值为void,但输出流中存在输出内容时,则不会去查找视图,而是将输入流中的内容直接响应到客户端,响应的内容类型是纯文本,如下代码所示:
@RequestMapping("/action34")
public void action34(HttpServletResponse response) throws IOException
{
response.getWriter().write("<h2>void method</h2>");
}
运行结果如下:
可以看到h2标签并未渲染成标题。
2.4、返回值为ModelAndView
在旧的Spring MVC中ModelAndView使用频率非常高,它可以同时指定须返回的模型与视图对象或名称,示例代码如下:
@RequestMapping("/action35")
public ModelAndView action35()
{
//1只指定视图
//return new ModelAndView("/bar/index");
//2分别指定视图与模型
//Map<String, Object> model=new HashMap<String,Object>();
//model.put("message", "ModelAndView action35");
//return new ModelAndView("/bar/index",model);
//3同时指定视图与模型
//return new ModelAndView("/bar/index","message","action35 ModelAndView ");
//4分开指定视图与模型
ModelAndView modelAndView=new ModelAndView();
//指定视图名称
modelAndView.setViewName("/bar/index");
//添加模型中的对象
modelAndView.addObject("message", "<h2>Hello ModelAndView</h2>");
return modelAndView;
}
ModelAndView有个多构造方法重载,单独设置属性也很方便,运行结果如下:
2.5、返回值为Map
当返回结果为Map时,相当于只是返回了Model,并未指定具体的视图,返回视图的办法与void是一样的,即URL= prefix前缀+控制器路径+方法名称 +suffix后缀组成,示例代码如下:
@RequestMapping("/action36")
public Map<String, Object> action36()
{
Map<String, Object> model=new HashMap<String,Object>();
model.put("message", "Hello Map");
model.put("other", "more item");
return model;
}
实际访问的路径是:/SpringMVC03/WEB-INF/views/bar/action36.jsp,返回给客户端的map相当于模型,在视图中可以取出。
2.6、返回值为任意类型
2.6.1、返回值为基本数据类型
当返回结果直接为int,double,boolean等基本数据类型时的状态,测试代码如下:
@RequestMapping("/action37")
public Integer action37()
{
return 9527;
}
测试运行的结果是:exception is java.lang.IllegalArgumentException: Unknown return value type异常。
如果确实需要直接将基本数据类型返回,则可以使用注解@ReponseBody。
@RequestMapping("/action38")
@ResponseBody
public int action38()
{
return 9527;
}
运行结果:
2.6.2、当返值为自定义类型
当返回值为自定义类型时Spring会把方法认为是视图名称,与返回值为void的类似办法处理URL,但页面中获得数据比较麻烦,示例代码如下:
@RequestMapping("/action39")
public Product action39()
{
return new Product(1,"iPhone",1980.5);
}
如果存在action39对应的视图,页面还是可以正常显示。
如果在action上添加@ResponseBody注解则返回的是Product本身,而非视图,Spring会选择一个合适的方式解析对象,默认是json。示例代码如下:
@RequestMapping("/action39")
@ResponseBody
public Product action39()
{
return new Product(1,"iPhone",1980.5);
}
运行结果:
如果是接收json值,则需要使用注解@RequestBody指定在相应参数上。
2.7、返回值为Model类型
该接口Model定义在包org.springframework.ui下,model对象会用于页面渲染,视图路径使用方法名,与void类似。示例代码如下:
@RequestMapping("/action40")
public Model action40(Model model)
{
model.addAttribute("message", "返回类型为org.springframework.ui.Model");
return model;
}
运行结果:
返回的类型还有许多如view等,通过view可指定一个具体的视图,如下载Excel、Pdf文档,其实它们也修改http的头部信息,手动同样可以实现,如下代码所示:
@RequestMapping("/action41")
@ResponseBody
public String action41(HttpServletResponse response)
{
response.setHeader("Content-type","application/octet-stream");
response.setHeader("Content-Disposition","attachment; filename=table.xls");
return "<table><tr><td>Hello</td><td>Excel</td></tr></table>";
}
运行结果:
2.8、小结
使用 String 作为请求处理方法的返回值类型是比较通用的方法,这样返回的逻辑视图名不会和请求 URL 绑定,具有很高的灵活性,而模型数据又可以通过Model控制。
使用void,map,Model时,返回对应的逻辑视图名称真实url为:prefix前缀+控制器路径+方法名 +suffix后缀组成。
使用String,ModelAndView返回视图名称可以不受请求的url绑定,ModelAndView可以设置返回的视图名称。
另外在非MVC中使用的许多办法在Action也可以使用。
目录
一、表单标签库
1.1、简介
从Spring2.0起就提供了一组全面的自动数据绑定标签来处理表单元素。生成的标签兼容HTML 4.01与XHTML 1.0。表单标签库中包含了可以用在JSP页面中渲染HTML元素的标签。表单标记库包含在spring-webmvc.jar中,库的描述符称为spring-form.tld,为了使用这些标签必须在jsp页面开头处声明这个tablib指令。
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
以下表格展示了标签库中的常用标签:
标签 |
描述 |
form |
渲染表单元素form |
input |
渲染<input type=”text”/>元素 |
password |
渲染<input type=”password”/>元素 |
hidden |
渲染<input type=”hidden”/>元素 |
textarea |
渲染textarea元素 |
checkbox |
渲染一个<input type=”checkbox”/>复选元素 |
checkboxs |
渲染多个<input type=”checkbox”/>元素 |
radiobutton |
渲染一个<input type=”radio”/>单选元素 |
radiobuttons |
渲染多个<input type=”radio”/>元素 |
select |
渲染一个选择元素 |
option |
渲染一个可选元素 |
options |
渲染多个可选元素列表 |
errors |
在span元素中渲染字段错误 |
1.2、常用属性
path:要绑定的属性路径,最重要的属性,多个元素必填,相当于 modelAttribute.getXXX() 。
cssClass:定义要应用到被渲染元素的CSS类,类样式。
cssStyle:定义要应用到被渲染元素的CSS样式,行内样式。
htmlEscape:接受true或者false,表示是否应该对被渲染的值进行HTML转义。
cssErrorClass:定义要应用到被渲染input元素的CSS类,如果bound属性中包含错误,则覆盖cssClass属性值。
1.3、form标签与input标签
这个标签会生成HTML form标签,同时为form内部所包含的标签提供一个绑定路径(binding path)。 它把命令对象(command object)存在PageContext中,这样form内部的标签就可以使用这个对象了。标签库中的其他标签都声明在form标签的内部。
让我们假设有一个叫User的领域对象,它是一个JavaBean,有着诸如 firstName和lastName这样的属性。我们将把它当作 一个表单支持对象(form backing object),它对应的表单控制器用 form.jsp页面来显示表单。
commandName:暴露表单对象的模型属性名称,默认为command,它定义了模型属性的名称,其中包含了一个backing object,其属性将用于填充生成的表单。如果该属性存在,则必须在返回包含该表单的视图的请求处理方法中添加相应的模型属性。
modelAttribute:暴露form backing object的模型属性名称,默认为command
commandName与modelAttribute功能基本一样,使用modelAttribute就可以了,因为commandName已被抛弃。
如果在页面中使用form不设置任意属性<form:form/>,解析后的结果如下:
<form id="command" action="/SpringMVC04/bar/action11" method="post"></form>
新建一个控制器,在控制器中添加一个action,代码如下:
package com.zhangguo.springmvc04.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.zhangguo.springmvc04.entities.Product;
@Controller
@RequestMapping("/bar")
public class BarController {
@RequestMapping("/action11")
public String action11(Model model){
//向模型中添加一个名为product的对象,用于渲染视图
model.addAttribute("product", new Product("Meizu note1", 999));
return "bar/action11";
}
}
在views/bar目录下添加action11.jsp页面,页面内容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>bar/action11</title>
</head>
<body>
<form:form modelAttribute="product">
<p>
<label for="name">名称:</label>
<form:input path="name" />
</p>
<p>
<label for="price">价格:</label>
<form:input path="price" />
</p>
</form:form>
</body>
</html>
form表单与模型中名称为product的对象进行绑定,form中的表单元素的path指的就是访问该对象的路径,如果没有该对象或找不到属性名将异常。系统将自动把指定模型中的值与页面进行绑定,渲染后的结果如下:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>bar/action11</title>
</head>
<body>
<form id="product" action="/SpringMVC04/bar/action11" method="post">
<p>
<label for="name">名称:</label>
<input id="name" name="name" type="text" value="Meizu note1"/>
</p>
<p>
<label for="price">价格:</label>
<input id="price" name="price" type="text" value="999.0"/>
</p>
</form>
</body>
</html>
运行结果:
模型可以为空,不是为null,中间可以没有数据,但非字符类型会取默认值,如价格会变成0.0。model.addAttribute("product", new Product()),结果如下:
input元素可以设置其它的属性,如前面提到的通用属性,修改后的表单如下:
<p>
<label for="name">名称:</label>
<form:input path="name" cssClass="textCss" cssStyle="color:blue" a="b" htmlEscape="false"/>
</p>
修改action11方法的内容如下:
//向模型中添加一个名为product的对象,用于渲染视图
model.addAttribute("product", new Product("Meizu note1<hr/>", 999));
渲染结果:
<p>
<label for="name">名称:</label>
<input id="name" name="name" class="textCss" style="color:blue" a="b" type="text" value="Meizu note1<hr/>"/>
</p>
默认从服务器发送到客户端的数据中是会编码的,如示例中<hr/>,会解析成<hr>,但我们设置属性htmlEscape="false"结果原样输出;我们在标签中设置a="b"原样解析出来,这里给开发者留了很大的空间,如想使用原input标签的属性都可以直接写。
1.4、checkbox标签
form:checkbox元素将渲染成一个复选框,通过该元素可以获得3种不同类型的值,分别是boolean,数组,基本数据类型,添加一个新的实体类Person,如下所示:
package com.zhangguo.springmvc04.entities;
public class Person {
/*
* 婚否
*/
private boolean isMarried;
/*
* 爱好
*/
private String[] hobbies;
/**
* 学历
*/
private String education;
public boolean getIsMarried() {
return isMarried;
}
public void setIsMarried(boolean isMarried) {
this.isMarried = isMarried;
}
public String[] getHobbies() {
return hobbies;
}
public void setHobbies(String[] hobbies) {
this.hobbies = hobbies;
}
public String getEducation() {
return education;
}
public void setEducation(String education) {
this.education = education;
}
}
特别注意的是boolean类型的值生成的get/set属性名称前是不带get与set的,这样会引起异常,建议手动修改。
在控制器中新增2个action,代码如下:
//checkbox
@RequestMapping("/action21")
public String action21(Model model){
model.addAttribute("person", new Person());
return "bar/action21";
}
@RequestMapping("/action22")
@ResponseBody
public Person action22(HttpServletResponse response,Person person){
return person;
}
在views/bar目录下添加action21视图,视图脚本如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>bar/action21</title>
</head>
<body>
<form:form modelAttribute="person" action="action22">
<p>
<label for="name">婚否:</label>
<form:checkbox path="isMarried" />
</p>
<p>
<label for="name">爱好:</label>
<form:checkbox path="hobbies" value="读书"/>读书
<form:checkbox path="hobbies" value="上网"/>上网
<form:checkbox path="hobbies" value="电影"/>电影
</p>
<p>
<label for="name">毕业:</label>
<form:checkbox path="education" value="本科"/>大学本科
</p>
<p>
<button>提交</button>
</p>
</form:form>
</body>
</html>
渲染后的视图如下:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>bar/action21</title>
</head>
<body>
<form id="person" action="action22" method="post">
<p>
<label for="name">婚否:</label>
<input id="isMarried1" name="isMarried" type="checkbox" value="true"/><input type="hidden" name="_isMarried" value="on"/>
</p>
<p>
<label for="name">爱好:</label>
<input id="hobbies1" name="hobbies" type="checkbox" value="读书"/><input type="hidden" name="_hobbies" value="on"/>读书
<input id="hobbies2" name="hobbies" type="checkbox" value="上网"/><input type="hidden" name="_hobbies" value="on"/>上网
<input id="hobbies3" name="hobbies" type="checkbox" value="电影"/><input type="hidden" name="_hobbies" value="on"/>电影
</p>
<p>
<label for="name">毕业:</label>
<input id="education1" name="education" type="checkbox" value="本科"/><input type="hidden" name="_education" value="on"/>大学本科
</p>
<p>
<button>提交</button>
</p>
</form>
</body>
</html>
运行结果:
form:checkbox在渲染成input标签里会变成2个表单元素,这样可以确保用户没有选择内容时也会将值带会服务器,默认是没有这样的。
小结:checkbox有三种使用方法
第一种用法:若绑定值是java.lang.Boolean类型,则值为true时,input(checkbox)标为checked(选中)。其value(值)属性对应于setValue(Object)值属性的解析值。
第二种用法:若绑定值是Array(数组)类型或java.util.Collection,则配置的setValue(Object)值出现在绑定的Collection中时,input(checkbox)标为checked(选中)。
第三种用法:若绑定值为其他类型,则当配置的setValue(Object)等于其绑定值时,input(checkbox)标为checked(选中)。
1.5、radiobutton标签
这个标签生成类型为radio的HTML input 标签,也就是常见的单选框。这个标签的典型用法是一次声明多个标签实例,所有的标签都有相同的path属性,但是他们的value属性不同。
定义2个action,代码如下:
@RequestMapping("/action31")
public String action31(Model model){
model.addAttribute("person", new Person());
return "bar/action31";
}
@RequestMapping("/action32")
@ResponseBody
public Person action32(HttpServletResponse response,Person person){
return person;
}
在views/bar下定义视图action31.jsp,脚本如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>bar/action31</title>
</head>
<body>
<form:form modelAttribute="person" action="action32">
<p>
<label for="name">学历:</label>
<form:radiobutton path="education" value="专科"/>专科
<form:radiobutton path="education" value="本科"/>本科
<form:radiobutton path="education" value="研究生"/>研究生
</p>
<p>
<button>提交</button>
</p>
</form:form>
</body>
</html>
运行后页面渲染结果:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>bar/action31</title>
</head>
<body>
<form id="person" action="action32" method="post">
<p>
<label for="name">学历:</label>
<input id="education1" name="education" type="radio" value="专科"/>专科
<input id="education2" name="education" type="radio" value="本科"/>本科
<input id="education3" name="education" type="radio" value="研究生"/>研究生
</p>
<p>
<button>提交</button>
</p>
</form>
</body>
</html>
运行结果:
1.6、password标签
这个标签生成类型为password的HTML input标签,渲染后生成一个密码框。input标签的值和表单支持对象相应属性的值保持一致。该标签与input类似,但有一个特殊的属性showPassword, 是否将对象中的值绑定到密码框中,默认为false,也意味着密码框中不会出现默认的掩码。
修改action31,修改后如下所示:
@RequestMapping("/action31")
public String action31(Model model){
Person person=new Person();
person.setEducation("edu");
model.addAttribute("person", person);
return "bar/action31";
}
当页面脚本如下时:
<p>
<label>密码:</label>
<form:password path="education" showPassword="true"/>
</p>
渲染结果:
<p>
<label>密码:</label>
<input id="education" name="education" type="password" value="edu"/>
</p>
当页面脚本如下时:
<p>
<label>密码:</label>
<form:password path="education" showPassword="false"/>
</p>
渲染结果:
<p>
<label>密码:</label>
<input id="education" name="education" type="password" value=""/>
</p>
1.7、select标签
这个标签生成HTML select标签,就是下拉框,多选框。在生成的HTML代码中,被选中的选项和表单支持对象相应属性的值保持一致。这个标签也支持嵌套的option和options标签。
定义两个action,代码如下:
//select 下拉列表
@RequestMapping("/action41")
public String action41(Model model){
List<ProductType> productTypes = new ArrayList<ProductType>();
productTypes.add(new ProductType(11, "数码电子"));
productTypes.add(new ProductType(21, "鞋帽服饰"));
productTypes.add(new ProductType(31, "图书音像"));
productTypes.add(new ProductType(41, "五金家电"));
productTypes.add(new ProductType(51, "生鲜水果"));
model.addAttribute("productTypes", productTypes);
model.addAttribute("person", new Person());
return "bar/action41";
}
@RequestMapping("/action42")
@ResponseBody
public Person action42(HttpServletResponse response,Person person){
return person;
}
在action41中为模型添加了一个属性productTypes,该对象用于绑定到页面的下拉列表框。
在views/bar下添加视图action41.jsp,脚本如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>bar/action41</title>
</head>
<body>
<form:form modelAttribute="person" action="action42">
<p>
<label for="name">产品类型:</label>
<form:select size="3" multiple="multiple" path="education" items="${productTypes}" itemLabel="name" itemValue="id"></form:select>
</p>
<p>
<button>提交</button>
</p>
</form:form>
</body>
</html>
size="3" 表示可见项为3项,默认可见项为1项
multiple="multiple" 允许多选,默认为单选
path="education" 与表单中指定的modelAttribute对象进行双向绑定
items="${productTypes}" 绑定到下拉列表的集合对象
itemLabel="name" 集合中的对象用于作为下拉列表option的text属性
itemValue="id" 集合中的对象用于作为下拉列表option的value属性
渲染后的页面:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>bar/action41</title>
</head>
<body>
<form id="person" action="action42" method="post">
<p>
<label for="name">产品类型:</label>
<select id="education" name="education" multiple="multiple" size="3">
<option value="11">数码电子</option>
<option value="21">鞋帽服饰</option>
<option value="31">图书音像</option>
<option value="41">五金家电</option>
<option value="51">生鲜水果</option></select>
<input type="hidden" name="_education" value="1" />
</p>
<p>
<button>提交</button>
</p>
</form>
</body>
</html>
请注意渲染后一个form:selelct标签变成了2个标签,多出一个hidden,保证没有选择时也有值带回服务器。
运行结果:
1.8、option标签
这个标签生成HTML option标签,可以用于生成select表单元素中的单项,没有path属性,有label与value属性。新增2个action,代码如下:
//option
@RequestMapping("/action51")
public String action51(Model model){
model.addAttribute("person", new Person());
return "bar/action51";
}
@RequestMapping("/action52")
@ResponseBody
public Person action52(HttpServletResponse response,Person person){
return person;
}
在views/bar下新增页面action51.jsp,内容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>bar/action51</title>
</head>
<body>
<form:form modelAttribute="person" action="action52">
<p>
<label for="name">学历:</label>
<form:select path="education">
<form:option value="" >--请选择--</form:option>
<form:option value="大专">大专</form:option>
<form:option value="本科">本科</form:option>
<form:option value="研究生">研究生</form:option>
</form:select>
</p>
<p>
<button>提交</button>
</p>
</form:form>
</body>
</html>
渲染后的页面:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>bar/action51</title>
</head>
<body>
<form id="person" action="action52" method="post">
<p>
<label for="name">学历:</label>
<select id="education" name="education">
<option value="">--请选择--</option>
<option value="大专">大专</option>
<option value="本科">本科</option>
<option value="研究生">研究生</option>
</select>
</p>
<p>
<button>提交</button>
</p>
</form>
</body>
</html>
运行结果:
1.9、options标签
这个标签生成一系列的HTML option标签,可以用它生成select标签中的子标签,在控制器中新增两个action,代码如下:
//options
@RequestMapping("/action61")
public String action61(Model model){
List<ProductType> productTypes = new ArrayList<ProductType>();
productTypes.add(new ProductType(11, "数码电子"));
productTypes.add(new ProductType(21, "鞋帽服饰"));
productTypes.add(new ProductType(31, "图书音像"));
productTypes.add(new ProductType(41, "五金家电"));
productTypes.add(new ProductType(51, "生鲜水果"));
model.addAttribute("productTypes", productTypes);
model.addAttribute("person", new Person());
return "bar/action61";
}
@RequestMapping("/action62")
@ResponseBody
public Person action62(HttpServletResponse response,Person person){
return person;
}
在views/bar下增加一个名为action61.jsp的页面,页面脚本如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>bar/action61</title>
</head>
<body>
<form:form modelAttribute="person" action="action62">
<p>
<label for="name">产品类型:</label>
<form:select path="education">
<form:option value="">--请选择--</form:option>
<form:options items="${productTypes}" itemLabel="name" itemValue="id"/>
</form:select>
</p>
<p>
<button>提交</button>
</p>
</form:form>
</body>
</html>
绑定集合的方法与select类似,渲染后的页面生成结果如下:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>bar/action61</title>
</head>
<body>
<form id="person" action="action62" method="post">
<p>
<label for="name">产品类型:</label>
<select id="education" name="education">
<option value="">--请选择--</option>
<option value="11">数码电子</option>
<option value="21">鞋帽服饰</option>
<option value="31">图书音像</option>
<option value="41">五金家电</option>
<option value="51">生鲜水果</option>
</select>
</p>
<p>
<button>提交</button>
</p>
</form>
</body>
</html>
通过这个方式实现了一个请选择标签,运行结果如下:
上面的这个例子同时使用了option标签和options标签。这两个标签生成的HTML代码是相同的,但是第一个option标签允许你在JSP中明确声明这个标签的值只供显示使用,并不绑定到表单支持对象的属性上。
1.10、textarea、errors标签
这个标签生成HTML textarea标签,就是一个多行文本标签,用法与input非常类似。errors标签用于显示错误信息,如下脚本:
<tr>
<td>学历:</td>
<td><form:textarea path="education" rows="3" cols="20" /></td>
<td><form:errors path="education" /></td>
</tr>
将被渲染成:
<tr>
<td>学历:</td>
<td><textarea id="education" name="education" rows="3" cols="20"></textarea></td>
<td></td>
</tr>
因为当前并没有对应的错误信息,所以errors标签并未生成任何HTML脚本。errors标签生成类型为'span'的HTML标签,用来显示表单验证时出现的错误信息。通过这个标签,你可以访问控制器(controller)和与控制器关联的验证器(validator)产生的错误信息。
1.11、hidden标签
这个标签生成类型为hidden的HTML input标签。在生成的HTML代码中,input标签的值和表单支持对象相应属性的值保持一致。如果你需要声明一个类型为hidden的input标签,但是表单支持对象中没有对应的属性,你只能使用HTML的标签。在控制器下新增两个action,代码如下:
//hidden
@RequestMapping("/action71")
public String action71(Model model){
Person person=new Person();
person.setEducation("99");
model.addAttribute("person", person);
return "bar/action71";
}
@RequestMapping("/action72")
@ResponseBody
public Person action72(HttpServletResponse response,Person person){
return person;
}
在views/bar目录下新增视图action71.jsp,脚本如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>bar/action71</title>
</head>
<body>
<form:form modelAttribute="person" action="action72">
<p>
<form:hidden path="education" />
<input type="hidden" value="1" name="id">
</p>
<p>
<button>提交</button>
</p>
</form:form>
</body>
</html>
渲染后的页面:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>bar/action61</title>
</head>
<body>
<form id="person" action="action62" method="post">
<p>
<input id="education" name="education" type="hidden" value="99"/>
<input type="hidden" value="1" name="id">
</p>
<p>
<button>提交</button>
</p>
</form>
</body>
</html>
运行结果:
隐藏域用于保持页面状态。
1.12、radiobuttons 单选列表与checkboxs复选列表
radiobuttons将生成一组单选框,只允许多个中选择1个;checkboxs生成一组复选列表,允许多选。添加两个action,代码如下:
//radiobuttons,checkboxs
@RequestMapping("/action81")
public String action81(Model model) {
List<ProductType> productTypes = new ArrayList<ProductType>();
productTypes.add(new ProductType(11, "数码电子"));
productTypes.add(new ProductType(21, "鞋帽服饰"));
productTypes.add(new ProductType(31, "图书音像"));
productTypes.add(new ProductType(41, "五金家电"));
productTypes.add(new ProductType(51, "生鲜水果"));
model.addAttribute("productTypes", productTypes);
model.addAttribute("person", new Person());
return "bar/action81";
}
@RequestMapping("/action82")
@ResponseBody
public Person action82(HttpServletResponse response, Person person) {
return person;
}
在views/bar目录下添加一个名为action81.jsp的视图,脚本如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>bar/action81</title>
</head>
<body>
<form:form modelAttribute="person" action="action82">
<p>
<label for="name">产品类型:</label>
<form:radiobuttons path="education" items="${productTypes}" itemLabel="name" itemValue="id" delimiter="," element="a"/>
</p>
<p>
<label for="name">产品类型:</label>
<form:checkboxes path="education" items="${productTypes}" itemLabel="name" itemValue="id" delimiter="-"/>
</p>
<p>
<button>提交</button>
</p>
</form:form>
</body>
</html>
属性delimiter=",",表示生成的单项间使用“,”号分隔,默认为空。
属性element="a",表示生成的单项容器,默认为span。
渲染后结果如下:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>bar/action81</title>
</head>
<body>
<form id="person" action="action82" method="post">
<p>
<label for="name">产品类型:</label>
<a><input id="education1" name="education" type="radio" value="11"/><label for="education1">数码电子</label></a><a>,<input id="education2" name="education" type="radio" value="21"/><label for="education2">鞋帽服饰</label></a><a>,<input id="education3" name="education" type="radio" value="31"/><label for="education3">图书音像</label></a><a>,<input id="education4" name="education" type="radio" value="41"/><label for="education4">五金家电</label></a><a>,<input id="education5" name="education" type="radio" value="51"/><label for="education5">生鲜水果</label></a>
</p>
<p>
<label for="name">产品类型:</label>
<span><input id="education6" name="education" type="checkbox" value="11"/><label for="education6">数码电子</label></span><span>-<input id="education7" name="education" type="checkbox" value="21"/><label for="education7">鞋帽服饰</label></span><span>-<input id="education8" name="education" type="checkbox" value="31"/><label for="education8">图书音像</label></span><span>-<input id="education9" name="education" type="checkbox" value="41"/><label for="education9">五金家电</label></span><span>-<input id="education10" name="education" type="checkbox" value="51"/><label for="education10">生鲜水果</label></span><input type="hidden" name="_education" value="on"/>
</p>
<p>
<button>提交</button>
</p>
</form>
</body>
</html>
运行结果如下:
二、视图解析器
多数MVC框架都为Web应用程序提供一种它自己处理视图的办法,Spring MVC 提供视图解析器,它使用ViewResolver进行视图解析,让用户在浏览器中渲染模型。ViewResolver是一种开箱即用的技术,能够解析JSP、Velocity模板、FreeMarker模板和XSLT等多种视图。
Spring处理视图最重要的两个接口是ViewResolver和View。ViewResolver接口在视图名称和真正的视图之间提供映射关系; 而View接口则处理请求将真正的视图呈现给用户。
2.1、ViewResolver视图解析器
在Spring MVC控制器中,所有的请求处理方法(Action)必须解析出一个逻辑视图名称,无论是显式的(返回String,View或ModelAndView)还是隐式的(基于约定的,如视图名就是方法名)。Spring中的视图由视图解析器处理这个逻辑视图名称,Spring常用的视图解析器有如下几种:
AbstractCachingViewResolver:用来缓存视图的抽象视图解析器。通常情况下,视图在使用前就准备好了。继承该解析器就能够使用视图缓存。这是一个抽象类,这种视图解析器会把它曾经解析过的视图缓存起来,然后每次要解析视图的时候先从缓存里面找,如果找到了对应的视图就直接返回,如果没有就创建一个新的视图对象,然后把它放到一个用于缓存的map中,接着再把新建的视图返回。使用这种视图缓存的方式可以把解析视图的性能问题降到最低。
XmlViewResolver :XML视图解析器。它实现了ViewResolver接口,接受相同DTD定义的XML配置文件作为Spring的XML bean工厂。它继承自AbstractCachingViewResolver抽象类,所以它也是支持视图缓存的。通俗来说就是通过xml指定逻辑名称与真实视图间的关系,示例如下:
<bean class="org.springframework.web.servlet.view.XmlViewResolver">
<property name="location" value="/WEB-INF/views.xml"/>
<property name="order" value="2"/>
</bean>
views.xml是逻辑名与真实视图名的映射文件,order是定义多个视图时的优先级,可以这样定义:
<?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-4.3.xsd">
<bean id="index" class="org.springframework.web.servlet.view.InternalResourceView">
<property name="url" value="/index.jsp" />
</bean>
</beans>
id就是逻辑名称了,在使用时可以在请求处理方法中这样指定:
@RequestMapping("/index")
public String index() {
return "index";
}
从配置可以看出最终还是使用InternalResourceView完成了视图解析。
ResourceBundleViewResolver:它使用了ResourceBundle定义下的bean,实现了ViewResolver接口,指定了绑定包的名称。通常情况下,配置文件会定义在classpath下的properties文件中,默认的文件名字是views.properties。
UrlBasedViewResolver:它简单实现了ViewResolver接口,它不用显式定义,直接影响逻辑视图到URL的映射。它让你不用任何映射就能通过逻辑视图名称访问资源。它是对ViewResolver的一种简单实现,而且继承了AbstractCachingViewResolver,主要就是提供的一种拼接URL的方式来解析视图,它可以让我们通过prefix属性指定一个指定的前缀,通过suffix属性指定一个指定的后缀,然后把返回的逻辑视图名称加上指定的前缀和后缀就是指定的视图URL了。如prefix=/WEB-INF/views/,suffix=.jsp,返回的视图名称viewName=bar/index,则UrlBasedViewResolver解析出来的视图URL就是/WEB-INF/views/bar/index.jsp。redirect:前缀表示重定向,forword:前缀表示转发。使用UrlBasedViewResolver的时候必须指定属性viewClass,表示解析成哪种视图,一般使用较多的就是InternalResourceView,利用它来展现jsp,但是当我们使用JSTL的时候我们必须使用org.springframework.web.servlet.view.JstlView。
InternalResourceViewResolver:内部视图解析器。它是URLBasedViewResolver的子类,所以URLBasedViewResolver支持的特性它都支持。在实际应用中InternalResourceViewResolver也是使用的最广泛的一个视图解析器。
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/views/" />
<!-- 后缀 -->
<property name="suffix" value=".jsp" />
</bean>
在JSP视图技术中,Spring MVC经常会使用 UrlBasedViewResolver视图解析器,该解析器会将视图名称翻译成URL并通过RequestDispatcher处理请求后渲染视图。修改springmvc-servlet.xml配置文件,增加如下视图解析器:
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
VelocityViewResolver:Velocity视图解析器,UrlBasedViewResolver的子类,VelocityViewResolver会把返回的逻辑视图解析为VelocityView。
FreeMarkerViewResolver:FreeMarker视图解析器,UrlBasedViewResolver的子类,FreeMarkerViewResolver会把Controller处理方法返回的逻辑视图解析为FreeMarkerView,使用FreeMarkerViewResolver的时候不需要我们指定其viewClass,因为FreeMarkerViewResolver中已经把viewClass为FreeMarkerView了。Spring本身支持了对Freemarker的集成。只需要配置一个针对Freemarker的视图解析器即可。
ContentNegotiatingViewResolver:内容协商视图解析器,这个视图解析器允许你用同样的内容数据来呈现不同的view,在RESTful服务中可用。
2.2、链式视图解析器
Spring支持同时配置多个视图解析器,也就是链式视图解析器。这样,在某些情况下,就能够重写某些视图。如果我们配置了多个视图解析器,并想要给视图解析器排序的话,设定order属性就可以指定解析器执行的顺序。order的值越高,解析器执行的顺序越晚,当一个ViewResolver在进行视图解析后返回的View对象是null的话就表示该ViewResolver不能解析该视图,这个时候如果还存在其他order值比它大的ViewResolver就会调用剩余的ViewResolver中的order值最小的那个来解析该视图,依此类推。InternalResourceViewResolver这种能解析所有的视图,即永远能返回一个非空View对象的ViewResolver一定要把它放在ViewResolver链的最后面:
<!-- jsp jstl -->
<bean id="JSPViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="viewNames" value="*jsp" />
<property name="contentType" value="text/html; charset=utf-8"/>
<property name="prefix" value="/" />
<property name="suffix" value="" />
<property name="order" value="1"></property>
</bean>
<!-- FreeMarker -->
<bean id="FMViewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.freemarker.FreeMarkerView"/>
<property name="viewNames" value="*html" />
<property name="contentType" value="text/html; charset=utf-8"/>
<property name="cache" value="true" />
<property name="prefix" value="/" />
<property name="suffix" value="" />
<property name="order" value="0"></property>
</bean>
viewClass指定了视图渲染类,viewNames指定视图名称匹配规则如名称以html开头或结束,contentType支持了页面头部信息匹配规则。
2.3、FreeMarker与多视图解析示例
2.3.1、新增两个视图解析器
修改Spring MVC配置文件springmvc-servlet.xml,在beans结点中增加两个视图解析器,一个为内部解析器用于解析jsp与JSTL,另一个为解析FreeMaker格式,修改后的文件如下所示:
<?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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">
<!-- 自动扫描包,实现支持注解的IOC -->
<context:component-scan base-package="com.zhangguo.MavenTest" />
<!-- Spring MVC不处理静态资源 -->
<mvc:default-servlet-handler />
<!-- 支持mvc注解驱动 -->
<mvc:annotation-driven enable-matrix-variables="true" />
<!-- 配置映射媒体类型的策略 -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="removeSemicolonContent" value="false" />
</bean>
<!-- 内部视图解析器,JSP与JSTL模板 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="internalResourceViewResolver">
<!--指定视图渲染类 -->
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<!--自动添加到路径中的前缀 -->
<property name="prefix" value="/WEB-INF/views/" />
<!--自动添加到路径中的后缀 -->
<property name="suffix" value=".jsp" />
<!--设置所有视图的内容类型,如果视图本身设置内容类型视图类可以忽略 -->
<property name="contentType" value="text/html;charset=UTF-8" />
<!-- 优先级,越小越前 -->
<property name="order" value="2" />
</bean>
<!-- FreeMarker视图解析器与属性配置 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<!--是否启用缓存 -->
<property name="cache" value="true" />
<!--自动添加到路径中的前缀 -->
<property name="prefix" value="" />
<!--自动添加到路径中的后缀 -->
<property name="suffix" value=".html" />
<!--指定视图渲染类 -->
<property name="viewClass"
value="org.springframework.web.servlet.view.freemarker.FreeMarkerView" />
<!-- 设置是否暴露Spring的macro辅助类库,默认为true -->
<property name="exposeSpringMacroHelpers" value="true" />
<!-- 是否应将所有request属性添加到与模板合并之前的模型。默认为false。 -->
<property name="exposeRequestAttributes" value="true" />
<!-- 是否应将所有session属性添加到与模板合并之前的模型。默认为false。 -->
<property name="exposeSessionAttributes" value="true" />
<!-- 在页面中使用${rc.contextPath}就可获得contextPath -->
<property name="requestContextAttribute" value="rc" />
<!--设置所有视图的内容类型,如果视图本身设置内容类型视图类可以忽略 -->
<property name="contentType" value="text/html;charset=UTF-8" />
<!-- 优先级,越小越前 -->
<property name="order" value="1" />
</bean>
<!-- 配置FreeMarker细节 -->
<bean id="freemarkerConfig"
class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<!-- 模板路径 -->
<property name="templateLoaderPath" value="/WEB-INF/htmlviews" />
<property name="freemarkerSettings">
<props>
<!-- 刷新模板的周期,单位为秒 -->
<prop key="template_update_delay">5</prop>
<!--模板的编码格式 -->
<prop key="defaultEncoding">UTF-8</prop>
<!--url编码格式 -->
<prop key="url_escaping_charset">UTF-8</prop>
<!--此属性可以防止模板解析空值时的错误 -->
<prop key="classic_compatible">true</prop>
<!--该模板所使用的国际化语言环境选项-->
<prop key="locale">zh_CN</prop>
<!--布尔值格式-->
<prop key="boolean_format">true,false</prop>
<!--日期时间格式-->
<prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop>
<!--时间格式-->
<prop key="time_format">HH:mm:ss</prop>
<!--数字格式-->
<prop key="number_format">0.######</prop>
<!--自动开启/关闭空白移除,默认为true-->
<prop key="whitespace_stripping">true</prop>
</props>
</property>
</bean>
</beans>
这里要注意的是的order越小解析优化级越高,在视图解析过程中,如果order为1的视图解析器不能正确解析视图的话,会将结果交给order为2的视图解析器,这里为2的视图解析器是InternalResourceViewResolver,它总是会生成一个视图的,所以一部内部视图在放在视图解析链的末尾,成一什么都没有找到他还会生成一个404的view返回。
2.3.2、修改pom.xml,添加依赖
为了使用FreeMarker,需要引用spring-context-support与FreeMarker的jar包,修改后的pom.xml配置文件如下:
<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>com.zhangguo</groupId>
<artifactId>SpringMVC04</artifactId>
<version>0.0.1</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.0.RELEASE</spring.version>
</properties>
<dependencies>
<!--Spring框架核心库 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Spring MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- JSTL -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Servlet核心包 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<!--JSP应用程序接口 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.5.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.2</version>
</dependency>
<!-- FreeMarker -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
</dependencies>
</project>
依赖成功后的包:
2.3.3、新增加Controller与两个Action
新增一个名为FooController的控制器,增加两个请求处理方法jstl与ftl,jstl让第2个视图解析器解析,ftl让第1个解析器解析,第1个视图解析器也是默认的视图解析器,示例代码如下:
package com.zhangguo.MavenTest.Controllers;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/foo")
public class FooController {
@RequestMapping("/jstl")
public String jstl(Model model) {
model.addAttribute("message", "Hello JSTL View!");
return "foo/jstl";
}
@RequestMapping("/ftl")
public String ftl(Model model) {
model.addAttribute("users", new String[]{"tom","mark","jack"});
model.addAttribute("message", "Hello FreeMarker View!");
return "foo/ftl";
}
}
2.3.3、新增目录与视图
在WEB-INF/views/foo目录下新增jsp页面jstl.jsp页面,在WEB-INF/htmlviews/foo目录下新增ftl.html页面,目录结构如下:
jstl.jsp页面内容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>foo/jstl for JSTL</title>
</head>
<body>
${message}
</body>
</html>
ftl.html页面内容如下:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>foo/ftl for Freemarker</title>
</head>
<body>
<p>${message}</p>
<ul>
<#list users as user><li>${user}</li></#list>
</ul>
</body>
</html>
2.3.4、运行结果
当请求http://localhost:8087/MavenTest/foo/ftl时运行结果如下:
当请求http://localhost:8087/MavenTest/foo/jstl时运行结果如下:
2.3.5、小结
当访问/foo/ftl时会找到action ftl方法,该方法返回foo/ftl字符串,视图解析器中order为1的解析器去foo目录下找名称为ftl的视图,视图存在,将视图与模型渲染后输出。当访问/foo/jstl时会找到action jstl访问,该方法返回foo/jstl字符串,视图解析器中order为1的解析器去foo目录下找名称为jstl的视图,未能找到,解析失败,转到order为2的视图解析器解析,在目录foo下找到jstl的文件成功,将视图与模板渲染后输出。
如果想视图解析器更加直接的选择可以使用属性viewNames,如viewNames="html*",则会只解析视图名以html开头的视图。
在ftl.html页面中,使用了FreeMarker模板语言,具体的应用细节请看本博客的另一篇文章。
三、综合示例
为了巩固前面学习的内容,通过一个相对综合的示例串联前面学习过的一些知识点,主要实现产品管理管理功能,包含产品的添加,删除,修改,查询,多删除功能,运行结果如下:
3.1、新建一个基于Maven的Web项目
这一步在前面的文章中也多次提到,如果熟悉的话可以跳过。如果初学可以查看前面发布过一些文章中的内容,内容更加详细。如:Spring整合MyBatis(Maven+MySQL)一。
3.1.1、创建项目
新建一个名称为SpringMVC04的Maven简单项目,填写好项目名称,组名,打包选择war。
3.1.2、修改层面信息
在项目上右键选择属性,再选择“Project Facets”,先设置java运行环境为1.7,先去掉"Dynamic Web Module"前的勾,然后保存关闭;再打开勾选上"Dynamic Web Module",版本选择“3.0”;这里在左下解会出现一个超链接,创建“Web Content”,完成关闭。
3.1.3、修改项目的部署内容
项目上右键属性,选择“Deplyment Assembly”,删除不需要发布的内容如:带“test”的两个目录,WebContent目录,再添加一个main下的webapp目录。
修改后的结果如下所示:
3.1.4、修改项目内容。
将WebContent下的内容复制到/src/main/webapp下,再删除WebContent目录。
修改后的目录结构如下:
3.1.5、添加“服务器运行时(Server Runtime)”,当然如果选择直接依赖Servlet与jsp的jar包,则这一步可以跳过,添加后的结果如下:
3.2、添加依赖
项目主要依赖的jar包有Spring核心包、Spring MVC、JSTL、JSP、Servlet核心包、Jackson等,具体的pom.xml文件如下:
<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>com.zhangguo</groupId>
<artifactId>SpringMVC04</artifactId>
<version>0.0.1</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.0.RELEASE</spring.version>
</properties>
<dependencies>
<!--Spring框架核心库 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Spring MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- JSTL -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Servlet核心包 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<!--JSP应用程序接口 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.5.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.2</version>
</dependency>
</dependencies>
</project>
依赖成功后的结果如下:
3.3、配置Spring MVC运行环境
具体的内容请看该系列文章中的第一篇,Spring MVC运行环境引用的包在上一步中已完成,修改web.xml注册中心控制器,修改后的web.xml如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:springmvc-servlet.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>
</web-app>
在源代码根目录下添加spring mvc配置文件springmvc-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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">
<!-- 自动扫描包,实现支持注解的IOC -->
<context:component-scan base-package="com.zhangguo.springmvc04" />
<!-- Spring MVC不处理静态资源 -->
<mvc:default-servlet-handler />
<!-- 支持mvc注解驱动 -->
<mvc:annotation-driven enable-matrix-variables="true" />
<!-- 配置映射媒体类型的策略 -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="removeSemicolonContent" value="false" />
</bean>
<!-- 视图解析器 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="internalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/views/" />
<!-- 后缀 -->
<property name="suffix" value=".jsp" />
</bean>
</beans>
3.4、新建POJO实体(entity)
在包com.zhangguo.springmvc04.entities新增加产品类型类ProductType,代码如下所示:
View Code
添加产品POJO类Product,具体代码如下:
View Code
3.5、新建业务层(Service)
在包com.zhangguo.springmvc04.services下创建产品类型服务接口ProductTypeService,代码如下:
package com.zhangguo.springmvc04.services;
import java.util.List;
import com.zhangguo.springmvc04.entities.ProductType;
/**
* 产品类型服务
*
*/
public interface ProductTypeService {
/**
* 根据产品类型编号获得产品类型对象
*/
public ProductType getProductTypeById(int id);
/**
* 获得所有的产品类型
*/
public List<ProductType> getAllProductTypes();
}
实现类ProductTypeServiceImpl,代码如下:
package com.zhangguo.springmvc04.services;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.zhangguo.springmvc04.entities.ProductType;
@Service
public class ProductTypeServiceImpl implements ProductTypeService {
private static List<ProductType> productTypes;
static {
productTypes = new ArrayList<ProductType>();
productTypes.add(new ProductType(11, "数码电子"));
productTypes.add(new ProductType(21, "鞋帽服饰"));
productTypes.add(new ProductType(31, "图书音像"));
productTypes.add(new ProductType(41, "五金家电"));
productTypes.add(new ProductType(51, "生鲜水果"));
}
@Override
public ProductType getProductTypeById(int id) {
for (ProductType productType : productTypes) {
if (productType.getId() == id) {
return productType;
}
}
return null;
}
@Override
public List<ProductType> getAllProductTypes() {
return productTypes;
}
}
创建产品服务接口ProductService,代码如下:
package com.zhangguo.springmvc04.services;
import java.util.List;
import com.zhangguo.springmvc04.entities.Product;
public interface ProductService {
/*
* 获得所有的产品
*/
List<Product> getAllProducts();
/*
* 获得产品通过编号
*/
Product getProductById(int id);
/*
* 获得产品名称通过名称
*/
List<Product> getProductsByName(String productName);
/**
* 新增产品对象
*/
void addProduct(Product enttiy) throws Exception;
/**
* 更新产品对象
*/
public void updateProduct(Product entity) throws Exception;
/**
* 删除产品对象
*/
void deleteProduct(int id);
/**
* 多删除产品对象
*/
void deletesProduct(int[] ids);
}
实现类ProductServiceImpl,代码如下:
package com.zhangguo.springmvc04.services;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.zhangguo.springmvc04.entities.Product;
@Service
public class ProductServiceImpl implements ProductService {
private static List<Product> products;
static {
ProductTypeService productTypeService = new ProductTypeServiceImpl();
products = new ArrayList<Product>();
products.add(new Product(198, "Huwei P8", 4985.6, productTypeService.getProductTypeById(11)));
products.add(new Product(298, "李宁运动鞋", 498.56, productTypeService.getProductTypeById(21)));
products.add(new Product(398, "Spring MVC权威指南", 49.856, productTypeService.getProductTypeById(31)));
products.add(new Product(498, "山东国光苹果", 4.9856, productTypeService.getProductTypeById(51)));
products.add(new Product(598, "8开门超级大冰箱", 49856.1, productTypeService.getProductTypeById(41)));
}
/*
* 获得所有的产品
*/
@Override
public List<Product> getAllProducts() {
return products;
}
/*
* 获得产品通过编号
*/
@Override
public Product getProductById(int id) {
for (Product product : products) {
if (product.getId() == id) {
return product;
}
}
return null;
}
/*
* 获得产品名称通过名称
*/
@Override
public List<Product> getProductsByName(String productName) {
if(productName==null||productName.equals("")){
return getAllProducts();
}
List<Product> result = new ArrayList<Product>();
for (Product product : products) {
if (product.getName().contains(productName)) {
result.add(product);
}
}
return result;
}
/**
* 新增
* @throws Exception
*/
@Override
public void addProduct(Product entity) throws Exception {
if(entity.getName()==null||entity.getName().equals("")){
throw new Exception("产品名称必须填写");
}
if (products.size() > 0) {
entity.setId(products.get(products.size() - 1).getId() + 1);
} else {
entity.setId(1);
}
products.add(entity);
}
/*
* 更新
*/
public void updateProduct(Product entity) throws Exception
{
if(entity.getPrice()<0){
throw new Exception("价格必须大于0");
}
Product source=getProductById(entity.getId());
source.setName(entity.getName());
source.setPrice(entity.getPrice());
source.setProductType(entity.getProductType());
}
/**
* 删除
*/
@Override
public void deleteProduct(int id){
products.remove(getProductById(id));
}
/*
* 多删除
*/
@Override
public void deletesProduct(int[] ids){
for (int id : ids) {
deleteProduct(id);
}
}
}
3.6、实现展示、查询、删除与多删除功能
在com.zhangguo.springmvc04.controllers包下定义一个名为ProductController的控制器,代码如下所示:
package com.zhangguo.springmvc04.controllers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.zhangguo.springmvc04.services.ProductService;
@Controller
@RequestMapping
public class ProductController {
@Autowired
ProductService productService;
//展示与搜索action
@RequestMapping
public String index(Model model, String searchKey) {
model.addAttribute("products", productService.getProductsByName(searchKey));
model.addAttribute("searchKey", searchKey);
return "product/index";
}
//删除,id为路径变量
@RequestMapping("/delete/{id}")
public String delete(@PathVariable int id){
productService.deleteProduct(id);
return "redirect:/";
}
//多删除,ids的值为多个id参数组成
@RequestMapping("/deletes")
public String deletes(@RequestParam("id")int[] ids){
productService.deletesProduct(ids);
return "redirect:/";
}
}
控制器上的路径映射value并未指定值是让该控制器为默认控制器,index请求处理方法在路径映射注解@RequestMapping中也并未指定value值是让该action为默认action,所有当我们访问系统时这个index就成了欢迎页。
定义所有页面风格用的main.css样式,脚本如下:
View Code
在views目录下新建目录product,在product目录下新建一个视图index.jsp,页面脚本如下所示:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link href="<c:url value="/styles/main.css"/>" type="text/css" rel="stylesheet" />
<title>产品管理</title>
</head>
<body>
<div class="main">
<h2 class="title"><span>产品管理</span></h2>
<form method="get">
名称:<input type="text" name="searchKey" value="${searchKey}"/>
<input type="submit" value="搜索" class="btn out"/>
</form>
<form action="deletes" method="post">
<table border="1" width="100%" class="tab">
<tr>
<th><input type="checkbox" id="chbAll"></th>
<th>编号</th>
<th>产品名</th>
<th>价格</th>
<th>类型</th>
<th>操作</th>
</tr>
<c:forEach var="product" items="${products}">
<tr>
<th><input type="checkbox" name="id" value="${product.id}"></th>
<td>${product.id}</td>
<td>${product.name}</td>
<td>${product.price}</td>
<td>${product.productType.name}</td>
<td>
<a href="delete/${product.id}" class="abtn">删除</a>
<a href="edit/${product.id}" class="abtn">编辑</a>
</td>
</tr>
</c:forEach>
</table>
<p style="color: red">${message}</p>
<p>
<a href="add" class="abtn out">添加</a>
<input type="submit" value="删除选择项" class="btn out"/>
</p>
<script type="text/javascript" src="<c:url value="/scripts/jQuery1.11.3/jquery-1.11.3.min.js"/>" ></script>
</form>
</div>
</body>
</html>
运行结果如下:
搜索:
删除与批量删除:
3.7、新增产品功能
在ProductController控制器中添加两个Action,一个用于渲染添加页面,另一个用于响应保存功能,代码如下:
// 新增,渲染出新增界面
@RequestMapping("/add")
public String add(Model model) {
// 与form绑定的模型
model.addAttribute("product", new Product());
// 用于生成下拉列表
model.addAttribute("productTypes", productTypeService.getAllProductTypes());
return "product/add";
}
// 新增保存,如果新增成功转回列表页,如果失败回新增页,保持页面数据
@RequestMapping("/addSave")
public String addSave(Model model,Product product) {
try {
//根据类型的编号获得类型对象
product.setProductType(productTypeService.getProductTypeById(product.getProductType().getId()));
productService.addProduct(product);
return "redirect:/";
} catch (Exception exp) {
// 与form绑定的模型
model.addAttribute("product", product);
// 用于生成下拉列表
model.addAttribute("productTypes", productTypeService.getAllProductTypes());
//错误消息
model.addAttribute("message", exp.getMessage());
return "product/add";
}
}
在views/product目录下新增视图add.jsp页面,页面脚本如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<link href="styles/main.css" type="text/css" rel="stylesheet" />
<title>新增产品</title>
</head>
<body>
<div class="main">
<h2 class="title"><span>新增产品</span></h2>
<form:form action="addSave" modelAttribute="product">
<fieldset>
<legend>产品</legend>
<p>
<label for="name">产品名称:</label>
<form:input path="name"/>
</p>
<p>
<label for="title">产品类型:</label>
<form:select path="productType.id" items="${productTypes}" itemLabel="name" itemValue="id">
</form:select>
</p>
<p>
<label for="price">产品价格:</label>
<form:input path="price"/>
</p>
<p>
<input type="submit" value="保存" class="btn out">
</p>
</fieldset>
</form:form>
<p style="color: red">${message}</p>
<p>
<a href="<c:url value="/" />" class="abtn out">返回列表</a>
</p>
</div>
</body>
</html>
运行结果:
3.8、编辑产品
在ProductController控制器中添加两个Action,一个用于渲染编辑页面,根据要编辑的产品编号获得产品对象,另一个用于响应保存功能,代码如下:
// 编辑,渲染出编辑界面,路径变量id是用户要编辑的产品编号
@RequestMapping("/edit/{id}")
public String edit(Model model,@PathVariable int id) {
// 与form绑定的模型
model.addAttribute("product", productService.getProductById(id));
// 用于生成下拉列表
model.addAttribute("productTypes", productTypeService.getAllProductTypes());
return "product/edit";
}
// 编辑后保存,如果更新成功转回列表页,如果失败回编辑页,保持页面数据
@RequestMapping("/editSave")
public String editSave(Model model,Product product) {
try {
//根据类型的编号获得类型对象
product.setProductType(productTypeService.getProductTypeById(product.getProductType().getId()));
productService.updateProduct(product);
return "redirect:/";
} catch (Exception exp) {
// 与form绑定的模型
model.addAttribute("product", product);
// 用于生成下拉列表
model.addAttribute("productTypes", productTypeService.getAllProductTypes());
//错误消息
model.addAttribute("message", exp.getMessage());
return "product/edit";
}
}
在views/product目录下新增视图edit.jsp页面,页面脚本如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<link href="<c:url value="/styles/main.css" />" type="text/css" rel="stylesheet" />
<title>编辑产品</title>
</head>
<body>
<div class="main">
<h2 class="title"><span>编辑产品</span></h2>
<form:form action="${pageContext.request.contextPath}/editSave" modelAttribute="product">
<fieldset>
<legend>产品</legend>
<p>
<label for="name">产品名称:</label>
<form:input path="name"/>
</p>
<p>
<label for="title">产品类型:</label>
<form:select path="productType.id" items="${productTypes}" itemLabel="name" itemValue="id">
</form:select>
</p>
<p>
<label for="price">产品价格:</label>
<form:input path="price"/>
</p>
<p>
<form:hidden path="id"/>
<input type="submit" value="保存" class="btn out">
</p>
</fieldset>
</form:form>
<p style="color: red">${message}</p>
<p>
<a href="<c:url value="/" />" class="abtn out">返回列表</a>
</p>
</div>
</body>
</html>
这里要注意路径问题使用c:url不能嵌套在form标签中,所以使用了${ctx},运行结果如下:
完成整个功能后的控制器代码如下:
package com.zhangguo.springmvc04.controllers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.zhangguo.springmvc04.entities.Product;
import com.zhangguo.springmvc04.services.ProductService;
import com.zhangguo.springmvc04.services.ProductTypeService;
@Controller
@RequestMapping
public class ProductController {
@Autowired
ProductService productService;
@Autowired
ProductTypeService productTypeService;
// 展示与搜索action
@RequestMapping
public String index(Model model, String searchKey) {
model.addAttribute("products", productService.getProductsByName(searchKey));
model.addAttribute("searchKey", searchKey);
return "product/index";
}
// 删除,id为路径变量
@RequestMapping("/delete/{id}")
public String delete(@PathVariable int id) {
productService.deleteProduct(id);
return "redirect:/";
}
// 多删除,ids的值为多个id参数组成
@RequestMapping("/deletes")
public String deletes(@RequestParam("id") int[] ids) {
productService.deletesProduct(ids);
return "redirect:/";
}
// 新增,渲染出新增界面
@RequestMapping("/add")
public String add(Model model) {
// 与form绑定的模型
model.addAttribute("product", new Product());
// 用于生成下拉列表
model.addAttribute("productTypes", productTypeService.getAllProductTypes());
return "product/add";
}
// 新增保存,如果新增成功转回列表页,如果失败回新增页,保持页面数据
@RequestMapping("/addSave")
public String addSave(Model model,Product product) {
try {
//根据类型的编号获得类型对象
product.setProductType(productTypeService.getProductTypeById(product.getProductType().getId()));
productService.addProduct(product);
return "redirect:/";
} catch (Exception exp) {
// 与form绑定的模型
model.addAttribute("product", product);
// 用于生成下拉列表
model.addAttribute("productTypes", productTypeService.getAllProductTypes());
//错误消息
model.addAttribute("message", exp.getMessage());
return "product/add";
}
}
// 编辑,渲染出编辑界面,路径变量id是用户要编辑的产品编号
@RequestMapping("/edit/{id}")
public String edit(Model model,@PathVariable int id) {
// 与form绑定的模型
model.addAttribute("product", productService.getProductById(id));
// 用于生成下拉列表
model.addAttribute("productTypes", productTypeService.getAllProductTypes());
return "product/edit";
}
// 编辑后保存,如果更新成功转回列表页,如果失败回编辑页,保持页面数据
@RequestMapping("/editSave")
public String editSave(Model model,Product product) {
try {
//根据类型的编号获得类型对象
product.setProductType(productTypeService.getProductTypeById(product.getProductType().getId()));
productService.updateProduct(product);
return "redirect:/";
} catch (Exception exp) {
// 与form绑定的模型
model.addAttribute("product", product);
// 用于生成下拉列表
model.addAttribute("productTypes", productTypeService.getAllProductTypes());
//错误消息
model.addAttribute("message", exp.getMessage());
return "product/edit";
}
}
}
目录
Spring MVC不仅是在架构上改变了项目,使代码变得可复用、可维护与可扩展,其实在功能上也加强了不少。 验证与文件上传是许多项目中不可缺少的一部分。在项目中验证非常重要,首先是安全性考虑,如防止注入攻击,XSS等;其次还可以确保数据的完整性,如输入的格式,内容,长度,大小等。Spring MVC可以使用验证器Validator与JSR303完成后台验证功能。这里也会介绍方便的前端验证方法。
一、Spring MVC验证器Validator
Spring MVC验证器Validator是一个接口,通过实现该接口来定义对实体对象的验证,接口如下所示:
package org.springframework.validation;
/**
* Spring MVC内置的验证器接口
*/
public interface Validator {
/**
* 是否可以验证该类型
*/
boolean supports(Class<?> clazz);
/**
* 执行验证 target表示要验证的对象 error表示错误信息
*/
void validate(Object target, Errors errors);
}
1.1、定义验证器
package com.zhangguo.springmvc51.entities;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
/**
* 产品验证器
*
*/
public class ProductValidator implements Validator {
//当前验证器可以验证的类型
@Override
public boolean supports(Class<?> clazz) {
return Product.class.isAssignableFrom(clazz);
}
//执行校验
@Override
public void validate(Object target, Errors errors) {
//将要验证的对象转换成Product类型
Product entity=(Product)target;
//如果产品名称为空或为空格,使用工具类
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required", "产品名称必须填写");
//价格,手动判断
if(entity.getPrice()<0){
errors.rejectValue("price", "product.price.gtZero", "产品价格必须大于等于0");
}
//产品类型必须选择
if(entity.getProductType().getId()==0){
errors.rejectValue("productType.id", "product.productType.id.required", "请选择产品类型");
}
}
}
ValidationUtils是一个工具类,中间有一些方可以用于判断内容是否有误。
1.2、执行校验
// 新增保存,如果新增成功转回列表页,如果失败回新增页,保持页面数据
@RequestMapping("/addSave")
public String addSave(Model model, Product product, BindingResult bindingResult) {
// 创建一个产品验证器
ProductValidator validator = new ProductValidator();
// 执行验证,将验证的结果给bindingResult,该类型继承Errors
validator.validate(product, bindingResult);
// 获得所有的字段错误信息,非必要
for (FieldError fielderror : bindingResult.getFieldErrors()) {
System.out.println(fielderror.getField() + "," + fielderror.getCode() + "," + fielderror.getDefaultMessage());
}
// 是否存在错误,如果没有,执行添加
if (!bindingResult.hasErrors()) {
// 根据类型的编号获得类型对象
product.setProductType(productTypeService.getProductTypeById(product.getProductType().getId()));
productService.addProduct(product);
return "redirect:/";
} else {
// 与form绑定的模型
model.addAttribute("product", product);
// 用于生成下拉列表
model.addAttribute("productTypes", productTypeService.getAllProductTypes());
return "product/add";
}
}
注意在参数中增加了一个BindingResult类型的对象,该类型继承自Errors,获得绑定结果,承载错误信息,该对象中有一些方法可以获得完整的错误信息,可以使用hasErrors方法判断是否产生了错误。
1.3、在UI中添加错误标签
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<link href="styles/main.css" type="text/css" rel="stylesheet" />
<title>新增产品</title>
</head>
<body>
<div class="main">
<h2 class="title"><span>新增产品</span></h2>
<form:form action="addSave" modelAttribute="product">
<fieldset>
<legend>产品</legend>
<p>
<label for="name">产品名称:</label>
<form:input path="name"/>
<form:errors path="name" cssClass="error"></form:errors>
</p>
<p>
<label for="title">产品类型:</label>
<form:select path="productType.id">
<form:option value="0">--请选择--</form:option>
<form:options items="${productTypes}" itemLabel="name" itemValue="id"/>
</form:select>
<form:errors path="productType.id" cssClass="error"></form:errors>
</p>
<p>
<label for="price">产品价格:</label>
<form:input path="price"/>
<form:errors path="price" cssClass="error"></form:errors>
</p>
<p>
<input type="submit" value="保存" class="btn out">
</p>
</fieldset>
</form:form>
<p style="color: red">${message}</p>
<p>
<a href="<c:url value="/" />" class="abtn out">返回列表</a>
</p>
</div>
</body>
</html>
发生错误时解析的结果:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<link href="styles/main.css" type="text/css" rel="stylesheet" />
<title>新增产品</title>
</head>
<body>
<div class="main">
<h2 class="title"><span>新增产品</span></h2>
<form id="product" action="addSave" method="post">
<fieldset>
<legend>产品</legend>
<p>
<label for="name">产品名称:</label>
<input id="name" name="name" type="text" value=""/>
<span id="name.errors" class="error">产品名称必须填写</span>
</p>
<p>
<label for="title">产品类型:</label>
<select id="productType.id" name="productType.id">
<option value="0" selected="selected">--请选择--</option>
<option value="11">数码电子</option><option value="21">鞋帽服饰</option><option value="31">图书音像</option><option value="41">五金家电</option><option value="51">生鲜水果</option>
</select>
<span id="productType.id.errors" class="error">请选择产品类型</span>
</p>
<p>
<label for="price">产品价格:</label>
<input id="price" name="price" type="text" value="-10.0"/>
<span id="price.errors" class="error">产品价格必须大于等于0</span>
</p>
<p>
<input type="submit" value="保存" class="btn out">
</p>
</fieldset>
</form>
<p style="color: red"></p>
<p>
<a href="/SpringMVC51/" class="abtn out">返回列表</a>
</p>
</div>
</body>
</html>
1.4、测试运行
控制台输出:
二、JSR303验证器
JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。https://jcp.org/en/home/index
JSR 303 – Bean Validation 是一个数据验证的规范。JSR303只是一个标准,是一验证规范,对这个标准的实现有:
hibernate-validator,Apache BVal等。这里我们使用hibernate-validator实现校验。
2.1、添加hibernate-validator依赖
修改配置pom.xml配置文件,添加依赖。
<!--JSR303 Bean校验-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.2.Final</version>
</dependency>
2.2、注解Bean
在bean中设置验证规则,示例代码如下:
package com.zhangguo.springmvc51.entities;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.Range;
/**
* 产品
*/
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
/*
* 编号
*/
private int id;
/*
* 名称
*/
@Size(min=1,max=50,message="名称长度必须介于{2}-{1}之间")
@Pattern(regexp="^[\\w\\u4e00-\\u9fa5]{0,10}$",message="格式错误,必须是字母数字与中文")
private String name;
/*
* 价格
*/
@Range(min=0,max=1000000,message="价格只允许在{2}-{1}之间")
private double price;
/*
* 产品类型
*/
private ProductType productType;
public Product() {
productType=new ProductType();
}
public Product(String name, double price) {
super();
this.name = name;
this.price = price;
}
public Product(int id, String name, double price, ProductType type) {
super();
this.id = id;
this.name = name;
this.price = price;
this.productType = type;
}
@Override
public String toString() {
return "编号(id):" + this.getId() + ",名称(name):" + this.getName() + ",价格(price):" + this.getPrice()
+ ",类型(productType.Name):" + this.getProductType().getName();
}
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 double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public ProductType getProductType() {
return productType;
}
public void setProductType(ProductType productType) {
this.productType = productType;
}
}
更多的验证注解如下所示:
2.2.1、空值检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
2.2.2、Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
2.2.3、长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) Validates that the annotated string is between min and max included.
2.2.4、日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
2.2.5、正则
@Pattern 验证 String 对象是否符合正则表达式的规则
2.2.6、数值检查
建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为String为"",Integer为null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
2.2.7、范围
@Range(min=, max=) 检查被注解对象的值是否处于min与max之间,闭区间,包含min与max值
@Range(min=10000,max=50000,message="必须介于{2}-{1}之间")
2.2.8、其它注解
@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证),该注解使用在Action的参数上。
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)
2.3、注解控制器参数
在需要使用Bean验证的参数对象上注解@Valid,触发验证,示例代码如下:
package com.zhangguo.springmvc51.controllers;
import java.util.List;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.zhangguo.springmvc51.entities.Product;
import com.zhangguo.springmvc51.services.ProductService;
import com.zhangguo.springmvc51.services.ProductTypeService;
@Controller
@RequestMapping("/goods")
public class GoodsController {
@Autowired
ProductService productService;
@Autowired
ProductTypeService productTypeService;
// 新增,渲染出新增界面
@RequestMapping("/add")
public String add(Model model) {
// 与form绑定的模型
model.addAttribute("product", new Product());
// 用于生成下拉列表
model.addAttribute("productTypes", productTypeService.getAllProductTypes());
return "product/addGoods";
}
// 新增保存,如果新增成功转回列表页,如果失败回新增页,保持页面数据
@RequestMapping("/addGoodsSave")
public String addSave(Model model, @Valid Product product, BindingResult bindingResult) {
// 是否存在错误,如果没有,执行添加
if (!bindingResult.hasErrors()) {
// 根据类型的编号获得类型对象
product.setProductType(productTypeService.getProductTypeById(product.getProductType().getId()));
productService.addProduct(product);
return "redirect:/";
} else {
// 与form绑定的模型
model.addAttribute("product", product);
// 用于生成下拉列表
model.addAttribute("productTypes", productTypeService.getAllProductTypes());
return "product/addGoods";
}
}
@RequestMapping("/products")
@ResponseBody
public List<Product> getProduct(){
return productService.getAllProducts();
}
}
1.4、在UI中添加错误标签
这里与Spring MVC Validator基本一致,在product目录下新增一个名为addGoods.jsp的页面,脚本如下所示:
View Code
1.5、测试运行
小结:从上面的示例可以看出这种验证更加方便直观,一次定义反复使用,以编辑更新时验证同样可以使用;另外验证的具体信息可以存放在配置文件中,如message.properties,这样便于国际化与修改。
三、使用jQuery扩展插件Validate实现前端校验
jquery.validate是基于jQuery的一个B/S客户端验证插件,借助jQuery的优势,我们可以迅速验证一些常见的输入,大大提高了开发效率,下面是很多年前本人做的学习笔记:
3.1、jQuery扩展插件validate—1基本使用方法
3.2、jQuery扩展插件validate—2通过参数设置验证规则
3.3、jQuery扩展插件validate—3通过参数设置错误信息
3.4、jQuery扩展插件validate—4设置错误提示的样式
3.5、jQuery扩展插件validate—5添加自定义验证方法
3.6、jQuery扩展插件validate—6radio、checkbox、select的验证
注意:validate只是使验证变得方便,简单,本质还是使用js,不论多么强大的js验证,当用户把js禁用或使用机器直接发起请求时都不能确保数据的完整性,所有不要把希望寄托在客户端验证,个人认为每一个客户端验证都要服务器进行再次验证。
四、文件上传
在Spring MVC中有两种实现上传文件的办法,第一种是Servlet3.0以下的版本通过commons-fileupload与commons-io完成的通用上传,第二种是Servlet3.0以上的版本的Spring内置标准上传,不需借助第3方组件。通用上传也兼容Servlet3.0以上的版本。
4.1、Servlet3.0以下的通过commons-fileupload上传
4.1.1、添加上传依赖包
因为需要借助第三方上传组件commons-fileupload与commons-io,所以要修改pom.xml文件添加依赖,依赖的内容如下:
<!--文件上传 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
依赖成功后的结果:
4.1.2、新增上传页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>上传文件</title>
</head>
<body>
<h2>上传文件</h2>
<form action="fileSave" method="post" enctype="multipart/form-data">
<p>
<label for="files">文件:</label>
<input type="file" name="files" id="files" multiple="multiple" />
</p>
<p>
<button>提交</button>
</p>
<p>
${message}
</p>
</form>
</body>
</html>
如果有成功上传,页面中有几个关键点要注意:method的值必为Post;enctype必须为multipart/form-data,该类型的编码格式专门用于二进制数据类型;上传表单元素必须拥有name属性;
4.1.3、修改配置文件,增加上传配置
默认情总下Spring MVC对文件上传的视图内容是不能解析的,要配置一个特别的解析器解析上传的内容,修改springmvc-servlet.xml配置文件,增加如下配置内容:
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="utf-8" />
<property name="maxUploadSize" value="10485760000" />
<property name="maxInMemorySize" value="40960" />
</bean>
增加了一个类型为CommonsMultipartResolver类型的解析器,各属性的意义:
defaultEncoding:默认编码格式
maxUploadSize:上传文件最大限制(字节byte)
maxInMemorySize:缓冲区大小
当Spring的前置中心控制器检查到客户端发送了一个多分部请求,定义在上下文中的解析器将被激活并接手处理。解析器将当前的HttpServletRequest包装成一个支持多部分文件上传的MultipartHttpServletRequest对象。在控制器中可以获得上传的文件信息。
CommonsMultipartResolver用于通用的文件上传,支持各种版本的Servlet。
StandardServletMultipartResolver用于Servlet3.0以上的版本上传文件。
4.1.4、增加控制器与Action
package com.zhangguo.springmvc51.controllers;
import java.io.File;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;
@Controller
@RequestMapping("/up")
public class UpFileController {
@RequestMapping("/file")
public String file(Model model){
return "up/upfile";
}
@RequestMapping(value="/fileSave",method=RequestMethod.POST)
public String fileSave(Model model,MultipartFile[] files,HttpServletRequest request) throws Exception{
//文件存放的位置
String path=request.getServletContext().getRealPath("/files");
for (MultipartFile file : files) {
System.out.println(file.getOriginalFilename());
System.out.println(file.getSize());
System.out.println("--------------------------");
File tempFile=new File(path, file.getOriginalFilename());
file.transferTo(tempFile);
}
System.out.println(path);
return "up/upfile";
}
}
注意这里定义的是一个数组,可以接受多个文件上传,如果单文件上传可以修改为MultipartFile类型;另外上传文件的细节在这里并没有花时间处理,比如文件重名的问题,路径问题,关于重名最简单的办法是重新命名为GUID文件名。
4.1.5、测试运行
4.2、Servlet3.0以上文件上传
Servlet3.0以上的版本不再需要第三方组件Commons.io和commons-fileupload,上传的方式与4.1提到基本一样,但配置稍有区别,可以使用@MultipartConfig注解在Servlet上进行配置上传,也可以在web.xml上进行配置。
4.2.1、修改web.xml配置上传参数
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:springmvc-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<!--Servlet3.0以上文件上传配置 -->
<multipart-config>
<max-file-size>5242880</max-file-size><!--上传单个文件的最大限制5MB -->
<max-request-size>20971520</max-request-size><!--请求的最大限制20MB,一次上传多个文件时一共的大小 -->
<file-size-threshold>0</file-size-threshold><!--当文件的大小超过临界值时将写入磁盘 -->
</multipart-config>
</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>
- · file-size-threshold:数字类型,当文件大小超过指定的大小后将写入到硬盘上。默认是0,表示所有大小的文件上传后都会作为一个临时文件写入到硬盘上。
- · location:指定上传文件存放的目录。当我们指定了location后,我们在调用Part的write(String fileName)方法把文件写入到硬盘的时候可以,文件名称可以不用带路径,但是如果fileName带了绝对路径,那将以fileName所带路径为准把文件写入磁盘,不建议指定。
- · max-file-size:数值类型,表示单个文件的最大大小。默认为-1,表示不限制。当有单个文件的大小超过了max-file-size指定的值时将抛出IllegalStateException异常。
- · max-request-size:数值类型,表示一次上传文件的最大大小。默认为-1,表示不限制。当上传时所有文件的大小超过了max-request-size时也将抛出IllegalStateException异常。
4.2.2、修改pom.xml依赖信息
把pom.xml中对文件上传第三方的依赖删除,删除依赖保存后的结果:
4.2.3、修改springmvc-servlet.xml配置信息
将原有的文件上传通用解析器更换为标准解析器,修改后的配置如下所示:
<!--文件上传解析器 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
</bean>
定义了一个标准的文件上传解析器,更多属性可以查看这个类的源码。这步非常关键,否则上传会失败。另外id不要换成别的名称,更换后可能会上传失败。
4.2.4、定义视图
在views/up/下定义名称为file3.jsp文件,内容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>上传文件 - Servlet3.0</title>
</head>
<body>
<h2>上传文件 - Servlet3.0</h2>
<form action="file3Save" method="post" enctype="multipart/form-data">
<p>
<label for="files">文件:</label>
<input type="file" name="files" id="files" multiple="multiple" />
</p>
<p>
<button>提交</button>
</p>
<p>
${message}
</p>
</form>
</body>
</html>
multiple="multiple"这个属性是HTML5新增加的属性,一些旧版的浏览器可能不支持,使用JavaScript可以处理一下。
4.2.5、定义Aaction
在UpFileController中定义两个action,一个叫file3用于展示上传页面,一个叫file3Save用于处理上传文,代码如下:
package com.zhangguo.springmvc51.controllers;
import java.io.File;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;
@Controller
@RequestMapping("/up")
public class UpFileController {
@RequestMapping("/file")
public String file(Model model){
return "up/upfile";
}
@RequestMapping(value="/fileSave",method=RequestMethod.POST)
public String fileSave(Model model,MultipartFile[] files,HttpServletRequest request) throws Exception{
//文件存放的位置
String path=request.getServletContext().getRealPath("/files");
for (MultipartFile file : files) {
System.out.println(file.getOriginalFilename());
System.out.println(file.getSize());
System.out.println("--------------------------");
File tempFile=new File(path, file.getOriginalFilename());
file.transferTo(tempFile);
}
System.out.println(path);
return "up/upfile";
}
@RequestMapping("/file3")
public String file3(Model model){
return "up/upfile3";
}
@RequestMapping(value="/file3Save",method=RequestMethod.POST)
public String file3Save(Model model,MultipartFile[] files,HttpServletRequest request) throws Exception{
//文件存放的位置
String path=request.getSession().getServletContext().getRealPath("/files");
System.out.println(path);
String msg="";
for (MultipartFile file : files) {
//保存文件
File tempFile=new File(path, file.getOriginalFilename());
file.transferTo(tempFile);
msg+="<img src='../files/"+file.getOriginalFilename()+"' width='200' />";
}
model.addAttribute("message", msg);
return "up/upfile3";
}
}
4.2.6、测试运行
Spring MVC 学习总结(六)——Spring+Spring MVC+MyBatis框架集成
目录
- 一、新建一个基于Maven的Web项目
- 二、创建数据库与表
- 三、添加依赖包
- 四、新建POJO实体层
- 五、新建MyBatis SQL映射层
- 六、JUnit测试数据访问
- 七、完成Spring整合MyBatis配置
- 八、配置web.xml加载Spring容器与MVC
- 九、创建服务层
- 十、完成商品管理功能
- 十二、总结
- 十三、示例下载与预览
与SSH(Struts/Spring/Hibernate/)一样,Spring+SpringMVC+MyBatis也有一个简称SSM,Spring实现业务对象管理,Spring MVC负责请求的转发和视图管理, MyBatis作为数据对象持久化引擎。这样搭配的优点是:轻量、自由度高、Spring与Spring MVC契合度更好。通过一个商品管理示例完成SSM框架的集成,可以将前面学习过的一些内容整合起来,使用到的知识包含:Spring、Spring MVC、MyBatis、JSR303校验、分页、文件上传、路径处理等。
一、新建一个基于Maven的Web项目
1.1、请勾选“Create a simple project”,创建一个简单的项目,这里不使用模板。也可以使用模板,选择WebApp,如果使用模板这里就不应该勾选。如下图所示:
1.2、填写好包名、项目名,选择打包类型为:war,如下图所示:
1.3、项目创建好后可能会发现有错误,选择项目,右键“属性properties”->"层面Project Facets"->"Java"修改版本号为1.7,默认为1.5或其它版本,先去掉“Dynamic Web Module”保存后再勾选,选择版本为3.0,再按箭头所示操作,步骤如下图所示:
1.4、删除WebContent后会发现项目的pom.xml文件报错,是因为找不到指定位置的web.xml文件引起的。再进入项目的属性,选择“Deployment Assembly”项目部署项,删除“src/test/java”、“src/test/resources”与“WebContent”目录,因为这三项不需要部署出去。
1.5、新建完成后发现有错误,是因为没有JavaEE Server Runtime引起的,在项目上右键属性选择“Java Build Path”项,点击“Add Library...”添加引用。也可以不选择Server Runtime可以在Maven中直接引用。目录结构如下所示:
提示:如果您是第一次使用Maven,详细的步骤请查看另一篇随笔:《Spring整合MyBatis(Maven+MySQL)一》。
二、创建数据库与表
打开MySQL数据库,创建一个表,这里以goods表为例,一个用于存放商品的表,共4个字段id表示编号,name表示商品名称,picture表示图片,price表示价格。SQL脚本如下:
/*
Navicat MySQL Data Transfer
Source Server : localhost
Source Server Version : 50536
Source Host : localhost:3306
Source Database : db1
Target Server Type : MYSQL
Target Server Version : 50536
File Encoding : 65001
Date: 2016-07-20 10:13:58
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `goods`
-- ----------------------------
DROP TABLE IF EXISTS `goods`;
CREATE TABLE `goods` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(200) NOT NULL,
`price` decimal(10,2) DEFAULT '0.00',
`picture` varchar(100) DEFAULT 'default.jpg',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of goods
-- ----------------------------
INSERT INTO `goods` VALUES ('1', 'G7 中原G7三合一浓醇咖啡固体饮料1200', '66.50', '1.jpg');
INSERT INTO `goods` VALUES ('2', '百草味东北松子200gx2袋 坚果炒货零', '42.90', '2.jpg');
INSERT INTO `goods` VALUES ('3', '奈津香 桂圆干500gx2袋莆田特产5A桂', '39.90', '3.jpg');
INSERT INTO `goods` VALUES ('4', '益达尊享护齿装草本40粒+冰柠40粒+西', '25.90', '4.jpg');
INSERT INTO `goods` VALUES ('5', '猴坑茶业2016新茶原产地手工太平猴魁特', '168.00', '5.jpg');
INSERT INTO `goods` VALUES ('6', '嘻鱿记 休闲零食 麻辣香辣奶香炭烧 5种', '39.80', '6.jpg');
INSERT INTO `goods` VALUES ('7', '荣业鸿福五分瘦腊肠 香港土特产香肠腊味', '126.80', '7.jpg');
INSERT INTO `goods` VALUES ('8', '蓓琳娜(BELLINA)3L PDO特级初榨橄榄油', '178.00', '8.jpg');
INSERT INTO `goods` VALUES ('10', '荣业鸿福五分瘦腊肠 香港土特产香肠腊味', '30.60', 'b454b44f-868e-4efe-ae17-91e9e6a58390.jpg');
表结构如下所示:
三、添加依赖包
项目主要依赖的jar包有Spring核心包、Spring AOP包、Spring MVC包、MyBatis ORM包、MyBatis-Spring适配包、JSTL、JUnit、Log4j2等,具体的pom.xml文件如下:
<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>com.zhangguo</groupId>
<artifactId>SSMall</artifactId>
<version>0.0.3</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.0.RELEASE</spring.version>
</properties>
<dependencies>
<!--Spring框架核心库 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Spring MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- aspectJ AOP 织入器 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<!--mybatis-spring适配器 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<!--Spring java数据库访问包,在本例中主要用于提供数据源 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--mysql数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<!--log4j日志包 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.6.1</version>
</dependency>
<!-- mybatis ORM框架 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency>
<!-- JUnit单元测试工具 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<!--c3p0 连接池 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!-- JSTL -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Servlet核心包 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<!--JSP -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.5.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.2</version>
</dependency>
<!--JSR303 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.2.Final</version>
</dependency>
<!--文件上传 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<!-- FreeMarker -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
</dependencies>
</project>
如果是第一次依赖相关的包,则需要下载时间,请耐心等待,如果下载失败请手动下载(http://search.maven.org/)后复制到本地的资源库中。依赖后的项目结果如下:
四、新建POJO实体层
为了实现与数据库中的books表进行关系映射新建一个Goods商品类,具体代码如下:
package com.zhangguo.ssmall.entities;
import java.io.Serializable;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
/**
* 商品实体
*
*/
public class Goods implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
/*
* 编号
*/
private int id;
/*
* 名称
*/
@Pattern(regexp="^[^><&#]{1,50}$",message="{pattern}")
@NotNull(message="{notNull}")
private String name;
/*
* 价格
*/
@Min(value=1,message="必须大于或等于1")
private double price;
/*
* 图片
*/
private String picture;
public Goods() {
}
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 double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String getPicture() {
return picture;
}
public void setPicture(String picture) {
this.picture = picture;
}
@Override
public String toString() {
return "id:"+getId()+",name:"+getName()+",price:"+getPrice()+",picture:"+getPicture();
}
}
为了实现校验,在成员变量上设置了一些注解信息。
五、新建MyBatis SQL映射层
这个项目中我们采用接口与xml结合的形式完成关系与对象间的映射,在接口中定义一些数据访问的方法,在xml文件中定义实现数据访问需要的sql脚本。商品数据访问映射接口如下:
package com.zhangguo.ssmall.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.zhangguo.ssmall.entities.Goods;
public interface GoodsDAO {
/**
* 获得商品信息并分页
*/
public List<Goods> getGoodsPager(@Param("skip") int skip,@Param("size") int size);
/**
* 获得单个商品通过编号
*/
public Goods getGoodsById(int id);
/**
* 获得商品总数
*/
public int getGoodsCount();
/*
* 新增加商品
*/
public int insert(Goods entity);
/**
* 删除商品
*/
public int delete(int id);
/**
* 修改商品
*/
public int update(Goods entity);
}
为MyBatis ORM创建的映射文件GoodsMapper.xml(命名尽量都遵循一个规则,便于扫描,这里约定以实体名+Mapper)如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--命名空间应该是对应接口的包名+接口名 -->
<mapper namespace="com.zhangguo.ssmall.mapper.GoodsDAO">
<!--获得商品信息并分页 -->
<select id="getGoodsPager" resultType="Goods">
select
id,name,price,picture from goods limit #{skip},#{size}
</select>
<!-- 获得单个商品通过编号 -->
<select id="getGoodsById" parameterType="int" resultType="Goods">
select
id,name,price,picture from goods where id=#{id}
</select>
<!--获得商品总数 -->
<select id="getGoodsCount" resultType="int">
select count(*) from goods
</select>
<!--新增加商品 -->
<insert id="insert" parameterType="Goods">
insert into
goods(name,price,picture) values(#{name},#{price},#{picture});
</insert>
<!-- 删除商品 -->
<delete id="delete">
delete from goods where id=#{id}
</delete>
<!-- 修改商品 -->
<update id="update" parameterType="Goods">
update goods set
name=#{name},price=#{price},picture=#{picture} where id=#{id}
</update>
</mapper>
六、JUnit测试数据访问
为了保证数据访问正常,使用JUnit进行单元测试,在另一个源代码目录src/test/java下添加一个名为TestGoods的测试用例,编写完成的测试用例如下:
package com.zhangguo.ssmall.test;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import com.zhangguo.ssmall.entities.Goods;
import com.zhangguo.ssmall.mapper.GoodsDAO;
import junit.framework.Assert;
public class TestGoods{
@Test
public void getGoodsPagerTest() {
int skip=4;
int size=2;
SqlSession session=MyBatisUtil.getSession();
try {
GoodsDAO bookdao=session.getMapper(GoodsDAO.class);
List<Goods> goods=bookdao.getGoodsPager(skip, size);
Assert.assertEquals(2, goods.size());
} finally {
session.close();
}
}
@Test
public void getGoodsByIdTest() {
SqlSession session=MyBatisUtil.getSession();
try {
GoodsDAO bookdao=session.getMapper(GoodsDAO.class);
Goods goods=bookdao.getGoodsById(1);
Assert.assertEquals(1, goods.getId());
} finally {
session.close();
}
}
@Test
public void getGoodsCountTest() {
SqlSession session=MyBatisUtil.getSession();
try {
GoodsDAO bookdao=session.getMapper(GoodsDAO.class);
Assert.assertEquals(9, bookdao.getGoodsCount());
} finally {
session.close();
}
}
@Test
public void insertTest() {
SqlSession session=MyBatisUtil.getSession();
try {
Goods entity=new Goods();
entity.setName("正宗无锡阳山水蜜桃新鲜水果水密桃12个6斤装江浙沪皖顺丰包邮");
entity.setPrice(108);
entity.setPicture("nopic.jpg");
GoodsDAO bookdao=session.getMapper(GoodsDAO.class);
Assert.assertEquals(1, bookdao.insert(entity));
} finally {
session.close();
}
}
@Test
public void deleteTest() {
SqlSession session=MyBatisUtil.getSession();
try {
GoodsDAO bookdao=session.getMapper(GoodsDAO.class);
Assert.assertEquals(1, bookdao.delete(12));
} finally {
session.close();
}
}
@Test
public void update() {
SqlSession session=MyBatisUtil.getSession();
try {
GoodsDAO bookdao=session.getMapper(GoodsDAO.class);
Goods entity=bookdao.getGoodsById(12);
entity.setName("正宗无锡阳山水蜜桃新鲜水果水密桃12个6斤装");
entity.setPrice(107);
entity.setPicture("nopicture.jpg");
Assert.assertEquals(1, bookdao.update(entity));
} finally {
session.close();
}
}
}
MyBatis访问数据库的工具类如下:
package com.zhangguo.ssmall.test;
import java.io.InputStream;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public abstract class MyBatisUtil {
public static SqlSessionFactory getSqlSessionFactory(){
// 获得环境配置文件流
InputStream config = MyBatisUtil.class.getClassLoader().getResourceAsStream("MyBatisCfg.xml");
// 创建sql会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config);
return factory;
}
//获得会话
public static SqlSession getSession(){
return getSqlSessionFactory().openSession(true);
}
/**
* 获得得sql会话
* @param isAutoCommit 是否自动提交,如果为false则需要sqlSession.commit();rollback();
* @return sql会话
*/
public static SqlSession getSession(boolean isAutoCommit){
return getSqlSessionFactory().openSession(isAutoCommit);
}
}
MyBatis配置文件MyBatisCfg.xml如下所示:
View Code
配置文件中使用到了db.properties属性文件,该文件用于存放数据库连接信息,文件内容如下:
#mysql
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/db1
mysql.uid=root
mysql.password=root
运行测试,一切正常,测试结果如下:
这里需要注意的是MyBatis配置文件的内容在后面与Spring整合后是会变化的,使用JUnit测试并未使用到Spring框架。
七、完成Spring整合MyBatis配置
7.1、在源代码的根目录下修改db.properties文件,用于存放数据库连接信息,文件内容如下:
#mysql
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/db1
mysql.uid=root
mysql.password=root
mysql.acquireIncrement=5
mysql.initialPoolSize=10
mysql.minPoolSize=5
mysql.maxPoolSize=20
7.2、在源代码的根目录下新建 applicationContext.xml文件,用于整合MyBatis与Spring,非常关键,具体的内容如下:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!--1 引入属性文件,在配置中占位使用 -->
<context:property-placeholder location="classpath*:db.properties" />
<!--2 配置C3P0数据源 -->
<bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<!--驱动类名 -->
<property name="driverClass" value="${mysql.driver}" />
<!-- url -->
<property name="jdbcUrl" value="${mysql.url}" />
<!-- 用户名 -->
<property name="user" value="${mysql.uid}" />
<!-- 密码 -->
<property name="password" value="${mysql.password}" />
<!-- 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数 -->
<property name="acquireIncrement" value="${mysql.acquireIncrement}"></property>
<!-- 初始连接池大小 -->
<property name="initialPoolSize" value="${mysql.initialPoolSize}"></property>
<!-- 连接池中连接最小个数 -->
<property name="minPoolSize" value="${mysql.minPoolSize}"></property>
<!-- 连接池中连接最大个数 -->
<property name="maxPoolSize" value="${mysql.maxPoolSize}"></property>
</bean>
<!--3 会话工厂bean sqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 配置文件路径 -->
<property name="configLocation" value="classpath:MyBatisCfg.xml"></property>
<!-- 数据源 -->
<property name="dataSource" ref="datasource"></property>
<!-- sql映射文件路径 -->
<property name="mapperLocations" value="classpath*:com/zhangguo/ssmall/mapper/*Mapper.xml"></property>
</bean>
<!--4 自动扫描对象关系映射 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定会话工厂,如果当前上下文中只定义了一个则该属性可省去 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
<!-- 指定要自动扫描接口的基础包,实现接口 -->
<property name="basePackage" value="com.zhangguo.ssmall.mapper"></property>
</bean>
<!--5 声明式事务管理 -->
<!--定义事物管理器,由spring管理事务 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"></property>
</bean>
<!--支持注解驱动的事务管理,指定事务管理器 -->
<tx:annotation-driven transaction-manager="transactionManager" />
<!--6 容器自动扫描IOC组件 -->
<context:component-scan base-package="com.zhangguo.ssmall"></context:component-scan>
<!--7 aspectj支持自动代理实现AOP功能 -->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
</beans>
从配置文件中可以看出第3点会话工厂配置中指定了MyBatis配置文件的位置与名称,其实也可以省去,在这里可以通过属性配置好。但个人认为当多个框架整合在一起时最后将配置文件分开,便于修改。修改后的MyBatisCfg.xml文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties"></properties>
<settings>
<!--指定mybatis使用日志组件 -->
<setting name="logImpl" value="LOG4J2" />
<!--开启全局的二级缓存 -->
<setting name="cacheEnabled" value="false" />
<!--开启延时加载,如果有关联关系,则默认不会获取数据
延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。
特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。
在association中指定fetchType="eager(立即)" 或者 lazy(延迟)
默认:false
-->
<setting name="lazyLoadingEnabled" value="true" />
<!--true时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;
false,每种属性将会按需加载。
默认为:true-->
<setting name="aggressiveLazyLoading" value="false" />
</settings>
<typeAliases>
<package name="com.zhangguo.ssmall.entities" />
</typeAliases>
<!--
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${mysql.driver}" />
<property name="url" value="${mysql.url}" />
<property name="username" value="${mysql.uid}" />
<property name="password" value="${mysql.password}" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/zhangguo/ssmall/mapper/GoodsMapper.xml" />
</mappers>-->
</configuration>
中间有一大段注释了,是因为MyBatis-Spring适配器已完成了这部分内容的工作,注释不删除的原因是因为JUnit测试时还要使用,其它也可以使用两个不同的文件。
八、配置web.xml加载Spring容器与MVC
修改web.xml文件,注册加载Spring容器所需的监听器;注册Spring MVC前置控制器Servlet,中间还设置了Servlet3.0上传所需的参数;添加了一个全局的编码过滤器。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<listener>
<description>Spring容器加载监听器</description>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<description>设置Spring加载时的配置文件位置,默认位置在WEB-INF/lib目录下</description>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:applicationContext.xml</param-value>
</context-param>
<!--Spring MVC 前置Servlet,中心控制器 -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<!--Spring MVC配置文件路径 -->
<param-value>classpath*:springmvc-servlet.xml</param-value>
</init-param>
<!-- 启动动优先级,越小越早加载 -->
<load-on-startup>1</load-on-startup>
<!--Servlet3.0以上文件上传配置 -->
<multipart-config>
<!--上传文件的最大限制5MB -->
<max-file-size>5242880</max-file-size>
<!--请求的最大限制20MB -->
<max-request-size>20971520</max-request-size>
<!--当文件的大小超过临界值时将写入磁盘 -->
<file-size-threshold>0</file-size-threshold>
</multipart-config>
</servlet>
<!-- 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>
在src/main/java源代码目录下添加applicationContext.xml文件,用于配置Spring,内容在上一节中已列出。
在src/main/java源代码目录下添加Spring MVC配置文件springmvc-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" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">
<!-- 自动扫描包,实现支持注解的IOC -->
<context:component-scan base-package="com.zhangguo.ssmall" />
<!-- Spring MVC不处理静态资源 -->
<mvc:default-servlet-handler />
<!-- 支持mvc注解驱动 -->
<mvc:annotation-driven enable-matrix-variables="true" />
<!-- 配置映射媒体类型的策略 -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="removeSemicolonContent" value="false" />
</bean>
<!-- 内部视图解析器,JSP与JSTL模板 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="internalResourceViewResolver">
<!--指定视图渲染类 -->
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<!--自动添加到路径中的前缀 -->
<property name="prefix" value="/WEB-INF/views/jstl" />
<!--自动添加到路径中的后缀 -->
<property name="suffix" value=".jsp" />
<!--设置所有视图的内容类型,如果视图本身设置内容类型视图类可以忽略 -->
<property name="contentType" value="text/html;charset=UTF-8" />
<!-- 优先级,越小越前 -->
<property name="order" value="2" />
</bean>
<!-- FreeMarker视图解析器与属性配置 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<!--是否启用缓存 -->
<property name="cache" value="true" />
<!--自动添加到路径中的前缀 -->
<property name="prefix" value="" />
<!--自动添加到路径中的后缀 -->
<property name="suffix" value=".html" />
<!--指定视图渲染类 -->
<property name="viewClass"
value="org.springframework.web.servlet.view.freemarker.FreeMarkerView" />
<!-- 设置是否暴露Spring的macro辅助类库,默认为true -->
<property name="exposeSpringMacroHelpers" value="true" />
<!-- 是否应将所有request属性添加到与模板合并之前的模型。默认为false。 -->
<property name="exposeRequestAttributes" value="true" />
<!-- 是否应将所有session属性添加到与模板合并之前的模型。默认为false。 -->
<property name="exposeSessionAttributes" value="true" />
<!-- 在页面中使用${rc.contextPath}就可获得contextPath -->
<property name="requestContextAttribute" value="rc" />
<!--设置所有视图的内容类型,如果视图本身设置内容类型视图类可以忽略 -->
<property name="contentType" value="text/html;charset=UTF-8" />
<!-- 优先级,越小越前 -->
<property name="order" value="1" />
</bean>
<!-- 配置FreeMarker细节 -->
<bean id="freemarkerConfig"
class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<!-- 模板路径 -->
<property name="templateLoaderPath" value="/WEB-INF/views/ftl" />
<property name="freemarkerSettings">
<props>
<!-- 刷新模板的周期,单位为秒 -->
<prop key="template_update_delay">5</prop>
<!--模板的编码格式 -->
<prop key="defaultEncoding">UTF-8</prop>
<!--url编码格式 -->
<prop key="url_escaping_charset">UTF-8</prop>
<!--此属性可以防止模板解析空值时的错误 -->
<prop key="classic_compatible">true</prop>
<!--该模板所使用的国际化语言环境选项 -->
<prop key="locale">zh_CN</prop>
<!--布尔值格式 -->
<prop key="boolean_format">true,false</prop>
<!--日期时间格式 -->
<prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop>
<!--时间格式 -->
<prop key="time_format">HH:mm:ss</prop>
<!--数字格式 -->
<prop key="number_format">0.######</prop>
<!--自动开启/关闭空白移除,默认为true -->
<prop key="whitespace_stripping">true</prop>
</props>
</property>
</bean>
<!--文件上传解析器 -->
<!--Spring MVC默认不能识别multipart格式的文件内容 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
</bean>
</beans>
九、创建服务层
在包com.zhangguo.ssmall.services下添加GoodsService.java文件,该文件是一个服务接口,内容如下:
package com.zhangguo.ssmall.services;
import java.util.List;
import com.zhangguo.ssmall.entities.Goods;
/**
* 商品业务接口
*
*/
public interface GoodsService {
//分页
List<Goods> getGoodsPager(int pageNO, int size);
//获得单个商品对象
Goods getGoodsById(int id);
//获得商品总数
int getGoodsCount();
//添加
int insert(Goods entity);
//删除单个
int delete(int id);
//删除多个
int deletes(int[] ids);
//更新
int update(Goods entity);
}
在包com.zhangguo.ssmall.services下添加类GoodsServiceImpl.java,实现接口GoodsService,用于完成商品业务逻辑,由于是示例代码所以比较空;中间使用了两个注解一个是@Service,用于提供给需要服务的类自动装配,当Spring IOC容器启动时被扫描到该类型会自动添加实例到Spring容器中;另一个注解是@Resource用于完成自动装配功能,在Spring容器中找到GoodsDAO类型的对象,代码如下:
package com.zhangguo.ssmall.services;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import com.zhangguo.ssmall.entities.Goods;
import com.zhangguo.ssmall.mapper.GoodsDAO;
/**
* 商品业务实现
*
*/
//自动添加到Spring容器中
@Service
public class GoodsServiceImpl implements GoodsService{
//自动装配
@Resource
GoodsDAO goodsdao;
//分页
@Override
public List<Goods> getGoodsPager(int pageNO, int size) {
int skip=(pageNO-1)*size;
return goodsdao.getGoodsPager(skip, size);
}
//获得单个产品对象
@Override
public Goods getGoodsById(int id) {
return goodsdao.getGoodsById(id);
}
//获得商品总数
@Override
public int getGoodsCount() {
return goodsdao.getGoodsCount();
}
//添加
@Override
public int insert(Goods entity) {
return goodsdao.insert(entity);
}
//删除单个
@Override
public int delete(int id) {
return goodsdao.delete(id);
}
//删除多个
@Override
public int deletes(int[] ids) {
int rows=0;
for (int id : ids) {
rows+=delete(id);
}
return rows;
}
//更新
@Override
public int update(Goods entity) {
return goodsdao.update(entity);
}
}
十、完成商品管理功能
10.1、商品列表与分页
定义GoodsController控制器,映射访问路径,需要使用到的商品服务使用自动装配完成,代码如下:
package com.zhangguo.ssmall.controllers;
import javax.annotation.Resource;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.zhangguo.ssmall.services.GoodsService;
@Controller
@RequestMapping("/goods")
public class GoodsController {
@Resource
GoodsService goodsService;
/*
* 产品列表与分页Action
*/
@RequestMapping("/list")
public String list(Model model,@RequestParam(required=false,defaultValue="1") int pageNO){
int size=5;
model.addAttribute("size",size);
model.addAttribute("pageNO",pageNO);
model.addAttribute("count",goodsService.getGoodsCount());
model.addAttribute("goods", goodsService.getGoodsPager(pageNO, size));
return "goods/list";
}
}
参数size表示每页记录数,pageNO表示当前页号,处于第几页,count表示总记录数。
在views/jstl/goods目录下添加视图list.jsp页面,页面的内容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link href="<c:url value="/styles/main.css"/>" type="text/css" rel="stylesheet" />
<title>商品管理</title>
</head>
<body>
<div class="main">
<h2 class="title"><span>商品管理</span></h2>
<form action="deletes" method="post">
<table border="1" width="100%" class="tab">
<tr>
<th><input type="checkbox" id="chbAll"></th>
<th>编号</th>
<th>产品名</th>
<th>价格</th>
<th>类型</th>
<th>操作</th>
</tr>
<c:forEach var="entity" items="${goods}">
<tr>
<th><input type="checkbox" name="id" value="${entity.id}"></th>
<td>${entity.id}</td>
<td>${entity.name}</td>
<td><img src="<c:url value="/images/${entity.picture}"/>" height="40"/></td>
<td>${entity.price}</td>
<td>
<a href="delete/${entity.id}" class="abtn">删除</a>
<a href="edit/${entity.id}" class="abtn">编辑</a>
</td>
</tr>
</c:forEach>
</table>
<div id="pager"></div>
<p>
<a href="add" class="abtn out">添加</a>
<input type="submit" value="批量删除" class="btn out"/>
</p>
<p style="color: red">${message}</p>
<!--分页 -->
<script type="text/javascript" src="<c:url value="/scripts/jQuery1.11.3/jquery-1.11.3.min.js"/>" ></script>
<link href="<c:url value="/scripts/pagination22/pagination.css"/>" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="<c:url value="/scripts/pagination22/jquery.pagination2.2.js"/>" ></script>
<script type="text/javascript">
//初始化分页组件
var count=${count};
var size=${size};
var pageNO=${pageNO};
$("#pager").pagination(count, {
items_per_page:size,
current_page:pageNO-1,
next_text:"下一页",
prev_text:"上一页",
num_edge_entries:2,
load_first_page:false,
callback:handlePaginationClick
});
//回调方法
function handlePaginationClick(new_page_index, pagination_container){
location.href="list?pageNO="+(new_page_index+1);
}
var defaultSrc="<c:url value="/images/default.jpg"/>";
$(".tab img").bind("error",function(){
$(this).prop("src",defaultSrc);
});
</script>
</form>
</div>
</body>
</html>
为了实现分页,添加了一个jQuery插件pagination,该插件的详细参数如下所示:
View Code
测试运行结果:
页面中有一个简单处理加载图片失败的事件,当图片加载出错时使用default.jpg图,如编号为38的图片就是默认图。
11.2、删除与多删除功能
为了实现删除与多删除功能,修改控制器,增加2个action,delete请求处理方法用于删除单个记录,id是路径变量指定要删除的商品编号;pageNO是请求参数,保持状态的目的是为了删除后让页面继续停留在某一页,不过这里有问题的是当某一页的内容只有一条记录里就需要重新计算了;rediredtAttributes是为了保持重定向后的message值。
/*
* 删除单个产品对象Action
*/
@RequestMapping("/delete/{id}")
public String delete(Model model,@PathVariable int id,@RequestParam(required=false,defaultValue="1") int pageNO,RedirectAttributes redirectAttributes){
if(goodsService.delete(id)>0)
{
redirectAttributes.addFlashAttribute("message", "删除成功!");
}else{
redirectAttributes.addFlashAttribute("message", "删除失败!");
}
return "redirect:/goods/list?pageNO="+pageNO;
}
/*
* 删除多个产品对象Action
*/
@RequestMapping("/deletes")
public String deletes(Model model,@RequestParam int[] id,@RequestParam(required=false,defaultValue="1") int pageNO,RedirectAttributes redirectAttributes){
//执行删除
int rows=goodsService.deletes(id);
if(rows>0)
{
redirectAttributes.addFlashAttribute("message", "删除"+rows+"行记录成功!");
}else{
redirectAttributes.addFlashAttribute("message", "删除失败!");
}
return "redirect:/goods/list?pageNO="+pageNO;
}
为了配合删除,修改list.jsp页面,修改后的list.jsp页面如下所示:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link href="<c:url value="/styles/main.css"/>" type="text/css" rel="stylesheet" />
<title>商品管理</title>
</head>
<body>
<div class="main">
<h2 class="title"><span>商品管理</span></h2>
<form action="<c:url value="/goods/deletes?pageNO=${pageNO}"/>" method="post">
<table border="1" width="100%" class="tab">
<tr>
<th><input type="checkbox" id="chbAll"></th>
<th>编号</th>
<th>产品名</th>
<th>价格</th>
<th>类型</th>
<th>操作</th>
</tr>
<c:forEach var="entity" items="${goods}">
<tr>
<th><input type="checkbox" name="id" value="${entity.id}"></th>
<td>${entity.id}</td>
<td>${entity.name}</td>
<td><img src="<c:url value="/images/${entity.picture}"/>" height="40"/></td>
<td>${entity.price}</td>
<td>
<a href="<c:url value="/goods/"/>delete/${entity.id}?pageNO=${pageNO}" class="abtn">删除</a>
<a href="<c:url value="/goods/"/>edit/${entity.id}" class="abtn">编辑</a>
<a href="<c:url value="/goods/"/>upPicture/${entity.id}" class="abtn">上传</a>
</td>
</tr>
</c:forEach>
</table>
<div id="pager"></div>
<p>
<a href="add" class="abtn out">添加</a>
<input type="submit" value="批量删除" class="btn out"/>
</p>
<p style="color: red">${message}</p>
<!--分页 -->
<script type="text/javascript" src="<c:url value="/scripts/jQuery1.11.3/jquery-1.11.3.min.js"/>" ></script>
<link href="<c:url value="/scripts/pagination22/pagination.css"/>" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="<c:url value="/scripts/pagination22/jquery.pagination2.2.js"/>" ></script>
<script type="text/javascript">
//初始化分页组件
var count=${count};
var size=${size};
var pageNO=${pageNO};
$("#pager").pagination(count, {
items_per_page:size,
current_page:pageNO-1,
next_text:"下一页",
prev_text:"上一页",
num_edge_entries:2,
load_first_page:false,
callback:handlePaginationClick
});
//回调方法
function handlePaginationClick(new_page_index, pagination_container){
location.href="<c:url value="/goods/"/>list?pageNO="+(new_page_index+1);
}
var defaultSrc="<c:url value="/images/default.jpg"/>";
$(".tab img").bind("error",function(){
$(this).prop("src",defaultSrc);
});
</script>
</form>
</div>
</body>
</html>
运行结果如下所示:
基中的多删除功能可以改进为一次性让数据库删除完成。
11.3、新增商品功能
在控制器中添加2个action,一个是add用于完成添加页面展示,一个是addSave用于完成添加保存处理,代码如下:
/*
* 添加商品
*/
@RequestMapping("/add")
public String add(Model model){
model.addAttribute("entity", new Goods());
return "goods/add";
}
/*
* 添加商品保存
*/
@RequestMapping("/addSave")
public String addSave(Model model,@ModelAttribute("entity") @Valid Goods entity,BindingResult bindingResult){
//如果模型中存在错误
if(!bindingResult.hasErrors()){
if(goodsService.insert(entity)>0)
{
return "redirect:/goods/list";
}
}
model.addAttribute("entity", entity);
return "goods/add";
}
这里有一个问题是因为使用了JSR303校验,当保存对象是需要在参数前注解@ModelAttribute("entity") @Valid,用于激活校验,否则页面将不会有错误展示,非常奇怪的问题;我在第五章中并没有发现该问题。
为了配合Bean Validation,定义的Goods Bean需要注解,内容如下:
/*
* 名称
*/
@Pattern(regexp="^[^><&#]{1,50}$",message="{pattern}")
@NotNull(message="{notNull}")
private String name;
/*
* 价格
*/
@Min(value=1,message="必须大于或等于1")
private double price;
这里的错误消息来源一个是直接写在注解中,另一个来自消息文件;{pattern}来自消息文件ValidationMessages.properties,在src/main/java目录下新建该文件,文件内容如下:
pattern=格式错误
notNull=不允许为空
这里需注意的是,默认情况下中文会显示成utf-8编码格式如:
pattern=\u683C\u5F0F\u9519\u8BEF
notNull=\u4E0D\u5141\u8BB8\u4E3A\u7A7A
为了正常显示,可以安装一个插件,让属性文件支持正常显示中文,插件名称是properties-editor,点击“Helo”->“Marketplace”,搜索插件名称,显示内容如下:
点击Install,进入下一步:
完成后在properties文件上右键选择“Open With”,具体步骤如下:
在views/jstl/goods目录下新增加add.jsp页面,页面内容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<link href="<c:url value="/styles/main.css" />" type="text/css" rel="stylesheet" />
<title>新增商品</title>
</head>
<body>
<div class="main">
<h2 class="title"><span>新增商品</span></h2>
<form:form action="addSave" modelAttribute="entity">
<fieldset>
<legend>商品</legend>
<p>
<label for="name">商品名称:</label>
<form:input path="name" size="50"/>
<form:errors path="name" cssClass="error"></form:errors>
</p>
<p>
<label for="price">商品价格:</label>
<form:input path="price"/>
<form:errors path="price" cssClass="error"></form:errors>
</p>
<p>
<input type="submit" value="保存" class="btn out">
</p>
</fieldset>
</form:form>
<p style="color: red">${message}</p>
<form:errors path="*"></form:errors>
<p>
<a href="<c:url value="/goods/list" />" class="abtn out">返回列表</a>
</p>
</div>
</body>
</html>
运行结果:
11.4、编辑商品功能
与新增加类似,在控制器下新增两个action,一个用于展示编辑,有一个用于执行编辑后保存,代码如下所示:
/*
* 编辑商品
*/
@RequestMapping("/edit/{id}")
public String edit(Model model,@PathVariable int id){
model.addAttribute("entity", goodsService.getGoodsById(id));
return "goods/edit";
}
/*
* 编辑商品保存
*/
@RequestMapping("/editSave")
public String editSave(Model model,@ModelAttribute("entity") @Valid Goods entity,BindingResult bindingResult){
//如果模型中存在错误
if(!bindingResult.hasErrors()){
if(goodsService.update(entity)>0)
{
return "redirect:list";
}
}
model.addAttribute("entity", entity);
return "/goods/edit";
}
在views/jstl/goods目录下新增加edit.jsp页面,页面内容如下:
<%@page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<link href="<c:url value="/styles/main.css" />" type="text/css"
rel="stylesheet" />
<title>编辑商品</title>
<base href="<c:url value="/" />" />
</head>
<body>
<div class="main">
<h2 class="title">
<span>编辑商品</span>
</h2>
<form:form action="goods/editSave" modelAttribute="entity">
<fieldset>
<legend>商品</legend>
<p>
<label for="name">商品名称:</label>
<form:input path="name" size="50" />
<form:errors path="name" cssClass="error"></form:errors>
</p>
<p>
<label for="price">商品价格:</label>
<form:input path="price" />
<form:errors path="price" cssClass="error"></form:errors>
</p>
<p>
<form:hidden path="picture" />
<form:hidden path="id" />
<input type="submit" value="保存" class="btn out">
</p>
</fieldset>
</form:form>
<p style="color: red">${message}</p>
<form:errors path="*"></form:errors>
<p>
<a href="goods/list" class="abtn out">返回列表</a>
</p>
</div>
</body>
</html>
运行结果:
11.5、上传图片功能
这里使用Servlet3.0实现文件上传,相关配置已经在前面的配置文件中设置好了,在控制器中增加两个action,代码如下:
/**
* 上传图片
*/
@RequestMapping("/upPicture/{id}")
public String upPicture(Model model,@PathVariable int id){
model.addAttribute("entity", goodsService.getGoodsById(id));
return "goods/upfile";
}
/*
* 上传图片保存
*/
@RequestMapping("/upPictureSave/{id}")
public String upPictureSave(Model model,@PathVariable int id,MultipartFile picFile,HttpServletRequest request){
Goods entity=goodsService.getGoodsById(id);
//如果选择了文件
if(picFile!=null){
//如果文件大小不为0
if(picFile.getSize()>0){
//获得上传位置
String path=request.getServletContext().getRealPath("/images");
//生成文件名
String filename=UUID.randomUUID().toString()+picFile.getOriginalFilename().substring(picFile.getOriginalFilename().lastIndexOf("."));
File tempFile=new File(path, filename);
try {
//保存文件
picFile.transferTo(tempFile);
//更新数据
entity.setPicture(filename);
goodsService.update(entity);
//转向列表页
return "redirect:/goods/list";
} catch (Exception e) {
e.printStackTrace();
}
}
}
model.addAttribute("entity", entity);
return "goods/upfile";
}
在views/jstl/goods目录下新增加upfile.jsp页面,页面内容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<link href="<c:url value="/styles/main.css" />" type="text/css"
rel="stylesheet" />
<title>上传图片</title>
</head>
<body>
<div class="main">
<h2 class="title">
<span>上传图片</span>
</h2>
<form action="<c:url value="/goods/upPictureSave/${entity.id}" />" method="post"
enctype="multipart/form-data">
<fieldset>
<legend>商品</legend>
<p>
<label for="name">商品名称:</label> ${entity.name}
</p>
<p>
<label for="price">商品价格:</label>${entity.price}
</p>
<p>
<label for="title">商品图片:</label> <input type="file" name="picFile" />
</p>
<p>
<input type="submit" value="上传" class="btn out">
</p>
</fieldset>
</form>
<p style="color: red">${message}</p>
<p>
<a href="<c:url value="/goods/list" />" class="abtn out">返回列表</a>
</p>
</div>
</body>
</html>
运行结果如下:
11.6、日志、首页、样式与最终的控制器
为了将MyBatis与Hibernate Validation的日志信息展示在控制中,需要添加log4j2的引用,这部分内容在pom.xml中已配置完成了,另外在项目的根目录下需要添加一个log4j2的配置文件log4j2.xml,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="off" monitorInterval="1800">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
在webapp目录下添加index.jsp,首页是这个程序的入口,只完成了转发功能,页面内容如下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<jsp:forward page="goods/list"></jsp:forward>
所有页面基本都引用了同一个样式表styles/main.css文件,文件内容如下:
View Code
最终的控制器GoodsController.java文件内容如下:
package com.zhangguo.ssmall.controllers;
import java.io.File;
import java.util.UUID;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.zhangguo.ssmall.entities.Goods;
import com.zhangguo.ssmall.services.GoodsService;
@Controller
@RequestMapping("/goods")
public class GoodsController {
@Resource
GoodsService goodsService;
/*
* 产品列表与分页Action
*/
@RequestMapping("/list")
public String list(Model model,@RequestParam(required=false,defaultValue="1") int pageNO){
int size=5;
model.addAttribute("size",size);
model.addAttribute("pageNO",pageNO);
model.addAttribute("count",goodsService.getGoodsCount());
model.addAttribute("goods", goodsService.getGoodsPager(pageNO, size));
return "goods/list";
}
/*
* 删除单个产品对象Action
*/
@RequestMapping("/delete/{id}")
public String delete(Model model,@PathVariable int id,@RequestParam(required=false,defaultValue="1") int pageNO,RedirectAttributes redirectAttributes){
if(goodsService.delete(id)>0)
{
redirectAttributes.addFlashAttribute("message", "删除成功!");
}else{
redirectAttributes.addFlashAttribute("message", "删除失败!");
}
return "redirect:/goods/list?pageNO="+pageNO;
}
/*
* 删除多个产品对象Action
*/
@RequestMapping("/deletes")
public String deletes(Model model,@RequestParam int[] id,@RequestParam(required=false,defaultValue="1") int pageNO,RedirectAttributes redirectAttributes){
//执行删除
int rows=goodsService.deletes(id);
if(rows>0)
{
redirectAttributes.addFlashAttribute("message", "删除"+rows+"行记录成功!");
}else{
redirectAttributes.addFlashAttribute("message", "删除失败!");
}
return "redirect:/goods/list?pageNO="+pageNO;
}
/*
* 添加商品
*/
@RequestMapping("/add")
public String add(Model model){
model.addAttribute("entity", new Goods());
return "goods/add";
}
/*
* 添加商品保存
*/
@RequestMapping("/addSave")
public String addSave(Model model,@ModelAttribute("entity") @Valid Goods entity,BindingResult bindingResult){
//如果模型中存在错误
if(!bindingResult.hasErrors()){
if(goodsService.insert(entity)>0)
{
return "redirect:/goods/list";
}
}
model.addAttribute("entity", entity);
return "goods/add";
}
/*
* 编辑商品
*/
@RequestMapping("/edit/{id}")
public String edit(Model model,@PathVariable int id){
model.addAttribute("entity", goodsService.getGoodsById(id));
return "goods/edit";
}
/*
* 编辑商品保存
*/
@RequestMapping("/editSave")
public String editSave(Model model,@ModelAttribute("entity") @Valid Goods entity,BindingResult bindingResult){
//如果模型中存在错误
if(!bindingResult.hasErrors()){
if(goodsService.update(entity)>0)
{
return "redirect:list";
}
}
model.addAttribute("entity", entity);
return "/goods/edit";
}
/**
* 上传图片
*/
@RequestMapping("/upPicture/{id}")
public String upPicture(Model model,@PathVariable int id){
model.addAttribute("entity", goodsService.getGoodsById(id));
return "goods/upfile";
}
/*
* 上传图片保存
*/
@RequestMapping("/upPictureSave/{id}")
public String upPictureSave(Model model,@PathVariable int id,MultipartFile picFile,HttpServletRequest request){
Goods entity=goodsService.getGoodsById(id);
//如果选择了文件
if(picFile!=null){
//如果文件大小不为0
if(picFile.getSize()>0){
//获得上传位置
String path=request.getServletContext().getRealPath("/images");
//生成文件名
String filename=UUID.randomUUID().toString()+picFile.getOriginalFilename().substring(picFile.getOriginalFilename().lastIndexOf("."));
File tempFile=new File(path, filename);
try {
//保存文件
picFile.transferTo(tempFile);
//更新数据
entity.setPicture(filename);
goodsService.update(entity);
//转向列表页
return "redirect:/goods/list";
} catch (Exception e) {
e.printStackTrace();
}
}
}
model.addAttribute("entity", entity);
return "goods/upfile";
}
}
十二、总结
通个该示例将前面几章的内容整合起来,巩固了前几章的内容;示例中还可以尝试使用FreeMarker视图;示例中没有前端验证都是后台验证,可以使用jQuery扩展插件Validate实现前端校验;有些功能可以结合AJAX完成更加合理;路径是要非常小心的,后台重定向时,前台提交表单的路径,可以使用base标签和c:url。内容比较简单,适合初学,只是希望能起到抛砖引玉、以小见大的作用,谢谢阅读!