Spring学习--构建Spring Web应用程序
Posted 灰色天空_graySky
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring学习--构建Spring Web应用程序相关的知识,希望对你有一定的参考价值。
一.Spring MVC起步
看过猫和老鼠的小伙伴都可以想象Tom猫所制作的捕鼠器:它的目标 是发送一个小钢球,让它经过一系列稀奇古怪的装置,最后触发捕鼠 器。小钢球穿过各种复杂的配件,从一个斜坡上滚下来,被跷跷板弹起,绕过一个微型摩天轮,然后被橡胶靴从桶中踢出去。经过这些后,小钢球会对那只可怜又无辜的老鼠进行捕获。而Spring MVC框架与捕鼠器有些类似。Spring将请求在调度Servlet、处理器映射(handler mapping)、控制器以及视图解析器(view resolver)之间移动.
--跟踪Spring MVC的请求
每当用户在Web浏览器中点击链接或提交表单的时候,请求就开始工作了。对请求的工作描述就像是快递投送员。与快递员一样,请求会将信息从一个地方带到另一个地方。但是请求是一个十分繁忙的家伙。从离开浏览器开始到获取响应返回,它会经历好多站,在每站都会留下一些信息同时也会带上其他信息:
--在请求离开浏览器时①,会带有用户所请求内容的信息,至少会包含请求的URL.但是还可能带有其他的信息,例如用户提交的表单信息.请求路程的第一站是Spring的DispatcherServlet.SpringMVC所有的请求都回通过一个前端控制器(front controller)Servlet.前端控制器是常用的Web应用程序模式,在这里一个单实例的的Servlet将请求委托给应用程序的其他组件来执行实际的处理.在Spring MVC之中,DispatcherServlet就是前端控制器.DispatcherServlet的任务是将请求发送给Spring MVC控制器(controller).控制器是一个用于处理请求的Spring组件.在典型的应用程序中中可能会有多个控制器,DispatcherServlet需要知道应该将请求发送给哪个控制器。所以DispatcherServlet以会查询一个或多个处理器映射(handler mapping)②来确定请求的下一站在哪里。
处理器映射会根据请求所携带的URL信息来进行决策。一旦选择了合适的控制器,DispatcherServlet会将请求发送给选中的控制器③。到了控制器,请求会卸下其负载(用户提交的信 息)并耐心等待控制器处理这些信息。(实际上,设计良好的控制器 本身只处理很少甚至不处理工作,而是将业务逻辑委托给一个或多个 服务对象进行处理。)控制器在完成逻辑处理后,通常会产生一些信息,这些信息需要返回 给用户并在浏览器上显示。这些信息被称为模型(model)。不过仅 仅给用户返回原始的信息是不够的——这些信息需要以用户友好的方 式进行格式化,一般会是html。所以,信息需要发送给一个视图 (view),通常会是JSP。控制器所做的最后一件事就是将模型数据打包,并且标示出用于渲染 输出的视图名。它接下来会将请求连同模型和视图名发送回 DispatcherServlet ④。
这样,控制器就不会与特定的视图相耦合,传递给 DispatcherServlet的视图名并不直接表示某个特定的JSP。实际 上,它甚至并不能确定视图就是JSP。相反,它仅仅传递了一个逻辑 名称,这个名字将会用来查找产生结果的真正视 图。DispatcherServlet将会使用视图解析器(view resolver)⑤ 来将逻辑视图名匹配为一个特定的视图实现,它可能是也可能不是 JSP。既然DispatcherServlet已经知道由哪个视图渲染结果,那请求的任务基本上也就完成了。它的最后一站是视图的实现(可能是 JSP)⑥ ,在这里它交付模型数据。请求的任务就完成了。视图将使用 模型数据渲染输出,这个输出会通过响应对象传递给客户端(不会像听上去那样硬编码) ⑦。
--请求要经过很多的步骤,最终才能形成返回给客户端的响 应。大多数的步骤都是在Spring框架内部完成的.我们来进行Spring MVC的基础搭配;
--配置DispatcherServlet
DispatcherServlet是Spring MVC的核心。在这里请求会第一次 接触到框架,它要负责将请求路由到其他的组件之中。 按照传统的方式,像DispatcherServlet这样的Servlet会配置在 web.xml文件中,这个文件会放到应用的WAR包里面。这是配 置DispatcherServlet的方法之一,但是,借助于Servlet 3规范和 Spring 3.1的功能增强,这种方式已经不是唯一的方案了,我们可以使用Java将DispatcherServlet配置在Servlet容器中,而不会再使用web.xml文件。如下的程序清单展示了所需的Java类:
1 package config;
2
3 import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
4
5 /**
6 * @author : S K Y
7 * @version :0.0.1
8 */
9 public class SpringWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
10 @Override
11 protected Class<?>[] getRootConfigClasses() {
12 return new Class<?>[]{RootConfig.class};
13 }
14
15 @Override
16 protected Class<?>[] getServletConfigClasses() { //指定配置类
17 return new Class<?>[]{WebConfig.class};
18 }
19
20 @Override
21 protected String[] getServletMappings() {
22 return new String[]{"/"}; //将DispatcherServlet映射到"/"
23 }
24 }
--我们可能只需要知道扩展AbstractAnnotationConfigDispatcherServletInitializer的任意类都会自动地配置Dispatcher-Servlet和Spring应用上下文,Spring的应用上下文会位于应用程序的Servlet上下文之中。
--AbstractAnnotationConfigDispatcherServletInitializer剖析
在Servlet 3.0环境 中,容器会在类路径中查找实现 javax.servlet.ServletContainerInitializer接口的类, 如果能发现的话,就会用它来配置Servlet容器。 Spring提供了这个接口的实现,名 为SpringServletContainerInitializer,这个类反过来又会 查找实现WebApplicationInitializer的类并将配置的任务交给 它们来完成。Spring 3.2引入了一个便利的 WebApplicationInitializer基础实现,也就 是AbstractAnnotationConfigDispatcherServletInitializer 因为我们的SpringWebAppInitializer扩展了 AbstractAnnotationConfig DispatcherServletInitializer(同时也就实现了 WebApplicationInitializer),因此当部署到Servlet 3.0容器中的时候,容器会自动发现它,并用它来配置Servlet上下文。 尽管它的名字很长,但 是AbstractAnnotationConfigDispatcherServlet- Initializer使用起来很简便。
--我们重写了AbstractAnnotationConfigDispatcherServletInitializer中的三个方法:
1.getServletMappings():它会将一个或多个路径映 射到DispatcherServlet上。在本例中,它映射的是“/”,这表示 它会是应用的默认Servlet。它会处理进入应用的所有请求。
--为了理解其他的两个方法,我们首先要理解DispatcherServlet 和一个Servlet监听器(也就是ContextLoaderListener)的关系:
当DispatcherServlet启动的时候,它会创建Spring应用上下文,并加载配置文件或配置类中所声明的bean。在SpringWebAppInitializer.getServletConfigClasses()方法中,我们要求DispatcherServlet加载应用上下文时,使用定义在WebConfig配置类(使用Java配置)中的bean。但是在Spring Web应用中,通常还会有另外一个应用上下文。另外的这个应用上下文是由ContextLoaderListener创建的。我们希望DispatcherServlet加载包含Web组件的bean,如控制 器、视图解析器以及处理器映射,而ContextLoaderListener要 加载应用中的其他bean。这些bean通常是驱动应用后端的中间层和数 据层组件。
实际上,AbstractAnnotationConfigDispatcherServletInitializer 会同时创建DispatcherServlet和 ContextLoaderListener。GetServlet-ConfigClasses() 方法返回的带有@Configuration注解的类将会用来定 义DispatcherServlet应用上下文中的 bean。getRootConfigClasses()方法返回的带 有@Configuration注解的类将会用来配置ContextLoaderListener创建的应用上下文中的bean。
在本例中,根配置定义在RootConfig中,DispatcherServlet 的配置声明在WebConfig中(目前还没有进行实际定义)。如果按照这种方式配置DispatcherServlet,而不是使用web.xml 的话,那唯一问题在于它只能部署到支持Servlet 3.0的服务器中才能 正常工作,如Tomcat 7或更高版本。Servlet 3.0规范在2009年12月份就 发布了,因此很有可能你会将应用部署到支持Servlet 3.0的Servlet容 器之中。
--启用Spring MVC
我们有多种方式来配置DispatcherServlet,与之类似,启用 Spring MVC组件的方法也不仅一种。以前,Spring是使用XML进行配 置的,你可以使用<mvc:annotation-driven>启用注解驱动的 Spring MVC。当然我们也可以基于JavaConfig进行配置:
1 package config;
2
3 import org.springframework.context.annotation.Configuration;
4 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
5
6 /**
7 * @author : S K Y
8 * @version :0.0.1
9 */
10 @Configuration
11 @EnableWebMvc
12 public class WebConfig {
13 }
--这可以运行起来,它的确能够启用Spring MVC,但还有不少问题要解决:
1.没有配置视图解析器。如果这样的话,Spring默认会使用BeanNameView-Resolver,这个视图解析器会查找ID与视图名称匹配的bean,并且查找的bean要实现View接口,它以这样 的方式来解析视图。
2.没有启用组件扫描。这样的结果就是,Spring只能找到显式声明 在配置类中的控制器。
3.这样配置的话,DispatcherServlet会映射为应用的默认 Servlet,所以它会处理所有的请求,包括对静态资源的请求,如图片和样式表(在大多数情况下,这可能并不是你想要的效果)。
--我们需要在WebConfig这个最小的Spring MVC配置上再加一 些内容,从而让它变得真正有用:
1 package config;
2
3 import org.springframework.context.annotation.Bean;
4 import org.springframework.context.annotation.ComponentScan;
5 import org.springframework.context.annotation.Configuration;
6 import org.springframework.web.servlet.ViewResolver;
7 import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
8 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
9 import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
10 import org.springframework.web.servlet.view.InternalResourceViewResolver;
11
12 /**
13 * @author : S K Y
14 * @version :0.0.1
15 */
16 @Configuration
17 @EnableWebMvc //启用Spring MVC
18 @ComponentScan(basePackages = {"web"}) //启用组件扫描
19 public class WebConfig extends WebMvcConfigurerAdapter {
20 @Bean
21 public ViewResolver viewResolver() {
22 //配置JSP视图解析器
23 InternalResourceViewResolver resolver = new InternalResourceViewResolver();
24 resolver.setPrefix("/WEB-INF/views/"); //设置jsp所在的目录
25 resolver.setSuffix(".jsp"); //设置后缀名称
26 resolver.setExposeContextBeansAsAttributes(true);
27 return resolver;
28 }
29
30 @Override
31 public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
32 configurer.enable(); //配置静态资源的处理
33 }
34 }
--在WebConfig中第一件需要注意的事情是WebConfig现在添加了 @ComponentScan注解,因此将会扫描web包来查找组件。 我们所编写的控制器将会带有@Controller注 解,这会使其成为组件扫描时的候选bean。因此,我们不需要在配置类中显式声明任何的控制器。我们重写了configureDefaultServletHandling()方法使得DispatcherServlet将对静态资源的请求转发到Servlet容器中默认的Servlet上,而不是使用DispatcherServlet本身来处理此类请求。
--配置RootConfig
1 package com.sky.config;
2
3 import org.springframework.context.annotation.ComponentScan;
4 import org.springframework.context.annotation.Configuration;
5 import org.springframework.context.annotation.FilterType;
6 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
7
8 /**
9 * @author : S K Y
10 * @version :0.0.1
11 */
12 @Configuration
13 //在进行Web编程的时候还是得规范化包名,因此web包也在com.sky下
14 @ComponentScan(basePackages = {"com.sky"},
15 excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)})
16 public class RootConfig {
17 }
--唯一需要注意的是RootConfig使用了@ComponentScan注解。这样的话,我们就有很多机会用非Web的组件来充实完善 RootConfig。
--SpringWeb应用简介
为了实现在线社交的功能,我们将要构建一个简单的微博 (microblogging)应用。在很多方面,我们所构建的应用与最早的微 博应用Twitter很类似。在这个过程中,我们会添加一些小的变化。当 然,我们要使用Spring技术来构建这个应用。 因为从Twitter借鉴了灵感并且通过Spring来进行实现,所以它就有了 一个名字:Spitter。Spittr应用有两个基本的领域概念:Spitter(应用的用户)和 Spittle(用户发布的简短状态更新)。
二.编写基本的控制器
在Spring MVC中,控制器只是方法上添加了@RequestMapping注解 的类,这个注解声明了它们所要处理的请求。开始的时候,我们尽可能简单,假设控制器类要处理对“/”的请求, 并渲染应用的首页:
1 package com.sky.web;
2
3 import org.springframework.stereotype.Controller;
4 import org.springframework.web.bind.annotation.RequestMapping;
5 import org.springframework.web.bind.annotation.RequestMethod;
6
7 /**
8 * @author : S K Y
9 * @version :0.0.1
10 */
11 @Controller //声明为一个控制器
12 public class WebController {
13 @RequestMapping(value = "/", method = RequestMethod.GET)
14 public String home() {
15 return "home";
16 }
17 }
--@Controller是一个构造型(stereotype)的注解,它基于 @Component注解。在这里,它的目的就是辅助实现组件扫描。因 为HomeController带有@Controller注解,因此组件扫描器会自 动找到HomeController,并将其声明为Spring应用上下文中的一个 bean。其实,你也可以让HomeController带有@Component注解,它所 实现的效果是一样的,但是在表意性上可能会差一些,无法确定 HomeController是什么组件类型。
--HomeController唯一的一个方法,也就是home()方法,带 有@RequestMapping注解。它的value属性指定了这个方法所要处 理的请求路径,method属性细化了它所处理的HTTP方法。在本例 中,当收到对“/”的HTTP GET请求时,就会调用home()方法。home()方法其实并没有做太多的事情:它返回了一 个String类型的“home”。这个String将会被Spring MVC解读为要 渲染的视图名称。DispatcherServlet会要求视图解析器将这个 逻辑名称解析为实际的视图。
--定义一个简单的JSP
1 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
2 <html>
3 <head>
4 <title>微博</title>
5 </head>
6 <body>
7 <h1>欢迎来到微博</h1>
8 </body>
9 </html>
--这样我们启动Tomcat服务器就可以正常访问到我们的主页了,当然这里有一个坑,在idea中默认的打包输出的名称并不是项目名称,因此我们需要修改,在File -->Project Sturcture:
将上方的name修改为我们的项目名称,当然为了确保顺利,可以在idea的Tomcat中查看一下是否正确,在这里附上修改Tomcat中文乱码问题的方法:打开tomcat的安装路径找到conf文件夹,打开server.xml配置文件
--添加默认字符集为UTF-8,如果说idea中控制台打印的中文信息的乱码也让你不舒服的话,可以打开idea安装的bean路径,找到idea.exe.vmoptions及idea64.exe.vmoptions,打开之后在末尾加上-Dfile.encoding=UTF-8,随后重启idea,我们的乱码问题就可以得到有效的解决
--我们可以看到最终顺利的打开了我们的网站
--当然我们也可以通过自动化测试来测试我们的home()方法
1 package com.sky.test;
2
3 import com.sky.web.WebController;
4 import org.junit.Assert;
5 import org.junit.Test;
6
7 /**
8 * @author : S K Y
9 * @version :0.0.1
10 */
11 public class WebControllerTest {
12 @Test
13 public void testHomePage(){
14 WebController webController = new WebController();
15 Assert.assertEquals("home",webController.home());
16 }
17 }
--但它只测试了home()方法中会发生什 么。在测试中会直接调用home()方法,并断言返回包含“home”值的 String。它完全没有站在Spring MVC控制器的视角进行测试。这个 测试没有断言当接收到针对“/”的GET请求时会调用home()方法。因 为它返回的值就是“home”,所以也没有真正判断home是视图的名称。从Spring 3.2 开始,我们可以按照控制器的方式来测试SpringMVC中的控制器了,Spring现在包含了一种mock SpringMVC并针对控制器执行HTTP请求的机制.
1 package com.sky.test;
2
3 import com.sky.web.WebController;
4 import org.junit.Assert;
5 import org.junit.Test;
6 import org.springframework.test.web.servlet.MockMvc;
7
8 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
9
10 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
11 import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
12
13 /**
14 * @author : S K Y
15 * @version :0.0.1
16 */
17 public class WebControllerTest {
18 @Test
19 public void testHomePage() throws Exception {
20 WebController webController = new WebController();
21 Assert.assertEquals("home", webController.home());
22 //搭建MockMVC
23 MockMvc mockMvc = standaloneSetup(webController).build();
24
25 mockMvc.perform(get("/")) //对"/"执行GET请求
26 .andExpect(view().name("home")); //预期得到home视图
27 }
28 }
--我们可以借用MockMvcBuilders提供的静态方法standaloneSetup()来加载我们的controller,再使用bulid()方法来获取MockMvc的实例.在mock.perform()中我们传入的参数为MockMvcRequestBuilders中的静态方法get(String url),用于指定我们进行访问的get请求地址,该方法会返回一个ResultActions实例,我们可以使用andExpect()方法指定参数为MockMvcResultMatchers.view().name(String name),来传入我们预期得到的视图的名称.
--定义类级别的请求处理
我们可以拆分@RequestMapping,并将其路径映射部分到类级别上:
1 package com.sky.web;
2
3 import org.springframework.stereotype.Controller;
4 import org.springframework.web.bind.annotation.RequestMapping;
5 import org.springframework.web.bind.annotation.RequestMethod;
6
7 /**
8 * @author : S K Y
9 * @version :0.0.1
10 */
11 @Controller //声明为一个控制器
12 @RequestMapping("/") //将控制器映射到"/"
13 public class WebController {
14 @RequestMapping(method = RequestMethod.GET) //处理GET请求
15 public String home() {
16 return "home";
17 }
18 }
--此次我们在WebController 中,将路径转义到了类级别的@RequestMapping上,而HTTP方法依然映射在方法级别上.当控制器在类级别上添加@RequestMapping注解时,这个注解会应用到控制器的所有处理方法上.处理器方法上的@RequestMapping注解会对类级别上的@RequestMapping的声明进行补充.就WebController而言,这里只有一个控制器方法.与类级别的@RequestMapping合并之后,这个方法的@RequestMapping表明home()将会处理对"/"路径的GET请求.
--我们可以观察@RequestMapping的构成:
1 package org.springframework.web.bind.annotation;
2
3 import java.lang.annotation.Documented;
4 import java.lang.annotation.ElementType;
5 import java.lang.annotation.Retention;
6 import java.lang.annotation.RetentionPolicy;
7 import java.lang.annotation.Target;
8 import java.util.concurrent.Callable;
9
10 import org.springframework.core.annotation.AliasFor;
11
12
13 @Target({ElementType.METHOD, ElementType.TYPE})
14 @Retention(RetentionPolicy.RUNTIME)
15 @Documented
16 @Mapping
17 public @interface RequestMapping {
18
19 String name() default "";
20
21 @AliasFor("path")
22 String[] value() default {};
23
24 @AliasFor("value")
25 String[] path() default {};
26
27 RequestMethod[] method() default {};
28
29 String[] params() default {};
30
31 String[] headers() default {};
32
33 String[] consumes() default {};
34
35 String[] produces() default {};
36
37 }
--我们可以发现其可以接受的value值并不只是一个String参数,而是一个String类型的数组,这表名了我们可以对一个@RequestMapping设置多个请求映射
1 package com.sky.web;
2
3 import org.springframework.stereotype.Controller;
4 import org.springframework.web.bind.annotation.RequestMapping;
5 import org.springframework.web.bind.annotation.RequestMethod;
6
7 /**
8 * @author : S K Y
9 * @version :0.0.1
10 */
11 @Controller //声明为一个控制器
12 @RequestMapping(value = {"/", "homepage"}) //将控制器映射到"/"
13 public class WebController {
14 @RequestMapping(method = RequestMethod.GET) //处理GET请求
15 public String home() {
16 return "home";
17 }
18 }
--此时我们将类级别的@RequestMapping映射到了"/"及"homepage"上,我们尝试启用服务器访问homepage:
--可以发现我们再次成功访问到了我们的home.jsp.但是我们需要知道的是,目前所实现的WebController只不过是一个最简单的示例而已,真正的控制器@COntroller是不会那么简单的,在Spring应用中,我们需要有一个页面展现最近提交的Spittle列表,因此我们需要使用新的方法来处理这个页面.
--我们需要定义数据库访问的Repository.为了实现解耦以及避免陷入数据库访问的细节之中,我们可以将Repository定义为一个接口,并在稍后实现它.此时,我们只需要一个能够获取Spittle列表的Repository:
1 package com.sky.data;
2
3 import com.sky.spittle.Spittle;
4
5 import java.util.List;
6
7 /**
8 * @author : S K Y
9 * @version :0.0.1
10 */
11 public interface SpittleRepository {
12 /**
13 * 获取提交的Spittle列表
14 *
15 * @param max 所返回的Spittle属性中,Spittle ID属性的最大值
16 * @param count 要返回的Spittle对象的数量
17 * @return 返回当前查询获得的Spittle列表
18 */
19 List<Spittle> findSpittles(long max, int count);
20 }
1 package com.sky.spittle;
2
3 import org.apache.commons.lang3.builder.EqualsBuilder;
4 import org.apache.commons.lang3.builder.HashCodeBuilder;
5
6 import java.util.Date;
7 import java.util.Objects;
8
9 /**
10 * @author : S K Y
11 * @version :0.0.1
12 */
13 public class Spittle {
14 private long id; //ID属性
15 private String message; //当前提交的消息内容
16 private Date time; //当前提交的时间
17 private double latitude; //维度
18 private double longitude; //精度
19
20 public Spittle(String message, Date time) {
21 this.message = message;
22 this.time = time;
23 }
24
25 public Spittle(String message, Date time, double latitude, double longitude) {
26 this.message = message;
27 this.time = time;
28 this.latitude = latitude;
29 this.longitude = longitude;
30 }
31
32 public long getId() {
33 return id;
34 }
35
36 public String getMessage() {
37 return message;
38 }
39
40 public Date getTime() {
41 return time;
42 }
43
44 public double getLatitude() {
以上是关于Spring学习--构建Spring Web应用程序的主要内容,如果未能解决你的问题,请参考以下文章