1、Spring Web MVC是什么
Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,将web层进行职责解耦,基于请求驱动指的就是使用请求-响应模型,框架的目的就是帮助我们简化开发,Spring Web MVC也是要简化我们日常Web开发的。
另外还有一种基于组件的、事件驱动的Web框架在此就不介绍了,如Tapestry、JSF等。
Spring Web MVC也是服务到工作者模式的实现,但进行可优化。前端控制器是DispatcherServlet;
应用控制器其实拆为处理器映射器(Handler Mapping)进行处理器管理和视图解析器(View Resolver)进行视图管理;页面控制器/动作/处理器为Controller接口(仅包含ModelAndView handleRequest(request, response) 方法)的实现(也可以是任何的POJO类);支持本地化(Locale)解析、主题(Theme)解析及文件上传等;提供了非常灵活的数据验证、格式化和数据绑定机制;提供了强大的约定大于配置(惯例优先原则)的契约式编程支持。
2、Spring Web MVC能帮我们做什么
√让我们能非常简单的设计出干净的Web层和薄薄的Web层;
√进行更简洁的Web层的开发;
√天生与Spring框架集成(如IoC容器、AOP等);
√提供强大的约定大于配置的契约式编程支持;
√能简单的进行Web层的单元测试;
√支持灵活的URL到页面控制器的映射;
√非常容易与其他视图技术集成,如Velocity、FreeMarker等等,因为模型数据不放在特定的API里,而是放在一个Model里(Map
数据结构实现,因此很容易被其他框架使用);
√非常灵活的数据验证、格式化和数据绑定机制,能使用任何对象进行数据绑定,不必实现特定框架的API;
√提供一套强大的JSP标签库,简化JSP开发;
√支持灵活的本地化、主题等解析;
√更加简单的异常处理;
√对静态资源的支持;
√支持Restful风格。
3、准备开发环境和运行环境:
☆开发工具:eclipse
☆运行环境:resin-pro-3.0.28
☆工程:web工程(springmvc-spring3.0)
☆spring框架下载:
spring-framework-3.1.1.RELEASE-with-docs.zip(下载地址略)
☆依赖jar包:
为了简单,将spring-framework-3.1.1.RELEASE-with-docs.zip/dist/下的所有jar包拷贝到项目的WEB-INF/lib目录下;
并且添加依赖包commons、log4j等,这里就不一一列出来了。
CREATE TABLE USER(
user_id INT AUTO_INCREMENT PRIMARY KEY,
user_name VARCHAR,
PASSWORD VARCHAR,
credits INT,
last_ip VARCHAR,
last_date DATE
)
//记录用户登录日志
CREATE TABLE loginlog(
loginlog_id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT ,
ip VARCHAR,
login_time DATE
)
在user表中添加测试数据:admin,密码为123456
5、创建工程
工程目录如下图所示:
5.1 创建领域对象
领域对象,也就是我们通常所说的实体类,它代表了业务的状态,一般来说,领域对象属于业务层,但他贯穿展示层,业务层和持久层,并最终被持久化到数据库中。
用户领域对象:
public class User implements Serializable {
public User() {
// TODO Auto-generated constructor stub
}
private int userid;
private String username;
private String password;
private int credits;
private String lastIp;
private Date lastDate;
public int getUserid() {
return userid;
}
public void setUserid(int userid) {
this.userid = userid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getLastIp() {
return lastIp;
}
public void setLastIp(String lastIp) {
this.lastIp = lastIp;
}
public Date getLastDate() {
return lastDate;
}
public void setLastDate(Date lastDate) {
this.lastDate = lastDate;
}
public int getCredits() {
return credits;
}
public void setCredits(int credits) {
this.credits = credits;
}
}
登陆日志领域对象:
public class Loginlog implements Serializable{
public Loginlog() {
// TODO Auto-generated constructor stub
}
private int loginlogId;
private int userId;
private String ip;
private Date loginTime;
public int getLoginlogId() {
return loginlogId;
}
public void setLoginlogId(int loginlogId) {
this.loginlogId = loginlogId;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Date getLoginTime() {
return loginTime;
}
public void setLoginTime(Date loginTime) {
this.loginTime = loginTime;
}
}
5.2 UserDao
首先,我们来定义访问user的Dao,它包括3个方法
- getMatchCount():根据用户名和密码获取匹配的用户数。1表示正确,0表示没有此用户或账户密码不对。
- findUserByUsername();根据用户名找到用户。
- updateLoginInfo();更新用户积分,记录用户最后登录的时间,ip等信息
@Repository
// 通过spring注释定义一个Dao
public class UserDao {
@Autowired
// 自动注入JdbcTemplate的bean
private JdbcTemplate jdbcTemplate;
// 根据用户名和密码,获取匹配的用户数,有返回1,没有返回0
public int getMatchCount(String username, String password) {
String QUERY = "select count(*) from user where user_name = ? and password = ?";
return jdbcTemplate.queryForInt(QUERY, new Object[] { username,
password });
}
// 根据用户名获得user对象
public User findUserByUsername(final String username) {
String QUERY = "select * from user where user_name = ?";
final User user = new User();
jdbcTemplate.query(QUERY, new Object[] { username },
new RowCallbackHandler() {
public void processRow(ResultSet rs) throws SQLException {
// TODO Auto-generated method stub
user.setUserid(rs.getInt("user_id"));
user.setPassword(rs.getString("password"));
user.setCredits(rs.getInt("credits"));
user.setLastDate(rs.getDate("last_date"));
user.setLastIp(rs.getString("last_ip"));
user.setUsername(rs.getString("user_name"));
}
});
return user;
}
//更新用户积分、最后登录时间、登陆ip
public void updateLoginInfo(User user){
String UPDATE = "update user set credits = ? , last_ip = ?,last_date = ? where user_id = ?";
jdbcTemplate.update(UPDATE, new Object[]{user.getCredits(),user.getLastIp(),user.getLastDate(),user.getUserid()});
}
}
其中findUserByUsername()方法有点复杂,其中我们用到了JdbcTemplate.query(String sql,Object[] args,RowCallBackHandler rch)方法,他有3个参数:
- sql:sql语句,允许带“?”的占位符
- args:sql中占位符对应的参数数组,按顺序排放
- rch:查询结果的处理回调接口,该回调接口有一个方法processRow(Result rs),负责将查询的结果从Result中装载到类似于领域对象的对象实例中。
5.3 LoginlogDao
LoginlogDao负责记录用户的登陆日志,它仅有一个insertLoginLog()方法
@Repository
public class LoginlogDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void insertLoginlog(Loginlog loginlog){
String insert = "insert into loginlog (user_id,ip,login_time) values (?,?,?) ";
jdbcTemplate.update(insert, new Object[]{loginlog.getUserId(),loginlog.getIp(),loginlog.getLoginTime()});
}
}
5.4 在spring中装配Dao
在项目工程的src目录下创建一个applicationContext.xml文件,配置基本结构如下:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 引入aop及tx命名空间所对应的Schema文件 -->
<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"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd"> ... </beans>
DaoBean的配置如下:
<!-- 扫描类包,将标注spring注解的类自动转化为Bean,同时完成Bean的注入 -->
<!-- spring2.5 以后才支持 -->
<context:component-scan base-package="com.huitong.spring.dao" />
<!-- 定义一个使用dbcp实现的数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close" p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/test" p:username="root" p:password="1234" />
<!-- 定义jdbc模板bean -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource" />
我们使用spring的context:component-scan扫描制定类包下的所有类,这样再类中定义的spring注解(如@Repository、@Autowired等)才能生效。好了,这样2个Dao就完成了,现在先放到一边。
5.4 业务层
UserService业务接口有3个业务方法:
- hasMatchUser();用于检查用户名密码的正确性
- findUserByUsername();以用户名为条件,加载User对象
- loginSuccess();在用户登陆成功后调用,更新用户最后登陆时间和ip等信息,同时记录用户登陆日志
UserService类:
@Service //将userservice标注为一个服务层的bean
public class UserService {
@Autowired
private UserDao userDaoImpl;
@Autowired
private LoginlogDao loginlogDao;
public boolean hasMatchUser(String username,String password){
return userDaoImpl.getMatchCount(username, password)>0;
}
public User findUserByUsername(String username){
return userDaoImpl.findUserByUsername(username);
}
public void loginSuccess(User user){
user.setCredits(5+user.getCredits());
Loginlog loginlog = new Loginlog();
loginlog.setUserId(user.getUserid());
loginlog.setLoginTime(user.getLastDate());
loginlog.setIp(user.getLastIp());
userDaoImpl.updateLoginInfo(user);
loginlogDao.insertLoginlog(loginlog);
}
}
前两个方法通过简单的调用Dao完成对应的功能;loginSuccess()根据入参user对象构造出loginLog对象,并将user.credits递增5,即用户没登陆一次赚取5个积分,然后更新到t_user中,在往t_loginlog中,添加一条记录。
5.5 在spring中装配service
打开原来的applicationContext.xml文件,添加如下代码:
<!-- 扫描service类包,应用spring的注解配置 -->
<context:component-scan base-package="com.huitong.spring.service" />
<!-- 配置事物管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource" />
<!-- 通过aop配置提供事物增强,让service包下所有bean的所有方法拥有事物 -->
<aop:config proxy-target-class="true">
<aop:pointcut expression="execution(* com.huitong.spring.service.*.*(..))"
id="serviceMethod" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" />
</tx:attributes>
</tx:advice>
5.6 单元测试
到这里,我们可以使用JUnit进行单元测试,验证一下,由于很简单,过程我就不写了。
5.7 配置springMVC框架
首先,对web.xml文件配置,如下:
<!-- 启动spring容器的监听器 ,他将引用上面的上下文参数获得spring的配置文件地址 -->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!-- springMVC 的servlet -->
<!-- <servlet-name>标签配的值为springServlet(<servlet-name>springServlet</servlet-name>),再加上“-servlet”后缀而形成的springServlet-servlet.xml文件名
并且这个路径在/WEB-INF/下 -->
<servlet>
<servlet-name>springServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:servlet-do.xml</param-value>
</init-param> -->
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<!-- 从类路径下加载spring配置文件,classpath关键字默认类路径下加载 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
5.8 处理登陆请求--POJO控制器类
//标注成为一个springMVC的controller
@Controller
public class LoginController {
@Autowired
public UserService serviceImpl;
// 负责处理login.do的请求
@RequestMapping(value = "/loginPage.do")
public ModelAndView loginPage(HttpServletRequest request,
HttpServletResponse response) {
return new ModelAndView("/login");
}
// 负责处理loginCheck.do的请求
@RequestMapping(value = "/loginCheck.do")
public ModelAndView loginCheck(HttpServletRequest request,
HttpServletResponse response, LoginCommand loginCommand) {
boolean isValideUser = serviceImpl.hasMatchUser(
loginCommand.getUserName(), loginCommand.getPassword());
if (!isValideUser) {
System.out.println("转到login页面。。。");
return new ModelAndView("login", "error", "用户名或密码错误");
} else {
User user = serviceImpl.findUserByUsername(loginCommand
.getUserName());
user.setLastIp(request.getRemoteAddr());
user.setLastDate(new Date());
serviceImpl.loginSuccess(user);
request.getSession().setAttribute("user", user);
return new ModelAndView("main");
}
}
}
通过spring的@Controller注解可以将POJO的任何一个类标注为springMVC的控制器,处理HTTP请求。一个控制器可以对应多个不同的HTTP请求,通过@RequestMapping指定如何映射请求路径,其中,请求的参数会根据参数名称默认契约自动绑定到响应方法的入参中,如这里的loginCommand。
请求响应方法可以返回一个ModerAndView,或直接返回一个字符串,springMVC回解析并转向目标响应页面。这里我们只要知道它代表一个视图。前面我们用到的loginCommand对象,他是一个POJO,没有继承特定的父类或实现特定的接口,并且只有username和password两个属性。如下:
public class LoginCommand {
private String userName;
private String password;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
5.9 springMVC配置文件
编写好loginCommand之后,我们需要申明该控制器servletSpring-servlet.xml,扫描web路径,指定springMVC的视图解析器,如下:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 引入aop及tx命名空间所对应的Schema文件 -->
<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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">
<!-- 启用spring mvc 注解 -->
<mvc:annotation-driven />
<!-- 扫描web包,添加spring注解 -->
<context:component-scan base-package="com.huitong.spring.web"></context:component-scan>
<!-- 配置视图解析器,将ModelAndView解析为具体的页面 对转向页面的路径解析。 -->
<!-- 前缀 prefix 后缀 suffix -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
<bean id="exceptionResolver"
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="defaultErrorView" value="failure" />
<property name="exceptionMappings">
<props>
<prop key="java.sql.SQLException">/error/error.jsp</prop>
<prop key="java.lang.RuntimeException">/error/error.jsp</prop>
</props>
</property>
</bean>
</beans>
5.10 jsp前台页面
login.jsp,如下:
<%@ page language="java" contentType="text/html;charset=utf-8" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>my spring3.0</title>
<title>My JSP 'index.jsp' starting page</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->
</head>
<body>
<c:if test="${!empty error}">
<font color="red"><c:out value="${error }" /></font>
</c:if>
<form action="<c:url value="/loginCheck.do"/> " 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>
main.jsp登陆之后的显示页面,如下:
<%@ page language="java" contentType="text/html;charset=utf-8"
import="java.util.*" pageEncoding="utf-8"%>
<%@ page isELIgnored="false" %>
...
</head>
<br /> ${sessionScope.user.username},欢迎您进入,您的积分为${user.credits}.
<body>
</body>
</html>
这里需要使用<%@ page isELIgnored="false" %>设置el表达式生效。
好了,到这里这个项目就完成了,可以运行查看效果了。
PS:由于本人水平有限,文中难免出现错误之处,恳请大家批评指正,我会及时修改更新,谢谢。