SpringMVC初探

SpringMVC初探

上方的图片是《Spring实战第四版》给出的,DispatcherServlet处理请求,发给对应的控制器(Controller),控制器返回应答的模型数据和视图名称,DispatcherServlet再去找视图,做出应答发送给用户(浏览器)。下方的是第五版的,简化了服务器端的细节,实际上在服务器端仍然有那些流程。

以下是《Spring实战》第五章的项目流程:

1. 创建项目

通过maven创建项目,在pom.xml中添加依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>

maven的镜像源设置参考别处教程,这里不细说,只是提一下配置maven遇到的bug:

NoSuchMethodError,这个可能是版本问题,新版没有这个方法,或者是 jar包冲突 ,我遇到的情况是maven导入了这个包和另一个旧项目,之间发生了冲突。

PKIX path building failed

1
http\://maven.aliyun.com/nexus/content/repositories/central/.error=Could not transfer artifact com.github.stefanbirkner\:system-rules\:pom\:1.19.0 from/to alimaven (http\://maven.aliyun.com/nexus/content/repositories/central/)\: sun.security.validator.ValidatorException\: PKIX path building failed\: sun.security.provider.certpath.SunCertPathBuilderException\: unable to find valid certification path to requested target 

这个是在用阿里云镜像时常出现的bug,第一次遇到时发现是阿里云仓库导入依赖的写法不同,另一次解决不了干脆放弃阿里云,用 http://repo1.maven.org/maven2/ .看到阿里云仓库评论不止我遇到了这个问题。

maven遇到包无法导入的问题,进仓库删文件再reimport总是效果不错

2.写基本的配置文件、控制类、测试类

配置DispatcherServlet(SpittrWebAppInitializer)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package spittr.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class SprittrWebAppInitializer 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,/为默认
}
//配置DispatcherServlet
//扩展了AbstractAnnotationConfigDispatcherServletInitializer的类会自动配置DispatcherServlet和Spring应用上下文
}

WebConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package 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.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan("spittr.web")//扫描该目录下的控制器
public class WebConfig implements WebMvcConfigurer {
@Bean
public ViewResolver viewResolver(){
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
// resolver.setViewClass(JstlView.class);
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setExposeContextBeansAsAttributes(true);
return resolver;
}

public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}

RootConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package spittr.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.web.servlet.config.annotation.EnableWebMvc;


@Configuration
@ComponentScan(basePackages={"spittr"},excludeFilters={@Filter(type=FilterType.ANNOTATION,value=EnableWebMvc.class)
})

public class RootConfig {
}

以上三个是配置类,接下来是控制器Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package spittr.web;

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

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

@RequestMapping({"/","/homepage"})//处理对'/'、'/homepage'的请求
@Controller //声明为控制器,目的是找到它并生成Bean,和Conponent效果类似
public class HomeController {
@RequestMapping(method=GET) //处理对'/'、'/homepage'的get请求
public String home() {
return "home"; //视图名为home,传回给DispatcherServlet
}
}

webapp->WEB-INF->views新建home.jsp文件

1
2
3
4
5
6
7
8
9
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>hello</h1>
</body>
</html>

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package spittr.web;
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.*;

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

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

在测试中用到了Mock,有关Mock的使用参见 :https://segmentfault.com/a/1190000006746409

总之使用Mockito进行测试,导入依赖。这里值得学习。前后端分离Restful的开发方法,适合测试。

1
2
3
4
5
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.0.111-beta</version>
</dependency>

3.部署项目

Tomcat与jdk之间有版本对应关系如下

web 版本jdk 版本tomcat 版本
version 2.2jdk 1.1tomcat 3.3
version 2.3jdk 1.3tomcat 4.1
version 2.4jdk 1.4tomcat 5.5
version 2.5jdk 1.5tomcat 6.0
version 3.0jdk 1.6tomcat 7.0
version 3.1jdk 1.7tomcat 8.0 或 tomcat 8.5
version 4.0jdk 1.8tomcat 9.0

这里记录下自己部署的流程:

在IDEA中点击右上角倒数第二个Project Structure->Artifacts,添加Web Application achieve,from 已有的Web Application explored(如果没有就先添加一个Web Application explored)。

在Tomcat中运行:选择Build->build artifacts,将得到的out文件夹中.war文件放到Tomcat的whatsapp目录下,运行bin目录中的startup.bat,访问localhost:端口/文件名,即是项目的首页。

在IDEA中运行:在运行窗口上edit configurations, 添加一个tomcat服务,填写目录与端口,在Before launch 中添加build artifact, 即之前的.war文件。

4.更多的控制器与测试

在写好了DispatcherServlet后,我们的功能都是由增加和修改控制器来完成。DispatcherServlet将请求发送给不同的控制器,控制器根据请求的类型和值返回视图名或其他信息(这些信息被称作模型),之后通过JSP把信息处理为HTML格式返回给用户。

看看我们的控制器

1.最普通的,仅返回视图名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package spittr.web;

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

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

@RequestMapping({"/","/homepage"})//处理对'/'、'/homepage'的请求
@Controller //声明为控制器,目的是找到它并生成Bean,和Conponent效果类似
public class HomeController {
@RequestMapping(method=GET) //处理对'/'、'/homepage'的get请求
public String home() {
return "home"; //视图名为home,传回给DispatcherServlet
}
}

2.处理带参数请求的控制器函数

1
2
3
4
5
6
7
8
9
10
@RequestMapping(method=RequestMethod.GET)
public List spittles(
@RequestParam(value="max",
defaultValue="50") long max,
@RequestParam(value="count", defaultValue="20") int count)
//通过请求传入两个参数,默认值分别为50,20
//如请求URL"/spittles?max=238900&count=50"
{
return spittleRepository.findSpittles(max, count);//返回List,其他的视图名等都包括在模型中
}

3.处理路径参数的控制器函数

对于一个资源,形如”/spittles/show?spittle_id=12345”的请求,可以替换为”/spittles/12345”的形式,这样的形式可以直接识别要查找的资源,不必通过带参数的操作。

1
2
3
4
5
6
7
@RequestMapping(value="/{spittleId}", method=RequestMethod.GET)
public String spittle(
@PathVariable("spittleId") long spittleId,
Model model) {
model.addAttribute(spittleRepository.findOne(spittleId));//模型中增加了ID
return "spittle";
}

分析我们的测试例,使用mock测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import org.junit.Test;
import org.springframework.test.web.servlet.MockMvc;
import spittr.Spittle;
import spittr.data.SpittleRepository;
import spittr.web.SpittleController;
import java.util.Date;

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

public class SpittleIDTest {
@Test
public void testSpittle() throws Exception {
Spittle expectedSpittle = new Spittle("Hello", new Date());
SpittleRepository mockRepository = mock(SpittleRepository.class);
when(mockRepository.findOne(12345)).thenReturn(expectedSpittle);

SpittleController controller = new SpittleController(mockRepository);
MockMvc mockMvc = standaloneSetup(controller).build();
mockMvc.perform(get("/spittles/12345"))
.andExpect(view().name("spittle"))
.andExpect(model().attributeExists("spittle"))
.andExpect(model().attribute("spittle", expectedSpittle));
}
}

很明显分为了两个部分,前半部分是服务器的内容预设。后半部分是进行访问,与之前预设内容比较。这部分做完之后,我们可以看到如此的目录

之后就去看springboot了,一键建站还看什么springmvc…就这样