Spring-Spring MVC + Spring JDBC + Spring Transaction + Maven 构建web登录模块
- 概述
- 功能简介
- 环境准备
- 构建工具Maven
- 数据库脚本Oracle
- 建立工程
- 类包及Spring配置文件规划
- 持久层
- 建立领域对象
- 用户领域对象
- 登录日志领域对象
- UserDao
- LoginLogDao
- 在Spring中装配DAO
- 建立领域对象
- 业务层
- UserService
- 在Spring中装配Service
- 使用TestNGN对业务类进行单元测试
- 展现层
- 配置Spring MVC 框架
- 处理登录请求
- POJO控制器类
- Spring MVC 配置文件
- ModelAndView的解析配置
- JSP视图页面
- loginjsp
- mainjsp
- 运行Web应用
- 第一种方式 maven插件部署web应用服务器
- 第二种方式 部署到容器中
- 总结
- 附件 pomxml
概述
本模块主要涉及以下部分:
- Maven构建web项目的过程
- 登录流程介绍
- 基于Spring JDBC的持久层实现
- 基于Spring 声明式事务的业务层实现
- 基于Spring MVC的展现层实现
- 运行Web应用
功能简介
用户访问login.jsp,返回带有用户名和密码输入框的登录页面
用户输入用户名和密码,提交表单到服务器,Spring根据配置文件调用LoginController控制器响应请求
LoginController调用UserService的hasMatchUser()方法来判断用户是否存在
如果用户不存在,跳转到登录页面,给出提示,否则进入下一步
如果用户存在,LoginController调用UserService的findUserByName方法,进行登录成功后的业务处理:首先调用updateLoginInfo的updateLoginInfo方法 增加5分积分,然后创建一个LoginLog对象,将LoginLog写入数据库
重定向到主页面,将响应返回给用户
环境准备
构建工具Maven
请参考上篇博文 Maven-EclipseEE使用Maven构建Java web项目从0到1
数据库脚本(Oracle)
-- Create table
create table TEMP_USER
(user_id NUMBER not null,user_name VARCHAR2(50),password VARCHAR2(10),credits NUMBER,last_visit DATE,last_ip VARCHAR2(20)
)
tablespace TAB_XGJpctfree 10initrans 1maxtrans 255storage(initial 64Kminextents 1maxextents unlimited);
-- Create/Recreate primary, unique and foreign key constraints
alter table TEMP_USERadd constraint PK_USER_ID primary key (USER_ID)using index tablespace TAB_XGJpctfree 10initrans 2maxtrans 255;-- Create sequence
create sequence TEMP_USER_ID_SEQ
minvalue 1
maxvalue 999999999
start with 1
increment by 1
cache 20;-- Create table
create table TEMP_LOGIN_LOG
(login_log_id NUMBER not null,user_id NUMBER not null,ip VARCHAR2(20),login_datetime DATE
)
tablespace TAB_XGJpctfree 10initrans 1maxtrans 255storage(initial 64Kminextents 1maxextents unlimited);
-- Create/Recreate primary, unique and foreign key constraints
alter table TEMP_LOGIN_LOGadd constraint PK_LOGIN_LOG_ID primary key (LOGIN_LOG_ID)using index tablespace TAB_XGJpctfree 10initrans 2maxtrans 255;
alter table TEMP_LOGIN_LOGadd constraint FK_USER_ID foreign key (USER_ID)references TEMP_USER (USER_ID);-- Create sequence
create sequence TEMP_LOGIN_LOG_ID_SEQ
minvalue 1
maxvalue 999999999
start with 1
increment by 1
cache 20;--simulate data
INSERT INTO TEMP_USER (user_id,user_name,password) VALUES(TEMP_USER_ID_SEQ.Nextval ,'xgj','123456');
COMMIT;
建立工程
请参考上篇博文 Maven-EclipseEE使用Maven构建Java web项目从0到1
类包及Spring配置文件规划
类包以分层的方式进行组织,供划分为dao、domain、service、web。
domain对象严格意义上讲属于业务层,但是由于domain对象可能同时被持久层和展现层共享,所以一般单独的将其划分在一个包中。
持久层
持久层负责数据的访问和操作,DAO类被上层的业务类调用。
这里使用Spring JDBC作为持久层的实现技术。
建立领域对象
领域对象Domain Object 也被称为实体类,它代表了业务的状态、且贯穿展现层、业务层和持久层,并追踪持久化到数据库中。
领域对象不一定等同于数据库表,但对于简单的应用来讲的话,领域对象往往拥有对应的数据库表。
持久层的主要工作是从数据库表中加载数据并实例化领域对象,或将领域对象持久化到数据库表中。
我们使用的案例中主要涉及两个领域对象 User 和 LoginLog, 分别对应 TEMP_USER 和 TEMP_LOGIN_LOG 表。
用户领域对象
可以看成对TEMP_USER表的对象映射,每个字段对应一个对象属性。
package com.xgj.domain;import java.io.Serializable;
import java.util.Date;/*** @ClassName: User* @Description: User实体类,可以简单的理解为对 temp_user表的字段映射* @author: Mr.Yang* @date: 2017年6月24日 下午6:45:12*/// 领域对象一般都要实现Serializable接口,以便可以序列化
public class User implements Serializable {private int userId;private String username;private String password;private int credit;private Date lastVisit;private String lastIP;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 int getCredit() {return credit;}public void setCredit(int credit) {this.credit = credit;}public Date getLastVisit() {return lastVisit;}public void setLastVisit(Date lastVisit) {this.lastVisit = lastVisit;}public String getLastIP() {return lastIP;}public void setLastIP(String lastIP) {this.lastIP = lastIP;}}
登录日志领域对象
对TEMP_LOGIN_LOG 表的对象映射
package com.xgj.domain;import java.io.Serializable;
import java.util.Date;/*** * @ClassName: LoginLog* * @Description: 登录信息实体类,可以简单的理解为对 TEMP_LOGIN_LOG表的字段映射* * @author: Mr.Yang* * @date: 2017年6月24日 下午8:12:28* * *///领域对象一般都要实现Serializable接口,以便可以序列化
public class LoginLog implements Serializable {private int login_log_id;private int user_id;private String ip;private Date login_datetime;public int getLogin_log_id() {return login_log_id;}public void setLogin_log_id(int login_log_id) {this.login_log_id = login_log_id;}public int getUser_id() {return user_id;}public void setUser_id(int user_id) {this.user_id = user_id;}public String getIp() {return ip;}public void setIp(String ip) {this.ip = ip;}public Date getLogin_datetime() {return login_datetime;}public void setLogin_datetime(Date login_datetime) {this.login_datetime = login_datetime;}
}
UserDao
首先来定义访问User的DAO。
按照设计,主要包括3个方法
- getMatchCount(String username,String passwd)
- findUserbyUsername(tring username)
- updateLoginInfo(User user) 更新用户积分、最后登录IP和最后登录时间
package com.xgj.dao;import java.sql.ResultSet;
import java.sql.SQLException;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.stereotype.Repository;import com.xgj.domain.User;/*** * @ClassName: UserDao* * @Description: 定义访问User的DAO.操作TEMP_USER表* * @author: Mr.Yang* * @date: 2017年6月24日 下午8:19:34* * */
@Repository // 通过Spring注解定义一个DAO
public class UserDao {private JdbcTemplate jdbcTemplate;private final static String MATCH_COUNT_SQL = " SELECT count(*) FROM temp_user "+ " WHERE user_name =? and password=? ";private final static String UPDATE_LOGIN_INFO_SQL = " UPDATE temp_user SET "+ " last_visit=?,last_ip=?,credits=? WHERE user_id =? ";@Autowired // 自动注入JdbcTemplate的beanpublic void setJdbcTemplate(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}/*** * * @Title: getMatchCount* * @Description: 查询是否有匹配的user* * @param username* @param password* * @return: int*/public int getMatchCount(String username, String password) {return jdbcTemplate.queryForObject(MATCH_COUNT_SQL, new Object[] { username, password }, Integer.class);}/*** * @Title: findUserbyUsername* * @Description: 根据username查询用户信息* * @param username* * @return: User*/public User findUserbyUsername(final String username) {String querySql = " SELECT user_id,user_name,credits " + " FROM temp_user WHERE user_name =? ";final User user = new User();// 根据username查询用户信息jdbcTemplate.query(querySql, new Object[] { username }, new RowCallbackHandler() {public void processRow(ResultSet rs) throws SQLException {user.setUserId(rs.getInt("user_id"));user.setUsername(username);user.setCredit(rs.getInt("credits"));}});return user;}/*** @Title: updateLoginInfo* * @Description: updateLoginInfo* * @param user* * @return: void*/public void updateLoginInfo(User user) {jdbcTemplate.update(UPDATE_LOGIN_INFO_SQL,new Object[] { user.getLastVisit(), user.getLastIP(), user.getCredit(), user.getUserId() });}}
Spring 2.5之后可以使用注解的方式定义Bean,较之XML的配置方式,注解配置的方式更加简单明了,推荐使用。
通过@Repository定义一个DAO Bean,然后使用@Autowired将Spring容器中的Bean注入进来。
操作数据库使用Spring JDBC ,对传统的JDBC API进行了薄层封装。 Spring JDBC通过模板类
org.springframework.jdbc.core.JdbcTemplate封装了样板式的o码,用户可以很轻松的完成大部分数据访问操作。
LoginLogDao
LoginLogDao 负责记录用户的登录日志,仅仅有一个方法 insertLoginLog(LoginLog loginLog)
package com.xgj.dao;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;import com.xgj.domain.LoginLog;@Repository // 通过Spring注解定义一个DAO
public class LoginLogDao {private JdbcTemplate jdbcTemplate;// 保存登陆日志SQLprivate final static String INSERT_LOGIN_LOG_SQL = "INSERT INTO TEMP_LOGIN_LOG(login_log_id,user_id,ip,login_datetime) VALUES(TEMP_LOGIN_LOG_ID_SEQ.nextval,?,?,?)";@Autowired // 自动注入JdbcTemplate的Beanpublic void setJdbcTemplate(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}public void insertLoginLog(LoginLog loginLog) {Object[] args = { loginLog.getUser_id(), loginLog.getIp(), loginLog.getLogin_datetime() };jdbcTemplate.update(INSERT_LOGIN_LOG_SQL, args);System.out.println("成功");}}
在Spring中装配DAO
我们在编写DAO接口实现类的时候,我们并没有看到打开、释放数据库connection的代码, 那DAO类是如何访问数据库的呢?
是的 JdbcTemplate封装起来了, JdbcTemplate 本身需要一个数据源DataSource,这样它就可以从DataSource中获取或者返回连接。
在UserDAO和LoginDAO都提供了一个带@Autowired注解的JdbcTemplate变量,所以我们必须先声明一个数据源,然后定义一个JdbcTemplate变量,通过Spring的容器上下文自动绑定机制注入bean.
Maven工程,在src\resources(资源文件目录),创建Spring的配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!--引用Spring的多个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/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd<!-- 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 --><context:component-scan base-package="com.xgj.dao"/><!-- 配置数据源 http://langgufu.iteye.com/blog/2200588--><!-- mysql<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close" p:driverClassName="com.mysql.jdbc.Driver"p:url="jdbc:mysql://localhost:3306/sampledb"p:username="root"p:password="123456" /> --><!-- oracle 两种写法<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"><property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/><property name="url" value="jdbc:oracle:thin:@172.25.246.11:1521:cc"/><property name="username" value="cctb"/><property name="password" value="zsmart2017"/></bean> --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"p:driverClassName="oracle.jdbc.driver.OracleDriver"p:url="jdbc:oracle:thin:@172.25.246.11:1521:cc"p:username="cctb"p:password="smart" /><!-- 配置Jdbc模板 --><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"p:dataSource-ref="dataSource" /></beans>
- 我们使用
<context:component-scan base-package="目录名称"/>
扫描指定类包下的所有类,这样在类中定义的注解比如@Respoitiry @Autowired等才会产生作用 - 我们使用Jakarta的DBCP开源数据实现方案定义一个数据源
- 配置 将 上一步的DataSource注入到JdbcTemplate中, 而这个JdbcTemplate将通过@Autowired自动注入到LoginDao和UserDao的Bean中, 可见Spring可以很好地将注解配置和XML配置统一起来。
这样 就完成了登录模块的持久层所有的开发工作
业务层
我们设计的登录模块,业务层仅有一个业务类 UserService。
UserService负责将持久层的UserDAO和LoginLogDao组织起来,完成用户密码认证、登录日志记录等操作。
UserService
三个业务方法
- boolean hasMatchUser(String username, String password)
- User findUserByName(String username)
- void loginSuccess(User user)
UserService的实现类需要调用DAO层的两个DAO完成业务逻辑操作。
package com.xgj.service;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import com.xgj.dao.LoginLogDao;
import com.xgj.dao.UserDao;
import com.xgj.domain.LoginLog;
import com.xgj.domain.User;@Service // 将UserService标注为一个服务层的bean
public class UserService {private UserDao userDao;private LoginLogDao loginLogDao;@Autowired // 注入beanpublic void setUserDao(UserDao userDao) {this.userDao = userDao;}@Autowired // 注入beanpublic void setLoginLogDao(LoginLogDao loginLogDao) {this.loginLogDao = loginLogDao;}public boolean hasMatchUser(String username, String password) {return userDao.getMatchCount(username, password) > 0;}public User findUserByName(String username) {return userDao.findUserbyUsername(username);}@Transactionalpublic void loginSuccess(User user) {// credit 增加5分user.setCredit(5 + user.getCredit());LoginLog loginLog = new LoginLog();loginLog.setUser_id(user.getUserId());loginLog.setIp(user.getLastIP());loginLog.setLogin_datetime(user.getLastVisit());userDao.updateLoginInfo(user);loginLogDao.insertLoginLog(loginLog);}}
其中 loginSuccess方法将两个DAO组织起来,共同完成一个事务性的数据操作:更新temp_user表 并 添加temp_login_Log表的记录。
我们在UserService中看不出来任何事务操作的影子,这正是Spring的高明之处,通过注解和配置文件配置的方式,将我们从事务操作单调机械的代码中解脱出来。
下面我们来看下Spring是如何装载Service的
在Spring中装配Service
事务管理的代码虽然仅仅是以注解@Transactional的方式出现在程序代码中,但是我们必须告诉Spring哪些业务类需要工作在事务环境下以及事务的规则等,以方便Spring根据这些信息自动为目标业务类添加事务管理的功能。
<?xml version="1.0" encoding="UTF-8" ?>
<!--(1)引入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/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-4.3.xsd"><context:component-scan base-package="com.xgj.dao"/><!-- 2)扫描类Service类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 --><context:component-scan base-package="com.xgj.service"/><!-- 配置数据源 http://langgufu.iteye.com/blog/2200588--><bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"p:driverClassName="oracle.jdbc.driver.OracleDriver"p:url="jdbc:oracle:thin:@172.25.246.11:1521:cc"p:username="cctb"p:password="smart" /><!-- 配置Jdbc模板 --><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"p:dataSource-ref="dataSource" /><!-- (3)配置事务管理器 --><bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"p:dataSource-ref="dataSource" /><!-- (4)通过AOP配置提供事务增强,让service包下所有Bean的所有方法拥有事务 --><aop:config proxy-target-class="true"><aop:pointcut id="serviceMethod"expression="(execution(* com.xgj.service..*(..))) and (@annotation(org.springframework.transaction.annotation.Transactional))" /><aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice" /></aop:config><tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><tx:method name="*" /></tx:attributes></tx:advice>
</beans>
查看 (1)(2)(3)(4) 步骤以及说明。
这样就完成了业务层的程序开发和配置工作, 接下来需要对该业务类进行单元测试
使用TestNGN对业务类进行单元测试
TestNG和Junit相比有了重大的改进,我们在这里使用TestNGN使用单元测试。
步骤:
- 添加TestNGN依赖包到跟模块的pom.xml文件中
<dependency><groupId>org.testng</groupId><artifactId>testng</artifactId><version>${testng.version}</version><scope>test</scope></dependency>
其中 版本为 6.8.7
<properties><testng.version>6.8.7</testng.version>
</properties>
因本模块采用Maven构建 ,所以在约定目录下 src\test\java测试目录下创建和UserService一致的包结构,并创建UserService对应的测试类UserServiceTest ,目录如下:
编写测试代码
package com.xgj.service;import java.util.Date;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests;
import org.testng.annotations.Test;
import static org.testng.Assert.*;import com.xgj.domain.User;@ContextConfiguration("classpath*:/spring-context.xml")
public class UserServiceTest extends AbstractTransactionalTestNGSpringContextTests {@Autowiredprivate UserService userService;@Testpublic void testHashUserMatch(){boolean b1 = userService.hasMatchUser("xgj", "123456");boolean b2 = userService.hasMatchUser("wrongname", "123456");assertTrue(b1);assertTrue(!b2);}@Testpublic void testFindUserByUserName()throws Exception{for(int i =0; i< 100;i++) {User user = userService.findUserByName("xgj");assertEquals(user.getUsername(), "xgj");}}@Testpublic void testAddLoginLog() {User user = userService.findUserByName("xgj");user.setUserId(1);user.setUsername("xgj");user.setLastIP("172.24.21.53");user.setLastVisit(new Date());userService.loginSuccess(user);}
}
Spring4.0的测试框架很好的整合了TestNGN单元测试框架, 测试代码通过扩展Spring测试框架提供的测试基类AbstractTransactionTestNGSpringContextTests来启动测试运行器。
通过@ContextConfiguration注解来指定Spring的配置文件。
使用@Autowired 将Spring容器中的Bean注入到测试类中。
在测试方法前通过TestNGN的@Test注解方法即可将方法标注为测试方法。
- 运行测试类
选中项目,右键 Runas —Maven Test
观察运行结果,确保无错误。
展现层
我们已经开发完成了持久层和业务层 ,是时候提供一个简单的页面了~
在这里我们使用SpringMVC作为展现层的技术选择。
Spring3.0提供了REST风格的MVC,使Spring MVC变的更加轻便、已用。
Spring4.0对MVC进行了全面的增强,支持跨域注解@CrossOrigin配置、GroovyWeb集成、Gson、Jackson、Protobuf的HttpMessageConverter消息转换器等。
配置Spring MVC 框架
- 配置web.xml,以便Web容器启动的时候能够自动启动Spring容器。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"version="3.0"><display-name>Archetype Created Web Application</display-name><!--(1) 从类路径下加载Spring配置文件,classpath关键字--><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring-context.xml</param-value></context-param><!--(2)负责启动Spring容器的监听器,它将引用(1)处的上下文参数获得Spring配置文件的地址 --><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener>....
</web-app>
在(1)处通过web容器上线文参数指定Spring配置文件的地址,多个配置文件使用逗号或者空格分隔,建议逗号。
然后在(2)处指定Spring所提供的ContextLoaderListener的web容器监听器,该监听器活在web容器启动时自动运行,它会根据contextConfigLocationWeb容器参数获取Spring配置文件,并启动Spring容器。
注意需要将log4j.propertis日志配置文件放在类路径下,以便日志引擎自动生效。
最后需要配置SpringMVC相关的信息, SpringMVC也是通过一个Servlet来截取URL请求,然后再进行相关的处理
<!--(1)Spring MVC 的主Servlet--><servlet><servlet-name>smart</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><load-on-startup>3</load-on-startup></servlet><!--(2)Spring MVC 处理的URL--><servlet-mapping><servlet-name>smart</servlet-name><url-pattern>*.html</url-pattern></servlet-mapping>
在(1)处生命了一个Servlet,Spring MVC 也拥有一个Spring配置文件(后面涉及),该配置文件的文件名称和此处定义的Servlet名有一个契约,即<Servlet名>-servlet.xml
的形式.
在这里,我们定义的Servlet的名称为smart ,因此在/WEB-INF目录下必须提供一个名为smart-servlet.xml的Spring MVC 配置文件。但是这个配置文件无需通过web.xml的contextConfigLocation上下文参数进行声明, Spring MVC 会自动将smart-servlet.xml文件和Spring的其他配置文件进行拼装。
在(2)处对这个Servlet的URL路径进行定义,在这里让所有以.html为后缀的URL都能被smart Servlet截获, 进而转由Spring MVC框架进行处理。
这种方式的好处: 使用.html后缀 ,一方面用户不能通过URL直接指导我们采用了何种服务端技术,另一方面.html是静态网页的后缀,可以骗过搜索引擎,增加被收录的概率。 推荐采用这种后缀。
对已那些真正无需任何动态处理的静态网页,则可以使用.htm后缀加以区分,以避免被框架截获。
当请求被Spring MVC截获后,首先根据请求的URL来查找目标的处理控制器,并将请求参数封装“命令”对象一起传给控制器处理, 然后控制器调用Spring容器中的业务Bean完成业务处理工作并返回结果视图。
处理登录请求
POJO控制器类
首先编写LoginController类,负责处理登录请求,完成登录业务。
package com.xgj.web;import java.util.Date;import javax.servlet.http.HttpServletRequest;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;import com.xgj.domain.User;
import com.xgj.service.UserService;//(1)标注称为Spring MVC的Controller
@RestController
public class LoginController{private UserService userService;// (2)负责处理 /index.html的请求@RequestMapping(value = "/index.html")public String loginPage(){return "login";}// (3)负责处理 /loginCheck.html的请求@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.findUserByName(loginCommand.getUserName());user.setLastIP(request.getLocalAddr());user.setLastVisit(new Date());userService.loginSuccess(user);request.getSession().setAttribute("user", user);return new ModelAndView("main");}}// 自动注入bean@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}
}
在(1)处通过Spring MVC的 @RestController(或者@Controller)注解可以将任何一个POJO的类标注为Spring MVC的控制器,处理HTTP请求。 当然了 标注了 @RestController(或者@Controller)首先是一个Bean,所以可以通过使用@Autowired进行Bean的注入。
一个控制器可以拥有多个处理映射不同HTTP请求路径的方法,比如(2)(3)。
请求参数会根据参数名称默认契约自动绑定到相应方法的入参中。 比如 在(3)中的loginCheck(HttpServletRequest request,LoginCommand loginCommand)方法中,请求参数会按照名称匹配到loginCommand的入参中。
请求响应方法可以返回一个ModelAndView,或者直接返回一个字符串,SoringMVC会解析并转向目标响应页面。
ModelAndView对象既包括视图信息,又包括视图渲染所需要的模型数据信息。
刚才提到的LoginCommand对象是一个POJO,没有继承特定的父类或者实现特定的接口。 LoginCommand仅仅包括用户名和密码这两个属性(和请求的用户密码参数名称一样)
package com.xgj.web;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;}
}
Spring MVC 配置文件
编写好LoginCommand后,需要在smart-servlet.xml中声明该控制器,扫描Web路径,指定SpringMVC的视图解析器。
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans-4.3.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <!-- 扫描web包,应用Spring的注解 --><context:component-scan base-package="com.xgj.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的解析配置
在LoginController 控制器类中
.....
return new ModelAndView("login", "error", "密码错误,请重新登录");
......
return new ModelAndView("main");
ModelAndView的第一个参数代表视图的逻辑名,第二、第三个参数分别为数据模型名称和数据模型对象,数据模型对象将以数据模型名称为参数名放置到request的属性中
那么Spring是如何将视图逻辑名称解析为具体的视图页面呢?
同样的 我们需要在smart-servlet中提供一个定义解析规则的bean
<!-- 配置视图解析器,将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" />
Spring MVC为视图名到具体视图的映射提供了需要到中方法,这里我们使用 InternalResourceViewResolver。
它通过为视图逻辑名添加前后缀的方式进行进行解析。
比如 逻辑视图名为 “login”,将根据配置解析为/WEB-INF/jsp/login.jsp ,同样的 逻辑视图名为“main”,将解析为/WEB-INF/jsp/main.jsp
JSP视图页面
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>
JSTL标签中中引用了 error变量正是LoginController中返回的 return new ModelAndView(“login”, “error”, “密码错误,请重新登录”); 对象所声明的error参数。
由于我们将 jsp放在了 WEB-INF/jsp目录下,所以无法通过URL直接访问,必须通过LoginController控制类中标注了@RequestMapping(value = “/index.html”)的loginPage()方法进行转发。
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" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>论坛</title>
</head>
<body>${user.username},欢迎您进入论坛,您当前积分为${user.credit};
</body>
</html>
main页面 仅适用JSTL标签显示一条欢迎信息。
运行Web应用
第一种方式 maven插件部署web应用服务器
这里我们使用jetty服务器插件,首先在pom.xml中配置插件
<!-- jetty插件 --><plugin><groupId>org.mortbay.jetty</groupId><artifactId>maven-jetty-plugin</artifactId><version>6.1.25</version><configuration><connectors><connector implementation="org.mortbay.jetty.nio.SelectChannelConnector"><port>8080</port><maxIdleTime>60000</maxIdleTime></connector></connectors><contextPath>/${project.artifactId}</contextPath><scanIntervalSeconds>5</scanIntervalSeconds></configuration></plugin>
运行: 选中项目,右键 Run as - Run Jetty
http://localhost:8080/hello-spring4/index.html
第二种方式 部署到容器中
在eclipse中配置 Tomcat 容器,然后
选中项目,右键 Run as - Run on Server
同样可以达到访问的目的
总结
虽然是一个小的登录模块,重点是体会开发的过程,使用Spring MVC、SpringJDBC、Spring声明式事务等技术。
附件 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/maven-v4_0_0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.xgj</groupId><artifactId>hello-spring4</artifactId><packaging>war</packaging><version>0.0.1-SNAPSHOT</version><name>hello-spring4 Maven Webapp</name><url>http://maven.apache.org</url><dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>3.8.1</version><scope>test</scope></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>${servlet.version}</version><scope>provided</scope></dependency><!-- spring 依赖 --><dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context-support</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>${spring.version}</version></dependency><dependency><groupId>commons-dbcp</groupId><artifactId>commons-dbcp</artifactId><version>${commons-dbcp.version}</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>${aspectj.version}</version></dependency><dependency><groupId>org.testng</groupId><artifactId>testng</artifactId><version>${testng.version}</version><scope>test</scope></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>${spring.version}</version><scope>test</scope></dependency><!-- 添加oracle jdbc driver --><dependency><groupId>com.oracle</groupId><artifactId>ojdbc6</artifactId><version>${oracle.version}</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>jstl</artifactId><version>${jstl.version}</version></dependency></dependencies><build><finalName>hello-spring4</finalName><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.1</version><configuration><source>1.7</source><target>1.7</target></configuration></plugin><!-- jetty插件 --><plugin><groupId>org.mortbay.jetty</groupId><artifactId>maven-jetty-plugin</artifactId><version>6.1.25</version><configuration><connectors><connector implementation="org.mortbay.jetty.nio.SelectChannelConnector"><port>8080</port><maxIdleTime>60000</maxIdleTime></connector></connectors><contextPath>/${project.artifactId}</contextPath><scanIntervalSeconds>5</scanIntervalSeconds></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>2.17</version><configuration><parallel>methods</parallel><threadCount>10</threadCount></configuration></plugin></plugins></build><properties><file.encoding>UTF-8</file.encoding><spring.version>4.3.9.RELEASE</spring.version><servlet.version>3.1.0</servlet.version><aspectj.version>1.8.1</aspectj.version><commons-dbcp.version>1.4</commons-dbcp.version><jetty.version>8.1.8.v20121106</jetty.version><slf4j.version>1.7.5</slf4j.version><testng.version>6.8.7</testng.version><oracle.version>11.2.0.4.0</oracle.version><jstl.version>1.2</jstl.version></properties>
</project>
Spring-Spring MVC + Spring JDBC + Spring Transaction + Maven 构建web登录模块相关推荐
- idea使用maven构建web应用
关于idea的下载和安装以及maven的配置,网上资源很多.可以百度自行配置,本文主要介绍idea使用maven构建web应用,因为作者自己花费了一天的时间才成功到.所以将配置方法总结. 假设你已经下 ...
- maven(3)------maven构建web项目详细步骤
eclipse集成工具,轻松通过maven构建web项目步骤如下: 一, 右键,new -->project, 进入下一页面 二,选择"Maven Project", 点击下 ...
- 在IDEA上使用maven构建WEB工程,出现Unable to compile class for JSP错误,页面500. ————解决方案
交代一下环境:jdk1.8 tomcat8.5.50 maven3.6.3 在IDEA上使用maven构建WEB工程,通过servlet跳转jsp时出现下述错误: 解决思路: 1. ...
- Maven:构建web工程出现Failed to execute goal org.apache:maven-archetype-plugin:3.1.1:generate (default-cli)
QUESTION:Maven:构建web工程出现Failed to execute goal org.apache:maven-archetype-plugin:3.1.1:generate (def ...
- maven学习(4)-Maven 构建Web 项目
紧接着上一节(3),现在maven新建web项目,user-web.模拟一个用户登录的需求: 工程结构: pom.xml: <project xmlns="http://maven.a ...
- 使用maven构建web项目
maven安装 1) 下载Maven:http://maven.apache.org/download.html 2) 解压缩下载的zip文件到本地目录下,比如:D:\Maven 3) 添加D:\Ma ...
- html5引入spring标签,[MVC]5 使用Spring标签库
A.Spring标签库 Web项目若使用Spring Web MVC并使用JSP作为表现的话.从Spring2.0版本开始提供一套标签库可供使用. 使用标签库无非是易于开发,维护之类云云.这里就不阐述 ...
- 基于Maven构建Web项目
1.下载Maven,并配置好环境变量 2.打开命令行窗口,输入以下命令构建Maven Web项目 mvn archetype:generate -DgroupId=com.hello -Dartifa ...
- maven 构建web程序出现问题
这几天一直在学习maven,刚入门的第一步我就没有进行的下去,使用mvn archetype:create 命令 会报错: Failed to execute goal org.apache.mav ...
最新文章
- 年度重磅:《AI聚变:2018年优秀AI应用案例TOP 20》正式发布
- js实现图片不能显示时使用替换图片显示
- 【Paper】2017_Limit-Cycle-Based Decoupled Design of Circle Formation Control with Collision Avoidance
- 中国新型储能市场技术进展与投资经营模式建议分析报告2022-2028年版
- html请求接口_python接口自动化测试 - 2.Django开发接口
- python批量写入数据库engine_python 快速写入postgresql数据库方法
- c语言判定三角形方法,c语言判定三角形的各种类型——请大家指点
- 线程同步以及yield()、wait()、Notify()、Notifyall()
- python 输入字符串_输入输出,字符串如影随形 | Python基础连载(五)
- java的empty_Java Stack empty()方法与示例
- Gradle DSL method not found: ‘compile()’
- DataGrip 上手体验,真香!
- 关于MARATHON和容器的端口映射
- linux shc shell脚本_用shc加密shell脚本
- php课程设计感想,设计心得体会
- 理解offset in Python
- error:unable to access jarfile cracker2017.jar解决方法
- C语言每日一练——第28天:要求输出国际象棋棋盘
- 计算机网络-学习笔记
- 【UE】倒计时归零时结束游戏
热门文章
- 数学系列 - 概率论 - 泊松分布和(负)指数分布
- C++基类和派生类的构造函数(二)
- python中shift_python对列进行平移变换的方法(shift)
- 学完python_学完Python都可以做什么
- arima模型 p q d 确定_基于ARIMA预测股指期货价格走势
- 文巾解题 231. 2的幂
- matlab画一个局部放大的图中图
- 咦?奇怪的知识又增加了?以数学建模的方式打开海啸传播模型(附部分matlab源码)
- 多层神经网络(BP算法)介绍
- shell 循环判断语法