SSM项目环境快速搭建

SSM项目的环境搭建

环境搭建的目标

工程创建

创建父工程

  1. 创建空 maven工程 xxx-parent 作为父工程

  2. 修改父工程中的 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>org.example</groupid>
      <artifactid>FastSSMProjectBuild</artifactid>
      <version>1.0-SNAPSHOT</version>
    
      <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <spring.version>4.3.20.RELEASE</spring.version>
        <mybatis.version>3.2.8</mybatis.version>
        <mysql.version>5.1.3</mysql.version>
        <druid.version>1.0.31</druid.version>
        <spring-mybatis.version>1.2.2</spring-mybatis.version>
        <pagehelper.version>4.0.0</pagehelper.version>
        <aspectjweaver.version>1.9.2</aspectjweaver.version>
        <cglib.version>2.2</cglib.version>
        <slf4j-api.version>1.7.7</slf4j-api.version>
        <logback-classic.version>1.2.3</logback-classic.version>
        <jcl-over-slf4j.version>>1.7.25</jcl-over-slf4j.version>
        <jul-to-slf4j.version>>1.7.25</jul-to-slf4j.version>
        <jackson.version>2.9.8</jackson.version>
        <jstl.version>1.2</jstl.version>
        <junit.version>4.12</junit.version>
        <servlet.version>2.5</servlet.version>
        <jsp.version>2.1.3-b06</jsp.version>
        <gson.version>2.8.5</gson.version>
        <springsecurity.version>4.2.10.RELEASE</springsecurity.version>
      </properties>
    
      <modules>
        <module>WebProject</module>
        <module>ServiceComp</module>
        <module>DaoComp</module>
        <module>ControllerComp</module>
        <module>EntityComp</module>
        <module>CommonComp</module>
      </modules>
    
      <packaging>pom</packaging>
    
      <dependencymanagement>
        <dependencies>
            <!-- Spring 依赖 -->
          <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>spring-orm</artifactid>
            <version>${spring.version}</version>
          </dependency>
    
          <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>spring-webmvc</artifactid>
            <version>${spring.version}</version>
          </dependency>
    
          <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>spring-test</artifactid>
            <version>${spring.version}</version>
          </dependency>
    
          <dependency>
            <groupid>org.aspectj</groupid>
            <artifactid>aspectjweaver</artifactid>
            <version>${aspectjweaver.version}</version>
          </dependency>
    
          <dependency>
            <groupid>cglib</groupid>
            <artifactid>cglib</artifactid>
            <version>${cglib.version}</version>
          </dependency>
    
            <!-- 数据库依赖 -->
    		<!-- MySQL 驱动 -->
          <dependency>
            <groupid>mysql</groupid>
            <artifactid>mysql-connector-java</artifactid>
            <version>${mysql.version}</version>
          </dependency>
    
            <!-- 数据源 -->
          <dependency>
            <groupid>com.alibaba</groupid>
            <artifactid>druid</artifactid>
            <version>${druid.version}</version>
          </dependency>
    
            <!-- MyBatis -->
          <dependency>
            <groupid>org.mybatis</groupid>
            <artifactid>mybatis</artifactid>
            <version>${mybatis.version}</version>
          </dependency>
    
            <!-- MyBatis 与 Spring 整合 -->
          <dependency>
            <groupid>org.mybatis</groupid>
            <artifactid>mybatis-spring</artifactid>
            <version>${spring-mybatis.version}</version>
          </dependency>
    
            <!-- MyBatis 分页插件 -->
          <dependency>
            <groupid>com.github.pagehelper</groupid>
            <artifactid>pagehelper</artifactid>
            <version>${pagehelper.version}</version>
          </dependency>
    
            <!-- 日志 -->
          <dependency>
            <groupid>org.slf4j</groupid>
            <artifactid>slf4j-api</artifactid>
            <version>${slf4j-api.version}</version>
          </dependency>
          <dependency>
            <groupid>ch.qos.logback</groupid>
            <artifactid>logback-classic</artifactid>
            <version>${logback-classic.version}</version>
          </dependency>
    
          <!-- 其他日志框架的中间转换包 -->
          <dependency>
            <groupid>org.slf4j</groupid>
            <artifactid>jcl-over-slf4j</artifactid>
            <version>1.7.25</version>
          </dependency>
          <dependency>
            <groupid>org.slf4j</groupid>
            <artifactid>jul-to-slf4j</artifactid>
            <version>1.7.25</version>
          </dependency>
          <!-- Spring 进行 JSON 数据转换依赖 -->
          <dependency>
            <groupid>com.fasterxml.jackson.core</groupid>
            <artifactid>jackson-core</artifactid>
            <version>${jackson.version}</version>
          </dependency>
          <dependency>
            <groupid>com.fasterxml.jackson.core</groupid>
            <artifactid>jackson-databind</artifactid>
            <version>${jackson.version}</version>
          </dependency>
          <!-- JSTL 标签库 -->
          <dependency>
            <groupid>jstl</groupid>
            <artifactid>jstl</artifactid>
            <version>${jstl.version}</version>
          </dependency>
    
          <!-- junit 测试 -->
          <dependency>
            <groupid>junit</groupid>
            <artifactid>junit</artifactid>
            <version>${junit.version}</version>
            <scope>test</scope>
          </dependency>
          <!-- 引入 Servlet 容器中相关依赖 -->
          <dependency>
            <groupid>javax.servlet</groupid>
            <artifactid>servlet-api</artifactid>
            <version>${servlet.version}</version>
            <scope>provided</scope>
          </dependency>
          <!-- JSP 页面使用的依赖 -->
          <dependency>
            <groupid>javax.servlet.jsp</groupid>
            <artifactid>jsp-api</artifactid>
            <version>${jsp.version}</version>
            <scope>provided</scope>
          </dependency>
          <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
          <dependency>
            <groupid>com.google.code.gson</groupid>
            <artifactid>gson</artifactid>
            <version>${gson.version}</version>
          </dependency>
    
          <!-- SpringSecurity 对 Web 应用进行权限管理 -->
          <dependency>
            <groupid>org.springframework.security</groupid>
            <artifactid>spring-security-web</artifactid>
            <version>${springsecurity.version}</version>
          </dependency>
          <!-- SpringSecurity 配置 -->
          <dependency>
            <groupid>org.springframework.security</groupid>
            <artifactid>spring-security-config</artifactid>
            <version>${springsecurity.version}</version>
          </dependency>
          <!-- SpringSecurity 标签库 -->
          <dependency>
            <groupid>org.springframework.security</groupid>
            <artifactid>spring-security-taglibs</artifactid>
            <version>${springsecurity.version}</version>
          </dependency>
        </dependencies>
      </dependencymanagement>
    
      <build>
        <resources>
          <resource>
            <directory>src/main/resources</directory>
            <includes>
              <include>**/*.properties</include>
              <include>**/*.xml</include>
              <include>**/*.yml</include>
            </includes>
            <filtering>false</filtering>
          </resource>
          <resource>
            <directory>src/main/java</directory>
            <includes>
              <include>**/*.properties</include>
              <include>**/*.xml</include>
              <include>**/*.yml</include>
            </includes>
            <filtering>false</filtering>
          </resource>
        </resources>
      </build>
    </project>
    
  3. 注意:父工程的打包方式为 <packaging>pom</packaging>

  4. 在父工程中,使用 <dependencymanagement>标签包裹 <dependencies>,故父类工程实际上只是声明了这些 jar包的引用,实际并没有包涵进来

创建子工程

注意事项:

  1. 在带 /WEB-INF/web.xml的网络工程项目中,设置 pom.xml中的 <packaging>war</packaging>

  2. 各个子项目在引入时,可以直接使用父项目中已经声明了的资源,如果不填写引入的 jar包的版本号 <version>,那么默认引入的就是夫项目中指定的版本,也可以自己指定单独的版本

  3. 如果不需要某个 jar包中的某些牵连的依赖,可以使用 <exclusion>标签排除

  4. 如果不同 module之间需要项目引用,就需要在当前的 pom.xml<dependencies>标签中声明

    <dependencies>
        <dependency>
            <groupid>org.example</groupid>
            <artifactid>EntityComp</artifactid>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    
        <dependency>
            <groupid>org.slf4j</groupid>
            <artifactid>slf4j-api</artifactid>
        </dependency>
    
        <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>spring-webmvc</artifactid>
            <exclusions>
                <exclusion>
                    <groupid>org.springframework</groupid>
                    <artifactid>spring-beans</artifactid>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    
  5. 因为每个新的 module都是通过 空的maven项目创建的,所以在 Web项目中需要添加 Web Framework

Spring整合 MyBatis

  1. 在子工程中加入搭建环境需要的依赖:

    mysql-connector-java, mybatis, mybatis-spring, spring-tx, spring-orm,aspectjweaver, cglib, druid, pagehelper
    
  2. 创建 jdbc.properties准备数据库连接相关的信息

    jdbc.user=root
    jdbc.password=zhao
    jdbc.url=jdbc:mysql://localhost:3306/project_crowd?useSSL=false&characterEncoding=UTF-8&useUnicode=true&serverTimezone=UTC
    jdbc.driver=com.mysql.jdbc.Driver
    
  3. 创建 mybatis的配置文件 mybatis-config.xml,具体的配置都可以放到 spring的配置文件中进行

    <!--?xml version="1.0" encoding="UTF-8"?-->
    
    <configuration>
    
    </configuration>
    
  4. 创建 spring同数据库相关的配置文件 spring-mybatis.xml

    <!--?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:context="http://www.springframework.org/schema/context" 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:property-placeholder location="classpath:jdbc.properties">
    
        <!--    配置数据源-->
        <bean id="druid" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
            <property name="username" value="${jdbc.user}">
            <property name="password" value="${jdbc.password}">
            <property name="url" value="${jdbc.url}">
            <property name="driverClassName" value="${jdbc.driver}">
        </property></property></property></property></bean>
    
        <!--    配置 SqlSessionFactoryBean整合 MyBatis-->
        <bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
            <!--        指定 MyBatis全局配置文件的位置-->
            <property name="configLocation" value="classpath:mybatis/mybatis-config.xml">
            <!--        装配数据源-->
            <property name="dataSource" ref="druid">
            <!--        指定 Mapper.xml配置文件的位置-->
            <property name="mapperLocations" value="classpath:mybatis/mapper/*Mapper.xml">
    
            <property name="plugins">
                <array>
                    <!--配置 pageHelper插件-->
                    <bean class="com.github.pagehelper.PageHelper">
                        <!--配置相关属性-->
                        <property name="properties">
                            <props>
                                <!-- 配置数据库方言,告诉 PageHelper当前使用的数据库-->
                                <prop key="dialect">mysql</prop>
                                <!--配置页码的合理化修正:在 1~总页数之间修正页码-->
                                <prop key="reasonable">true</prop>
                            </props>
                        </property>
                    </bean>
                </array>
            </property>
        </property></property></property></bean>
    
        <!--    配置 MapperScannerConfigurer来扫描 Mapper接口所在的包-->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" id="mapperScannerConfigurer">
            <property name="basePackage" value="com.zwb.crowd.mapper">
        </property></bean>
    </context:property-placeholder></beans>
    

Spring的测试类

使用 JUnit4对已有的代码进行测试时,因为需要使用 Spring对类进行管理,所以相比以前使用 JUnit时只添加 @Test注解时,需要做更多的处理。

  1. 需要添加 @ContextConfiguration()注解,设置 locations属性为需要加载的配置文件名称
  2. 添加 @RunWith()注解,设置 value属性为 SpringJUnit4ClassRunner.class

比如:

import com.zwb.crowd.entity.Admin;
import com.zwb.crowd.entity.Menu;
import com.zwb.crowd.entity.Role;
import com.zwb.crowd.exception.LoginAcctDuplicateException;
import com.zwb.crowd.mapper.AdminMapper;
import com.zwb.crowd.mapper.RoleMapper;
import com.zwb.crowd.service.api.AdminService;
import com.zwb.crowd.service.api.MenuService;
import com.zwb.crowd.util.CrowdUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

/**
 * @author :OliQ
 * @date :Created in 2021/8/8 14:13
 * <p>
 * 在类上标记必要的注解,Spring整合 JUnit
 */
@ContextConfiguration(locations = {"classpath:spring-persist-mybatis.xml", "classpath:spring-persist-tx.xml", "classpath:spring-web-mvc.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class CrowdTest {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private MenuService menuService;

    @Autowired
    private AdminMapper adminMapper;

    @Autowired
    private AdminService adminService;

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Test
    public void testBCrypt() {
        System.out.println("oliver: " + passwordEncoder.encode("oliver"));
        System.out.println("adminOperator: " + passwordEncoder.encode("adminOperator"));
        System.out.println("roleOperator: " + passwordEncoder.encode("roleOperator"));
    }

    @Test
    public void testGetAllMenu() {
        List</p><menu> all = menuService.getAll();
        System.out.println(all);
    }

    @Test
    public void createSomeTestRoles() {
        for (int i = 0; i < 123; i++) {
            Role role = new Role(null, "test" + i);
            roleMapper.insert(role);
        }
    }

    @Test
    public void createSomeTestAdmins() throws LoginAcctDuplicateException {
        for (int i = 0; i < 132; i++) {
            Admin admin = new Admin(null, "test" + i, null, "test" + i, "test" + i + "@qq.com", null);
            admin.setUserPswd(CrowdUtil.md5("test" + i));
            adminService.saveAdmin(admin);
        }
    }

    @Test
    public void testTx() throws LoginAcctDuplicateException {
        Admin admin = new Admin(null, "tom", "tom", "杰瑞", "jerry@qq.com", null);
        adminService.saveAdmin(admin);
    }


    @Test
    public void testLog() {
        // 获取 Logger对象,传入的一般就是当前类
        Logger logger = LoggerFactory.getLogger(CrowdTest.class);

        // 根据不同的日志级别打印日志
        logger.debug("Hello I am in Debug level");
        logger.debug("Hello I am in Debug level");
        logger.debug("Hello I am in Debug level");

        logger.info("Info level");
        logger.info("Info level");
        logger.info("Info level");

        logger.warn("warning warning warning");
        logger.warn("warning warning warning");
        logger.warn("warning warning warning");


        logger.error("error");
        logger.error("error");
        logger.error("error");
    }


    @Test
    public void testInsertAdmin() {
        Admin admin = new Admin(null, "oliver", "oliver", "oliver", "oli@qq.com", null);
        int insert = adminMapper.insert(admin);

        // sysout的打印方式本质上是一个 IO操作,比较消耗性能不能在上线后使用
        // 即使上线前删除,也很可能有遗漏,而且非常的麻烦
        // 如果选用日志系统,就可以通过日志的级别控制信息的打印
        System.out.println(insert);
    }

    @Test
    public void testSelectAdmin() {
        Admin admin = adminMapper.selectByPrimaryKey(1);
        System.out.println(admin);
    }


    @Test
    public void testConnection() throws SQLException {
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
    }

}

日志系统

在系统运行过程中,往往会出现这样那样的错误,我们需要通过日志进行排查

为什么使用日志而不是 sysout?

  • sysout 如果不删除,那么执行到这里必然会打印;
  • 如果使用日志方式打印,可以通过日志级别控制信息是否打印
  • sysout性能影响大些

框架日志系统的替换

默认情况下,slf4j会搭配 commons-logging进行使用,此时像 SpringMyBatis并不会通过 slf4j进行日志打印。所以我们需要进行框架日志系统的替换。

<!-- 替换掉 commons-logging -->
<dependency>
    <groupid>org.springframework</groupid>
    <artifactid>spring-orm</artifactid>
    <exclusions>
        <exclusion>
            <groupid>commons-logging</groupid>
            <artifactid>commons-logging</artifactid>
        </exclusion>
    </exclusions>
</dependency>

<!-- 加入转换包 -->
<dependency>
    <groupid>org.slf4j</groupid>
    <artifactid>jcl-over-slf4j</artifactid>
    <version>1.7.25</version>
</dependency>

添加 logback配置文件

为了规范化日志的打印格式,可以在 resources目录下添加配置文件 logback.xml

<!--?xml version="1.0" encoding="UTF-8"?-->
<configuration debug="true">

    <!-- 指定日志输出的位置 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!-- 日志输出的格式 -->
            <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体
            内容、换行 -->
            <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
        </encoder>
    </appender>


    <!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
    <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
    <root level="DEBUG">
        <!-- 指定打印日志的 appender,这里通过“STDOUT”引用了前面配置的 appender -->
        <appender-ref ref="STDOUT">
    </appender-ref></root>


    <!-- 根据特殊需求指定局部日志级别 -->
    <logger name="com.zwb.crowd.mapper" level="DEBUG">
</logger></configuration>

声明式事物

使用 Spring来全面接管数据库事务,使用 声明式代替 编程式

try {
    // 核心操作前:开启事务(关闭自动提交)
    // 对应 AOP 的前置通知
    connection.setAutoCommit(false);
    // 核心操作
    adminService.updateXxx(xxx, xxx);
    // 核心操作成功:提交事务
    // 对应 AOP 的返回通知
    connection.commit();
} catch (Exception e) {
    // 核心操作失败:回滚事务
    // 对应 AOP 的异常通知
    connection.rollBack();
} finally {
    // 不论成功还是失败,核心操作终归是结束了
    // 核心操作不管是怎么结束的,都需要释放数据库连接
    // 对应 AOP 的后置通知
    if (connection != null) {
        connection.close();
    }
}

需要加入的依赖是:

  • aspectjweaver
  • cglib
<!-- AOP 所需依赖 -->
<dependency>
    <groupid>org.aspectj</groupid>
    <artifactid>aspectjweaver</artifactid>
</dependency>
<!-- AOP 所需依赖 -->
<dependency>
    <groupid>cglib</groupid>
    <artifactid>cglib</artifactid>
</dependency>

XML配置事物

创建 xml配置文件 spring-tx.xml

<!--?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: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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.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">

    <!--    配置自动扫描的包,主要是为了把 Service扫描到 IOC容器中-->
    <context:component-scan base-package="com.zwb.crowd.service">

    <!--    配置事物管理器-->
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="dataSourceTransactionManager">
        <!--        装配数据源-->
        <property name="dataSource" ref="druid">
    </property></bean>

    <!--    配置事务的切面-->
    <aop:config>
        <!--        考虑到 SpringSecurity中,避免把 UserDetailsService加入到事物控制中,让切入点表达式定位到 ServiceImpl-->
        <aop:pointcut id="txPointcut" expression="execution(* *..*ServiceImpl.*(..))">

        <!--        将切入点表达式和事物通知关联起来-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut">
    </aop:advisor></aop:pointcut></aop:config>

    <!--    配置事物的通知-->
    <tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">

        <!--        配置事物的属性-->
        <tx:attributes>

            <!--            查询的方法:配置只读属性,进行优化-->
            <!--
                        在基于 XML的声明式事物中,tx:method是必须配置的,
                        如果某个方法没有配置对应的 tx:method,那么事务对这个方法就不生效
                        -->
            <tx:method name="get*" read-only="true">
            <tx:method name="find*" read-only="true">
            <tx:method name="query*" read-only="true">
            <tx:method name="count*" read-only="true">

            <!--            增删改方法:配置事物的传播行为,回滚异常-->
            <tx:method name="save*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception">
            <tx:method name="update*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception">
            <tx:method name="remove*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception">
            <tx:method name="batch*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception">

        </tx:method></tx:method></tx:method></tx:method></tx:method></tx:method></tx:method></tx:method></tx:attributes>
    </tx:advice>
</context:component-scan></beans>

Spring整合 SpringMVC

加入依赖

<dependency>
    <groupid>org.springframework</groupid>
    <artifactid>spring-webmvc</artifactid>
</dependency>

配置 web.xml

<!--?xml version="1.0" encoding="UTF-8"?-->
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">

    <!--    配置 ContextLoaderListener加载 Spring配置文件-->
    <!--<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-persist-*.xml</param-value>
  </context-param>-->

    <!--<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>-->

    <!--    配置 DispatcherServlet-->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <!-- 指定字符集 -->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <!-- 强制请求设置字符集 -->
        <init-param>
            <param-name>forceRequestEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
        <!-- 强制响应设置字符集 -->
        <init-param>
            <param-name>forceResponseEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>

    <!-- 这个Filter执行的顺序要在所有其他Filter前面 -->
    <!-- 原因如下: -->
    <!-- request.setCharacterEncoding(encoding)必须在request.getParameter()前面 -->
    <!-- response.setCharacterEncoding(encoding)必须在response.getWriter()前面 -->
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- The front controller of this Spring Web application, responsible for handling all application requests -->
    <servlet>
        <servlet-name>springDispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-*.xml</param-value>
        </init-param>
        <!-- Servlet默认生命周期中,创建对象是在第一次接收到请求时 -->
        <!-- 而DispatcherServlet创建对象后有大量的“框架初始化”工作,不适合在第一次请求时来做 -->
        <!-- 设置load-on-startup就是为了让DispatcherServlet在Web应用启动时创建对象、初始化 -->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- Map all requests to the DispatcherServlet for handling -->
    <servlet-mapping>
        <servlet-name>springDispatcherServlet</servlet-name>
        <!-- url-pattern配置方式一:/表示拦截所有请求 -->
        <!-- <url-pattern>/</url-pattern> -->

        <!-- url-pattern配置方式二:配置请求扩展名 -->
        <!-- 优点1:xxx.css、xxx.js、xxx.png等等静态资源完全不经过SpringMVC,不需要特殊处理 -->
        <!-- 优点2:可以实现伪静态效果。表面上看起来是访问一个HTML文件这样的静态资源,但是实际上是经过Java代码运算的结果。 -->
        <!-- 	伪静态作用1:给黑客入侵增加难度。 -->
        <!-- 	伪静态作用2:有利于SEO优化(让百度、谷歌这样的搜索引擎更容易找到我们项目)。 -->
        <!-- 缺点:不符合RESTFul风格 -->
        <url-pattern>*.html</url-pattern>

        <!-- 为什么要另外再配置json扩展名呢? -->
        <!-- 如果一个Ajax请求扩展名是html,但是实际服务器给浏览器返回的是json数据,二者就不匹配了,会出现406错误。 -->
        <!-- 为了让Ajax请求能够顺利拿到JSON格式的响应数据,我们另外配置json扩展名 -->
        <url-pattern>*.json</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

注意:CharacterEncodingFilter是为了解决 POST 请求的字符乱码问题,在 web.xml 中存在多个 Filter
时,让这个 Filter 作为过滤器链中的第一个 Filter

创建 SpringMVC的配置文件 spring-webmvc.xml

<!--?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:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <import resource="classpath:spring-persist-tx.xml">
    <import resource="classpath:spring-persist-mybatis.xml">

    <!--    配置注解驱动-->
    <mvc:annotation-driven>

    <!--    配置自动扫描的包路径-->
    <context:component-scan base-package="com.zwb.crowd.mvc">

    <mvc:default-servlet-handler>

    <!--    配置视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="resolver">
        <property name="prefix" value="/WEB-INF/">
        <property name="suffix" value=".jsp">
    </property></property></bean>

    <!--    配置基于 XML的异常映射-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" id="exceptionResolver">
        <!--        配置异常类型和具体视图的对应关系时-->
        <property name="exceptionMappings">
            <props>
                <!--                key属性指定异常全类名,标签体中指定对应的视图名(不需要写前后缀,它也会通过视图解析器的)-->
                <prop key="java.lang.Exception">system-error</prop>
            </props>
        </property>
    </bean>

    <!--    配置 view-controller,直接把请求地址和视图名称关联起来,不必再写 Controller方法了-->
    <mvc:view-controller path="/admin/to/login/page.html" view-name="admin-login">
    <mvc:view-controller path="/admin/to/main/page.html" view-name="admin-main">
    <mvc:view-controller path="/admin/to/add/page.html" view-name="admin-add">
    <mvc:view-controller path="/role/to/page.html" view-name="role-page">
    <mvc:view-controller path="/menu/to/page.html" view-name="menu-page">

    <!--    注册拦截器-->
    <!--<mvc:interceptors>
    <mvc:interceptor>
      <!&ndash;            mapping 是配置要拦截的资源&ndash;>
      <!&ndash;            /*表示一层路径,如:/a&ndash;>
      <!&ndash;            /**表示多层路径,如:/aaa/bbb/cc&ndash;>
      <mvc:mapping path="/**"/>
      <!&ndash;            exclude-mapping 是配置不拦截的资源&ndash;>
      <mvc:exclude-mapping path="/admin/to/login/page.html"/>
      <mvc:exclude-mapping path="/admin/do/login.html"/>
      <mvc:exclude-mapping path="/admin/to/login/quit.html"/>
      <!&ndash;            具体的拦截器的类&ndash;>
      <bean class="com.zwb.crowd.mvc.interceptor.LoginInterceptor" id="loginInterceptor"/>
    </mvc:interceptor>
  </mvc:interceptors>-->

</mvc:view-controller></mvc:view-controller></mvc:view-controller></mvc:view-controller></mvc:view-controller></mvc:default-servlet-handler></context:component-scan></mvc:annotation-driven></import></import></beans>

前端 JSP页面

加入依赖

<!-- 引入 Servlet 容器中相关依赖 -->
<dependency>
    <groupid>javax.servlet</groupid>
    <artifactid>servlet-api</artifactid>
    <scope>provided</scope>
</dependency>

<!-- JSP 页面使用的依赖 -->
<dependency>
    <groupid>javax.servlet.jsp</groupid>
    <artifactid>jsp-api</artifactid>
    <scope>provided</scope>
</dependency>

在页面上添加 <base>标签,保证其中的页面跳转、超链接等都基于一个共同的网站名

<base href="http://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/">

需要注意的点:

  • <base>标签必须写在 ``标签内
  • <base>标签必须写在带有具体路径的标签前面
  • serverName部分 EL 表达式和 serverPort 部分 EL 表达式之间必须写“:”
  • erverPort部分 EL表达式和 contextPath部分EL表达式之间绝对不能写“/”
    • 原因:contextPath 部分 EL 表达式本身就是“/”开头
    • 如果多写一个“/”会干扰 Cookie 的工作机制
  • serverPort 部分 EL 表达式后面必须写“/”

异常映射

作用:统一管理项目中的异常

  • 抛出异常
  • 显示异常信息
    • 普通请求:在页面上显示异常信息
    • Ajax 请求:返回 JSON 数据

因为针对不同的请求,我们都需要返回不同的数据,所以需要鉴定请求的类型。

判断依据:判断请求头中 Accept: application/json, text/javascript; X-Request-With: XMLHttpRequest

/**
     * 判断是否是 Ajax请求
     *
     * @return
     */
public static boolean judgeRequestType(HttpServletRequest request) {
    // 获取请求消息头
    String acceptHeader = request.getHeader("Accept");
    String xRequestHeader = request.getHeader("X-Requested-With");

    return (acceptHeader != null && acceptHeader.contains("application/json"))
        || (xRequestHeader != null && xRequestHeader.equals("XMLHttpRequest"));
}

基于 XML的异常映射方式

spring-web-mvc.xml中做如下配置

<!--    配置基于 XML的异常映射-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" id="exceptionResolver">
    <!--        配置异常类型和具体视图的对应关系时-->
    <property name="exceptionMappings">
        <props>
            <!--                key属性指定异常全类名,标签体中指定对应的视图名(不需要写前后缀,它也会通过视图解析器的)-->
            <prop key="java.lang.Exception">system-error</prop>
        </props>
    </property>
</bean>

基于注解的异常映射方式

加入 JSON处理相关的依赖

<dependency>
    <groupid>com.google.code.gson</groupid>
    <artifactid>gson</artifactid>
    <version>2.8.5</version>
</dependency>
package com.zwb.crowd.mvc.config;

/**
 * @author :OliQ
 * @date :Created in 2021/8/10 17:29
 * <p>
 * @ControllerAdvice 表示当前类是一个基于注解的异常处理器类
 * @EXceptionHandler 将一个具体的异常类型和一个方法关联起来
 */
@ControllerAdvice
public class CrowdExceptionResolver {

    @ExceptionHandler(value = LoginAcctDuplicatForUpdateException.class)
    public ModelAndView resolveLoginAcctDuplicatForUpdateException(LoginAcctDuplicatForUpdateException exception, HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 设置需要跳转的页面名称
        String viewName = "system-error";
        return commonResolve(viewName, exception, request, response);
    }

    @ExceptionHandler(value = LoginAcctDuplicateException.class)
    public ModelAndView resolveLoginAcctDuplicateException(LoginAcctDuplicateException exception, HttpServletRequest request, HttpServletResponse response) throws IOException {
        String viewName = "admin-add";
        return commonResolve(viewName, exception, request, response);
    }


    @ExceptionHandler(value = LoginFailedException.class)
    public ModelAndView resolveLoginFailedException(LoginFailedException exception, HttpServletRequest request, HttpServletResponse response) throws IOException {
        String viewName = "admin-login";
        return commonResolve(viewName, exception, request, response);
    }

    /**
     * 处理 空指针异常
     *
     * @param exception
     * @param request
     * @param response
     * @return
     * @throws IOException
     */
    @ExceptionHandler(value = NullPointerException.class)
    public ModelAndView resolveNullPointerException(
        // 实际捕获到的异常类型
        NullPointerException exception,
        // 当前请求对象
        HttpServletRequest request,
        HttpServletResponse response) throws IOException {

        String viewName = "system-error";
        // 返回 ModelAndView
        return commonResolve(viewName, exception, request, response);
    }

    /**
     * 处理数学异常(测试用)
     *
     * @param exception
     * @param request
     * @param response
     * @return
     * @throws IOException
     */
    @ExceptionHandler(value = ArithmeticException.class)
    public ModelAndView resolveArithmeticException(ArithmeticException exception, HttpServletRequest request, HttpServletResponse response) throws IOException {
        String viewName = "system-error";
        return commonResolve(viewName, exception, request, response);
    }


    /**
     * 实际的异常处理的方法
     *
     * @param viewName
     * @param exception
     * @param request
     * @param response
     * @return
     * @throws IOException
     */
    private ModelAndView commonResolve(String viewName,
                                       Exception exception,
                                       HttpServletRequest request,
                                       HttpServletResponse response) throws IOException {
        // 判断当前请求类型
        boolean result = CrowdUtil.judgeRequestType(request);
        // 如果是 Ajax请求
        if (result) {
            // 创建一个 ResultEntity对象
            ResultEntity<object> failed = ResultEntity.failed(exception.getMessage());
            // 创建 Gson对象
            Gson gson = new Gson();
            // 将 ResultEntity对象转换为 JSON字符串
            String json = gson.toJson(failed);
            // 将 JSON字符串作为响应体返回给浏览器
            response.getWriter().write(json);

            // 由于上面已经通过了 Response对象返回了响应,所以不提供了 ModelAndView
            return null;
        }

        // 如果 不是 Ajax请求
        ModelAndView mv = new ModelAndView();
        // 将 Exception对象存入模型
        mv.addObject(CrowdConstant.ATTR_NAME_EXCEPTION, exception);
        // 设置对应的视图
        mv.setViewName(viewName);
        return mv;
    }
}

常量处理

在请求返回中经常需要返回某些特定的常量值,因此可以将其设置为 静态的常量值

package com.zwb.crowd.constant;

/**
 * @author :OliQ
 * @date :Created in 2021/8/11 14:09
 */
public class CrowdConstant {
    // 异常处理后返回的属性名
    public static final String ATTR_NAME_EXCEPTION = "exception";
    // 已登录的管理员
    public static final String ATTR_NAME_LOGIN_ADMIN = "loginAdmin";

    public static final String ATTR_NAME_PAGE_INFO = "pageInfo";



    // 账号名重复
    public static final String MESSAGE_LOGIN_REPETITIONAL = "系统错误,账号名不唯一";

    // 登陆失败
    public static final String MESSAGE_LOGIN_FAILED = "登陆失败,账号或密码错误!";
    // 注册失败,账号已存在
    public static final String MESSAGE_LOGIN_ACCOUNT_DUPLICATE = "该账号已被注册!";

    // 注册或者登陆输入的密码不合法
    public static final String MESSAGE_LOGIN_PASSWORD_INVALIDATE = "密码不合法 !";

    // 未登录就访问资源
    public static final String MESSAGE_ACCESS_FORBIDEN = "尚未登陆,访问受限!";
}

异步值的返回

当处理 Ajax请求,需要返回数据时,不能简单的返回数据库查到的值。

需要使用一个特定的类,内含 请求操作的结果标识和数据

package com.zwb.crowd.util;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 统一整个项目中 Ajax请求返回的结果
 *
 * @author :OliQ
 * @date :Created in 2021/8/10 16:36
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResultEntity<t> {

    public static final String SUCCESS = "SUCCESS";
    public static final String FAILED = "FAILED";

    // 用来封装当前请求处理的结果是成功还是失败
    private String result;

    // 请求处理失败时返回的错误消息
    private String message;

    // 要返回的数据
    private T data;


    /**
     * 请求处理成功且不需要返回数据时
     *
     * @param <e>
     * @return
     */
    public static <e> ResultEntity<e> successWithoutData() {
        return new ResultEntity<e>(SUCCESS, null, null);
    }


    /**
     * 请求处理成功且需要返回数据时
     *
     * @param e
     * @param <e>
     * @return
     */
    public static <e> ResultEntity<e> successWithData(E e) {
        return new ResultEntity<e>(SUCCESS, null, e);
    }

    /**
     * 请求处理失败
     *
     * @param message
     * @param <e>
     * @return
     */
    public static <e> ResultEntity<e> failed(String message) {
        return new ResultEntity<>(FAILED, message, null);
    }
}

posted @ 2021-08-31 15:03  小么VinVin  阅读(52)  评论(0编辑  收藏  举报