前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring-Spring MVC + Spring JDBC + Spring Transaction + Maven 构建web登录模块

Spring-Spring MVC + Spring JDBC + Spring Transaction + Maven 构建web登录模块

作者头像
小小工匠
发布2021-08-16 16:05:16
7690
发布2021-08-16 16:05:16
举报
文章被收录于专栏:小工匠聊架构

概述

本模块主要涉及以下部分:

  • Maven构建web项目的过程
  • 登录流程介绍
  • 基于Spring JDBC的持久层实现
  • 基于Spring 声明式事务的业务层实现
  • 基于Spring MVC的展现层实现
  • 运行Web应用

功能简介

这里写图片描述
这里写图片描述
  1. 用户访问login.jsp,返回带有用户名和密码输入框的登录页面
  2. 用户输入用户名和密码,提交表单到服务器,Spring根据配置文件调用LoginController控制器响应请求
  3. LoginController调用UserService的hasMatchUser()方法来判断用户是否存在
  4. 如果用户不存在,跳转到登录页面,给出提示,否则进入下一步
  5. 如果用户存在,LoginController调用UserService的findUserByName方法,进行登录成功后的业务处理:首先调用updateLoginInfo的updateLoginInfo方法 增加5分积分,然后创建一个LoginLog对象,将LoginLog写入数据库
  6. 重定向到主页面,将响应返回给用户

环境准备

构建工具Maven

请参考上篇博文 Maven-EclipseEE使用Maven构建Java web项目从0到1


数据库脚本(Oracle)

代码语言:javascript
复制
-- 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_XGJ
  pctfree 10
  initrans 1
  maxtrans 255
  storage
  (
    initial 64K
    minextents 1
    maxextents unlimited
  );
-- Create/Recreate primary, unique and foreign key constraints 
alter table TEMP_USER
  add constraint PK_USER_ID primary key (USER_ID)
  using index 
  tablespace TAB_XGJ
  pctfree 10
  initrans 2
  maxtrans 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_XGJ
  pctfree 10
  initrans 1
  maxtrans 255
  storage
  (
    initial 64K
    minextents 1
    maxextents unlimited
  );
-- Create/Recreate primary, unique and foreign key constraints 
alter table TEMP_LOGIN_LOG
  add constraint PK_LOGIN_LOG_ID primary key (LOGIN_LOG_ID)
  using index 
  tablespace TAB_XGJ
  pctfree 10
  initrans 2
  maxtrans 255;
alter table TEMP_LOGIN_LOG
  add 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表的对象映射,每个字段对应一个对象属性。

代码语言:javascript
复制
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 表的对象映射

代码语言:javascript
复制
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和最后登录时间
代码语言:javascript
复制
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的bean
    public 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)

代码语言:javascript
复制
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;
    // 保存登陆日志SQL
    private 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的Bean
    public 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的配置文件

这里写图片描述
这里写图片描述
代码语言:javascript
复制
<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.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context-4.3.xsd


    
    com.xgj.dao"/>

    

    

     

    <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" />


    
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
        p:dataSource-ref="dataSource" />

beans>
  • 我们使用 扫描指定类包下的所有类,这样在类中定义的注解比如@Respoitiry @Autowired等才会产生作用
  • 我们使用Jakarta的DBCP开源数据实现方案定义一个数据源
  • 配置 将 上一步的DataSource注入到JdbcTemplate中, 而这个JdbcTemplate将通过@Autowired自动注入到LoginDao和UserDao的Bean中, 可见Spring可以很好地将注解配置和XML配置统一起来。

这样 就完成了登录模块的持久层所有的开发工作


业务层

我们设计的登录模块,业务层仅有一个业务类 UserService。

UserService负责将持久层的UserDAO和LoginLogDao组织起来,完成用户密码认证、登录日志记录等操作。

UserService

三个业务方法

  1. boolean hasMatchUser(String username, String password)
  2. User findUserByName(String username)
  3. void loginSuccess(User user)

UserService的实现类需要调用DAO层的两个DAO完成业务逻辑操作。

代码语言:javascript
复制
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 // 注入bean
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Autowired // 注入bean
    public 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);
    }

    @Transactional
    public 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根据这些信息自动为目标业务类添加事务管理的功能。

代码语言:javascript
复制
<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.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context-4.3.xsd



       http://www.springframework.org/schema/tx 
       http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">


    <context:component-scan base-package="com.xgj.dao"/>
     
    <context:component-scan base-package="com.xgj.service"/>

    
    <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" />


    
    <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: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文件中
代码语言:javascript
复制
    <dependency>
            <groupId>org.testnggroupId>
            <artifactId>testngartifactId>
            <version>${testng.version}version>
            <scope>testscope>
        dependency>

其中 版本为 6.8.7

代码语言:javascript
复制
<properties>
    <testng.version>6.8.7testng.version>
properties>
  • 因本模块采用Maven构建 ,所以在约定目录下 src\test\java测试目录下创建和UserService一致的包结构,并创建UserService对应的测试类UserServiceTest ,目录如下:
这里写图片描述
这里写图片描述
  • 编写测试代码
代码语言:javascript
复制
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 {

    @Autowired
    private UserService userService;

    @Test
    public void testHashUserMatch(){
        boolean b1 = userService.hasMatchUser("xgj", "123456");
        boolean b2 = userService.hasMatchUser("wrongname", "123456");
        assertTrue(b1);
        assertTrue(!b2);
    }


    @Test
    public void testFindUserByUserName()throws Exception{
       for(int i =0; i< 100;i++) {
           User user = userService.findUserByName("xgj");
           assertEquals(user.getUsername(), "xgj");
       }

   }


    @Test
    public 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容器。
代码语言:javascript
复制
<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 Applicationdisplay-name>

    
    <context-param>
        <param-name>contextConfigLocationparam-name>
        <param-value>classpath:spring-context.xmlparam-value>
    context-param>

 
    <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请求,然后再进行相关的处理

代码语言:javascript
复制
    <servlet>
        <servlet-name>smartservlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        servlet-class>
        <load-on-startup>3load-on-startup>
    servlet>
    
    <servlet-mapping>
        <servlet-name>smartservlet-name>
        <url-pattern>*.htmlurl-pattern>
    servlet-mapping>

在(1)处生命了一个Servlet,Spring MVC 也拥有一个Spring配置文件(后面涉及),该配置文件的文件名称和此处定义的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类,负责处理登录请求,完成登录业务。

代码语言:javascript
复制
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
    @Autowired
    public 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仅仅包括用户名和密码这两个属性(和请求的用户密码参数名称一样)

代码语言:javascript
复制
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的视图解析器。

代码语言:javascript
复制
<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.3.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context-4.3.xsd"> 
    
    <context:component-scan base-package="com.xgj.web"/> 

    
    <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 控制器类中

代码语言:javascript
复制
.....
return new ModelAndView("login", "error", "密码错误,请重新登录");
......
return new ModelAndView("main");

ModelAndView的第一个参数代表视图的逻辑名,第二、第三个参数分别为数据模型名称和数据模型对象,数据模型对象将以数据模型名称为参数名放置到request的属性中

那么Spring是如何将视图逻辑名称解析为具体的视图页面呢? 同样的 我们需要在smart-servlet中提供一个定义解析规则的bean

代码语言:javascript
复制
    <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

代码语言:javascript
复制
<%@ 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="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

代码语言:javascript
复制
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>论坛title>
head>
<body>
    ${user.username},欢迎您进入论坛,您当前积分为${user.credit};
body>
html>

main页面 仅适用JSTL标签显示一条欢迎信息。


运行Web应用

第一种方式 maven插件部署web应用服务器

这里我们使用jetty服务器插件,首先在pom.xml中配置插件

代码语言:javascript
复制
            <plugin>
                <groupId>org.mortbay.jettygroupId>
                <artifactId>maven-jetty-pluginartifactId>
                <version>6.1.25version>
                <configuration>
                    <connectors>
                        <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
                            <port>8080port>
                            <maxIdleTime>60000maxIdleTime>
                        connector>
                    connectors>
                    <contextPath>/${project.artifactId}contextPath>
                    <scanIntervalSeconds>5scanIntervalSeconds>
                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

代码语言:javascript
复制
<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.0modelVersion>
    <groupId>com.xgjgroupId>
    <artifactId>hello-spring4artifactId>
    <packaging>warpackaging>
    <version>0.0.1-SNAPSHOTversion>
    <name>hello-spring4 Maven Webappname>
    <url>http://maven.apache.orgurl>
    <dependencies>
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>3.8.1version>
            <scope>testscope>
        dependency>

        <dependency>
            <groupId>javax.servletgroupId>
            <artifactId>javax.servlet-apiartifactId>
            <version>${servlet.version}version>
            <scope>providedscope>
        dependency>

        
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-beansartifactId>
            <version>${spring.version}version>
        dependency>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-contextartifactId>
            <version>${spring.version}version>
        dependency>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-context-supportartifactId>
            <version>${spring.version}version>
        dependency>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-jdbcartifactId>
            <version>${spring.version}version>
        dependency>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-webmvcartifactId>
            <version>${spring.version}version>
        dependency>

        <dependency>
            <groupId>commons-dbcpgroupId>
            <artifactId>commons-dbcpartifactId>
            <version>${commons-dbcp.version}version>
        dependency>


        <dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjweaverartifactId>
            <version>${aspectj.version}version>
        dependency>


        <dependency>
            <groupId>org.testnggroupId>
            <artifactId>testngartifactId>
            <version>${testng.version}version>
            <scope>testscope>
        dependency>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-testartifactId>
            <version>${spring.version}version>
            <scope>testscope>
        dependency>

        
        <dependency>
            <groupId>com.oraclegroupId>
            <artifactId>ojdbc6artifactId>
            <version>${oracle.version}version>
        dependency>

        <dependency>
            <groupId>javax.servletgroupId>
            <artifactId>jstlartifactId>
            <version>${jstl.version}version>
        dependency>
    dependencies>

    <build>
        <finalName>hello-spring4finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <version>3.1version>
                <configuration>
                    <source>1.7source>
                    <target>1.7target>
                configuration>
            plugin>

            
            <plugin>
                <groupId>org.mortbay.jettygroupId>
                <artifactId>maven-jetty-pluginartifactId>
                <version>6.1.25version>
                <configuration>
                    <connectors>
                        <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
                            <port>8080port>
                            <maxIdleTime>60000maxIdleTime>
                        connector>
                    connectors>
                    <contextPath>/${project.artifactId}contextPath>
                    <scanIntervalSeconds>5scanIntervalSeconds>
                configuration>
            plugin>

            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-surefire-pluginartifactId>
                <version>2.17version>
                <configuration>
                    <parallel>methodsparallel>
                    <threadCount>10threadCount>
                configuration>
            plugin>

        plugins>
    build>

    <properties>
        <file.encoding>UTF-8file.encoding>
        <spring.version>4.3.9.RELEASEspring.version>
        <servlet.version>3.1.0servlet.version>
        <aspectj.version>1.8.1aspectj.version>
        <commons-dbcp.version>1.4commons-dbcp.version>
        <jetty.version>8.1.8.v20121106jetty.version>
        <slf4j.version>1.7.5slf4j.version>
        <testng.version>6.8.7testng.version>
        <oracle.version>11.2.0.4.0oracle.version>
        <jstl.version>1.2jstl.version>
    properties>
project>
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017/06/29 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 功能简介
  • 环境准备
    • 构建工具Maven
      • 数据库脚本(Oracle)
        • 建立工程
          • 类包及Spring配置文件规划
          • 持久层
            • 建立领域对象
              • 用户领域对象
              • 登录日志领域对象
            • UserDao
              • LoginLogDao
                • 在Spring中装配DAO
                • 业务层
                  • UserService
                    • 在Spring中装配Service
                    • 使用TestNGN对业务类进行单元测试
                    • 展现层
                      • 配置Spring MVC 框架
                        • 处理登录请求
                          • POJO控制器类
                          • Spring MVC 配置文件
                          • ModelAndView的解析配置
                        • JSP视图页面
                          • login.jsp
                          • main.jsp
                          • 第一种方式 maven插件部署web应用服务器
                          • 第二种方式 部署到容器中
                      • 运行Web应用
                      • 总结
                      • 附件 pom.xml
                      相关产品与服务
                      容器服务
                      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档