第5章 构建Spring Web应用程序
1、SpringMVC请求:
请求---->DispatcherServlet(请求的第一站,前端控制器,将请求委托给其他应用程序来处理)
DispatcherServlet---->相应的控制器(通过查询处理器映射器来查询到底传递给那个控制器)
控制器--(模型和视图名)-->DispatcherServlet---->视图解析器--(视图)-->相应
2、配置DispatcherServlet
1:传统的方法是讲DispatcherServlet配置在web.xml中。
2:这里我们将DispatcherServlet配置在Servlet中。
这种方法是传统的xml配置的替代,但是需要部署在支持Spring3.0的Web容器中。(Tomcat7或者更高)
/** DispatcherServlet启动会引起Spring应用上下文的创建,应用上下文加载Web组件的bean(控制器、视图解析器、处理器) ContextLoaderListener的创建会加载其他bean(后端中间层、数据层的bean) AbstractAnnotationConfigDispatcherServletInitializer会同时创建 DispatcherServlet和ContextLoaderListener。 getServletClasses()用于指定Spring应用上下文的bean的文件(包含@Configuration注解的java类) getRootConfigClasses()用于指定ContextLoaderListener创建的上下文加载的bean的配置文件(包含@Configuration注解的java类) */ public class SpitterWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{ @Override protected String[] getServletMappings(){ return new String[]{"/"}; //将DispatcherServlet映射到“/”,将会处理应用的所有请求。 } //用来指定ContextLoaderListener上下文中需要加载的bean的配置文件。 @Override protected Class<?>[] getRootConfigClasses(){ return new Class<?>[] {RootConfig.class}; } //DispatcherServlet启动的时候会创建Spring应用上下文。并加载bean。此处要求加载定义在WebConfig中的bean。 @Override protected Class<?>[] getServletConfigClasses(){ return new Class<?>[] {WebConfig.class}; } }
3、启用Spring MVC
1:使用xml配置:
<mvc:annotation-driven>
2:使用java文件配置(@EnableWebMVC注解)
注意如果只加@EnableWebMVC注解,这个配置文件不会配置视图解析器、没有启动组件扫描、等功能缺陷。
解决上述问题的配置:
@Configuration @EnableWebMvc //启用SpringMVC @ComponentScan("spitter.web") public class WebConfig extends WebMvcConfigurerAdapter{ @Bean public ViewResolver viewResolver(){ //配置jsp视图解析器 InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB-INF/views/"); resolver.setSuffix(".jsp"); resolver.setExposeContextBeansAsAttributes(true); return resolver; } //配置静态资源处理(要求DispatcherServlet对静态资源的请求转发到Servlet容器中默认的Servlet上,而不是DispatcherServlet类来处理) @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer){ configurer.enable(); } }
Spring应用上下文的已近随着DispatcherServlet的创建而创建了。接下来配置RootConfig。
@Configuration @ComponentScan(basePackage={"spitter"},excludeFilters = {@Filter(type=FilterType.ANNOTATION,value=EnableWebMvc.class)}) public class RootConfig{}
4、编写基本的控制器
例如:
@Controller //声明控制器 public class HomeController{ @RequestMapping(value="/",method = RequestMethod.GET) //匹配“/”的GET请求 public String home(){ return "home"; //返回视图名(结合视图解析器:视图为:/WEB-INF/views/home.jsp) } }
5、定义类级别的请求处理
例如:
@Controller @RequestMapping("/") public class HomeController{ @RequestMapping(method = RequestMethod.GET) //这个方法匹配的依然是"/"的GET请求。只不过将匹配路径的"/"放到了类级别。 public String home(){ return "home"; } }
//匹配多个路径
@controller @RequestMapping("/","/homepage") //可以同时匹配"/"和"/homepage" public class HomeController{...}
6、传递模型数据到视图中(返回视图的同时给视图传递模型)
1:首先我们定义一个用于访问数据的Repository:
public interface SpittleRepository{ List<Spittle> findSpittles(long max, int count); }
2:编写控制器
@Controller @RequestMapping("/spittles") public class SpittleController{ private SpittleRepository spittleRepository; @Autowired public SpittleController(SpittleRepository spittleRepository){ //构造器中传入用于访问数据的属性 this.spittleRepository = spittleRepository; } @RequestMapping(method = RequestMethod.GET) public String spittles(Model model){ //Model其实就是一个Map,它会被传递给视图。 model.addAttribute(spittleRepository.findSpittles(Long.MAX , 20)); //没有写key,会根据类型自动判断,这里判断为spittleList。 //也可以明确key //model.addAttribute("spittleList",spittleRepository.findSpittles(Long.MAX , 20)); return "spittles"; } }
//使用Map类型封装数据:
例如:
@RequestMapping(method = RequestMethod.GET) public String spittles(Map model){ model.put("spittleList",spittleRepository.findSpittles(Long.MAX , 20)); return "spittles"; }
//不写key不写返回视图的方法
@RequestMapping(method = RequestMethod.GET) public List<Spittle> spittles(){ return spittleList",spittleRepository.findSpittles(Long.MAX , 20); } //key:根据类型推断为:spittleList //视图名:因为这个方法匹配"/spittles",所以视图名为spittles。
7、接收请求的输入
SpringMVC从客户端中获取属性的方法:
查询参数、表单参数、路径参数。
7.1:处理查询参数:
//实现翻页功能,需要传入两个值,max、count。
@RequestMapping(method = RequestMethod.GET) public List<Spittle> spittles(@RequestParam("max") long max , @RequestParam("count") int count){ return spittleRepository.findSpittles(max , count); }
//设点参数的默认值
@RequestMapping(method = RequestMethod.GET) public List<Spittle> spittles( @RequestParam(value = "max" , defaultValue = MAX_LONG_AS_STRING) long max , @RequestParam(value = "count" , defaultValue = 10) int count ){ return spittleRepository.findSpittles(max , count); }
7.2:通过路径参数接收输入:
场景:我们需要一个名为SpittleID的参数查询指定的某一条记录:
/** 通过参数查询: 下面这个类中的showPaittle方法将配“/spittles/show?spittle_id”的GET请求。(/spittles/show?spittle_id=123445) */ @Controller @RequestMapping("/spittles") public class SpittleController{ @RequestMapping(value = "/show" , method = RequestMethod.GET) public String showPaittle( @RequestParam("spittle_id" long spittleId), Model model ){ model.addAttribute("key" , spittleRepository.findSpittles(spittleId)); return "spittle"; } }
/** 通过URL路径标识 下面的方法可以匹配“/spittles/spittleId”URL路径(/spittles/234423) */ @Controller @RequestMapping("/spittles") public class SpittleController{ @RequestMapping(value = "/{spittleId}" , method = RequestMethod.GET) //{}为占位符 public String spittle(@PathVariable("spittleId") Long spittleId , Model model){ //无论占位符部分的值是什么都会传到处理方法中。 model.addAttribute("key" , spittleRepository.findSpittles(spittleId)); return "spittle"; } } //@PathVariable("spittleId" Long spittleId , Model model)由于方法参数名和占位符一直所以可以胜率为: //@PathVariable(Long spittleId , Model model) //如果@PathVariable中的Value属性没有的话,会假设方法参数名称和路径占位符名称相同。
8、处理表单
例如表单:
<html> <head> <title>Spittr</title> </head> <body> <form> First Name: <input type = "text" name firstName="firstName" /> Last Name : <input type = "text" name LastName = "lastName" /> UserName : <input type = "text" name UserName = "userName" /> PassWord : <input type = "password" name = "password" /> <input type = "submit" value = "Register"> </form> </body> </html>
控制器:
@Controller @RequestMapping("/spitter") public class SpitterController{ private SpitterRepository spitterRepository; @Autowired public SpitterController(SpitterRepository spitterRepository){ //SpitterRepository是DAO层的类 this.spitterRepository = spitterRepository; } @RequestMapping(value = "/register" , method = GET) //这个请求用于跳转到注册页面 public String showRegisterFrom(){ return "register" } @RequestMapping(value = "/register" , method = POST) public String processRegister(Spitter spitter){ spitterRepository.save(spitter); //Spitter类中的属性对应了表单中的属性。按照名称匹配。 return "redirect:/spitter/"+spitter.getUserName; //重定向到别的页面。(redirect:会被解析为重定向。forward:是跳转) } }
处理重定向的控制器:
@RequestMapping(value ="/{username}" , method = GET) public String ShowSpitterProfile(@PathVariable String userName , Model model){ Spitter spitter = spitterRepository.findByUserName(userName); model.addAttribute(spitter); return "profile"; }
表单验证:
(表单中,填写的数据为空、数据太长等异常情况需要解决)
1、一种比较初级的方法是,在控制其中添加一些校验代码。(校验代码污染了控制器)
2、利用Spring对java校验API(JSR-303)的支持。(只需要在类路径下包含实现了Java校验API的类即可)
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、集合或数组,并且它的长度要符合给定的范围
在我们例子中只需要在Spittle类的属性上添加@NotNull和@Size注解即可
1:添加校验注解
public class Spittle{ private Long id; @NotNull @Size(min = 5 , max = 6) private String userName; @NotNull @Size(min = 5 , max = 6) private String passWord; @NotNull @Size(min = 5 , max = 6) private String firstName; @NotNull @Size(min = 5 , max = 6) private String lastName; }
2:启用校验功能(修改接收数据的控制器)
@RequestMapping(value = "/register" , method = POST) public String processRegister(@Valid Spitter spitter , Errors errors){ //校验Spitter输入 if(errors.hasErrors){ return "registerForm" //校验不通过,返回到注册页面 } spitterRepository.save(spitter); return "redirect:/spitter/"+spitter.getUserName; }