Spring实战笔记——构建Spring Web应用程序

Spring MVC起步

下图展示的是请求使用Spring MVC所经历的所有站点。

  • DispatcherServlet:是前端控制器,主要用来将请求发送给Spring MVC控制器
  • 处理器映射:应用程序中往往有许多控制器,前端控制器需要知道将请求发送到哪一个控制器,因此处理映射可以用来指定需要发送的控制器。
  • 处理器:用来处理请求,并返回数据
  • 模型及逻辑视图名:是控制器返回的用户数据以及视图名
  • 视图解析器:用来解析逻辑视图名,并匹配一个特定的视图实现
  • 视图:将数据模型进行渲染输出,最后响应给客户端

搭建Spring MVC

配置DispatcherServlet

DispatcherServlet是Spring MVC的核心
配置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.ruian.Spring</groupId>
    <artifactId>SpringMvc</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>

        <!--Mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <!--Mybatis-spring-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.1</version>
        </dependency>
        <!--JSP-->
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.2</version>
        </dependency>
        <!--spring系列-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.3.24.RELEASE</version>
        </dependency>
        <!--servlet-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
        </dependency>
        <!--jsp-jstl-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

    </dependencies>

    <build>
        <!--处理资源导出问题-->
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>


</project>

配置前端控制器

package spittr.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

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

    //指定配置类,加载上下文时读取配置类中的bean
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    //配置DispatcherServlet映射为'/'
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}
  • DispatcherServlet启动的时候会创建Spring应用上下文,并加载配置文件或配置类中所声明的bean。getServletConfigClasses()加载上下文时读取配置类中的bean
  • DispatcherServlet用来加载包含Web组件的bean,如控制器。而ContextLoaderListener用来加载应用中其他的bean,这些bean通常是驱动应用后端的中间件和数据层组件。
  • AbstractAnnotationConfigDispatcherServletInitializer会同时创建DispatcherServletContextLoaderListenergetServletConfigClasses()返回的带有@Configuration注解的类将会用来定义DispatcherServlet应用上下文中的bean。getRootConfigClasses()方法返回的带有@Configuration注解的类将会用来配置ContextLoaderListener创建的应用上下文的bean。

启用Spring MVC
一个简单的Spring MVC配置就是一个带有@EnableWebMvc注解的类

package spittr.web;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@EnableWebMvc
public class WebConfig {
}

以上代码需要解决的问题:

  • 配置视图解析器,默认视图解析器会找ID与视图名称匹配的bean,并且查找的bean要实现View接口。
  • 启用组件扫面,否则Spring只会找到显示声明在配置类中的控制器
  • DispatcherServlet会处理所有的请求,包括静态资源的请求

改进WebConfig

package spittr.web;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan("spittr.web")
public class WebConfig extends WebMvcConfigurerAdapter {

    @Bean
    //配置JSP视图解析器
    public ViewResolver viewResolver(){
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        resolver.setExposeContextBeansAsAttributes(true);
        return resolver;
    }

    //配置静态资源的处理
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }


}

编写控制器

Spring MVC控制器只是在方法上添加@RequestMapping注解的类。

package spittr.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import static org.springframework.web.bind.annotation.RequestMethod.GET;

//编写控制器
@Controller
public class HomeController {
    @RequestMapping(value = "/", method = GET)
    public String home(){
        return "home";
    }
    
}

编写jsp页面

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
  <head>
    <title>Spitter</title>
    <link rel="stylesheet"
          type="text/css"
          href="<c:url value="/resources/style.css" />" >
  </head>
  <body>
    <h1>Welcome to Spitter</h1>

    <a href="<c:url value="/spittles" />">Spittles</a> |
    <a href="<c:url value="/spitter/register" />">Register</a>
  </body>
</html>

启动tomcat服务器测试页面

测试控制器

package spittr.web;
//测试控制器

import org.junit.Test;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;

public class HomeControllerTest {
    @Test
    public void testHomePage() throws Exception{
        HomeController controller = new HomeController();
        //搭建MockMvc
        MockMvc mockMvc = standaloneSetup(controller).build();

        //对“/”执行get请求,并期望得到home视图
        mockMvc.perform(get("/")).andExpect(view().name("home"));

    }
}

利用这种方法进行测试Controller可以不用启动web服务器

定义级别的请求处理

package spittr.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import static org.springframework.web.bind.annotation.RequestMethod.GET;

//编写控制器
@Controller
@RequestMapping("/")
public class HomeController {
    @RequestMapping(method = GET)
    public String home(){
        return "home";
    }
}

@RequestMapping可以存在多个参数如:@RequestMapping({"/", "/homepage"})

传递模型数据到视图中

定义一个数据访问的Repository接口

package spittr.data;

import java.util.List;

public interface SpittleRepository {
    /**
     *
     * @param max : 表示Spittle ID属性的最大值
     * @param count:表明返回多少个Spittle对象
     * @return
     */
    List<Spittle> findSpittles(long max, int count);
}

创建Spittle类

package spittr.data;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

import java.util.Date;

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(null, message, time, null, null);
  }

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

  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");
  }

}

创建测试类

@Test
    public void shouldShowRecentSpittles() throws Exception{
        List<Spittle> expectedSpittles = createSpittleList(20);
        SpittleRepository mockRepository = Mockito.mock(SpittleRepository.class);
        when(mockRepository.findSpittles(Long.MAX_VALUE, 20))
        .thenReturn(expectedSpittles);
        SpittleController controller = new SpittleController(mockRepository);
        MockMvc mockMvc = standaloneSetup(controller).setSingleView(
                new InternalResourceView("/WEB-INF/views/spittles.jsp")
        ).build();

        mockMvc.perform(get("/spittles"))
                .andExpect(view().name("spittles"))
                .andExpect(model().attributeExists("spittleList"))
                .andExpect(model().attribute("spittleList", hasItems(expectedSpittles.toArray())));
    }

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

这个测试中,首先需要创建SpittleRepository接口的mock实现,并且在MockMvc构造器上调用了setSingleView(),这样mock框架就不用解析视图控制器中的视图名
编写SpittleController

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
@RequestMapping("/spittles")
public class SpittleController {
    private SpittleRepository spittleRepository;

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

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

该控制器中的spittles()方法中给定了一个Model作为参数,这样spittles()方法就可以将Repository中获取的Spittle列表填充到模型中。Model本质上是一个map,它会被传送至view,因此数据可以提供给客户端。如果在调用addAttribute()方法时没有指定key,那么就会根据值的对象类型推断确定,比如代码中传入的参数属性是List,那么key就是spittleList。最后,该方法返回spittles作为传动给model的视图名称。
addAttribute()也可以指定key
model.addAttribute("spittleList", spittleRepository.findSpittles(Long.MAX_VALUE, 20))
model.addAttribute()也可被替代为model.put()

接受请求的输入

Spring MVC允许多种方式将客户端中的数据传送到控制器的处理器方法中,包括:

  • 查询参数(Query Parameter)
  • 表单参数(Form Parameter)
  • 路径变量(Path Variable)

处理查询参数
使用@RequestParam注解

@RequestMapping(method = RequestMethod.GET)
    public List<Spittle> spittles(
            @RequestParam("max") Long max,
            @RequestParam("count") int count
    ){
        return spittleRepository.findSpittles(max, count);
    }

添加测试

//测试分页
    @Test
    public void shouldShowPagedSpittle() throws Exception{
        List<Spittle> expectedSpittle = createSpittleList(50);
        SpittleRepository mockRepository = mock(SpittleRepository.class);
        when(mockRepository.findSpittles(238900, 50))
                .thenReturn(expectedSpittle);
        SpittleController controller = new SpittleController(mockRepository);
        MockMvc mockMvc = standaloneSetup(controller).setSingleView(
                new InternalResourceView("/WEB-INF/views/spittles.jsp")
        ).build();

        mockMvc.perform(get("/spittles?max=238900&count=50"))
                .andExpect(view().name("spittles"))
                .andExpect(model().attributeExists("spittleList"))
                .andExpect(model().attribute("spittleList", hasItems(expectedSpittle.toArray())));
    }

为路径参数设置默认值

@RequestMapping(method = RequestMethod.GET)
    public List<Spittle> spittles(
            @RequestParam(value = "max", defaultValue = "9223372036854775807") Long max,
            @RequestParam(value = "count", defaultValue = "20") int count
    ){
        return spittleRepository.findSpittles(max, count);
    }

尽管defaultValue属性给定的是String类型的值,但是当绑定到方法的max时,它会转换为Long类型。

通过路径参数接受输入
利用@RequestParam("spittle_id")可以用来处理形如/spittles/show?spittle_id=10这样的请求,而@PathVariable("spittle_id")可以处理/spittle/123这样的请求,如果对'/spittle/456'发送请求,那么会将456传递进来,作为spittle_id的值。
编写控制器

@RequestMapping(value = "/{spittleId}",method = RequestMethod.GET)
    public String spittles(
            @PathVariable(value = "spittleId") Long spittleId,
            Model model
    ){
        model.addAttribute(spittleRepository.findOne(spittleId));
        return "spittle";
    }

当@PathVaribale属性的值,最有又作为方法的参数名称可以省略@PathVaribale中的value属性

@RequestMapping(value = "/{spittleId}",method = RequestMethod.GET)
    public String spittles(
            @PathVariable Long spittleId,
            Model model
    ){
        model.addAttribute(spittleRepository.findOne(spittleId));
        return "spittle";
    }

编写测试类

@Test
    public void testSpittle() throws Exception {
        Spittle expectedSpittle = new Spittle("hello", new Date());
        SpittleRepository mockRepository = mock(SpittleRepository.class);
        when(mockRepository.findOne(123))
                .thenReturn(expectedSpittle);
        SpittleController controller = new SpittleController(mockRepository);
        MockMvc mockMvc = standaloneSetup(controller).build();

        mockMvc.perform(get("/spittles/123"))
                .andExpect(view().name("spittle"))
                .andExpect(model().attributeExists("spittle"))
                .andExpect(model().attribute("spittle", expectedSpittle));
    }

处理表单

使用表单可以分为两个方面:展现表单以及处理用户通过表单提交的数据。SpitterController是一个新的控制器其代码如下所示:

package spittr.web;

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

@Controller
@RequestMapping("/spitter")
public class SpitterController {
    //处理提交的表单并注册为新用户
    private SpittleRepository spittleRepository;
    //注入SpitterRespository
    @Autowired
    public SpitterController(SpittleRepository spittleRepository){
        this.spittleRepository = spittleRepository;
    }
    
    public String processRegistration(Spittle spittle){
        spittleRepository.save(spittle);
        //重定向
        return "redirect:/spitter/" + spittle.getUserneme();
    }
    @RequestMapping(value = "/register", method = RequestMethod.GET)
    public String showRegistrationForm(){
        return "registerForm";
    }
    
}

重定向使用redirect:。当视图格式中有redirect:是会将视图解析为重定向,而不是视图名称。转发使用的是forward:前缀。
校验表单
Java校验API定义了多个注解,这些注解可以当到属性上,从而限制这些属性的值
@AsserFalse:所注解的元素必须是Boolean类型,并且值为false
@AssertTure:所注解的元素必须是Boolean类型,并且值为true
@DecimalMax:所注解的元素必须是数字,并且它的值要小于或等于给定的BigdecimalString的值
@DecimalMin:所注解的元素必须是数字,并且它的值要大于或等于给定的BigdecimalString的值
@Digits:所注解的元素必须是数字,并且它的值必须有指定的位数
@Future:所注解的元素必须是一个将来的日期
@Max:所注解的元素必须是数字,并且它的值要小于或等于给定的值
@Min:所注解的元素必须是数字,并且它的值要大于或等于给定的值
@NotNull:所注解的元素的值必须不能为null
@Null:所注解的元素的值必须为null
@Past:所注解的元素的值必须是一个已过去的日期
@Pattern:所注解的元素的值必须匹配给定的正则表达式
@Size:所注解的元素的值必须是String、集合或数组,并且它的长度要符合给定的范围。

在Spitter域上使用校验注解

package spittr;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.hibernate.validator.constraints.Email;

public class Spitter {

  private Long id;
  
  @NotNull
  @Size(min=5, max=16)
  private String username;

  @NotNull
  @Size(min=5, max=25)
  private String password;
  
  @NotNull
  @Size(min=2, max=30)
  private String firstName;

  @NotNull
  @Size(min=2, max=30)
  private String lastName;
  
  @NotNull
  @Email
  private String email;

  public Spitter() {}
  
  public Spitter(String username, String password, String firstName, String lastName, String email) {
    this(null, username, password, firstName, lastName, email);
  }

  public Spitter(Long id, String username, String password, String firstName, String lastName, String email) {
    this.id = id;
    this.username = username;
    this.password = password;
    this.firstName = firstName;
    this.lastName = lastName;
    this.email = email;
  }

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }
  
  public String getEmail() {
    return email;
  }
  
  public void setEmail(String email) {
    this.email = email;
  }

  @Override
  public boolean equals(Object that) {
    return EqualsBuilder.reflectionEquals(this, that, "firstName", "lastName", "username", "password", "email");
  }
  
  @Override
  public int hashCode() {
    return HashCodeBuilder.reflectionHashCode(this, "firstName", "lastName", "username", "password", "email");
  }

}

启用校验

//添加校验
    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String processRegistration(
            @Valid Spitter spitter,
            Errors errors
            ){
        if (errors.hasErrors()){
            return "registerForm";
        }
        
        spittleRepository.save(spitter);
        return "redirect:/spitter/" + spitter.getUsername();
        
    }

使用@Valid注解进行校验,当校验出现错误的时候可以使用Errors对象进行访问,Errors参数要紧跟在带有@Valid注解的参数后面,@Valid注解所标注的就是要校验的参数errors.hasErrors()可以判断校验是否错误

posted @ 2021-02-14 20:22  梦不是很远  阅读(32)  评论(0)    收藏  举报