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);
}
}

SSM项目环境快速搭建的更多相关文章

  1. Java Web 开发环境快速搭建

    Java Web 开发环境快速搭建 在因某种原因更换开发设备后,可依据此文快速搭建开发环境,恢复工作环境. Java开发环境: Windows 10 (64-bit) Oralce JDK Eclip ...

  2. ASP.NET MVC项目框架快速搭建实战

    MVC项目搭建笔记---- 项目框架采用ASP.NET MVC+Entity Framwork+Spring.Net等技术搭建,采用”Domain Model as View Model“的MVC开发 ...

  3. JAVA学习:maven开发环境快速搭建

    转自:http://tech.it168.com/a2011/1204/1283/000001283307.shtml 最近,开发中要用到maven,所以对maven进行了简单的学习.因为有个mave ...

  4. golang开源项目qor快速搭建网站qor-example运行实践

    最近想找几个基于Go语言开发的简单的开源项目学习下,分享给大家,github上有心人的收集的awesome-go项目集锦:github地址 发现一个Qor项目: Qor 是基于 Golang 开发的的 ...

  5. maven环境快速搭建(转)

    出处:http://www.cnblogs.com/fnng/archive/2011/12/02/2272610.html 最近,开发中要用到maven,所以对maven进行了简单的学习.因为有个m ...

  6. windows Android开发环境快速搭建和部署

    windows安装Android的开发环境相对来说比较简单,本文写给第一次想在自己Windows上建立Android开发环境的朋友们,为了确保大家能顺利完成开发环境的搭建,文章写的尽量详细,希望对初级 ...

  7. Java 以及JEE环境快速搭建

    吐槽一下 博主最近找了一个Java Development的实习,加上上个月末的考试周,所以很久没有更新博客. 上了一周的班,还没有在熟悉项目的阶段. 感想:哇,读别人的代码是一件很费力的事情啊!!! ...

  8. lnmp环境快速搭建及原理解析

    刚开始学习php的时候是在wamp环境下开发的,后来才接触到 lnmp 环境当时安装lnmp是按照一大长篇文档一步步的编译安装,当时是真不知道是在做什么啊!脑袋一片空白~~,只知道按照那么长的一篇文档 ...

  9. vue项目环境的搭建

    首先要明白Vue是基于node的,在公司要使用vue来开发项目的话肯定是要先安装node的,下面是搭建一个最简单的vue项目环境 一 安装node 这个可以去node的官网下载对应版本 安装好之后 c ...

随机推荐

  1. 工作效率-十五分钟让你快速学习Markdown语法到精通排版实践备忘

    关注「WeiyiGeek」公众号 设为「特别关注」每天带你玩转网络安全运维.应用开发.物联网IOT学习! 希望各位看友[关注.点赞.评论.收藏.投币],助力每一个梦想. 文章目录: 0x00 前言简述 ...

  2. Docker在手,天下我有,在Win10系统下利用Docker部署Gunicorn+Flask打造独立镜像

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_164 书接上回,之前一篇:Win10环境下使用Flask配合Celery异步推送实时/定时消息(Socket.io)/2020年最 ...

  3. 趣味问题《寻人启事》的Python程序解决

    偷懒了很久,今天我终于又来更新博客了~ 最近,我看到了一个趣味问题,或者说是数学游戏:<寻人启事>. 在表述这个问题前,我们需要了解一下"冰雹猜想": 对于任意一个正整 ...

  4. CSP-J2021 题解

    分糖果 题意 选择L~R中的某个数 , 使得x mod k的结果最大. 思路 分两种情况考虑: 若 L 和 R 对 K 取模后在同一区间,则必然在 x=R 位置取到最大值: 否则 L~R 必然跨越多个 ...

  5. JAVA语言基础组成(1)

    JAVA语言基础组成 关键字 关键字的定义和特点 定义:被Java语言赋予了特殊含义的单词 特点:关键字中所有字母都为小写 用于定义数据类型的关键字 class interface byte int ...

  6. Luogu1038 神经网络 (拓扑排序)

    拓扑排序,裸的,水的. 第一发:题读错,输出错,输入错,到处错 \(\longrightarrow\) 40pts (excuse me ?) 第二发:漏了输入层特判 \(\longrightarro ...

  7. MySQL入门笔记一

    MySQL应用笔记   一MySQL关系型数据库.开源,中小型公司常用类型的数据库Oracle 大型公司常用数据库 MySQL基本的命令一. 创建.删除.查看数据库(database)创建库creat ...

  8. java单线程100%利用率

    容器内就获取个cpu利用率,怎么就占用单核100%了呢 背景:这个是在centos7 + lxcfs 和jdk11 的环境上复现的 目前这个bug已经合入到了开源社区, 链接为 https://git ...

  9. 【NOI P模拟赛】寻找道路(bfs,最短路)

    题面 一道特殊的最短路题. 给一个 n n n 个点 m m m 条有向边的图,每条边上有数字 0 \tt0 0 或 1 \tt1 1 ,定义一个路径的长度为这个路径上依次经过的边上的数字拼在一起后在 ...

  10. 移动/联通APN提升

    绝大部分的时候信号满格速度特别慢 解决办法不一定对所有人有效可尝试一下 一般流程手机的设置-移动网络-移动数据-接入点名称(APN)-新建APN 中国移动如下配置 名称:随便写 APN:cmtds m ...