第一篇:搭建简单的 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> 标签中使用 //* 的区别:

具体原因在《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 页面