Spring实战-读书笔记(五)-构建Spring Web应用程序

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/fly_zxy/article/details/78720580

构建Spring Web应用程序

跟踪Spring MVC的请求

使用Spring构建的Web程序中,请求最先接触到的是Spring中的DispatcherServlet。从图中可以看见DispatcherServlet相当一个调度者,所有的核心环节最终都要汇总到DispatcherServlet中。

对图流程的概要说明:
  1. DispatcherServlet其实是一个Servlet,用于拦截客户端的所有请求。
  2. 拦截到客户端的请求会调用处理映射器,找到处理请求对应的控制器(controller)。
  3. 控制器会根据请求参数进行相关的业务处理,一般情况下控制器只负责解析请求中的参数,然后调用其它组件(如业务处理组件)并传递参数来处理请求。控制器在完成逻辑处理后,通常会产生一些信息,这些信息需要返回给用户并在浏览器上显示。这些信息被称为模型(model)。不过仅仅给用户返回原始的信息是不够的——这些信息需要以用户友好的方式进行格式化,一般会是HTML。所以,信息需要发送给一个视图(view),通常会是JSP。
  4. 在处理完成客户的请求后会返回逻辑视图的名称和模型。
  5. DispatcherServlet会调用视图解析器,将逻辑视图名称解析成物理视图名称(也就是视图在项目中的路径)。
  6. DispatcherServlet会调用视图实现,渲染视图,将模型交付给视图。
  7. 通过响应对象response将渲染完成的视图传递给客户端。这样就完成了响应。

构建Spring Web应用程序

书中给出的Spittr应用有点抽象,理解上不太符合中国程序员的国情。这里用一个简单的博客发布系统来替代Spittr应用来了解如何构建Spring Web应用程序。博客系统主要实现博客文章列表查看、博客文章查询和博客发布功能。Blog完全使用java类来配置Spring web,这也是个人对这种配置方式的一次体验。以下是Blog应用程序的截图程序结构截图:


配置DispatcherServlet

我们通常会在web.xml文件中配置DispatcherServlet。但是,借助于Servlet 3.0规范和Spring3.1功能的增强,这种方式已经不是唯一方式。我们将使用java类来配置DispatcherServlet。BlogWebAppInitializer 继承 AbstractAnnotationConfigDispatcherServletInitializer,这个类已经隐身的帮我配置了DispatcherServlet。我们找到传统的web.xml文件方式配置容器的方式和java类配置容器的方式进行对比,就会发现在java类配置方式中能找到和web.xml文件方式一一对应的地方,在接下来的代码中我们会看到对应的地方。Blog相关配置类如下:
BlogWebAppInitializer
package blog.config;

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

/**
 * java类配置Spring
 * 
 * @author Administrator
 *
 */
public class BlogWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

	/**
	 * 配置WebApplicationContent上下文
	 */
	@Override
	protected Class<?>[] getRootConfigClasses() {
		// TODO Auto-generated method stub
		return new Class<?>[] { RootConfig.class };
	}
	
	/**
	 * 配置WebApplicationContent上下文
	 */
	@Override
	protected Class<?>[] getServletConfigClasses() {
		// TODO Auto-generated method stub
		return new Class<?>[] { WebConfig.class };
	}

	@Override
	protected String[] getServletMappings() {
		// 配置DispatcherServlet拦截路径
		return new String[] { "/" };
	}

}
RootConfig类
package blog.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.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

/**
 * 用于初始化Sring的ApplicationContent(整个应用的上下文)
 * ApplicationContent一般包含后端相关的bean和配置,例如业务层和持久化层Bean
 * @author Administrator
 *
 */
@Configuration
@Import(DataConfig.class)
@ComponentScan(basePackages = { "blog" }, 
excludeFilters = { //不扫描blog.web //不扫描类上标记EnableWebMvc注解(也就是不扫描WebConfig.java)
		@Filter(type = FilterType.REGEX, pattern = "blog\\.web"),
		@Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class) 
	}
)
public class RootConfig {

}
DataConfig类
package blog.config;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;

/**
 * 配置访问数据相关的Bean
 * @author Administrator
 *
 */
@Configuration
public class DataConfig {

  @Bean
  public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)//设置数据库类型为H2内存数据库
            .addScript("blog.sql")			//数据库的建表脚步以及数据初始化脚步
            .build();
  }
  
  @Bean
  public JdbcOperations jdbcTemplate(DataSource dataSource) {
    return new JdbcTemplate(dataSource);
  }

}
RootConfig类
package blog.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.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

/**
 * 用于初始化Sring的ApplicationContent(整个应用的上下文)
 * ApplicationContent一般包含后端相关的bean和配置,例如业务层和持久化层Bean
 * @author Administrator
 *
 */
@Configuration
@Import(DataConfig.class)
@ComponentScan(basePackages = { "blog" }, 
excludeFilters = { //不扫描blog.web //不扫描类上标记EnableWebMvc注解(也就是不扫描WebConfig.java)
		@Filter(type = FilterType.REGEX, pattern = "blog\\.web"),
		@Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class) 
	}
)
public class RootConfig {

}

AbstractAnnotationConfigDispatcherServletInitializer剖析


我们发现BlogWebAppInitializer继承了AbstractAnnotationConfigDispatcherServletInitializer类。在Servlet 3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果能发现的话,就会用它来配置Servlet容器。

Spring提供了这个接口的实现,名为SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。

Spring 3.2引入了一个便利的WebApplicationInitializer基础实现,也就是AbstractAnnotationConfigDispatcherServletInitializer。因为我们的扩展了AbstractAnnotationConfig DispatcherServletInitializer(同时也就实现了WebApplicationInitializer),因此当部署到Servlet 3.0容器中的时候,容器会自动发现它,并用它来配置Servlet上下文。

两个应用上下文

当DispatcherServlet的时候,Spring会创建两个应用上下文。一个是通过getServletConfigClasses()方法创建,这个应用上下文是 Spring的WebApplicationContent(Web应用上下文),一般包含前端Bean和配置,例如前段的Controller类。另一个是通过getRootConfigClasses()方法创建,这个应用上下文是 Sring的ApplicationContent(整个应用的上下文)一般包含后端相关的bean和配置,例如业务层和持久化层Bean。

编写基本的控制器

BlogManager类中的Controller实现了查询全部博客文章、根据条件查询博客文章和博客发布功能。我们从这里可以看到主要涉及到@Controller和@RequestMapping注解。
@Controller:这个注解标记在类上类,表示该类是一个控制器。@Controller、@Service、@Repository注解都使用@Component注解标记,他们同@Component都是表示标记的类是一个Spring组件,使用@Controller、@Service、@Repository标记在类上只不过是语义更明确。@Service表示一个业务组件,@Repository表示一个持久化组件,@Repository更笼统些,就是表示一个组件。
@RequestMapping:这个注解标记在类和方法上。标记在方法用于配置方法处理的请求地址,其中value属性配置地址,method属性配置请求方式。标记在类似用于配置value属性的前缀。类如在类上标记@RequestMapping("/blogManager"),query()方法标记@RequestMapping(value = "/blogList", method = GET),那么query()方法将处理get方式请求的/blogManager/blogList地址。
package blog.web;

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

import java.io.UnsupportedEncodingException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import blog.data.AboutBlogRepository;
import blog.model.AboutBlogModel;

@Controller
@RequestMapping("/blogManager")
public class BlogManager {

	private AboutBlogRepository aboutBlogReq;
	private static final Log log = LogFactory.getLog(BlogManager.class);

	@Autowired
	public BlogManager(AboutBlogRepository aboutBlogReq) {
		this.aboutBlogReq = aboutBlogReq;
	}
	
	/**
	 * 查询全部博客文章
	 * @param model
	 * @return
	 */
	@RequestMapping(value = "/blogList", method = GET)
	public String query(Model model) {
		// 将模型传递到视图有A1,A2,A3三种方式
		// A1:key-value表示一个模型
		model.addAttribute("blogList", aboutBlogReq.query());
		// A2:传入模型数据,key默认为模型数据的类型,这里query返回类型是List<AboutBlogModel>,key名称为aboutBlogModelList
		// model.addAttribute(aboutBlogReq.query());
		return "blogList";
	}

	/* //A3:使用Map来代替Model
	 * @RequestMapping(value = "/blogList", method = GET) 
	 * public String query(Map map) { 
	 * 		map.put("blogList",aboutBlogReq.query());
	 * 		return "blogList"; 
	 * }
	 */
	
	/**
	 * 根据参数查询博客文章
	 * 使用Spring MVC接受查询参数
	 * @param title
	 * @param context
	 * @param model
	 * @return
	 */
	@RequestMapping(value = "/blogListByParam", method = GET)
	public String queryByParam(@RequestParam(value = "title", defaultValue = "") String title,
			@RequestParam(value = "context", defaultValue = "") String context, Model model) {
		// 请求地址没有带有title,context查询参数,将会报404错误,指定defaultValue属性即可解决
		model.addAttribute("blogList", aboutBlogReq.query(title, context));
		return "queryByParam";
	}

	/**
	 * 根据blogId查询博客文章信息
	 * 使用Spring MVC处理路径变量参数
	 * @param blogId
	 * @param model
	 * @return
	 */
	@RequestMapping(value = "/queryOne/{blogId}", method = GET)
	public String queryOne(@PathVariable() String blogId, Model model) {
		// 占位符{blogId}和形参blogId名称相同时可以省略@PathVariable注解value参数
		// 否则要像这样@PathVariable("blogId")显示声明占位符关联的形参
		model.addAttribute("aboutBlogInfo", aboutBlogReq.queryOne(blogId));
		return "queryOne";
	}
	
	/**
	 * 博客发布视图
	 * @param request
	 * @return
	 */
	@RequestMapping(value = "/wirte", method = GET)
	public String showWirteForm(HttpServletRequest request) {
		return "showWirteForm";
	}

	/**
	 * 博客发布操作
	 * 使用Spring MVC处理表单参数
	 * @param aboutBlogModel
	 * @param errors
	 * @param request
	 * @return
	 */
	@RequestMapping(value = "/wirte", method = POST)
	public String wirte(@Valid AboutBlogModel aboutBlogModel,Errors errors, HttpServletRequest request) {
		
		if(errors.hasErrors()) {
			return "showWirteForm";
		}
		
		String s="";
		try {
			s = new String(request.getParameter("name").getBytes("ISO-8859-1"),"UTF-8");
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		String encoding = request.getCharacterEncoding();
		log.debug("encoding:" + encoding+",name:"+s);
		String id = aboutBlogReq.add(aboutBlogModel);

		// 这里使用重定向,防止用户重复提交(比如多次点击刷新按钮)
		return "redirect:/blogManager/queryOne/" + id;
	}
}

接受请求的输入

Spring MVC允许以多种方式将客户端中的数据传送到控制器的处理器方法中,包括:
  1. 查询参数(Query Parameter)。
  2. 表单参数(Form Parameter)。
  3. 路径变量(Path Variable)。
查询参数:通过URL问号(?)之后的部分传递参数.比如我们通过文章标题和文章内容来查询博客文章,title和context查询参数就可以通过URL来传递。如下图:


表单参数:是通过HTML form标签提交的请求参数。比如博客发布时候需要录入文章标题、文章关键字和文章内容等信息。这些信息参数都会通过表单参数提交给Controller。


路径变量:通过URL路径传递参数。例如通过博客文章ID查询文章的信息,如果使用路径变量,请求url是这样: http://localhost:8080/blog/blogManager/queryOne/6f77e01b-7c84-4b9f-8b46-d674a93532df,6f77e01b-7c84-4b9f-8b46-d674a93532df为博客ID。

校验表单

我们在发布博客的时候,如果输入的关键信息为空可以提示给用户,可能还需要校验关键字必须是按逗号(,)分隔、文章内容必须在10到3000个字符等等。这些校验操作可以使用Spring对Java校验API(Java Validation API,又称JSR-303)的支持。从Spring 3.0开始,在Spring MVC中提供了对Java校验API的支持。在Spring MVC中要使用Java校验API的话,并不需要什么额外的配置。只要保证在类路径下包含这个Java API的实现即可,比如Hibernate Validator。Java校验API定义了多个注解,这些注解可以放到属性上,从而限制这些属性的值。所有的注解都位于javax.validation.constraints包中。一下列出了这些校验注解。
@AssertFalse 所注解的元素必须是Boolean类型,并且值为false
@AssertTrue 所注解的元素必须是Boolean类型,并且值为true
@DecimalMax 所注解的元素必须是数字,并且它的值要小于或等于给定的BigDecimalString值
@DecimalMin 所注解的元素必须是数字,并且它的值要大于或等于给定的BigDecimalString值
@Digits 所注解的元素必须是数字,并且它的值必须有指定的位数
@Future 所注解的元素的值必须是一个将来的日期
@Max 所注解的元素必须是数字,并且它的值要小于或等于给定的值
@Min 所注解的元素必须是数字,并且它的值要大于或等于给定的值
@NotNull 所注解元素的值必须不能为null
@Null 所注解元素的值必须为null
@Past 所注解的元素的值必须是一个已过去的日期
@Pattern 所注解的元素的值必须匹配给定的正则表达式
@Size 所注解的元素的值必须是String、集合或数组,并且它的长度要符合给定的范围
AboutBlogModel类
package blog.model;

import java.util.Date;

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

public class AboutBlogModel {
	private String id;
	@NotNull
	private String keyword;
	@NotNull
	private String description;
	@NotNull
	private String name;
	@NotNull
	private String title;
	@NotNull
	@Size(min=10,max=300)
	private String context;
	private Date wireteDate;

	public String getId() {
		return id;
	}

	public Date getWireteDate() {
		return wireteDate;
	}

	public void setWireteDate(Date wireteDate) {
		this.wireteDate = wireteDate;
	}

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

	public String getKeyword() {
		return keyword;
	}

	public void setKeyword(String keyword) {
		this.keyword = keyword;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getContext() {
		return context;
	}

	public void setContext(String context) {
		this.context = context;
	}

}

Blog项目下载地址: http://download.csdn.net/download/fly_zxy/10148466。到总结完这章内容为止,Blog还有个很大的缺陷,就是博客发布信息中有中文会乱码,在使用web.xml配置Servlet容器时将org.springframework.web.filter.CharacterEncodingFilter作为filter配置在web.xml中即可解决中文乱码问题。使用java类配置Servlet容器还没有找到思路将CharacterEncodingFilter用java类的方式配置到Servlet容器中。随着后续的阅读应该可以找到配置方法。


猜你喜欢

转载自blog.csdn.net/fly_zxy/article/details/78720580