上方的图片是《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\://m aven.aliyun.com/nexus/ content/repositories/ central/.error=Could not transfer artifact com.github.stefanbirkner\:system-rules\:pom\:1.19.0 from/ to alimaven (http\://m aven.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 []{"/" }; } }
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.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"}) @Controller public class HomeController { @RequestMapping(method=GET) public String home () { return "home" ; } }
在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.2 jdk 1.1 tomcat 3.3 version 2.3 jdk 1.3 tomcat 4.1 version 2.4 jdk 1.4 tomcat 5.5 version 2.5 jdk 1.5 tomcat 6.0 version 3.0 jdk 1.6 tomcat 7.0 version 3.1 jdk 1.7 tomcat 8.0 或 tomcat 8.5 version 4.0 jdk 1.8 tomcat 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"}) @Controller public class HomeController { @RequestMapping(method=GET) public String home () { return "home" ; } }
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) { return spittleRepository.findSpittles(max, count); }
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)); 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 …就这样