本章通过一个简单开发spring 应用的整体过程,实现应用按照持久层,业务层和展示层进行组织,从底层DAO 到WEB展示逐渐展示。

本章通过一个简单开发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对象,显示用户名和积分信息,

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值