SSM项目环境快速搭建
SSM项目的环境搭建
环境搭建的目标
工程创建
创建父工程
-
创建空
maven
工程xxx-parent
作为父工程 -
修改父工程中的
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>
-
注意:父工程的打包方式为
<packaging>pom</packaging>
-
在父工程中,使用
<dependencymanagement>
标签包裹<dependencies>
,故父类工程实际上只是声明了这些jar
包的引用,实际并没有包涵进来
创建子工程
注意事项:
-
在带
/WEB-INF/web.xml
的网络工程项目中,设置pom.xml
中的<packaging>war</packaging>
-
各个子项目在引入时,可以直接使用父项目中已经声明了的资源,如果不填写引入的
jar
包的版本号<version>
,那么默认引入的就是夫项目中指定的版本,也可以自己指定单独的版本 -
如果不需要某个
jar
包中的某些牵连的依赖,可以使用<exclusion>
标签排除 -
如果不同
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>
-
因为每个新的
module
都是通过 空的maven
项目创建的,所以在Web
项目中需要添加Web Framework
Spring整合 MyBatis
-
在子工程中加入搭建环境需要的依赖:
mysql-connector-java, mybatis, mybatis-spring, spring-tx, spring-orm,aspectjweaver, cglib, druid, pagehelper
-
创建
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
-
创建
mybatis
的配置文件mybatis-config.xml
,具体的配置都可以放到spring
的配置文件中进行<!--?xml version="1.0" encoding="UTF-8"?--> <configuration> </configuration>
-
创建
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
注解时,需要做更多的处理。
- 需要添加
@ContextConfiguration()
注解,设置locations
属性为需要加载的配置文件名称 - 添加
@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
进行使用,此时像 Spring
和 MyBatis
并不会通过 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>
<!– mapping 是配置要拦截的资源–>
<!– /*表示一层路径,如:/a–>
<!– /**表示多层路径,如:/aaa/bbb/cc–>
<mvc:mapping path="/**"/>
<!– exclude-mapping 是配置不拦截的资源–>
<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"/>
<!– 具体的拦截器的类–>
<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);
}
}