侧边栏壁纸
  • 累计撰写 251 篇文章
  • 累计创建 138 个标签
  • 累计收到 16 条评论

目 录CONTENT

文章目录

[笔记]SpringBoot实战(2)-Spring MVC 4.x

Sherlock
2018-12-12 / 0 评论 / 0 点赞 / 1332 阅读 / 11490 字 / 编辑
温馨提示:
本文最后更新于 2023-10-09,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

系列笔记:

先来几个图

SpringMVC执行流程:
SpringMVC执行流程图.png
springmvcprocess.jpg
拦截器执行流程图.png

图片来自:肥朝,感谢,如侵联删。

DispatcherServlet 继承图:
DispatcherServlet.png

Spring MVC

  • MVC:Model + View + Controller (数据模型+视图+控制器)。
  • 三层架构:Presentation tier + Application tier + Data tier (展现层 + 应用层 + 数据访问层)

实际上 MVC ==只存在三层架构的展现层==,M 实际上是数据模型,是包含数据的对象(Spring MVC里有一个专门的类Model用来和V之间的数据交互)。V 指的是视图页面(JSP,freemarkr,Velocity。。。),C 就是控制器(@Controller)。
而三层架构是整个应用的架构,是由Spring框架负责管理的。一般项目结构中都有Service层和DAO层,这两个反馈在应用层和数据访问层。

WEB 配置:

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynamic;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
// WebApplicationInitializer 是 Spring 提供用来配置Servlet 3.0+配置的接口,从而实现了替代web.xml的位置,实现此接口将会自动被 SpringServletContainerInitializer (用来启动 Servlet 3.0 容器)获取到。
public class WebInitializer implements WebApplicationInitializer {
	@Override
	public void onStartup(ServletContext servletContext)
			throws ServletException {
		AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(MyMvcConfig.class);
        // 新建 WebApplicationContext,注册配置类,并将其和当前 servletContext 关联
        ctx.setServletContext(servletContext);
        // 注册 Spring MVC 的 DispatcherServlet
        Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx)); 
        servlet.addMapping("/");
        servlet.setLoadOnStartup(1);
        servlet.setAsyncSupported(true);//1
	}
}

@RestController

是一个组合注解,组合了 @Controller 和 @ResponseBody。

静态资源映射

Spring MVC 的定制配置需要我们的配置类继承一个 WebMvcConfigurerAdapter 类并重写一些方法,一定要使用 @EnableWebMvc 注解开启 SpringMVC 支持。

Spring 5.0+ 已被标位废弃,推荐使用实现 WebMvcConfigurer 接口。

通过重写 addResourceHandlers(ResourceHandlerRegistry registry) 方法可配置静态资源映射。

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/assets/");
}

拦截器配置

拦截器(Interceor)实现对一个请求处理前后进行相关的业务处理,类似于Servlet的Filter。
通过重写 addInterceptors(InterceptorRegistry registry) 方法来注册自定义的拦截器。

可以让普通的Bean实现 HandlerInterceptor 接口或者继承 HandlerInterceptorAdapter 类,并重写 preHandle() 和 postHandle() 等方法,来实现自定义拦截器。

@ControllerAdvice

通过@ControllerAdvice(组合了@Component注解),我们可以将对于控制器的全局配置放置在同一个位置,注解了@Controller的类的方法可以使用@ExceptionHandler、@InitBinder、@ModelAttribute 注解到方法上,这对所有注解了 @RequestMapping 的控制器内的方法有效。

  • @ExceptionHandler : 用于全局处理控制器里的异常(通过value属性可以过滤拦截的条件)。
  • @InitBinder :用来设置 WebDataBinder,WebDataBinder用来自动绑定前台请求参数到Model中(可对request中的参数做特殊处理)。
  • @ModelAttribute :绑定键值对到 Model 中(在 @Controller 中可通过 @ModelAttribute 取到对应值)。

ViewController

对于大量无任何业务处理制作简单页面转向的情况,可以通过在配置类中重写 WebMvcConfigurerAdapter.addViewControllers(ViewControllerRegistry registry)方法来简化配置。

在SpingMVC中,路径参数如果带“.”的话,“.”后面的值将被忽略。
通过重写 configurePathMatch(PathMatchConfigurer configurer) 可以覆盖默认配置,以获取到“.”后面的值。

@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
    configurer.setUseSuffixPatternMatch(false);
}

文件上传

SPringMVC通过配置一个 MultipartResolver 来上传文件。在控制器中,通过 MultipartFile file 来接收文件,通过 MultipartFile[] files 来接收多个文件上传。

可以使用 FileUtils.writeByteArrayToFile() 快速写文件到磁盘

@RequestMapping(value = "/upload",method = RequestMethod.POST)
public @ResponseBody String upload(MultipartFile file) {//1
		try {
			FileUtils.writeByteArrayToFile(new File("e:/upload/"+file.getOriginalFilename()),
					file.getBytes()); //2
			return "ok";
		} catch (IOException e) {
			e.printStackTrace();
			return "wrong";
		}
}

自定义 HttpMessageConverter

通过实现 AbstractHttpMessageConverter 接口可以实现自定义的 HttpMessageConverter。
配置自定义的 HttpMessageConverter 的Bean,在SPringMVC里注册 HttpMessageConverter 有两个方法:

  • configureMessageConverters:重写会覆盖掉 SpringMVC 默认注册的多个 HttpMessageConverter
  • extendMessageConverters:仅添加一个自定义的HttpMessageConverter,不覆盖默认注册的HttpMessageConverter。

服务器端推送技术

书中服务器端推送方案基于:当客户端想服务器发送请求,服务端会抓住这个请求不放,等有数据更新的时候才返回给客户端,当客户端接收到消息后,再向服务器发送请求,周而复始。可以减少服务器的请求数量,大大减少服务器压力。个人理解就是长连接

基于SSE(Server Send Event ==服务端发送事件==)的服务器端推送(需要现代浏览器支持)

    @Controller
    public class SseController {
        @RequestMapping(value = "/push", produces = "text/event-stream") //1
        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() + "\n\n";
        }
    }

这里使用输出的媒体类型为 ==text/event-stream==,这是服务器端SSE的支持,在浏览器端同样需要客户端支持—— EventSource,可以添加SSE客户端监听,以获取服务器推送的消息。
js 端示例:

<script type="text/javascript">
 if (!!window.EventSource) {
	   var source = new EventSource('push'); 
	   s='';
           // 添加 SSE 客户端监听,再次获取服务端推送的消息
	   source.addEventListener('message', function(e) {
		   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>

基于Servlet 3.0+ 的异步方法特性(跨浏览器)

需要显式的开启异步支持(servlet.setAsyncSupported(true))。
异步任务的实现是通过控制器从另外一个线程返回一个 DeferredResult。
js 端可以使用jQuery的Ajax请求:

<script type="text/javascript">
	deferred();//1 页面一打开就向后台发送请求。
	function deferred() {
	    $.get('defer',function(data){
	        console.log(data); // 2
	        deferred(); // 3 一次请求完成后再向后台发送请求。
	    });
	}
</script>

SpringMVC 的测试

Spring MVC 单元测试示例:

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
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;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {MyMvcConfig.class})
// @WebAppConfiguration 注解在类上,用来声明加载的 ApplicationContext 是一个 WebApplicationContext ,他的属性指定的是Web资源的位置,默认为("src/main/webapp") 
@WebAppConfiguration("src/main/resources") 
public class TestControllerIntegrationTests {
	private MockMvc mockMvc; 
	@Autowired
	private DemoService demoService;// 测试用例中可以注入Spring的Bean
	@Autowired
	WebApplicationContext wac; // 可注入 WebApplicationContext 
	@Autowired
	MockHttpSession session; // 可注入模拟的 http session
	@Autowired
	MockHttpServletRequest request; // 可注入模拟的 http request

	@Before
	public void setup() {
            // 模拟MVC对象,初始化
            mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); //2
	}
	@Test
	public void testNormalController() throws Exception {
		mockMvc.perform(get("/normal")) // 模拟get请求
				.andExpect(status().isOk())// 预期返回200
				.andExpect(view().name("page"))
				.andExpect(forwardedUrl("/WEB-INF/classes/views/page.jsp"))
				.andExpect(model().attribute("msg", demoService.saySomething()));
	}
	@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()));
	}
}
0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区