初学 Spring MVC(基于 Spring in Action)

时间:2022-12-10 21:44:31

Spring MVC(Model-View-Controller)

当你看到本博文时,我猜你可能正面临着我已探索过的问题。

同其他博主一样,我先按照书上详细的介绍一下 Spring MVC,也是为了自己能理解的更深刻一些。

 

一、跟踪 Spring MVC 的请求

每当我们碰到各种问题时,我们总是会向浏览器求助。

正如你在百度里查找资料一样,搜索一下问题,点击一个链接,再查看里面的内容。

1、在请求离开浏览器时,总会带有你发出请求的信息,比如你可能刚刚在搜 Spring MVC,这就是信息。

2、请求的第一站是前端控制器,在 Spring MVC 中,它是 DispatcherServlet。

     它的作用是把请求发送到合适的 Spring MVC 控制器。

      因为有各种各样的请求需要控制器去处理,所以前端控制器是把请求发送到一个合适的控制器中。

3、用户发送了一个请求,当然想看的是结果了。所以控制器处理完请求后将会返回一些信息以便能让用户看见。

      这些希望被用户看见的信息称为模型(Model)。

      当然用户肯定是想看的舒服一点,所以这些信息需要发送到视图(View)时显示,通常是 JSP。

4、控制器的事情还没完,它的实际返回值是一个视图名,将视图名和模型数据传递给前端控制器(DispatcherServlet)。

5、既然知道由哪个视图显示用户希望看到的结果,那么请求的最后一站也就是视图的实现了。

 

二、搭建 Spring MVC

既然原理已经讲清楚了,那么接下来看个例子才能更好理解!

我们第一步也就是来配置上面所说到的东西,按照传统方式,通常是使用 web.xml 来配置的。

但是作者说的使用 JavaConfig 更好一些。那我们可以对比一下这两种方式。

1、配置 DispatcherServlet

package spittr.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
{

    @Override
    protected Class<?>[] getRootConfigClasses() 
    {
        return new Class<?>[] { RootConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() 
    {
        return new Class<?>[] { WebConfig.class };
    }

    @Override
    protected String[] getServletMappings() 
    {
        return new String[] { "/" };
    }

}

正如书本提到的,扩展 AbstractAnnotationConfigDispatcherServletInitializer 类的任意类---

都会自动配置 DispatcherServlet 和 Spring 应用上下文。

至于怎么配置可以看到下面重写的三个函数。

函数 getServletMappings 会处理所有的请求,也就是用户发出的请求都交由 SpringMVC 来处理。

函数 getRootConfigClasses 会根据 RootConfig 类来配置 ContextLoaderListener 创建的应用上下文的 bean。

函数 getServletConfigClasses 会根据 WebConfig 类来配置 DispatcherServlet 应用上下文的 bean。

另外配置类都要带有 @Configuration 注解,为了能让组件扫描知道这是配置类。

 

2、接下来看下这两个类的内容

package spittr.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver; @Configuration @EnableWebMvc @ComponentScan("spittr.web") public class WebConfig { @Bean public ViewResolver viewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB-INF/views/"); resolver.setSuffix(".jsp"); resolver.setExposeContextBeansAsAttributes(true); return resolver; } }

该类的 Configuration 需要导入 org.springframework.context.annotation.Configuration;

@EnableWebMvc 说明需要启用 SpringMVC,

需要导入 org.springframework.web.servlet.config.annotation.EnableWebMvc;

@ComponentScan 注解说明启用组件扫描,扫描的包为 spittr.web,

需要导入 org.springframework.context.annotation.ComponentScan;

书中说的处理静态资源可能不太好,需要继承 WebMvcConfigureAdapter 并重写 configureDefaultServletHanding 方法。

目前我还不太清楚哪里不好,所以这里把它删掉。

在这里可以看到有个视图解析器,它的作用是给控制器返回的视图名加个前缀和后缀,以便知道视图文件在哪里。

在该函数前面加了个@bean 注解,是为了将起加入到 spring 容器中去,不然该视图解析器不会生效。

 

3、接下来看下 RootConfig 的内容

package spittr.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@ComponentScan(basePackages = { "spittr" }, excludeFilters = {
        @Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class) })
public class RootConfig 
{

}

在这里我详细的说一下@ComponentScan的作用:
在配置类上配置 @ComponentScan 注明扫描的包名,该包及其子包下的类中,

只要代码中的类标注了 @Controller、@Service、@Repository、@Component 都会将其加载到 spring容器中。

当然 @bean 是显示的声明,可以直接加载到 spring容器中。

basePackages 表面组件扫描的包是那个,从Packages中可以看出,该属性的值可以是一个数组。

excludeFilters 指定扫描的时候需要排除的组件,includeFilters 指定扫描的时候只包含的组件,

useDefaultFilters 是否开启默认扫描规则。

例如:@ComponentScan(basePackages = "spittr",includeFilters = {},excludeFilters = {},useDefaultFilters = true)

excludeFilters 和 includeFilters 都是 Filter 数组 ,Filter中我们需要指定类型,分别为:

1、ANNOTATION 注解类型

2、ASSIGNABLE_TYPE 指定类型

3、ASPECTJ ASPECTJ 表达式

4、REGEX 正则表达式

5、CUSTOM 自定义

书上的例子是过滤 spittr 包中的注解类型的组件,后面跟的值我猜可能是过滤那个类里面的注解,使其不被加载的 Spring 容器中。

 

三、编写基本控制器

 1、HomeController

package spittr.web;
import static org.springframework.web.bind.annotation.RequestMethod.*;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/home")
public class HomeController {

  @RequestMapping(method = GET)
  public String home(Model model) {
    return "home";
  }

}

该控制器会处理 "/home" 的 GET 请求,返回的视图名为 home。

我们来看下 Spittr 的首页 index.jsp 以及 home.jsp:

<%@ taglib prefix="c"  uri="http://java.sun.com/jsp/jstl/core"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>Spittr</title>
    <link rel="stylesheet" type="text/css" href="/resources/styles.css">
  </head>
  <body>
    <h1>Welcome to Spittr</h1>
    <a href="<c:url value="/home"/>">Home</a> |
    <a href="<c:url value="/spittles"/>">Spittles</a> |
    <a href="<c:url value="/spitter/register"/>">Register</a>
    
  </body>
</html>

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
Welcome to you!
</body>
</html>

当我们运行 index.jsp 后点击 Home 就会显示 Home 页面。

 

四、传递模型数据到视图中

在我们的Home 控制器中并没有把数据返回到视图中,仅仅只返回了一个视图名,可以说是一个超级简单的控制器了。

现在让我们给视图加点数据进去。

我们先定义一个 Spittle 类:

package spittr;
import java.util.Date;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

public class Spittle 
{
    private final Long id;
    private final String message;
    private final Date time;
    private Double latitude;
    private Double longitude;

    public Spittle(String message, Date time)
    {
        this(message, time, null, null);
    }

    public Spittle(String message, Date time, Double latitude, Double longitude) 
    {
        this.id = null;
        this.message = message;
        this.time = time;
        this.latitude = latitude;
        this.longitude = longitude;
    }

    public long getId() {
        return id;
    }

    public String getMessage() 
    {
        return message;
    }

    public Date getTime()
    {
        return time;
    }

    public Double getLongitude() 
    {
        return longitude;
    }

    public Double getLatitude() 
    {
        return latitude;
    }

    @Override
    public boolean equals(Object that) 
    {
        return EqualsBuilder.reflectionEquals(this, that, "id", "time");
    }

    @Override
    public int hashCode()
    {
        return HashCodeBuilder.reflectionHashCode(this, "id", "time");
    }
}

然后假设我们的数据都在一个仓库里:

定义一个接口 SpittleRepository:

package spittr.data;
import java.util.List;
import org.springframework.stereotype.Component;
import spittr.Spittle;

@Component
public interface SpittleRepository 
{
    List<Spittle> findSpittles(long max, int count);

}

再实现它:

package spittr.data;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.stereotype.Repository;
import spittr.Spittle;
@Repository
public class JdbcSpittleRepository implements SpittleRepository 
{

    private List<Spittle> createSpittleList(int count) 
    {
        List<Spittle> spittles = new ArrayList<Spittle>();
        for (int i = 0; i < count; i++) 
        {
            spittles.add(new Spittle("Spittle" + i, new Date()));
        }
        return spittles;
    }

    @Override
    public List<Spittle> findSpittles(long max, int count) 
    {
        // TODO Auto-generated method stub

        return createSpittleList(count);
    }

}

 

现在我们的仓库实现了,可以生成 Spittle 数据。然后我们编写一个 Spittle 控制器:来把数据放到视图中。

package spittr.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import spittr.data.SpittleRepository;

@Controller
public class SpittleController 
{
    private SpittleRepository spittleRepository;

    @Autowired
    public SpittleController(SpittleRepository spittleRepository)
    { // 注入SpittleRepository
        this.setSpittleRepository(spittleRepository);
    }

    @RequestMapping(value="/spittles",method = RequestMethod.GET)
    public String spittles(Model model) 
    {
        // 将spittle添加到模型中
        model.addAttribute(spittleRepository.findSpittles(Long.MAX_VALUE, 20));
        return "spittles"; // 返回视图名
    }

    public SpittleRepository getSpittleRepository()
    {
        return spittleRepository;
    }

    public void setSpittleRepository(SpittleRepository spittleRepository) 
    {
        this.spittleRepository = spittleRepository;
    }

}

该控制器中的 @Autowired 注解会在 Spring容器中寻找有 @Repository 注解的bean,然后将该 bean 注入进去。

也就是在 Spring 容器中 寻找适当的 bean 注入到参数中去。

在控制器处理方法上,也就是带有 @RequestMapping 注解的方法的参数有个 Model 也就是模型,

控制器可以通过该参数将数据传递到视图上去。

现在我们看下 spittles.jsp 视图:

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib prefix="c"  uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="s" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>Spitter</title>
    <link rel="stylesheet" type="text/css" href="<c:url value="../resources/style.css" />" >
  </head>
    <div class="listTitle">
      <h1>Recent Spittles</h1>
      <ul class="spittleList">
        <c:forEach items="${spittleList}" var="spittle" >
          <li id="spittle_<c:out value="spittle.id"/>">
            <div class="spittleMessage"><c:out value="${spittle.message}" /></div>
            <div>
              <span class="spittleTime"><c:out value="${spittle.time}" /></span>
            </div>
          </li>
        </c:forEach>
      </ul>

    </div>
  </body>
</html>

最后让我们来看下效果:

初学 Spring MVC(基于 Spring in Action)       初学 Spring MVC(基于 Spring in Action)


 

 初学 Spring MVC(基于 Spring in Action)  


 

 初学 Spring MVC(基于 Spring in Action)                                 初学 Spring MVC(基于 Spring in Action)

 

如果是用 web.xml 来配置的话:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
  <display-name>Java_Web</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
  
  <servlet>
      <servlet-name>SpringDispatcherServlet</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>/WEB-INF/SpringMVC.xml</param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
  </servlet>
  
  <servlet-mapping>
      <servlet-name>SpringDispatcherServlet</servlet-name>
      <url-pattern>/</url-pattern>
  </servlet-mapping>
  
  <context-param>   
      <param-name>contextConfigLocation</param-name>   
      <param-value>/WEB-INF/AppContext.xml</param-value>   
  </context-param>
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener> </web-app>

另外说明一下,如果是 SpringMVC.xml 文件放在src里的话:<param-value>classpath:SpringMVC.xml<param-value>。

同理:AppContext.xml也是一样。

 

接下来我们看下SpringMVC.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:aop="http://www.springframework.org/schema/aop"
    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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">

 <context:component-scan base-package="spittr"></context:component-scan>
 <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/"></property>
    <property name="suffix" value=".jsp"></property>
 </bean>

</beans>

 该文件配置的是视图解析器,讲解了 javaConfig,这里就不多说了。

 

 

AppContext.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:aop="http://www.springframework.org/schema/aop"
    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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd">


</beans>

 

上面的包并没有单元测试和 mock 模拟测试包。

如果需要项目及相关包可以点此下载:Download