第5章-构建Spring Web应用程序

构建Spring Web应用程序

学习内容

映射请求到Spring控制器

透明地绑定表单参数

校验表单提交

简介

状态管理、工作流以及验证都是需要解决的重要特性,HTTP协议的无状态性决定了这些问题都不那么容易解决。

SpringWeb框架就是为了解决这些关注点而设计的。Spring MVC基于模型-视图-控制器模式(Model-View-ControllerMVC)实现,他能够帮你构建像Spring框架那样灵活和松耦合的Web应用程序。

Spring MVC起步

Spring将请求在调度Servlet、处理器映射(handler mapping)、控制器以及视图解析器(view resolver)之间移动。

 

1、跟踪Spring MVC的请求

 

 

流程详解:

在请求离开浏览器时<1>,会带有用户所请求内容的信息,至少会包含请求的URL。但是还可能带有其他的信息。例如用户提交的表单信息。

Spring MVC所有的请求都会通过一个前端控制器(front controllerServlet。前端控制器是常用的Web应用程序模式,在这里一个单实例的Servlet将请求委托给应用程序的其他组件来执行实际的处理。在Spring MVC中,DispatcherServlet就是前端控制器。

DispatcherServlet的任务是将请求发送给Spring MVC控制器。控制器是一个用于处理请求的Spring组件。在典型的应用程序中可能会有多个控制器,DispatcherServlet会查询一个或多个处理器映射(handler mapping<2>(处理器映射会根据请求所携带的URL信息来进行决策)来确定将请求发送给那个控制器。

一旦选择了合适的控制器,DispatcherServlet会将请求发送给选中的控制器<3>。到了控制器,请求会卸下其负载(用户提交的信息)并耐心等待控制器处理这些信息。(实际上,设计良好的控制器本身只处理很少甚至不处理工作,而是将业务逻辑委托给一个或多个服务对象进行处理)

控制器在完成逻辑处理后,通常会产生一些信息,这些信息需要返回给用户并在浏览器上显示。这些信息被称为模型(Model)。不过仅仅给用户返回原始的信息是不够的--这些信息需要以用户友好的方式进行格式化,一般会是HTML。所以,信息需要发送给一个视图(view),通常会是JSP

控制器所做的最后一件事就是将模型数据打包,并且标示出用于渲染输出的视图名。他接下来会将请求连同模型和视图名发送回DispatcherServlet<4>

这样,控制器就不会与特定的视图相耦合,传递给DispatcherServlet的视图名并不会直接标示某个特定的JSP。实际上,他甚至并不能确定视图就是JSP。相反,他仅仅传递了一个逻辑名称,这个名字将会用来查找产生结果的真正视图。DispatcherServlet将会使用视图解析器(view resolver<5>来将逻辑视图名匹配为一个特定的视图实现,他可能是也可能不是JSP

既然DispatcherServlet已经知道由那个视图渲染结果,那请求的任务基本上也就完成了。他的最后一站是视图的实现(可能是JSP<6>,在这里他交付模型数据。请求的任务就完成了。视图将使用模型数据渲染输出,这个输出会通过响应对象传递给客户端(不会像听上去那样硬编码)。

 

2、搭建Spring MVC

配置DispatcherServlet

DispatcherServletSpring MVC的核心。在这里请求会第一次接触到框架,他要负责将请求路由到其他的组件之中。

Servlet3规范之前和Spring3.1之前:DispatcherServlet这样的Servlet会配置在web.xml文件中,这个文件会放到应用的WAR包里面。

Servlet3规范之后和Spring3.1之后:可以使用JavaDispatcherServlet配置在Servlet容器中,而不会再使用web.xml文件。

配置DispatcherServlet代码如下:

package spittr.config;

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

import spittr.web.WebConfig;

public class SpitterWebInitializer 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[] { "/" };

  }

}

配置DispatcherServlet代码分析:

AbstractAnnotationConfigDispatcherServletInitializer剖析

扩展AbstractAnnotationConfigDispatcherServletInitializer的任意类都会自动地配置DispatcherServletSpring应用上下文,Spring的应用上下文会位于应用程序的Servlet上下文之中。在Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果能发现的话,就会用他来配置Servlet容器。Spring提供了这个接口的实现,名为SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给他们来完成。Spring3.2引入了一个便利的WebApplicationInitializer基础实现,也就是AbstractAnnotationConfigDispatcherServletInitializerSpitterWebInitializer扩展了AbstractAnnotationConfigDispatcherServletInitializer,因此当部署到Servlet3.0容器中的时候,容器会自动发现他,并用它来配置Servlet上下文。

第一个方法getServletMappings()

他会将一个或多个路径映射到DispatcherServlet上,本例中映射的是“/”,他表示会是应用的默认Servlet。他会处理进入应用的所有请求。

两个应用上下文之间的关系

DispatcherServlet启动的时候,他会创建Spring应用上下文,并加载配置文件或配置类中所声明的beangetServletConfigClasses()方法中,我们要求DispatcherServlet加载应用上下文时,使用定义在WebConfig配置类中的bean

但是在Spring Web应用中,通常还会有另外一个应用上下文。另外的这个应用上下文是由ContextLoaderListener创建的。我们希望DispatcherServlet加载包含Web组件的bean,如控制器、视图解析器以及处理器映射,而ContextLoaderListener要加载应用中的其他bean。这些bean通常是驱动应用后端的中间层和数据层组件。

实际上,AbstractAnnotationConfigDispatcherServletInitializer会同时创建DispatcherServletContextLoaderListenergetServletConfigClasses()方法返回的带有@Configuration注解的类将会用来定义DispatcherServlet应用上下文中的BeangetRootConfigClasses()方法返回的带有@Configuration注解的类将会用来配置ContextLoaderListener创建的应用上下文中的bean

AbstractAnnotationConfigDispatcherServletInitializer来配置DispatcherServlet是传统web.xml方式的替代方案。只能部署到支持Servlet3.0的服务器中。

3、启用Spring MVC

XML配置:可以使用<mvc:annotation-driven>启动注解驱动的Spring MVC

Java配置:@EnableWebMvc注解类

package org.hhc.spittr.config;

import org.springframework.context.annotation.Configuration;

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

 

@Configuration

@EnableWebMvc

public class Webconfig {

}

这可以运行起来,他的确能够启动Spring MVC,但还有不少问题要解决:

没有配置视图解析器。Spring默认会使用BeanNameView-Resolver,这个视图解析器会查找ID与视图名称匹配的Bean,并且查找的bean要实现View接口,他以这样的方式来解析视图。

没有启动组件扫描。

WebConfig最小但可用Spring MVC配置

package org.hhc.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.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("org.hhc.spittr.web")

public class Webconfig extends WebMvcConfigurerAdapter {

@Bean

public ViewResolver viewResolver() {

InternalResourceViewResolver resolver = new InternalResourceViewResolver();

resolver.setPrefix("/WEB-INF/views/");

resolver.setSuffix(".jsp");

return resolver;

}

 

@Override

public void configureDefaultServletHandling(

DefaultServletHandlerConfigurer configurer) {

configurer.enable();

}

}

通过调用DefaultServletHandlerConfigurerenable()方法,我们要求DispatcherServlet将对静态资源的请求转发到Servlet容器中默认的Servlet上,而不是使用DispatcherServlet本身来处理此请求。

RootConfig配置比较简单,使用@ComponentScan注解,我们就有很多机会用非Web的组件来充实完善RootConfig

编写基本的控制器

1、编写控制器

package org.hhc.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("/")

public class HomeController {

  @RequestMapping(method =GET)

  public String home(Modelmodel) {

    return "home";

  }

}

@RequestMapping注解:value代表路径;method属性细化了可处理的HTTP方法。

@Controller注解:他基于@Component注解。

2、测试控制器

package org.hhc.spittr.web;

import org.junit.Test;

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

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;

public class HomeControllerTest {

@Test

public void testHomePage()throws Exception {

HomeController controller =new HomeController();

MockMvc mockMvc =standaloneSetup(controller).build();

mockMvc.perform(get("/")).andExpect(

view().name("home"));

}

}

3、定义类级别的请求处理

使用的还是@RequestMapping

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

@Controller

@RequestMapping("/spittles")

public class SpittleController {

private SpittleRepositoryrepository;

 

@Autowired

public SpittleController(SpittleRepositoryrepository) {

this.repository =repository;

}

 

/**

 * @param model:map的key根据对象类型推断确定:spittleList

 * @return

 */

 @RequestMapping(method=RequestMethod.GET)

 public Stringspittles(Model model) {

 model.addAttribute(repository.findSpittles(Long.MAX_VALUE, 20));

 return "spittles";

 }

 

/**

 * @param model:指定key

 * @return

 */

@RequestMapping(method = RequestMethod.GET)

public Stringspittles(Model model) {

model.addAttribute("spittleList",

repository.findSpittles(Long.MAX_VALUE, 20));

return "spittles";

}

 

/**

 * @param model:如果希望使用非Spring类型的话,可以用Map来代替Model

 * @return

 */

@RequestMapping(method = RequestMethod.GET)

public String spittles(Map model) {

model.put("spittleList",

repository.findSpittles(Long.MAX_VALUE, 20));

return "spittles";

}

 

/**

 * 处理器方法像这样返回对象或集合时,这个值会放到模型中,模型的key会根据类型推断得出

 * 逻辑视图的名称将会根据请求路径推断得出。本例中视图名为:spittles

 * @return

 */

@RequestMapping(method = RequestMethod.GET)

public List<Spittle> spittles() {

return repository.findSpittles(Long.MAX_VALUE, 20);

}

}

上面选择哪种方式来编写spittles()方法,所达到的结果都是相同的。模型中会存储一个spittle列表,keyspittleList,然后这个列表会发送到名为spittles的视图中。按照我们配置的视图解析器,视图的JSP将会是”WEB-INF/views/spittles.jsp”。

 

接受请求的输入

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

查询参数

表单参数

路径变量

1、处理查询参数

@RequestMapping(method = RequestMethod.GET)

public List<Spittle> spittles(@RequestParam(value="max", defaultValue="20")long max,

@RequestParam(value="count", defaultValue="20")int count) {

return repository.findSpittles(max,count);

}

2、通过路径参数接受输入

@RequestMapping(value="/{spittleId}", method=RequestMethod.GET)

public String showSpittle(@PathVariable("spittleId")long spittleId, Model model){

return "spittle";

}

当占位符的名称与方法的参数名相同时,才可以简写:

@RequestMapping(value="/{spittleId}", method=RequestMethod.GET)

public String showSpittle(@PathVariable long spittleId, Model model) {

return "spittle";

}

处理表单

Web应用允许用户填充表单并将数据提交回应用中,通过这种方式实现与用户的交互。

使用表单分为两个方面:展现表单和处理用户通过表单提交的数据。

1、校验表单

  @RequestMapping(value="/register", method=POST)

  public String processRegistration(

      @Valid Spitterspitter,

      Errors errors) {

    if (errors.hasErrors()) {

      return "registerForm";

    }

    

    spitterRepository.save(spitter);

    return "redirect:/spitter/" +spitter.getUsername();

  }

 

public class Spitter {

  private Longid;

  @NotNull

  @Size(min=5, max=16)

  private Stringusername;

  @NotNull

  @Size(min=5, max=25)

  private Stringpassword;

  @NotNull

  @Size(min=2, max=30)

  private StringfirstName;

  @NotNull

  @Size(min=2, max=30)

  private StringlastName;

  @NotNull

  @Email

  private Stringemail;

}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值