第一篇:搭建简单的 SpringMVC 应用
第一篇:搭建简单的 SpringMVC 应用
根据模板创建模块
直接通过 IDEA 的 JavaEE 模板来创建 Web 程序

然后无脑下一步,可直接得到完整的 Java EE 项目的目录结构,类似于下图:

删除默认生成的 index.jsp 和 HelloServlet,并在 xyz.xiashuo
包下创建 basicspringmvc 文件夹,作为 SpringMVC 代码主目录
进行基本的配置
POM
添加依赖到 pom 文件的 <dependencies>
标签中,
<dependencies>
<!-- SpringMVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- 日志 -->
<!-- 能不能换成log4j2 TODO-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- Spring5和Thymeleaf整合包 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<junit.version>5.8.2</junit.version>
<spring.version>5.3.1</spring.version>
</properties>
基于 XML 配置
web.xml
使用代码约定的配置
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
使用此配置是,SpringMVC 默认会去 WEB-INF 下寻找名称为 <servlet-name>-servlet.xml
的默认的配置文件,例如,以下配置所对应 SpringMVC 的配置文件位于 WEB-INF 下,文件名为 springMVC-servlet.xml
这个在《从基于 Java 配置 Spring MVC 看 Spring MVC 加载过程中的容器初始化的相关源码解析》的 4.1.2 FrameworkServlet 中有提到
其中,
<url-pattern>
标签中使用 /
和 /*
的区别:
-
/
所匹配的请求可以是/login
或.html 或.js 或.css 方式的请求路径,但是/
不能匹配.jsp 请求路径的请求,因此就可以避免在访问 jsp 页面时,该请求被 DispatcherServlet 处理,从而找不到相应的页面 -
/*
则能够匹配所有请求,例如在使用过滤器时,若需要对所有请求进行过滤,就需要使用/*
的写法
具体原因在《Servlet3.1-Specification》中的 Tomcat的web.xml和应用中的web.xml的关系
有所提及
tomcat 服务器中有专门的 Servlet 处理 jsp 请求,不需要我们来自定义 servlet 来处理,所以 /
是不会匹配.jsp 的请求的,DispatcherServlet 也不需要。同时如果我们匹配了.jsp 的请求,那么我们就得实现.jsp 的默认 servlet 的逻辑,不然.jsp 的请求会无法解析。
使用自定义配置方式
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:SpringMVC.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
contextConfigLocation
为固定参数,参数值为 SpringMVC 框架的配置文件,classpath:
开头表示路径为编译输出路径,在 Maven 项目中就是 target/classes
,src/main/resources
路径下的内容会在编译的时候复制到 target\classes
中,所以我们把配置文件放在 src\main\resources
下即可。
我们都知道 Servlet 为了按需加载,会在第一次被调用的时候初始化,但是 DispatcherServlet 因为是 SpringMVC 的入口,初始化的时候会有非常多的工作要做,为了不在第一次请求 SpringMVC 的时候有明显的迟滞感,我们应该添加 <load-on-startup>
标签并设置值为 1,让 DispatcherServlet 在容器启动的时候就初始化。
SpringMVC.xml
在 src/main/resources
路径下创建 SpringMVC.xml,注意,创建 springMVC 配置文件的时候,IDEA 有默认的模板

得到的配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
添加基本的配置
<?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:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 自动扫描包 -->
<context:component-scan base-package="xyz.xiashuo.basicspringmvc"/>
<!-- 配置Thymeleaf视图解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<!-- 可配置多个 ThymeleafViewResolver ,order设置采用顺序-->
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<!-- 视图解析策略-->
<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/templates/"/>
<!-- 视图后缀 -->
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8"/>
</bean>
</property>
</bean>
</property>
</bean>
<!-- 处理静态资源,例如html、js、css、jpg 若只设置该标签,则只能访问静态资源,其他请求则无法访问 此时必须设置<mvc:annotation-driven/>解决问题 -->
<mvc:default-servlet-handler/>
<!-- 开启mvc注解驱动 -->
<mvc:annotation-driven>
<mvc:message-converters>
<!-- 处理响应中文内容乱码 -->
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="defaultCharset" value="UTF-8"/>
<property name="supportedMediaTypes">
<list>
<value>application/json</value>
<value>text/html</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
</beans>
有 SpringFramework 基础的兄弟一眼就能看出来,这个配置文件跟 Spring 的配置文件基本没啥区别,除了几个新的标签,比如 <mvc:xxx>
,很显然,SpringMVC 中绝对是集成了 SpringFramework 的功能的,而且跟 SpringFramework 一样,SpringMVC 也是有基于注解的配置方式,TODO,把链接放在这里。
这个配置文件中,比较重点的就是这个视图解析器 org.thymeleaf.spring5.view.ThymeleafViewResolver
,这个得从 SpringMVC 的架构图来解释它的功能和定位

所谓视图解析,实际上就是将视图名称拼上前缀和后缀,得到一个页面在项目中的路径,这个路径就是路径解析的结果,然后 SpringMVC 会将这个路径对应的页面文件返回给客户端
以后我们再自己研究,TODO,把链接放在这里
基于注解配置
在 xyz.xiashuo.basicspringmvc
路径下新建 config 文件夹,在下面新建配置文件
用 AbstractAnnotationConfigDispatcherServletInitializer 的子类代替 web.xml
在 Servlet3.0 环境中,容器会在类路径中查找实现 javax.servlet.ServletContainerInitializer 接口的类,如果找到的话就用它来配置 Servlet 容器。
Spring 提供了这个接口的实现,名为 SpringServletContainerInitializer,这个类反过来又会查找实现 WebApplicationInitializer 的类并将配置的任务交给它们来完成。Spring3.2 引入了一个便利的 WebApplicationInitializer 基础实现,名为 AbstractAnnotationConfigDispatcherServletInitializer,当我们的类扩展了 AbstractAnnotationConfigDispatcherServletInitializer 并将其部署到 Servlet3.0 容器的时候,容器会自动发现它,并用它来配置 Servlet 上下文。
/**
* 代替 web.xml
*
*/
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 指定spring的配置类
*
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
// 可以写多个
return new Class[]{SpringConfig.class};
}
/**
* 指定SpringMVC的配置类
*
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
// 可以写多个
return new Class[]{WebConfig.class};
}
/**
* 指定DispatcherServlet的映射规则,即url-pattern
*
* @return
*/
@Override
protected String[] getServletMappings() {
// 可以写多个
return new String[]{"/"};
}
/**
* 添加过滤器
*
* @return
*/
@Override
protected Filter[] getServletFilters() {
// 对应 xml配置中配置的
// <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-mapping>
// <filter-name>CharacterEncodingFilter</filter-name>
// <url-pattern>/*</url-pattern>
// </filter-mapping>
CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
encodingFilter.setEncoding("UTF-8");
encodingFilter.setForceRequestEncoding(true);
encodingFilter.setForceResponseEncoding(true);
// 对应 xml 中配置的
// <filter>
// <filter-name>HiddenHttpMethodFilter</filter-name>
// <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
// </filter>
// <filter-mapping>
// <filter-name>HiddenHttpMethodFilter</filter-name>
// <url-pattern>/*</url-pattern>
// </filter-mapping>
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
return new Filter[]{encodingFilter, hiddenHttpMethodFilter};
}
}
创建 SpringConfig 配置类,代替 Spring 的配置文件
@Configuration
public class SpringConfig {
//ssm整合之后,spring的配置信息写在此类中
}
创建 WebConfig 配置类,代替 SpringMVC 的配置文件 SpringMVC.xml
@Configuration
//扫描组件
@ComponentScan("xyz.xiashuo.springmvcannotationconfig")
//开启MVC注解驱动
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
//使用默认的servlet处理静态资源
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
//配置文件上传解析器
//@Bean
//public CommonsMultipartResolver multipartResolver(){
// return new CommonsMultipartResolver();
//}
//配置拦截器
//@Override
//public void addInterceptors(InterceptorRegistry registry) {
// FirstInterceptor firstInterceptor = new FirstInterceptor();
// registry.addInterceptor(firstInterceptor).addPathPatterns("/**");
//}
//配置视图控制
/*@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}*/
//配置异常映射
/*@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
Properties prop = new Properties();
prop.setProperty("java.lang.ArithmeticException", "error");
//设置异常映射
exceptionResolver.setExceptionMappings(prop);
//设置共享异常信息的键
exceptionResolver.setExceptionAttribute("ex");
resolvers.add(exceptionResolver);
}*/
//------------------------------ 配置视图解析器 ---------------------------------------------------
// 对应xml中的
// <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
// <!-- 可配置多个 ThymeleafViewResolver ,order设置采用顺序-->
// <property name="order" value="1"/>
// <property name="characterEncoding" value="UTF-8"/>
// <property name="templateEngine">
// <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
// <property name="templateResolver">
// <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
// <!-- 视图解析策略-->
// <!-- 视图前缀 -->
// <property name="prefix" value="/WEB-INF/templates/"/>
// <!-- 视图后缀 -->
// <property name="suffix" value=".html"/>
// <property name="templateMode" value="HTML5"/>
// <property name="characterEncoding" value="UTF-8"/>
// </bean>
// </property>
// </bean>
// </property>
// </bean>
//配置生成模板解析器
@Bean
public ITemplateResolver templateResolver() {
WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
// ServletContextTemplateResolver需要一个ServletContext作为构造参数,可通过WebApplicationContext 的方法获得
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(
webApplicationContext.getServletContext());
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setTemplateMode(TemplateMode.HTML);
return templateResolver;
}
//生成模板引擎并为模板引擎注入模板解析器
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
//生成视图解析器并未解析器注入模板引擎
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
}
编写控制器
在 xyz.xiashuo.basicspringmvc
路径下新建 controller 文件夹,然后新建 HelloController:
@Controller
public class HelloController {
// @RequestMapping注解:处理请求和控制器方法之间的映射关系
// @RequestMapping注解的value属性可以通过请求地址匹配请求,value值为/表示的当前工程的上下文路径
// 比如当前项目的上下文路径是 localhost:8080/BasicSpringMVC/ ,那么 @RequestMapping 中 value值为/的时候匹配的路径,就是 localhost:8080/BasicSpringMVC/
// 这个路径的localhost:8080很好理解,就是ip+端口,那BasicSpringMVC是什么呢?是web应用的名称,在tomcat中,应用的名称默认就是war包的名称,
// 那这个项目打出的war包的名称是在哪里确定的呢?答案是,在maven的pom文件中配置的,就是<name>标签的内容,也是<artifactId>标签的内容
@RequestMapping("/")
public String index() {
//设置视图名称
return "index";
}
}
编写视图页面
根据 SpringMVC.xml 中的视图解析器的前缀配置,在 /WEB-INF
下新建 templates
文件夹,然后在其中新建 index.html,PS:IDEA 也有模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>首页</h1>
Hello World!
</body>
</html>
如果 html 需要支持 thymeleaf 模板解析,则需要在 html 头部添加命名空间:xmlns="http://www.thymeleaf.org"
<html lang="en" xmlns="http://www.thymeleaf.org">
但是后面我发现,在 html 中使用了 th:
的时候,不添加 xmlns,也能正常解析
至此,简单的 SpringMVC 项目搭建完了。可以开始启动了。
启动
新建 Run/Debug Configuration,将其此模块作为一个 artifact deploy 到 Tomcat 应用服务器中。


点击 debug 启动
查看日志
注意,因为我们没有在 src/main/resources
添加 logback 的配置文件 logback.xml,所以 logback 会采用默认的日志级别 DEBUG 来记录日志。
加载过程首先是初始化 SpringMVC 容器相关的东西(包括 DispatcherServlet),没看懂,没关系,慢慢来,SpringMVC 容器初始化完成之后,直接调用了一次上下文路径,然后因为需要渲染视图,开始配置 Thymeleaf 相关配置,初始化 Thymeleaf 引擎

Thymeleaf 引擎初始化完成
首页加载完成,启动结束。
为什么会调用了一次上下文路径,这是因为 IDEA 的 Run/Debug Configuration 中配置了 Tomcat 启动之后,自动访问此路径

如果你把这个勾选去掉,Tomcat 的启动在 Deploy took 那里就会结束

当我们访问首页(上下文路径)的时候,输出日志:
RequestMappingHandlerMapping 就是请求映射处理器,可以清楚地看到它将 /BasicSpringMVC/
映射到 xyz.xiashuo.basicspringmvc.controller.HelloController#index()
中,HelloController 中地 index() 这就是我们编写的控制器。
添加新的页面
新建 target 视图,在 /WEB-INF/templates/
下新建 target.html,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>This my target</h1>
</body>
</html>
并修改 index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>首页</h1>
Hello World
<a th:href="@{/target}">跳转到目标页面</a>
</body>
</html>
并且在其中使用了 th:href="@{/traget}"
,这个表达式地作用是,在检测到 {}
中的内容是绝对路径(即以 /
开头的路径,以 .
开头的是相对路径)的时候,自动添加 web 应用的上下文路径,
我发现,在 html 中使用了 th:
的时候,不添加 xmlns="http://www.thymeleaf.org"
,也能正常解析
然后在 HelloController 中添加相应的控制器方法,用于将请求解析到 target 视图。
@RequestMapping("/target")
public String target() {
//设置视图名称
return "target";
}
然后重新部署,在首页中,可以看到,A 标签的 href 已经自动添加了当前 web 应用的上下文路径
这个 th:href="@{/}"
自动添加应用上下文路径的功能真的方便
点击链接,跳转到 target 页面