本节书摘来自异步社区《Spring 3.0就这么简单》一书中的第1章,第1.6节,作者: 陈雄华 , 林开雄著,更多章节内容可以访问云栖社区“异步社区”公众号查看
1.6 展现层
业务层和持久层的开发任务已经完成,该是为程序提供界面的时候了。Struts MVC框架由于抢尽天时地利,成为当下最流行的展现层框架。但也有很多人认为Spring MVC相比较于Struts更简单、更强大、更优雅。此外,由于Spring MVC出自于Spring之手,因此和Spring容器没有任何不兼容性,显得天衣无缝。
Spring 1.5新增了基于注解的MVC,而且Spring 3.1还提供了REST风格的MVC,Spring MVC已经变得轻便、强大、易用。我们将会在本书的第8章中学习Spring MVC的详细内容。
1.6.1 配置Spring MVC框架
首先需要对web.xml文件进行配置,以便Web容器启动时能够自动启动Spring容器,如代码清单1-13所示。
代码清单1-13 自动启动Spring容器的配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="1.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!--①从类路径下加载Spring配置文件,classpath关键字特指在类路径下加载_-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContext.xml
</param-value>
</context-param>
_<!--②负责启动Spring容器的监听器,它将引用①处的上下文参数获得Spring配置文件地址-->_
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
…
</web-app>
首先,通过Web容器上下文参数指定Spring配置文件的地址,如①所示。多个配置文件可用逗号或空格分隔,建议采用逗号分隔的方式。在②处指定Spring所提供的ContextLoaderListener的Web容器监听器,该监听器在Web容器启动时自动运行,它会根据contextConfigLocation Web容器参数获取Spring配置文件,并启动Spring容器。注意需要将log4J.propertis日志配置文件放置在类路径下,以便日志引擎自动生效。
接下来,需要配置Spring MVC相关的信息,Spring MVC像Struts一样,也通过一个Servlet截获URL请求,然后再进行相关的处理,如代码清单1-14所示。
代码清单1-14 Spring MVC地址映射
…
<!-- Spring MVC的主控Servlet -->
<servlet> ①
<servlet-name>viewspace</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<!-- Spring MVC处理的URL -->
<servlet-mapping>②
<servlet-name>viewspace</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
在①处声明了一个Servlet,Spring MVC也拥有一个Spring配置文件(稍后会涉及),该配置文件的文件名和此处定义的Servlet名有一个契约:即采用-servlet.xml的形式。在这里,因为Servlet名为viewspace,所以在/WEB-INF目录下必须提供一个viewspace- servlet.xml的Spring MVC配置文件,但这个配置文件无须通过web.xml的contextConfigLocation上下文参数进行声明,因为Spring MVC的Servlet会自动将viewspace -servlet.xml和Spring其他的配置文件进行拼装。
在②处对这个Servlet的URL路径映射进行定义,在这里让所有以.html为后缀的URL都能被viewspace Servlet截获,进而转由Spring MVC框架进行处理。我们知道,在Struts框架中,一般将URL后缀配置为.do,在Webwork中一般配置为.action,其实,框架本身和URL模式没有任何关系,用户大可使用喜欢的任何后缀。使用.html后缀,一方面,用户不能通过URL直接知道开发者采用了何种服务端技术;另一方面,.html是静态网页的后缀,可以骗过搜索引擎,增加被收录的概率,所以推荐采用这种后缀。对于那些真正的无须任何动态处理的静态网页,则可以使用.htm后缀加以区分,以避免被框架截获。
请求被Spring MVC截获后,首先根据请求的URL查找到目标的处理控制器,并将请求参数封装成一个“命令”对象一起传给控制器处理,控制器调用Spring容器中的业务Bean完成业务处理工作并返回结果视图。
1.6.2 处理登录请求
POJO控制器类
首先要编写的是LoginController,它负责处理登录请求,完成登录业务,并根据登录成功与否转向欢迎页面或失败页面,如代码清单1-15所示。
代码清单1-15 LoginController.java
package com.smart.web;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import com.smart.domain.User;
import com.smart.service.UserService;
@Controller①
@RequestMapping(value = "/admin"
public class LoginController{
@Autowired
private UserService userService;
@RequestMapping(value = "/login.html")②
public String loginPage(){
return "login";
}
@RequestMapping(value = "/loginCheck.html")③
public ModelAndView loginCheck(HttpServletRequest request,LoginCommand loginCommand){
boolean isValidUser =
userService.hasMatchUser(loginCommand.getUserName(),
loginCommand.getPassword());
if (!isValidUser) {
return new ModelAndView("login", "error", "用户名或密码错误。");
} else {
User user = userService.findUserByUserName(loginCommand
.getUserName());
user.setLastIp(request.getRemoteAddr());
user.setLastVisit(new Date());
userService.loginSuccess(user);
request.getSession().setAttribute("user", user);
return new ModelAndView("main");
}
}
}
在①处通过Spring MVC的@Controller注解可以将任何一个POJO的类标注为Spring MVC的控制器,处理HTTP的请求。当然标注了@Controller的类首先会是一个Bean,所以可以使用@Autowired进行Bean的注入。
一个控制器可以拥有多个对应不同HTTP请求路径的处理方法,通过@RequestMapping指定方法如何映射请求路径,如②和③所示。
请求的参数会根据参数名称默认契约自动绑定到响应方法的入参中,在③处的loginCheck(HttpServletRequest request,LoginCommand loginCommand)方法中,请求参数会按名称匹配绑定到loginCommand的入参中。
请求响应方法可以返回一个ModelAndView,或直接返回一个字符串,Spring MVC会解析之并转向目标响应页面。
ModelAndView对象既包括了视图信息又包括了视图渲染所需的模型数据信息,在这里用户仅需要了解它代表一个视图就可以了,在后面的内容中,读者将了解到Spring MVC如何根据这个对象转向真正的页面。
前面使用到的LoginCommand对象是一个POJO,它没有继承于特定的父类或实现特定的接口。LoginCommand类仅包括用户/密码这两个属性(和请求的用户/密码参数名称一样),如代码清单1-16所示。
代码清单1-16 LoginCommand
package com.smart.web;
public class LoginCommand {
private String userName;
private String password;
//省略get/setter方法
}
Spring MVC配置文件
编写好LoginCommand后,需要在viewspace-servlet.xml中声明该控制器,扫描Web路径,指定Spring MVC的视图解析器,如代码清单1-17所示。
代码清单1-17 viewspace-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:p="http://www.springframework. org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<!--①扫描web包,应用Spring的注解 -->
<context:component-scan base-package="com.smart.web"/>
<!--②配置视图解析器,将ModelAndView及字符串解析为具体的页面 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:viewClass="org.springframework.web.servlet.view.JstlView"
p:prefix="/WEB-INF/jsp/"
p:suffix=".jsp" />
</beans>
ModelAndView的解析配置
在代码清单1-15 的③处,控制器根据登录处理结果分别返回 ModelAndView ("login", "error", "用户名或密码错误。")和ModelAndView("main")。ModelAndView的第一个参数代表视图的逻辑名,第二个和第三个参数分别为数据模型名称和数据模型对象,数据模型对象将以数据模型名称为参数名放置到request的属性中。
Spring MVC如何将视图逻辑名解析为具体的视图页面呢?解决的思路也和上面的方法类似,需要在viewspace-servlet.xml中提供一个定义解析规则的Bean,如代码清单1-18所示。
代码清单1-18 viewspace-servlet.xml视图解析规则
…
<!--通过prefix指定在视图名前所添加的前缀,通过suffix指定在视图名后添加的后缀-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:viewClass="org.springframework.web.servlet.view.JstlView"
p:prefix="/WEB-INF/jsp/"
p:suffix=".jsp" />
Spring MVC为视图名到具体视图的映射提供了许多可供选择的方法。在这里,使用了InternalResourceViewResolver,它通过为视图逻辑名添加前后缀的方式进行解析。如视图逻辑名“login”将解析为/WEB-INF/jsp/login.jsp;视图逻辑名“main”将解析为/WEB-INF/jsp/main.jsp。
1.6.3 JSP视图页面
景区网站登录模块共包括两个JSP页面,分别是登录页面login.jsp和管理主页面main.jsp,下面将完成这两个页面的开发工作。
登录页面login.jsp
登录页面login.jsp的代码如代码清单1-19所示。
代码清单1-19 login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<title>景区网站登录</title>
</head>
<body>
<c:if test="${!empty error}"> ①
<font color="red"><c:out value="${error}" /></font>
</c:if>
<form action="<c:url value="/ loginCheck.html "/>" method= "post">②
用户名:
<input type="text" name="userName">
<br>
密 码:
<input type="password" name="password">
<br>
<input type="submit" value="登录" />
<input type="reset" value="重置" />
</form>
</body>
</html>
login.jsp页面既作为登录页面又作为登录失败后的响应页面。因此在 ①处使用JSTL标签对登录错误返回的信息进行处理。JSTL标签中引用了error变量,这个变量正是LoginController中返回的ModelAndView("login", "error", "用户名或密码错误。") 对象所声明的error参数。
login.jsp的登录表单提交到/loginController.html,如②所示。的JSTL标签会在URL前自动加上应用程序部署根目录,假设应用部署在网站的viewspace目录下,标签将输出/viewspace/loginController.html。通过标签很好地解决了开发和应用部署目录不一致的问题。
由于 login.jsp 放置在 WEB-INF/jsp 目录下,无法直接通过 URL 进行调用,它由LoginController 控制类中标注了@RequestMapping(value = "/login.html")的loginPage()进行转发,见代码清单1-15。
景区管理主页面main.jsp
登录成功的欢迎页面很简单,仅使用JSTL标签显示一条欢迎信息即可,如代码清单1-20所示。
代码清单1-20 main.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>景区后台管理主页面</title>
</head>
<body>
${user.userName},欢迎您进入景区后台管理! ①
</body>
</html>
①处访问Session域中的user对象,显示用户名和积分信息。这样,就完成了实例所有的开发任务。