spring快速入门案例:
案例名称“宝宝淘”论坛的登录模块,功能虽小,但是五脏俱全,而且登录业务大家熟悉,不难理解。
首页登录页面提供一个输入表单,输入用户名和密码,用户填写并提交表单后,服务端程序检查是否有匹配的用户名和密码。
如果不匹配返回登录页面并给出提示;如果匹配记录用户的成功登陆日志,更新用户的最后登录时间和ip,并给用户增加5个积分,然后重定向到欢迎页面。
分析:
持久层需要2个dao,分别是UserDao,LoginLogDao,在业务层对应一个业务类UserService,在展现层有一个LoginController类,和2个jsp页面,分别是登录页面login.jsp和欢迎页面main.jsp。
时序图
下面通过时序图描述登录模块的整体交互流程:
1、首先用户访问login.jsp,返回带用户名、密码表单的登录页面
2、用户在登录页面输入用户名、密码,提交表单到服务器,spring根据配置调用LoginController控制器响应登录请求
3、LoginController调用UserService#hasMatchUser()方法,根据用户名和密码查询是否存在匹配用户,UserService内部调用持久层的UserDao完成具体数据库访问操作
4、如果不存在匹配用户,重定向到login.jsp页面,并报告错误,否则到下一步
5、LoginController调用UserServicce#findUserByUserName()方法,加载匹配的User对象并更新用户最近一次登录时间和登录ip
6、LoginController调用UserService#loginSuccess()方法,进行登录成功的业务处理:
首先调用UserDao#updateLoginInfo()为用户添加5个积分,然后创建一个LoginLog对象,并利用LoginLogDao将其插入到数据库中
7、重定向到欢迎页面main.jsp,欢迎页面产生响应返回给用户
环境准备:
MySQL4.x不支持事务,所以需要MySQL5.x及以上
spring的声明式事务是依赖于底层数据库支持的
创建数据库表:
启动mysql,mysql -uroot -p1234 --port 3306
创建数据库
DROP DATABASE IF EXISTS sampledb;
CREATE DATABASE sampledb DEFAULT CHARACTER SET utf8;
USE sampledb;
创建数据表
##创建用户表
CREATE TABLE t_user(
user_id int auto_increment primary key,
user_name varchar(30),
credits int,
password varchar(32),
last_visit datetime,
last_ip varchar(23)
)ENGINE=InnoDB;
##创建用户登录日志表
CREATE TABLE t_login_log(
login_log_id int auto_increment primary key,
user_id int,
ip varchar(23),
login_datetime datetime
)ENGINE=InnoDB;
##初始化一条数据
INSERT INTO t_user(user_name,password)VALUES('admin','123456');
COMMIT;
也可以通过source命令运行脚本:
source D:\sampledb.sql
表结构
数据
建立工程:
我们为了方便,使用maven来构建web项目,具体方式查看:
IntelliJ IDEA创建maven web项目(IDEA新手适用)
使用的ide为intellij IDEA,建议将文件格式改为UTF-8来解决国际化问题,否则idea将使用默认的GBK编码格式
file-settings-editor-file encodings-将编码格式改为UTF-8
创建目录结构
说明:
spring的依赖
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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.baobaotao</groupId>
<artifactId>maven-web</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>maven-web Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<!--spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.10.RELEASE</version>
</dependency>
<!--日志-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!--数据库连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.1.1</version>
</dependency>
<!--测试框架-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.10.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--web-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
</dependencies>
<build>
<finalName>maven-web</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
类包规划
类包以分层的方式进行组织,共分为4个包,分别是dao、domain、service、web
领域对象从严格意义上属于业务层,但是由于领域对象可能同时被持久层和展现层共享,所以一般将其单独划分到一个包。
领域对象
领域对象也叫实体类,一般来说,领域对象属于业务层,但是他贯穿展现层、业务层、持久层,并最终被持久化到数据库中。
持久层从数据库表中加载数据并实例化领域对象,或者将领域对象持久化到数据表中。
论坛登录模块有2个领域对象:User和LoginLog,分别对应t_user和t_login_log数据表。
User.java
package com.baobaotao.domain;
import java.io.Serializable;
import java.util.Date;
//领域对象一般要实现Serializable接口,以便可以序列化
public class User implements Serializable {
private int userId;
private String userName;
private String password;
private int credits;
private String lastIp;
private Date lastVisit;
//getter setter ...
}
LoginLog.java
package com.baobaotao.domain;
import java.io.Serializable;
import java.util.Date;
//领域对象一般要实现Serializable接口,以便可以序列化
public class LoginLog implements Serializable {
private int loginLogId;
private int userId;
private String ip;
private Date loginDate;
//getter setter ...
}
数据访问层Dao
首先我们来定义访问User的dao,UserDao,他有3个方法:
getMatchCount();根据用户名密码获取匹配用户数,等于1表示用户名和密码正确,等于0表示用户名密码错误
这是最简单的用户身份认证方法,在实际应用中需要采用诸如密码加密等安全策略
findUserByUserName();根据用户名获取User对象
updateLoginInfo();更新用户积分、最后登录ip和最后登录时间
下面通过springJDBC技术实现这个dao
spring2.5之后可以使用注解方式定义Bean,相对于XML配置方式,注解配置明显简单方便。
UserDao.java
package com.baobaotao.dao;
import com.baobaotao.domain.User;
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 java.sql.ResultSet;
import java.sql.SQLException;
//通过spring注解定义一个dao
@Repository
public class UserDao{
//自动注入JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
public int getMatchCount(String userName,String password){
String sqlStr="SELECT COUNT(*) FROM t_user WHERE user_name=? and password=? ";
return jdbcTemplate.queryForInt(sqlStr,new Object[]{userName,password});
}
public User findUserByUserName(String userName){
//根据用户名查询用户的sql语句
String sqlStr="select user_id,user_name,credits from t_user where user_name=? ";
User user=new User();
jdbcTemplate.query(sqlStr, new Object[]{userName}, new RowCallbackHandler() {
//匿名类方式实现的回调函数
@Override
public void processRow(ResultSet rs) throws SQLException {
user.setUserId(rs.getInt("user_id"));
user.setUserName(userName);
user.setCredits(rs.getInt("credits"));
}
});
return user;
}
public void updateLoginInfo(User user){
String sqlStr="update t_user set last_visit=?,last_ip=?,credits=? where user_id=?";
jdbcTemplate.update(sqlStr,new Object[]{user.getLastVisit(),user.getLastIp(),user.getCredits(),user.getUserId()});
}
}
LoginLogDao负责记录用户登录日志,它仅有一个insertLoginLog()接口方法
LoginLogDao.java
package com.baobaotao.dao;
import com.baobaotao.domain.LoginLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class LoginLogDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void insertLoginLog(LoginLog loginLog){
String sqlStr="insert into t_login_log(user_id,ip,login_datetime)values(?,?,?)";
Object[] args=new Object[]{loginLog.getUserId(),loginLog.getIp(),loginLog.getLoginDate()};
jdbcTemplate.update(sqlStr,args);
}
}
编写applicationContext.xml配置文件
在src目录添加applicationContext.xml文件
我们使用spring的<context:component-scan>
扫描指定类包下的所有类,这样在类中定义的spring注解(@Repository,@Autowired)才能产生作用。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<!--扫描类包,将标注spring注解的类自动转化为Bean,同时完成Bean的注入-->
<context:component-scan base-package="com.baobaotao.dao"/>
<!-- 配置Spring默认的连接池 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/sampledb"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!--定义jdbc模板-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource"/>
</beans>
业务层
在论坛登录实例中,业务层只有一个业务类UserService,它负责将持久层UserDao和LoginLogDao组织起来完成用户密码认证,登录日志记录。
UserService.java
@Service
public class UserService{
@Autowired
private UserDao userDao;
@Autowired
private LoginLogDao loginLogDao;
public boolean hasMatchUser(String userName,String password){
int matchCount=userDao.getMatchCount(userName,password);
return matchCount>0;
}
public User findUserByUserName(String userName){
return userDao.findUserByUserName(userName);
}
public void loginSuccess(User user){
user.setCredits(5+user.getCredits());
LoginLog loginLog=new LoginLog();
loginLog.setUserId(user.getUserId());
loginLog.setIp(user.getLastIp());
loginLog.setLoginDate(user.getLastVisit());
userDao.updateLoginInfo(user);
loginLogDao.insertLoginLog(loginLog);
}
}
注意:
一般不会直接在数据库中以明文的方式保存用户的密码,因为这样容易造成密码的泄密问题,所以需将密码加密后以密文保存。
一种更有效的办法是保存密码的MD5摘要。
由于不能通过密码摘要反推出原来的密码,内部人员即使可以查看用户信息表,也无法知道用户的密码,所以摘要存储方式已经成为通用方式。
此外为了防止黑客使用工具进行密码暴力破解,大多数web使用了图片验证码功能,每次登陆都不相同,这样工具暴力破解无用武之地了。
在spring中装配service
事务管理的代码虽然不用出现在程序代码中,但是我们必须以某种方式告诉spring哪些业务类需要工作在事务环境中,以及事务的规则,以便spring自动根据这些信息为业务类添加事务管理功能。
修改applicationContext.xml文件:
扫描service类包,应用spring的注解配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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"
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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<!--扫描类包,将标注spring注解的类自动转化为Bean,同时完成Bean的注入-->
<context:component-scan base-package="com.baobaotao.dao"/>
<!--扫描service类包,应用spring的注解配置-->
<context:component-scan base-package="com.baobaotao.service"/>
<!-- 配置Spring默认的连接池 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/sampledb"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!--定义jdbc模板-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource"/>
<!--配置事务管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<!--通过AOP配置提供事务增强,让service包下所有Bean的所有方法拥有事务-->
<aop:config proxy-target-class="true">
<aop:pointcut id="serviceMethod" expression="execution(* com.baobaotao.service..*(..))"/>
<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>
单元测试
添加一个test包用来存放测试代码
创建TestUserService类测试UserService的方法
TestUserService.java
package com.baobaotao.test;
import com.baobaotao.domain.User;
import com.baobaotao.service.UserService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)//基于JUnit4的spring测试框架
@ContextConfiguration(locations={"/applicationContext.xml"})//启动spring容器
public class TestUserService{
@Autowired
private UserService userService;
@Test//标注测试方法
public void hasMatchUser(){
boolean b1=userService.hasMatchUser("admin","123456");
boolean b2=userService.hasMatchUser("admin","1111");
Assert.assertTrue(b1);
Assert.assertTrue(!b2);
}
@Test//标注测试方法
public void findUserByUserName(){
User user=userService.findUserByUserName("admin");
Assert.assertEquals(user.getUserName(),"admin");
}
}
点击小图标运行测试方法
展现层
配置springmvc框架
首先需要对web.xml文件进行配置,以便web容器启动时能够自动启动spring容器
web.xml
从类路径下加载spring配置文件,classpath关键字特指类路径下加载
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--1.从类路径下加载spring配置文件,classpath关键字特指类路径下加载-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContext.xml
</param-value>
</context-param>
<!--2.负责启动spring容器的监听器,他会引用1的上下文参数获取spring配置文件地址-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
</web-app>
首先通过web容器上下文参数指定spring配置文件地址,多个配置文件使用逗号或者空格分隔
指定spring提供的ContextLoaderListener的web容器监听器,他会在web启动自动运行,加载spring配置文件,并且启动spring容器。
注意:需要添加log4j.properties日志配置,以便日志引擎自动生效。
log4j.properties
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=d:/mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=info, stdout
配置SpringMVC框架
接下来,我们需要配置springmvc,他通过一个Servlet截获url请求,然后再进行相关处理
web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--1.从类路径下加载spring配置文件,classpath关键字特指类路径下加载-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContext.xml
</param-value>
</context-param>
<!--2.负责启动spring容器的监听器,他会引用1的上下文参数获取spring配置文件地址-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!--1.springmvc的主控servlet-->
<servlet>
<servlet-name>baobaotao</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
</servlet>
<!--2.springmvc处理的url-->
<servlet-mapping>
<servlet-name>baobaotao</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
</web-app>
springmvc也拥有一个spring配置文件,此处定义的Servlet有一个契约:servlet名-servlet.xml。
这里servlet名是baobaotao,所以必须提供一个baobaotao-servlet.xml的springmvc配置文件,springmvc的servlet会自动将baobaotao-servlet.xml和spring其他配置文件(baobaotao-dao.xml,baobaotao-service.xml)进行拼装。
在1声明了一个servletspringmvc也拥有一个spring配置文件,此处定义的Servlet有一个契约:servlet名-servlet.xml。
这里servlet名是baobaotao,所以必须提供一个baobaotao-servlet.xml的springmvc配置文件,springmvc的servlet会自动将baobaotao-servlet.xml和spring其他配置文件(baobaotao-dao.xml,baobaotao-service.xml)进行拼装。
在2对这个servlet的url路径映射定义,所有以.html为后缀的url都能被baobaotao servlet拦截,然后由springmvc框架处理。
使用.html后缀,用户不能通过url直接知道我们采用了何种服务端技术,另外.html是静态网页后缀,可以骗过搜索引擎,增加被收录的概率。
对于真正的无需任何动态处理的静态网页则可以使用.htm后缀区分,避免被框架截获。
请求被springmvc截获后,首先根据请求的url查找目标的处理控制器,并将请求参数封装到一个“命令”对象一起给控制器处理,控制器调用spring容器中的业务Bean完成业务处理工作并返回结果视图。
处理登录请求
pojo控制器
首先需要编写LoginController,他负责处理登录请求,完成登录业务,并根据登录成功与否转向欢迎页面或失败页面。
添加一个pojo包用来存放LoginCommand
LoginCommand.java
package com.baobaotao.pojo;
//登录请求pojo
public class LoginCommand{
private String userName;
private String password;
//...
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;
}
}
LoginController.java
package com.baobaotao.web;
import com.baobaotao.domain.User;
import com.baobaotao.pojo.LoginCommand;
import com.baobaotao.service.UserService;
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 javax.servlet.http.HttpServletRequest;
import java.util.Date;
//1.标注成为一个springmvc的controller
@Controller
public class LoginController{
@Autowired
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.findUserByUserName(loginCommand.getUserName());
user.setLastIp(request.getRemoteAddr());
user.setLastVisit(new Date());
userService.loginSuccess(user);
request.getSession().setAttribute("user",user);
return new ModelAndView("main");
}
}
}
1.springmvc的@Controller注解可以将任何一个pojo类标注为springmvc的控制器,处理http请求。
当然了,标注了@Controller的类首先是一个Bean,所以可以用@Autowired进行Bean注入。
2.一个控制器可以拥有多个对应不同http请求路径的处理方法,通过@RequestMapping指定方法如果映射请求路径
请求参数会根据参数名称匹配,绑定到loginCommand入参中。
3.请求响应方法可以返回一个ModelAndView,或直接返回一个字符串,springmvc会解析并转向目标响应页面。
配置springmvc
和web.xml在同一个目录
我们需要在baobaotao-servlet.xml中声明该控制器,扫描web路径,指定springmvc的视图解析器。
springmvc配置文件
baobaotao-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--1.扫描web包,应用spring的注解-->
<context:component-scan base-package="com.baobaotao.web"/>
<!--2.配置视图解析器,将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>
springmvc为视图名到具体视图映射提供了许多方法,这里我们使用InternalResourceViewResolver,他通过为视图逻辑名添加前后缀的方法进行解析。
如果视图逻辑名为“login”将解析为/WEB-INF/jsp/login.jsp;名为“main”的视图解析为/WEB-INF/jsp/main.jsp。
jsp视图页面
登录模块一共包括2个jsp页面,分别是登录页面login.jsp和欢迎页面main.jsp。
需要用到jstl的jar包
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
登录页面login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="utf-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page isELIgnored="false" %>
<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"/>">
用户名:<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登录表单提交到/loginCheck.html,jstl标签会在url前面自动加上应用程序部署根目录,通过<c:url/>很好解决了开发和应用部署目录不一致问题。
由于login.jsp页面防止在WEB-INF/jsp目录下,无法直接通过url调用,所以他由LoginController的loginPage进行转发。
欢迎页面main.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="utf-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page isELIgnored="false" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=IOS-8859-1">
<title>宝宝淘论坛</title>
</head>
<body>
${user.userName},欢迎您进入宝宝淘论坛,您当前的积分为${user.credits};
</body>
</html>
欢迎页面很简单,使用jstl标签显示一条欢迎信息,访问Session域中的user对象,显示用户名和积分信息。
运行web应用
启动tomcat,在浏览器输入:http://localhost:8080/index.html
用户名密码错误
用户名密码正确
每一次刷新页面,积分会加5分
总结:
我们使用了Spring MVC、Spring JDBC以及Spring声明式事务等技术实现了一个常见的论坛登录模块,让大家对如何使用Spring框架构建Web应用有了切身体会,同时还了解开发一个简单Web应用所需经理的开发过程。
当然实际开发中的过程的复杂性会更高,但是上所述步骤往往都是必需的,后续的功能开发也仅是在此基础上添砖加瓦。