1、项目搭建。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zhen</groupId> <artifactId>highlight_springmvc4</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <!-- Generic Properties --> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <!-- Web --> <jsp.version>2.2</jsp.version> <jstl.version>1.2</jstl.version> <servlet.version>3.1.0</servlet.version> <!-- Spring --> <spring-framework.version>4.1.5.RELEASE</spring-framework.version> <!-- Logging --> <logback.version>1.0.13</logback.version> <slf4j.version>1.7.5</slf4j.version> </properties> <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-web-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency> <!-- Spring MVC --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring-framework.version}</version> </dependency> <!-- 其他web依赖 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>${jstl.version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>${servlet.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>${jsp.version}</version> <scope>provided</scope> </dependency> <!-- Spring and Transaction --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring-framework.version}</version> </dependency> <!-- 使用SLF4J和LogBack作为日志 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.16</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${logback.version}</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>${logback.version}</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-access</artifactId> <version>${logback.version}</version> </dependency> <!-- jackson以及先关依赖,对象与json和xml的互换 --> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> <version>2.5.3</version> </dependency> <!-- file upload --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> <!-- 非必须,简化io操作 --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.3</version> </dependency> <!-- 测试依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring-framework.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.3</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> <!-- 内置jetty插件 --> <plugin> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>9.4.3.v20170317</version> <configuration> <httpConnector> <port>8080</port> </httpConnector> <webAppConfig> <contextPath>/highlight_springmvc4</contextPath> </webAppConfig> </configuration> </plugin> </plugins> </build> </project>
<?xml version="1.0" encoding="UTF-8"?> <configuration scan="true" scanPeriod="1 seconds"> <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"> <resetJUI>true</resetJUI> </contextListener> <jmxConfigurator/> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>logback: %d{HH:mm:ss.SSS} %logger{36} - %msg%n</pattern> </encoder> </appender> <logger name="org.springframework.web" level="DEBUG"> <root level="info"> <appender-ref ref="console"/> </root> </logger> </configuration>
页面放在src/main/resources下,是为了习惯spring Boot的页面放置方式
<%-- Created by IntelliJ IDEA. User: zhen Date: 2018/7/3 Time: 9:35 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Insert title here</title> </head> <body> <pre> Welcome to Spring MVC world </pre> </body> </html>
springMvc配置:(有些配置不需要可以不放置)
package com.zhen.highlight_springmvc4; import com.zhen.highlight_springmvc4.interceptor.DemoInterceptor; import com.zhen.highlight_springmvc4.messageconverter.MyMessageConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.multipart.commons.CommonsMultipartResolver; import org.springframework.web.servlet.config.annotation.*; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.JstlView; import java.util.List; /** * @author zhen * @Date 2018/7/3 9:37 */ @Configuration @EnableWebMvc @EnableScheduling @ComponentScan("com.zhen.highlight_springmvc4") public class MyMvcConfig extends WebMvcConfigurerAdapter { @Bean public InternalResourceViewResolver viewResolver(){ InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("/WEB-INF/classes/views/"); viewResolver.setSuffix(".jsp"); viewResolver.setViewClass(JstlView.class); return viewResolver; } @Bean public MultipartResolver multipartResolver(){ CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); multipartResolver.setMaxUploadSize(1000000); return multipartResolver; } @Bean public MyMessageConverter converter(){ return new MyMessageConverter(); } @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters){ converters.add(converter()); } @Bean public DemoInterceptor demoInterceptor(){ return new DemoInterceptor(); } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/assets/"); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(demoInterceptor()); } @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/index").setViewName("/index"); registry.addViewController("/toUpload").setViewName("/upload"); registry.addViewController("/converter").setViewName("/converter"); registry.addViewController("/sse").setViewName("/sse"); registry.addViewController("/async").setViewName("/async"); } @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.setUseSuffixPatternMatch(false); } }
Demo搭建中只配置视图解析器用来映射路径和实际页面的位置。
@EnableWebMvc注解会开启一些默认配置
web配置:
package com.zhen.highlight_springmvc4; import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRegistration; /** * @author zhen * @Date 2018/7/3 10:00 */ public class WebInitializer implements WebApplicationInitializer {//WebApplicationInitializer是Spring用来配置Servlet3.0+配置的接口 @Override public void onStartup(ServletContext servletContext) throws ServletException { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.register(MyMvcConfig.class); context.setServletContext(servletContext);//注册配置类 ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(context));//注册Spring MVC的DispatcherServlet servlet.addMapping("/"); servlet.setLoadOnStartup(1); servlet.setAsyncSupported(true);//开启异步方法支持 } }
简单控制器:
package com.zhen.highlight_springmvc4; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /** * @author zhen * @Date 2018/7/3 10:04 */ @Controller //利用@Controller注解声明是一个控制器 public class HelloController { @RequestMapping("/index") //@RequestMapping配置URL和方法之间的映射 public String hello(){ return "index"; //通过ViewResolver的Bean配置,返回值为index,说明页面放置的路径为/WEB-INF/classes/views/index.jsp } }
2、Spring MVC的常用注解
@Controller表明这个类是Spring MVC的Controller,dispatcher servlet会自动扫描注解了此注解的类,并将web请求映射到注解了@RequestMapping的方法上。在声明普通Bean的时候,使用@Component、@Service、@Repository、@Controller是等同的,因为它们都组合了@Compoment。独特的功能使用独特的注解。
@RequestMapping用来映射web请求、处理类和方法。可注解在类上和方法上,注解在方法上的@RequestMapping路径会继承注解在类上的路径。支持对request和response的媒体类型进行配置
@ResponseBody支持将返回值放在response体内,而不是一个页面,ajax可使用
@RequestBody允许将request的参数在request体重,而不是直接连接在地址后面
@PathVariable用来接收路径参数,此注解放在在参数前面
@RestController是个组合注解组合了@Controller和@ResponseBody两个注解
例子:
添加jackson以及相关依赖,获得对象和json或xml之间转换: <!-- jackson以及先关依赖,对象与json和xml的互换 --> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> <version>2.5.3</version> </dependency> package com.zhen.highlight_springmvc4.domain; /** * @author zhen * @Date 2018/7/3 10:16 */ public class DemoObj { private Long id; private String name; public DemoObj(){ //jackson对对象和json做转换时一定需要此空构造 super(); } public DemoObj(Long id, String name){ this.id = id; this.name = name; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } package com.zhen.highlight_springmvc4.web.ch4_3; import com.zhen.highlight_springmvc4.domain.DemoObj; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; /** * @author zhen * @Date 2018/7/3 10:19 */ @Controller //声明此类是一个控制器 @RequestMapping("/anno") //映射此类的访问路径是/anno public class DemoAnnoController { @RequestMapping(produces = "text/plain;charset=UTF-8") //未标注路径,因此使用类级别路径/anno,produces定制返回的媒体类型 public @ResponseBody String index(HttpServletRequest request){//接受HttpServletRequest作为参数 return "urlL" + request.getRequestURL() + "can access"; } @RequestMapping(value = "/pathvar/{str}", produces = "text/plain;charset=UTF-8")//接受路径参数 public @ResponseBody String demoPathVar(@PathVariable String str, HttpServletRequest request){ return "url:" + request.getRequestURL() + "can access, str: " + str; } @RequestMapping(value = "/requestParam", produces = "text/plain;charset=UTF-8") public @ResponseBody String passRequestParam(Long id, HttpServletRequest request){//注入基本类型 return "url: " + request.getRequestURL() + " can access, id: " + id; } @RequestMapping(value = "/obj", produces = "text/plain;charset=UTF-8") public @ResponseBody String passObj(DemoObj obj, HttpServletRequest request) {//注入对象类型 return "url: " + request.getRequestURL() + " can access, obj id: " + obj.getId() + " obj name: " + obj.getName(); } @RequestMapping(value = { "/name1", "/name2"}, produces = "text/plain;charset=UTF-8") //多个路径映射到统一url public @ResponseBody String remove(HttpServletRequest request) { return "url:" + request.getRequestURL() + " can access"; } } package com.zhen.highlight_springmvc4.web.ch4_3; import com.zhen.highlight_springmvc4.domain.DemoObj; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author zhen * @Date 2018/7/3 10:34 */ @RestController //组合注解,声明是控制器且返回数据时候不需要@ResponseBody @RequestMapping("/rest") public class DemoRestController { @RequestMapping(value = "getJson", produces = {"application/json;charset=UTF-8"}) //返回json public DemoObj getJson(DemoObj obj){ return new DemoObj(obj.getId()+1, obj.getName() + "yy"); } @RequestMapping(value = "/getXml", produces = "application/xml;charset=UTF-8") //返回xml public DemoObj getXml(DemoObj obj){ return new DemoObj(obj.getId()+1, obj.getName() + "yy"); } }
3、Spring MVC基本配置
Spring MVC的定制配置需要配置类集成WebMvcConfigureAdapter,且在这个类使用@EnableWebMvc注解
静态资源配置(让静态资源不被拦截):
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/assets/");//addResourceLocation指的是文件放置的位置,addResourceHandler指的是对外暴露的路径 }
拦截器配置:
package com.zhen.highlight_springmvc4.interceptor; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author zhen * @Date 2018/7/3 10:52 */ public class DemoInterceptor extends HandlerInterceptorAdapter { //集成HandlerInterceptorAdapter实现自定义拦截器 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //请求前执行 long startTime = System.currentTimeMillis(); request.setAttribute("startTime", startTime); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { //请求后执行 long startTime = (Long) request.getAttribute("startTime"); request.removeAttribute("startTime"); Long endTime = System.currentTimeMillis(); System.out.println("本次请求处理时间为:" + new Long(endTime - startTime) + "ms"); request.setAttribute("handlingTime", endTime - startTime); } } @Bean public DemoInterceptor demoInterceptor(){ //配置拦截器的bean return new DemoInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { //重写addInterceptors方法,注册拦截器 registry.addInterceptor(demoInterceptor()); }
@ControllerAdvice,将对于控制器的全局配置放置在同一个位置,注解了@Controller的类的方法可使用@ExceptionHandler、@InitBinder、@ModelAttribute注解到方法上,这对所有注解了@RequestMapping的控制器内的方法有效
package com.zhen.highlight_springmvc4.advice; import org.springframework.ui.Model; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.ModelAndView; /** * @author zhen * @Date 2018/7/3 11:16 */ @ControllerAdvice //@ControllerAdvice声明一个控制器建言 public class ExceptiopnHandlerAdvice { @ExceptionHandler(value = Exception.class) //定义全局处理,通过@Exception的value属性过滤拦截条件 public ModelAndView exception(Exception exception, WebRequest request) { ModelAndView modelAndView = new ModelAndView("error");//error页面 modelAndView.addObject("errorMessage", exception.getMessage()); return modelAndView; } @ModelAttribute //使用@ModelAttribute注解将键值对添加到全局 public void addAttributes(Model model){ model.addAttribute("msg", "额外信息"); } @InitBinder //通过@InitBinder注解定制WebDataBinder public void initBinder(WebDataBinder webDataBinder) { webDataBinder.setDisallowedFields("id"); //忽略request参数的id } } package com.zhen.highlight_springmvc4.web.ch4_4; import com.zhen.highlight_springmvc4.domain.DemoObj; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; /** * @author zhen * @Date 2018/7/3 11:21 */ @Controller public class AdviceController { @RequestMapping("/advice") public String getSomething(@ModelAttribute("msg") String msg, DemoObj obj){ throw new IllegalArgumentException("非常抱歉,参数有误/" + "来自@ModelAttribute:" + msg); } } <%-- Created by IntelliJ IDEA. User: zhen Date: 2018/7/3 Time: 11:24 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <html> <head> <title>@ControllerAdvice Demo</title> </head> <body> ${errorMessage} </body> </html>
其他配置:
快捷的viewController:
配置页面转向用到代码:
@RequestMapping("/index") //@RequestMapping配置URL和方法之间的映射 public String hello(){ return "index"; //通过ViewResolver的Bean配置,返回值为index,说明页面放置的路径为/WEB-INF/classes/views/index.jsp }
可以通过在配置中重写addViewControllers简化配置:
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/index").setViewName("/index");
registry.addViewController("/toUpload").setViewName("/upload");
registry.addViewController("/converter").setViewName("/converter");
registry.addViewController("/sse").setViewName("/sse");
registry.addViewController("/async").setViewName("/async");
}
路径匹配参数配置:
在Spring MVC中,路径参数如果带“.”的话,后面的值将忽略,可冲击感谢configurePathMatch方法则不忽略“.”后面的参数:
@Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.setUseSuffixPatternMatch(false); }
4、spring MVC的高级配置
文件上传:
添加依赖: <!-- file upload --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> <!-- 非必须,简化io操作 --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.3</version> </dependency> 上传页面: <%-- Created by IntelliJ IDEA. User: zhen Date: 2018/7/3 Time: 9:35 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>upload page</title> </head> <body> <div class="upload"> <form action="upload" enctype="multipart/form-data" method="post"> <input type="file" name="file"><br/> <input type="submit" value="上传"> </form> </div> </body> </html> 上传配置: @Bean public MultipartResolver multipartResolver(){ CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); multipartResolver.setMaxUploadSize(1000000); return multipartResolver; } 控制器: package com.zhen.highlight_springmvc4.web.ch4_5; import org.apache.commons.io.FileUtils; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; /** * @author zhen * @Date 2018/7/3 11:52 */ @Controller public class UploadController { @RequestMapping(value = "/upload", method = RequestMethod.POST) public @ResponseBody String upload(MultipartFile file){ try{ FileUtils.writeByteArrayToFile(new File("d:/temp/" + file.getOriginalFilename()), file.getBytes()); return "ok"; }catch (IOException e){ e.printStackTrace(); return "wrong"; } } }
信息处理:
HttpMessageConverter用来处理request和response里的数据。spring为我们内置了大量HttpMessageConverter。
消息处理器: package com.zhen.highlight_springmvc4.messageconverter; import com.zhen.highlight_springmvc4.domain.DemoObj; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractHttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.util.StreamUtils; import java.io.IOException; import java.nio.charset.Charset; /** * @author zhen * @Date 2018/7/3 13:37 */ public class MyMessageConverter extends AbstractHttpMessageConverter<DemoObj> { //继承AbstractHttpMessageConverter实现自定义的HttpMessageConverter public MyMessageConverter(){ super(new MediaType("application", "x-wisely", Charset.forName("UTF-8"))); //新建自定义的媒体类型 } @Override protected boolean supports(Class<?> aClass) {//表明只处理DemoObj这个类 return DemoObj.class.isAssignableFrom(aClass); } @Override protected DemoObj readInternal(Class<? extends DemoObj> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException { //处理请求数据 String temp = StreamUtils.copyToString(httpInputMessage.getBody(), Charset.forName("UTF-8")); String[] tempArr = temp.split("-"); return new DemoObj(new Long(tempArr[0]), tempArr[1]); } @Override protected void writeInternal(DemoObj demoObj, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {//处理输出数据 String out = "hello:" + demoObj.getId() + "-" + demoObj.getName(); httpOutputMessage.getBody().write(out.getBytes()); } } 配置处理器: @Bean public MyMessageConverter converter(){ return new MyMessageConverter(); } /* * 配置自定义的HttpMessageConverter两个方法: * configureMessageConverters(重载会覆盖掉Spring MVC默认注册得多个HttpMessageConverter) * extendMessageConverters(仅添加一个自定义的HttpMessageConverter,不覆盖默认的HttpMessageConverter) */ @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters){ converters.add(converter()); } controller: package com.zhen.highlight_springmvc4.web.ch4_5; import com.zhen.highlight_springmvc4.domain.DemoObj; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; /** * @author zhen * @Date 2018/7/3 13:51 */ @Controller public class ConverterController { @RequestMapping(value = "/convert", produces = { "application/x-wisely"}) public @ResponseBody DemoObj convert(@RequestBody DemoObj demoObj){ return demoObj; } } 页面: <%-- Created by IntelliJ IDEA. User: zhen Date: 2018/7/3 Time: 11:24 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <html> <head> <title>HttpMessageConverter Demo</title> </head> <body> <div id="resp"></div> <input type="button" onclick="req();" value="请求"/> <script type="text/javascript" src="assets/js/jquery.min.js"></script> <!-- 这里注意,script引入最好不要使用单闭合标签,可能会影响下面的脚本不被加载 --> <script> function req(){ $.ajax({ url: "convert", data: "1-wangyunfei", type: "POST", contentType: "application/x-wisely", success: function(data){ $("#resp").html(data); } }); } </script> </body> </html>
服务器推送技术:
早期是使用ajax轮询实现。
本节方案基于:当客户端想服务端发送请求,服务端就会抓住这个请求不放,等数据跟新才返回给客户端
基于SSE的服务器推送和基于servlet3+异步方法特征
package com.zhen.highlight_springmvc4.web.ch4_5; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import java.util.Random; /** * @author zhen * @Date 2018/7/3 14:21 */ @Controller public class SseController { //这里输出媒体使用text/event-stream类型,是服务端SSE的支持 @RequestMapping(value = "/push", produces="text/event-stream") public @ResponseBody String push(){ Random r = new Random(); try{ Thread.sleep(5000); } catch (InterruptedException e){ e.printStackTrace(); } return "data:Testing 1,2,3" + r.nextInt() + " "; } } <%-- Created by IntelliJ IDEA. User: zhen Date: 2018/7/3 Time: 11:24 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <html> <head> <title>SSE Demo</title> </head> <body> <div id="msgFrompPush"></div> <script type="text/javascript" src="assets/js/jquery.min.js"></script> <script type="text/javascript"> if (!!window.EventSource){//EventSource对象只有新式的浏览器才有(Chrome、FireFox等),EventSource是SSE的客户端 var source = new EventSource('push'); s = ''; source.addEventListener('message', function(e){//添加SSE监听,再此获取服务器推送的消息 s += e.data + "<br/>"; $("#msgFrompPush").html(s); }); source.addEventListener('open', function(e){ console.log("连接打开."); }, false); source.addEventListener('error', function(e){ if (e.readyState == EventSource.CLOSED){ console.log("连接关闭"); } else{ console.log(e.readyState); } }, false); } else{ console.log("你的浏览器不支持SSE"); } </script> </body> </html>
开启异步支持: package com.zhen.highlight_springmvc4; import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRegistration; /** * @author zhen * @Date 2018/7/3 10:00 */ public class WebInitializer implements WebApplicationInitializer {//WebApplicationInitializer是Spring用来配置Servlet3.0+配置的接口 @Override public void onStartup(ServletContext servletContext) throws ServletException { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.register(MyMvcConfig.class); context.setServletContext(servletContext);//注册配置类 ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(context));//注册Spring MVC的DispatcherServlet servlet.addMapping("/"); servlet.setLoadOnStartup(1); servlet.setAsyncSupported(true);//开启异步方法支持 } } 控制器: package com.zhen.highlight_springmvc4.web.ch4_5; import com.zhen.highlight_springmvc4.service.PushService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.context.request.async.DeferredResult; /** * @author zhen * @Date 2018/7/3 14:58 */ @Controller public class AysncController { @Autowired PushService pushService; @RequestMapping("/defer") @ResponseBody public DeferredResult<String> deferredCall(){ return pushService.getAsyncUpdate(); } } 定时任务: package com.zhen.highlight_springmvc4.service; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.web.context.request.async.DeferredResult; /** * @author zhen * @Date 2018/7/3 14:57 */ @Service public class PushService { private DeferredResult<String> deferredResult; public DeferredResult<String> getAsyncUpdate(){ deferredResult = new DeferredResult<String>(); return deferredResult; } @Scheduled(fixedRate = 5000) public void refresh(){ if (deferredResult != null) { deferredResult.setResult(new Long(System.currentTimeMillis()).toString()); } } } 页面: <%-- Created by IntelliJ IDEA. User: zhen Date: 2018/7/3 Time: 11:24 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <html> <head> <title>servlet async support</title> </head> <body> <script type="text/javascript" src="assets/js/jquery.min.js"></script> <script type="text/javascript"> deferred(); function deferred(){ $.get('defer', function(data){ console.log(data); deferred(); }); } </script> </body> </html>
注意使用定时任务需要@EnableScheduling定时任务支持
5、Spring MVC测试
为了测试Web项目不启动项目,使用Serlet相关的模拟对象:如MockMVC、MockHttpServletRequest、MockHttpServletResponse、MockHttpSession等
使用junit+spring testCOntext framework
依赖: <!-- 测试依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring-framework.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> 测试用例: package com.zhen.highlight_springmvc4.web.ch4_6; import com.zhen.highlight_springmvc4.MyMvcConfig; import com.zhen.highlight_springmvc4.service.DemoService; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpSession; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; /** * @author zhen * @Date 2018/7/3 15:23 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {MyMvcConfig.class}) @WebAppConfiguration("src/main/resources") public class TestControllerIntegrationTests { private MockMvc mockMvc; @Autowired private DemoService demoService; @Autowired private WebApplicationContext wac; @Autowired MockHttpSession session; @Autowired MockHttpServletRequest request; @Before public void setUp(){ this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); } @Test public void testNormalController() throws Exception { mockMvc.perform(get("/normal")) //get发送一个请求,链接为/normal .andExpect(status().isOk()) //预期返回状态是200 .andExpect(view().name("page")) //预期返回视图名为page .andExpect(forwardedUrl("/WEB-INF/classes/views/page.jsp")) //预期跳转地址为:/WEB-INF/classes/views/page.jsp .andExpect(model().attribute("msg", demoService.saySomething())); //预期model中的msg属性的值为hello } @Test public void testRestController() throws Exception{ mockMvc.perform(get("/testRest")) .andExpect(status().isOk()) .andExpect(content().contentType("text/plain;charset=UTF-8")) .andExpect(content().string(demoService.saySomething())); } } 涉及controller: package com.zhen.highlight_springmvc4.web.ch4_6; import com.zhen.highlight_springmvc4.service.DemoService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author zhen * @Date 2018/7/3 16:01 */ @RestController public class MyRestController { @Autowired DemoService demoService; @RequestMapping(value = "/testRest", produces = "text/plain;charset=UTF-8") public String testRest(){ return demoService.saySomething(); } } package com.zhen.highlight_springmvc4.web.ch4_6; import com.zhen.highlight_springmvc4.service.DemoService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; /** * @author zhen * @Date 2018/7/3 15:59 */ @Controller public class NormalController { @Autowired DemoService demoService; @RequestMapping("/normal") public String testPage(Model model){ model.addAttribute("msg", demoService.saySomething()); return "page"; } } 涉及页面: <%-- Created by IntelliJ IDEA. User: zhen Date: 2018/7/3 Time: 11:24 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <html> <head> <title>Test page</title> </head> <body> <pre> Welcome to Spring MVC world </pre> </body> </html>