使用Spring MVC

  • Servlet:能处理HTTP请求并将HTTP响应返回;
  • JSP:一种嵌套Java代码的HTML,将被编译为Servlet;
  • Filter:能过滤指定的URL以实现拦截功能;
  • Listener:监听指定的事件,如ServletContext、HttpSession的创建和销毁。

Spring提供的是一个IoC容器,所有的Bean,包括Controller,都在Spring IoC容器中被初始化,而Servlet容器由JavaEE服务器提供(如Tomcat)

搭建应用程序

首先创建基于Web的Maven工程,引入如下依赖:

<?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>com.softeem</groupId>
    <artifactId>spring-web-mvc</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--spring context-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>
        <!--spring jdbc for transaction-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>
        <!--Mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.4</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.4</version>
        </dependency>
        <!--Spring web mvc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>

        <!--mysql driver-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.21</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.20</version>
        </dependency>

        <!-- 嵌入式Tomcat -->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>9.0.26</version>
        </dependency>

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <version>9.0.26</version>
        </dependency>

        <!-- 集成Pebble View -->
        <dependency>
            <groupId>io.pebbletemplates</groupId>
            <artifactId>pebble-spring5</artifactId>
            <version>3.1.2</version>
        </dependency>

        <!-- 使用logback -->
<!--        <dependency>-->
<!--            <groupId>ch.qos.logback</groupId>-->
<!--            <artifactId>logback-core</artifactId>-->
<!--            <version>1.2.3</version>-->
<!--        </dependency>-->

<!--        <dependency>-->
<!--            <groupId>ch.qos.logback</groupId>-->
<!--            <artifactId>logback-classic</artifactId>-->
<!--            <version>1.2.3</version>-->
<!--        </dependency>-->
    </dependencies>
</project>

其中,src/main/webapp是标准web目录,WEB-INF存放web.xml,编译的class,第三方jar,以及不允许浏览器直接访问的View模版,static目录存放所有静态文件。

src/main/resources目录中存放的是Java程序读取的classpath资源文件

src/main/resources目录中存放的是Java程序读取的classpath资源文件,除了JDBC的配置文件jdbc.properties外,我们又新增了一个logback.xml,这是Logback的默认查找的配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT"
              class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n</Pattern>
        </layout>
    </appender>

    <logger name="com.softeem" level="info" additivity="false">
        <appender-ref ref="STDOUT" />
    </logger>

    <root level="info">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

上面给出了一个写入到标准输出的Logback配置,可以基于上述配置添加写入到文件的配置。

src/main/java中就是我们编写的Java代码了。

配置Spring MVC

和普通Spring配置一样,我们编写正常的AppConfig后,只需加上@EnableWebMvc注解,就“激活”了Spring MVC:

@Configuration
@ComponentScan
@MapperScan
@EnableTransactionManagement
@EnableWebMvc
@PropertySource("classpath:jdbc.properties")
public class AppConfig {
    ...
}

除了创建DataSourceJdbcTemplatePlatformTransactionManager外,AppConfig需要额外创建几个用于Spring MVC的Bean:

//    指定静态文件的映射路径和文件位置
    @Bean
    WebMvcConfigurer createWebMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void addResourceHandlers(ResourceHandlerRegistry registry) {
                registry.addResourceHandler("/static/**").addResourceLocations("/static/");
            }
        };
    }

WebMvcConfigurer并不是必须的,但我们在这里创建一个默认的WebMvcConfigurer,只覆写addResourceHandlers(),目的是让Spring MVC自动处理静态文件,并且映射路径为/static/**

另一个必须要创建的Bean是ViewResolver,因为Spring MVC允许集成任何模板引擎,使用哪个模板引擎,就实例化一个对应的ViewResolver

@Bean
    ViewResolver createViewResolver(@Autowired ServletContext servletContext){
        PebbleEngine engine = new PebbleEngine.Builder().autoEscaping(true)
                .cacheActive(false)
                .loader(new ServletLoader(servletContext))
                .extension(new SpringExtension())
                .build();
        PebbleViewResolver viewResolver = new PebbleViewResolver();
        viewResolver.setPrefix("/WEB-INF/template/");
        viewResolver.setSuffix("");
        viewResolver.setPebbleEngine(engine);
        return viewResolver;
    }

ViewResolver通过指定prefix和suffix来确定如何查找View。上述配置使用Pebble引擎,指定模板文件存放在/WEB-INF/tempaltes/目录下。

剩下的Bean都是普通的@Component,但Controller必须标记为@Controller,例如:

// Controller使用@Controller标记而不是@Component:
@Controller
public class UserController {
    // 正常使用@Autowired注入:
    @Autowired
    UserService userService;

    // 处理一个URL映射:
    @GetMapping("/")
    public ModelAndView index() {
        ...
    }
    ...
}

如果是普通的Java应用程序,我们通过main()方法可以很简单地创建一个Spring容器的实例:

public static void main(String[] args) {
    ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
}

在Web应用中启动Spring容器有很多种方法,可以通过Listener启动,也可以通过Servlet启动,可以使用XML配置,也可以使用注解配置。

第一步,我们在web.xml中配置Spring MVC提供的DispatcherServlet

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
        </init-param>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.softeem.AppConfig</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

初始化参数contextClass指定使用注解配置的AnnotationConfigWebApplicationContext,配置文件的位置参数contextConfigLocation指向AppConfig的完整类名,最后,把这个Servlet映射到/*,即处理所有URL。

上述配置可以看作一个样板配置,有了这个配置,Servlet容器会首先初始化Spring MVC的DispatcherServlet,在DispatcherServlet启动时,它根据配置AppConfig创建了一个类型是WebApplicationContext的IoC容器,完成所有Bean的初始化,并将容器绑到ServletContext上。

因为DispatcherServlet持有IoC容器,能从IoC容器中获取所有@Controller的Bean,因此,DispatcherServlet接收到所有HTTP请求后,根据Controller方法配置的路径,就可以正确地把请求转发到指定方法,并根据返回的ModelAndView决定如何渲染页面。

AppConfig中通过main()方法启动嵌入式Tomcat:

public static void main(String[] args) throws Exception {
    Tomcat tomcat = new Tomcat();
    tomcat.setPort(Integer.getInteger("port", 8080));
    tomcat.getConnector();
    Context ctx = tomcat.addWebapp("", new File("src/main/webapp").getAbsolutePath());
    WebResourceRoot resources = new StandardRoot(ctx);
    resources.addPreResources(
            new DirResourceSet(resources, "/WEB-INF/classes", new File("target/classes").getAbsolutePath(), "/"));
    ctx.setResources(resources);
    tomcat.start();
    tomcat.getServer().await();
}
小结

pom依赖

<!--    spring 容器-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
<!--    spring webMVC-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!-- 嵌入式Tomcat -->
    <dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-core</artifactId>
      <version>9.0.26</version>
    </dependency>
    <dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-jasper</artifactId>
      <version>9.0.26</version>
    </dependency>
1.  配置Web.xml
在web.xml中配置servlet: org.springframework.web.servlet.DispatcherServlet
在配置DispatcherServlet的时候需要注入两个init-params
	1. contextClass: 指定容器类型
	2. 指定对容器配置的配置类 AppConfig
<servlet>
  <servlet-name>dispatcher</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
    <param-name>contextClass</param-name>
    <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
  </init-param>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>com.softeem.AppConfig</param-value>
  </init-param>
  <load-on-startup>0</load-on-startup>
</servlet>

<servlet-mapping>
  <servlet-name>dispatcher</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>
2. 嵌入式tomcat容器的配置
        //嵌入Tomcat
        Tomcat tomcat = new Tomcat(); //实例化tomcat
        //设置端口
        tomcat.setPort(Integer.getInteger("port", 8080));
        //连接端口
        tomcat.getConnector();
        //设置容器上下文
        //contextPath: localhost:8080/contextPath   通常项目名,空字符串表示没有上下文名称
        //指定容器的webRoot目录位置
        Context ctx = tomcat.addWebapp("", new 	  File("src/main/webapp").getAbsolutePath());
        //根据上下文对象,创建资源目录对象
        WebResourceRoot resources = new StandardRoot(ctx);
        //在资源目录对象中指定classes文件夹的位置
        resources.addPreResources(
                new DirResourceSet(resources, "/WEB-INF/classes", new File("target/classes").getAbsolutePath(), "/"));
        ctx.setResources(resources);
        //开启tomcat
        tomcat.start();
        tomcat.getServer().await();
3. 设置MVC组件
    //    指定静态文件的映射路径和文件位置
    @Bean
    WebMvcConfigurer createWebMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void addResourceHandlers(ResourceHandlerRegistry registry) {
            // 设置静态文件的路径【请求路径】
            	registry.addResourceHandler("/static/**")
            	//对应路径的文件位置
            	.addResourceLocations("/static/");
            }
        };
    }
    
	

	//视图解析器,在搭载SpringMVC环境后,控制器返回的视图字符串会在该解析器中进行解析
	//寻找对应的视图, 保证视图安全
	@Bean
    ViewResolver createViewResolver(){
        //默认通用视图解析器,支持HTML/JSP
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setSuffix(".jsp"); //后缀名
        viewResolver.setPrefix("/WEB-INF/template/"); //前缀
        //controller-> index--------> /WEB-INF/template/index.jsp
        return viewResolver;
    }

注解使用

@EnableWebMvc   启用webMVC的支持
编写Controller

有了Web应用程序的最基本的结构,我们的重点就可以放在如何编写Controller上。Spring MVC对Controller没有固定的要求,也不需要实现特定的接口。以UserController为例,编写Controller只需要遵循以下要点:

总是标记@Controller而不是@Component

@Controller
public class UserController {
    ...
}

一个方法对应一个HTTP请求路径,用@GetMapping@PostMapping表示GET或POST请求:

@PostMapping("/signin")
public ModelAndView doSignin(
        @RequestParam("email") String email,
        @RequestParam("password") String password,
        HttpSession session) {
    ...
}

需要接收的HTTP参数以@RequestParam()标注,可以设置默认值。如果方法参数需要传入HttpServletRequestHttpServletResponse或者HttpSession,直接添加这个类型的参数即可,Spring MVC会自动按类型传入。

返回的ModelAndView通常包含View的路径和一个Map作为Model,但也可以没有Model,例如:

return new ModelAndView("signin.html"); // 仅View,没有Model

返回重定向时既可以写new ModelAndView("redirect:/signin"),也可以直接返回String:

public String index() {
    if (...) {
        return "redirect:/signin";
    } else {
        return "redirect:/profile";
    }
}

如果在方法内部直接操作HttpServletResponse发送响应,返回null表示无需进一步处理:

public ModelAndView download(HttpServletResponse response) {
    byte[] data = ...
    response.setContentType("application/octet-stream");
    OutputStream output = response.getOutputStream();
    output.write(data);
    output.flush();
    return null;
}

对URL进行分组,每组对应一个Controller是一种很好的组织形式,并可以在Controller的class定义出添加URL前缀,例如:

@Controller
@RequestMapping("/user")
public class UserController {
    // 注意实际URL映射是/user/profile
    @GetMapping("/profile")
    public ModelAndView profile() {
        ...
    }

    // 注意实际URL映射是/user/changePassword
    @GetMapping("/changePassword")
    public ModelAndView changePassword() {
        ...
    }
}

实际方法的URL映射总是前缀+路径,这种形式还可以有效避免不小心导致的重复的URL映射。

可见,Spring MVC允许我们编写既简单又灵活的Controller实现。

总结

使用Spring MVC时,整个Web应用程序按如下顺序启动:

  1. 启动Tomcat服务器;
  2. Tomcat读取web.xml并初始化DispatcherServlet;
  3. DispatcherServlet创建IoC容器并自动注册到ServletContext中。

启动后,浏览器发出的HTTP请求全部由DispatcherServlet接收,并根据配置转发到指定Controller的指定方法处理。