本章通过一个简单开发spring boot 应用的整体过程,实现应用按照持久层,业务层和展示层进行组织,从底层DAO 到WEB展示逐渐展示。
主要内容:
基于Spring JDBC的持久层实现
基于Spring 声明式事物的业务层实现
基于Spring MVC 的展示层结束
实现功能简介:
登陆 - WEB控制 - 用户名/密码错误 - 返回
登陆 - WEB控制 - 用户名/密码成功 - 欢迎页面
1:用户访问login.jsp ,返回带用户名/密码表单的登陆界面
2:用户在登陆页面输入用户名/密码,提交表单到服务器,Spring根据配置调用LoginController控制器响应登陆请求
3:LoginController 调用 userService/hashMatchUser()方法,根据用户名和密码查询是否有该用户存在,UserService 内部通过调用持久层UserDao 完成具体的数据库访问
4:如果不存在匹配用户,则重定向到login.jsp,并报告错误,否则进入下一步
5:loginController 调用userService/ findUserByUserName()方法,加载匹配的User对象,并更新用户最近一次登陆时间和登陆IP
6:loginController 调用 UserService/loginSuccess()方法,进行登陆成功的业务处理,首先调用UserDao/updateLoginInfo()方法, 为用户添加5个积分,然后创建一个LoginLog对象,并利用LoginLogDao 将其数据插入到数据库
7:重定向到还原页面main.jsp,展示用户信息
创建完工程后,需要在pom.xml 文件中配置Spring ,数据源,数据库链接驱动,Servlet类库的依赖信息
<?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.smart</groupId>
<artifactId>chapter1</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- 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>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>${servlet.version}</version>
<scope>provided</scope>
</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>
</dependencies>
<build>
<plugins>
<!-- 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>8000</port>
<maxIdleTime>60000</maxIdleTime>
</connector>
</connectors>
<contextPath>/bbs</contextPath>
<scanIntervalSeconds>0</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.2.2.RELEASE</spring.version>
<mysql.version>5.1.29</mysql.version>
<servlet.version>3.0-alpha-1</servlet.version>
<aspectj.version>1.8.1</aspectj.version>
<commons-codec.version>1.9</commons-codec.version>
<commons-dbcp.version>1.4</commons-dbcp.version>
<hibernate.validator.version>5.0.2.Final</hibernate.validator.version>
<jetty.version>8.1.8.v20121106</jetty.version>
<slf4j.version>1.7.5</slf4j.version>
<testng.version>6.8.7</testng.version>
</properties>
</project>
类包以及Spring文件规划
持久层
持久层负责数据的访问和操作,DAO类被上层的业务类调用,Spring 本身支持多种流行的ORM框架,这里使用Spring JDBC作为持久层的实现技术,
简历领域对象
领域对象也被称为实体类,它代表了业务的状态,切贯穿展示层,业务层和持久层,并最终持久化到数据库中,领域对象使数据库表操作以面向对象的方式进行,吃韭菜主要工作就是从数据库中加载数据并实例化领域对象,或者领域对象持久化到数据库表中
1:用户领域对象
用户领域对象可以看成U_user 表的对象映射,每个字段对应一个字段属性,User类主要有3类信息,分别为用户名/密码,积分/以及最后一次登录的信息
package com.smart.domain;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable{
private int userId;
private String userName;
private String password;
private int credits;
private String lastIp;
private Date lastVisit;
public String getLastIp() {
return lastIp;
}
public void setLastIp(String lastIp) {
this.lastIp = lastIp;
}
public Date getLastVisit() {
return lastVisit;
}
public void setLastVisit(Date lastVisit) {
this.lastVisit = lastVisit;
}
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 getCredits() {
return credits;
}
public void setCredits(int credits) {
this.credits = credits;
}
}
登录日志领域对象
用户每次登录成功后,都会记录一条登陆日志,该登陆日志包括3类信息,用户ID,IP,以及登陆时间
package com.smart.domain;
import java.io.Serializable;
import java.util.Date;
public class LoginLog implements Serializable{
private int loginLogId;
private int userId;
private String ip;
private Date loginDate;
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Date getLoginDate() {
return loginDate;
}
public void setLoginDate(Date loginDate) {
this.loginDate = loginDate;
}
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;
}
}
UserDao
首先来定义访问的User的Dao ,它包括3个方法 getMatchCount,findUserByUserName,updateLoginInfo
getMatchCount 根据用户名和密码获取匹配的用户数,等于1标识用户名/密码正确,等于0标识用户名或密码错误,
findUserByUserName 根据用户名或者User对象
updateLoginInfo 更新用户积分,最后登陆IP以及登陆时间
package com.smart.dao;
import com.smart.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;
@Repository //通过spring的注解定义一个dao
public class UserDao {
private JdbcTemplate jdbcTemplate;
private final static String MATCH_COUNT_SQL = " SELECT count(*) FROM t_user " +
" WHERE user_name =? and password=? ";
private final static String UPDATE_LOGIN_INFO_SQL = " UPDATE t_user SET " +
" last_visit=?,last_ip=?,credits=? WHERE user_id =?";
//根据用户名和密码获取匹配的用户数,等于1标识用户名/密码正确,等于0标识用户名或密码错误,
public int getMatchCount(String userName, String password) {
return jdbcTemplate.queryForObject(MATCH_COUNT_SQL, new Object[]{userName, password}, Integer.class);
}
public User findUserByUserNameNew(final String userName) {
String sqlStr = " SELECT user_id,user_name,credits , password FROM t_user WHERE user_name =? ";
final User user = new User();
jdbcTemplate.query(sqlStr, new Object[] { userName },
new RowCallbackHandler() {
public void processRow(ResultSet rs) throws SQLException {
user.setUserId(rs.getInt("user_id"));
user.setUserName(userName);
user.setPassword(rs.getString("password"));
user.setCredits(rs.getInt("credits"));
}
});
return user;
}
//根据用户名或者User对象
public User findUserByUserName(final String userName) {
String sqlStr = " SELECT user_id,user_name,credits FROM t_user WHERE user_name =? ";
final User user = new User();
jdbcTemplate.query(sqlStr, new Object[] { userName },
new RowCallbackHandler() {
public void processRow(ResultSet rs) throws SQLException {
user.setUserId(rs.getInt("user_id"));
user.setUserName(userName);
user.setCredits(rs.getInt("credits"));
}
});
return user;
}
//更新用户积分,最后登陆IP以及登陆时间
public void updateLoginInfo(User user) {
System.out.println("UPDATE_LOGIN_INFO_SQL"+UPDATE_LOGIN_INFO_SQL);
System.out.println(" user.getLastVisit()"+ user.getLastVisit());
System.out.println("user.getLastIp()"+user.getLastIp());
System.out.println("user.getCredits()"+user.getCredits());
jdbcTemplate.update(UPDATE_LOGIN_INFO_SQL, new Object[] { user.getLastVisit(), user.getLastIp(),user.getCredits(),user.getUserId()});
}
public void updateLoginInfoNew(User user) {
System.out.println("UPDATE_LOGIN_INFO_SQL"+UPDATE_LOGIN_INFO_SQL);
System.out.println(" user.getLastVisit()"+ user.getLastVisit());
System.out.println("user.getLastIp()"+user.getLastIp());
System.out.println("user.getCredits()"+user.getCredits());
jdbcTemplate.update(UPDATE_LOGIN_INFO_SQL, new Object[] { user.getLastVisit(), user.getLastIp(),user.getCredits(),user.getUserId()});
}
@Autowired //自动注入bean
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
LoginLogDao
LoginLogDao 负责记录用户的登陆日志,它只有一个insertLoginLog接口,与UserDao相似,其实现类也通过jdbcTemplate/update 方法完成登陆成功插入操作
package com.smart.dao;
import com.smart.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;
//保存登陆日志SQL
private final static String INSERT_LOGIN_LOG_SQL= "INSERT INTO t_login_log(user_id,ip,login_datetime) VALUES(?,?,?)";
public void insertLoginLog(LoginLog loginLog) {
Object[] args = { loginLog.getUserId(), loginLog.getIp(),
loginLog.getLoginDate() };
jdbcTemplate.update(INSERT_LOGIN_LOG_SQL, args);
}
}
在spring 中装配Dao
<?xml version="1.0" encoding="UTF-8"?>
<!--
引入aop 以及tx 命名空间所对应的Schema文件
处在<beans>的声明处添加 aop 和 Tx 命名空间的Schen定义文件的说明
在配置文件中就可以使用这两个空间下的配置空间了
-->
<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.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!--1 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 -->
<!-- 使用Spring的<context:component-scan> 扫描指定类包下的所有类,这样在
类中定义的Spring 注解,如@repositroy,@Autowired等才能产生作用-->
<context:component-scan base-package="com.smart.dao"/>
<context:component-scan base-package="com.smart.service"/>
<!--2 配置数据源 -->
<!--使用Jakarta的DBCP 开源数据源实现方法定义一个数据源,数据库驱动器类为
com.mysql.jdbc.Driver, 由于这里mysql数据库的服务端口为3306, 默认的为3306,
不对可在此处进行修改-->
<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="12345678" />
<!-- 配置Jdbc模板 -->
<!--3 配置了jdbcTeamplate Bean , 将配置数据源 dataSource 注入jdbcTeamplate中,
而这个jdbcTeamplate 将通过@Autowired 自动注入loginLog ,UserDao的bean中,
可见Spring 可以很好地将注解配置和xml配置统一起来-->
<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 ,Tx命名空间的而语法, 以AOP的方式为 com.smart.service 包下所有类的所有标注 @transactionManager注解
的方法都添加了事物增强,即它们都在工作在事物环境中-->
<aop:config proxy-target-class="true">
<aop:pointcut id="serviceMethod" expression="(execution(* com.smart.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 处,,我们使用Spring的<context:component-scan> 扫描指定类包下的所有类,这样在类中定义的Spring注解如:@Repository,@Autowired才能产生作用,
在2处,我们使用jakarta的DBCP开源数据源实现方案,定义一个数据源,数据库驱动类为 com.mysql.jdbc.Driver,由于这里mysql数据库服务端口为3306,可在此处修改
在3处,配置了jdbcTemplate Bean , 将2处声明的dataSource 注入 jdbcTemplate 中,而这个jdbcTeamplate 将通过@Autowired 自动注入loginLog ,UserDao的bean中, 可见Spring 可以很好地将注解配置和xml配置统一起来
业务层
UserService
package com.smart.service;
import com.smart.dao.LoginLogDao;
import com.smart.dao.UserDao;
import com.smart.domain.LoginLog;
import com.smart.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service //1 将userService 标注为一个服务层的dean
public class UserService {
private UserDao userDao;
private LoginLogDao loginLogDao;
public UserDao getUserDao() {
return userDao;
}
@Autowired//2注入UserDao Dao层的bean
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public LoginLogDao getLoginLogDao() {
return loginLogDao;
}
@Autowired //3注入loginLogDao Dao层的bean
public void setLoginLogDao(LoginLogDao loginLogDao) {
this.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);
}
/* 为loginSuccess方法标注 Transactional事物注解,让该方法运行在事物环境中
因为我们在Spring 事物管理器拦截切入表达式上加入了@Transactional 过滤 否则该方法将在无事物方法中运行
loginSuccess 方法根据入参User 对象构建出 loginlog对象并将user.credits 递增5,
即为用户每登陆一次获取5个积分,
然后调用updateLoginInfo,将数据更新
并同时调用 insertLoginLog 保存一条登陆记录*/
@Transactional //4
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);
}
}
首先在1处,通过@service 注解将UserService 标注为一个服务层的bean,然后在2和3处注入userDao 和loginLogDao 这两个Dao层的bean,接着通过hasMatchUser()findUserByName()业务方法简单调用DAO完成对应的功能,最后在4处为loginSuccess()方法标注 @Transactional事物注解,让该方法运行在事物环境中,(因为我们在Spring事物管理器拦截切入表达式中假如了@Transactional过滤)否则该方法将在无事物方法中运行。loginSuccess()方法更具入参,user对象构造出loginLog对象 并将user.credits递增5,即用户每次登陆可赚取5个积分。然后调用UserDao更新到U_user表在调用loginLogDao向t_login_log表添加一条数据。
在Spring中装备service
<?xml version="1.0" encoding="UTF-8"?>
<!-- 1
引入aop 以及tx 命名空间所对应的Schema文件
处在<beans>的声明处添加 aop 和 Tx 命名空间的Schen定义文件的说明
在配置文件中就可以使用这两个空间下的配置空间了
-->
<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.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!--2 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 -->
<!-- 使用Spring的<context:component-scan> 扫描指定类包下的所有类,这样在
类中定义的Spring 注解,如@repositroy,@Autowired等才能产生作用-->
<context:component-scan base-package="com.smart.dao"/>
<context:component-scan base-package="com.smart.service"/>
<!-- 配置数据源 -->
<!--使用Jakarta的DBCP 开源数据源实现方法定义一个数据源,数据库驱动器类为
com.mysql.jdbc.Driver, 由于这里mysql数据库的服务端口为3306, 默认的为3306,
不对可在此处进行修改-->
<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="12345678" />
<!-- 配置Jdbc模板 -->
<!-- 配置了jdbcTeamplate Bean , 将配置数据源 dataSource 注入jdbcTeamplate中,
而这个jdbcTeamplate 将通过@Autowired 自动注入loginLog ,UserDao的bean中,
可见Spring 可以很好地将注解配置和xml配置统一起来-->
<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 ,Tx命名空间的而语法, 以AOP的方式为 com.smart.service 包下所有类的所有标注 @transactionManager注解
的方法都添加了事物增强,即它们都在工作在事物环境中-->
<aop:config proxy-target-class="true">
<aop:pointcut id="serviceMethod" expression="(execution(* com.smart.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:在<beans>的声明处添加 aop 和 Tx 命名空间的Schen定义文件的说明
在配置文件中就可以使用这两个空间下的配置标签了
2: 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入,使用Spring的<context:component-scan> 扫描指定类包下的所有类,这样在 类中定义的Spring 注解,如@repositroy,@Autowired等才能产生作用
3:定义一个基于数据源的DataSourceTransactionManager,该事物管理器负责声明式事物的管理,改管理器需要引用dateSourceBean
4:通过AOP ,Tx命名空间的语法, 以AOP的方式为 com.smart.service 包下所有类的所有标注 @transactionManager注解 的方法都添加了事物增强,即它们都在工作在事物环境中
展示层
业务层以及持久层的开发已经完成,该为程序提供界面的时候了,
配置Spring MVC框架
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.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">
<!--(1) 从类路径下加载spring 配置文件,classpath 关键字特指类路径下加载
多个配置文件可用逗号分隔,建议采用逗号分隔方式,-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:smart-context.xml</param-value>
</context-param>
<listener>
<!--(2)负责启动spring 容器的监听器,它将引用 (1)处的上下文参数获得Spring配置文件的地址,
指定Spring所提供的ContextLoaderListener的web 容器监控器,改监听器在web容器启动时,自动运行,
它会根据Spring的参数获取Spring的配置文件,并启动Spring容器-->
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!--
Spring Mvc 地址映射
-->
<!--
(1)SpringMVC的主控Servlet
-->
<servlet>
<servlet-name>smart</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>3</load-on-startup>
</servlet>
<!--
SpringMvc处理的URL
-->
<servlet-mapping>
<servlet-name>smart</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
</web-app>
然后通过容器上下文的参数指定Spring配置文件地址, @, 多个配置文件可用逗号分隔, 在2处指定Spring所提供的ContextLoaderListener 的web容器监控,该监控在web容器启动时自动运行,它会更具ContextConfigLocation web容器参数获取Spring配置文件,并启动Spring容器。注意,需要将log4j.peopertis日志配置文件放置在类路径下,以便于日志引擎自动生效。
最后需要配置Spring MVC的相关信息,Spring mvc 和Struts 一样,也通过一个servlet 来获取URL请求,然后在进行相关处理
<!--
Spring Mvc 地址映射
-->
<!--
(1)SpringMVC的主控Servlet
-->
<servlet>1
<servlet-name>smart</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>3</load-on-startup>
</servlet>
<!--
SpringMvc处理的URL
-->
<servlet-mapping>2
<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 的ccontextConfigLocation上下文参数进行声明,因为Spring MVC 的servlet 会自动将 smart-servlet.xml 文件和Spring其它配置文件,(smart-dao.xml, smart-service.xml)进行拼装
在2处对这个Servlet的URL路径映射进行定义,在这里让所有以.html为后缀的URL都能被smart servlet 截获,进而转由Spring mvc框架进行处理
请求被spring mvc 获取后,首先根据请求的url 查询到目标的处理控制器,并将请求参数封装 “命令“对象一起传给控制器处理,然后,控制器调用spring 容器中业务bean 完成业务处理工作,并返回结果视图
处理登陆请求
1:pojo控制类
首先需要编写的loginController,它负责处理登陆请求,完成登陆业务,并根据登陆成功与否转向欢迎页面或者失败页面
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.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import com.smart.domain.User;
import com.smart.service.UserService;
@RestController
public class LoginController{
private UserService userService;
@RequestMapping(value = "/index.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.getLocalAddr());
user.setLastVisit(new Date());
userService.loginSuccess(user);
request.getSession().setAttribute("user", user);
return new ModelAndView("main");
}
}
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
在1处通过spring mvcde @Controller 注解可以将任何一个POJO的类标注为 spring mvc的控制器,处理http的请求,当然,标注了@Controller的类首先会死一个bean,所以可以使用@Autowired进行bean的注入
一个控制器可以拥有多个处理不同HTTP请求路径的方法,通过@RequestMapping指定方法如果映射请求路径 如2 3所示
请求参数会根据参数名称默认契约自动绑定到对应方法的入参中,例如:在3处的loginCheck(HttpServletRequest request,LoginCommand loginCommand)方法中,请求参数会按照名称匹配绑定到loginCommand的入参中
请求响应方法可以返回一个ModelAndView , 或直接返回一个字符串,spring mvc 会解析之并转向目标响应页面
ModelAndView 对象既包括视图信息,又包括视图渲染所需的模型数据,在这里用户需要了解它代表一张视图即可,
前面用到的LoginCommand 对象是一个POJO, 没有继承特定的父类或实现特定的接口,LoginCommand 类仅仅包括用户/密码这两个属性,(和请求的用户/密码参数名称一样)
package com.smart.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-servert.xml中声明控制器,扫描web路径, 指定spring mvc 的视图解析器
<?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-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.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>
ModeAndView的解析配置
在代码清单 -12的2 3 处,控制器根据用户登陆处理结果分别返回 ModelAndView("login","error","用户名或者密码错误"),和ModeAndView("main") ,ModelAndView的第一个参数代表视图的逻辑名。第二,第三个参数,分别为数据模型名称和数据模型对象,数据模型对象将以数据模型名称为参数放置到request 的属性中
spring mvc 如何将视图逻辑名解析为具体的视图页面呢?解决思路和上面方法类似,需要在smart-servlet-xml提供一个定义解析规则的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-info/jsp/login.jsp ,视图逻辑名为 main 将解析为/web-info/jsp/main.jsp
JSP视图页面
论坛登陆模块工包括两个JSP页面,分别是登陆页面,login.jsp 和欢迎页面 main.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>
login.jsp 页面有两个好处,即作为登陆页面,又作为登陆失败后的响应页面,所以在1处, 使用jstl 标签,对登陆错误返回的信息进行处理,在JSTL标签引用了error变量,这个变量正是loginController 中返回的ModelAndView("login","error","用户名或者密码错误")对象所声明的error参数
login.jsp的登陆表单提交到/loginController.html 如2所<c:url value="loginCheck.html"/>的JSTL标签会在URL前自动加上应用部署的根目录,假设应用部署在网站的bbt目录下,则<c:url>标签将输出/bbt/loginController.html,通过<c:url>标签很好的解决了开发和部署不一致的问题
犹豫login.jsp放在 WEB-INF/jsp 目录下,无法直接通过URL进行调用,所以它由loginController控制类标注了@RequestMapping(value = "/index.html")进行转发
欢迎页面 main.jsp
登陆成功欢迎页面就很简单,仅适用了JSTL标签显示一条欢迎信息即可
<%@ 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.credits}; 111111
</body>
</html>
1处访问session 域中的user对象,显示用户名和积分信息,