1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(4)

JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(4)

时间:2023-02-05 00:36:45

相关推荐

JavaEE 企业级分布式高级架构师(四)SpringMVC学习笔记(4)

SpringMVC学习笔记

高级应用篇ControllerAdvice@ControllerAdvice@ModelAttribute作用于方法作用于方法参数@InitBinder@ExceptionHandlerMock测试MockMvc介绍MockMvcMockMvcRequestBuildersResultActionsMvcResultMockMVC使用拦截器应用拦截器介绍定义拦截器配置拦截器多拦截器拦截规则拦截器应用(实现登录认证)CORS跨域解决方案客户端跨域处理(了解)请求分类标准简单请求非简单请求服务器端跨域处理CORS1CORS2父子容器

高级应用篇

ControllerAdvice

@ControllerAdvice

该注解顾名思义是一个增强器,是对注解了@Controller注解的类进行增强;该注解使用@Component注解,这样的话当我们使用<context:component-scan>扫描时也能扫描到;该注解内部使用@ExceptionHandler、@InitBinder、@ModelAttribute注解的方法会应用到所有的Controller类中 @RequestMapping 注解的方法。

/*** 自定义控制器增强器*/@ControllerAdvicepublic class MyControllerAdvice {// 应用到所有@RequestMapping注解的方法,在其执行之前把返回值放入ModelMap中@ModelAttributepublic Map<String, Object> ma(){Map<String, Object> map = new HashMap<>();map.put("name", "Tom");return map;}// 应用到所有【带参数】的@RequestMapping注解的方法,在其执行之前初始化数据绑定器@InitBinderpublic void initBinder(WebDataBinder dataBinder) {DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");dataBinder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));System.out.println("... initBinder ...");}// 应用到所有@RequestMapping注解的方法,在其抛出指定异常时执行@ExceptionHandler(Exception.class)@ResponseBodypublic String handleException(Exception e) {return e.getMessage();}}

@ModelAttribute

就是将数据放入Model中,然后可以在页面中展示该数据。该注解的特点是主要作用于ModelMap这个模型对象,用于在视图中显示数据。注意和@ResponseBody注解的使用是互斥的。

作用于方法

有没有@RequestMapping注解的方法都可以,在无@RequestMapping是的执行时机:在本类所有@RequestMapping注解方法之前执行

@Controller@RequestMapping("model-attribute")public class ModelAttributeController {// 如果方法有返回值,则直接将返回值放入ModelMap中,key可以指定@ModelAttributepublic User populateModel() {User user = new User();user.setUsername("zhangsan");return user;}// 如果方法没有返回值,则可以利用它的执行时机这一特点,做一些预处理@ModelAttributepublic void populateModel(@RequestParam String abc, Model model) {model.addAttribute("attributeName", abc);}@RequestMapping("/hello")public String hello(ModelMap map) {for (Object key : map.keySet()) {System.out.println(key+"<====>"+map.get(key));}// 返回逻辑视图return "hello";}}// attributeName<====>123// user<====>User [id=0, username=zhangsan, birthday=null, sex=null, address=null, itemList=[], itemMap={}]

@RequestMapping时:

@Controller@RequestMapping("model-attribute")public class ModelAttributeController {@RequestMapping(value = "/hello")@ModelAttributepublic String hello() {// 返回值会放入ModelMap中,key为返回值类型的首字母小写return "hello";}}

逻辑视图名为:model-attribute/hello.jsp,需要多建一级目录model-attribute

<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>入门案例</title></head><body><h3>${string}</h3></body></html>

测试访问 http://localhost:8080/model-attribute/hello

作用于方法参数

作用于有@RequestMapping注解的方法时: STEP1:获取ModelMap中指定的数据(由@ModelAttribute注解的方法产生;STEP2:将使用该注解的参数对象放到ModelMap中。如果STEP1和STEP2中的对象在ModelMap中key相同,则合并这两部分对象的数据。

// 对象合并使用方式,比如一个用户的大部分信息都是从数据库获取出来的,而表单过来的数据只有一个,此时使用对象合并就会方便很多@Controllerpublic class ModelAttributeController{@ModelAttribute("myUser")public User populate() {User user = new User();user.setUsername("zhangsan");return user;}@RequestMapping(value = "/hello")public String hello(@ModelAttribute("myUser") User user) {user.setSex("男");System.out.println(user);return "hello";}}// User [id=0, username=zhangsan, birthday=null, sex=男, address=null, itemList=[], itemMap={}]

@InitBinder

和Converter接口的作用一样,就是进行类型转换的。区别在于Converter可以实现【任意类型→任意类型】的转换,而InitBinder注解,只能实现【String→任意类型】的转换。有@InitBinder标识的方法,可以通过PropertyEditor解决数据类型转换问题,比如String–>Date类型。不过PropertyEditor只能完成String-->任意类型的转换,这一点不如Converter灵活。它可以对WebDataBinder对象进行初始化,WebDataBinderDataBinder的子类,用于完成由表单字段到JavaBean属性的绑定。注意:@InitBinder方法不能有返回值,它必须声明为void;@InitBinder方法的参数通常是WebDataBinder。

// 应用到所有【带参数】的@RequestMapping注解的方法,在其执行之前初始化数据绑定器@InitBinderpublic void initBinder(WebDataBinder dataBinder) {DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");dataBinder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));System.out.println("... initBinder ...");}

@ExceptionHandler

这个注解表示Controller中任何一个@RequestMapping方法发生异常,则会被注解了@ExceptionHandler的方法拦截到。拦截到对应的异常类则执行对应的方法,如果都没有匹配到异常类,则采用近亲匹配的方式。

// 应用到所有@RequestMapping注解的方法,在其抛出指定异常时执行@ExceptionHandler(Exception.class)@ResponseBodypublic String handleException(Exception ex) {// 异常处理思路// 自定义预期异常CustomException customException = null;// 如果抛出的是系统自定义的异常if (ex instanceof CustomException) {customException = (CustomException) ex;} else {customException = new CustomException("未知错误");}return customException.getMsg();}

Mock测试

什么是mock测试?

在单元测试过程中,对于某些不容易造成或者不容易获取的对象,用一个虚拟的对象来创建以便测试的 测试方法,就是Mock测试。这个虚拟的对象就是Mock对象,Mock对象就是真实对象在调试期间的代替品,比如:Servlet、Request等Servlet API相关对象本来是由Servlet容器(Tomcat)创建的。

为什么使用mock测试?

可以实现一键测试,避免开发模块之间的耦合,轻量、简单、灵活。MockMVC介绍:基于RESTful风格的Spring MVC单元测试,我们可以测试完整的Spring MVC流程,即从URL请求到控制器处理,再到视图渲染都可以测试。

MockMvc介绍

MockMvc

对于服务器端的Spring MVC测试支持主入口点,通过MockMVCBuilder构造,MockMVCBuilder由MockMVCBuilders建造者的静态方法去建造。核心方法:perform(RequestBuilder rb)——执行一个RequestBuilder请求,会自动执行SpringMVC的流程映射到相应的控制器执行处理,该方法的返回值是一个ResultActions。MockMVCBuilder:使用构建者模式来构造MockMvc的构造器。其主要有两个实现:StandaloneMockMVCBuilder和DefaultMockMVCBuilder,分别对应之前的两种测试方式。不过我们可以直接使用静态工厂MockMVCBuilders创建即可,不需要直接使用上面的两个实现类MockMVCBuilders:负责创建MockMVCBuilder对象,有两种创建方式: standaloneSetup(Object… controllers):通过参数指定一组控制器,这样就不需要从上下文获取了。webAppContextSetip(WebApplicationContext wac):指定WebApplicationContext,将会从该上下文获取相应的控制器并得到相应的MockMvc。

MockMvcRequestBuilders

用来构建Request请求的其主要有两个子类MockHttpServletRequestBuilders和MockMultipartHttpServletRequestBuilders(如文件上传使用),即用来Mock客户端请求需要的所有数据。

ResultActions

andExect:添加ResultMatcher验证规则,验证控制器执行完毕后结果是否正确;andDo:添加ResultHandler结果处理器,比如调试时打印结果到控制台andReturn:最后返回相应的MvcResult,然后进行自定义验证、进行下一步的异步处理MockMvcResultMatchers:用来匹配执行完请求后的结果验证,如果匹配失败将抛出相应的异常,包含了很多验证API方法。MockMvcResultHandlers:结果处理器,表示要对结果做点什么事情,比如使用MockMvcResultHandlers.print()输出整个响应结果信息。

MvcResult

单元测试执行结果,可以针对执行结果进行自定义验证逻辑。

MockMVC使用

添加依赖:

<!-- spring 单元测试组件包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.0.7.RELEASE</version></dependency><!-- 单元测试Junit --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency><!-- Mock测试使用的json-path依赖 --><dependency><groupId>com.jayway.jsonpath</groupId><artifactId>json-path</artifactId><version>2.2.0</version></dependency>

测试类:

/*** @WebAppConfiguration:可以在单元测试的时候,不用启动Servlet容器,就可以获取一个Web应用上下文* 以前的思路:tomcat启动之后,创建Servlet对象、创建ServletContext对象* --->创建spring容器(WebApplicationContext)--->spring容器存储到ServletContext对象中*/@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations = "classpath:springmvc.xml")@WebAppConfigurationpublic class TestMockMVC {// 自动获取由@WebAppConfiguration产生的web应用上下文@Autowiredprivate WebApplicationContext wac;private MockMvc mockMvc;@Beforepublic void setup(){// MockMvcBuilders -- MockMvcBuilder -- > MockMvcmockMvc = MockMvcBuilders.webAppContextSetup(wac).build();}// 测试测试请求:http://localhost:8080/rest/user/1/lisi/女@Testpublic void test() throws Exception{MvcResult result = mockMvc.perform(get("/rest/user/1/lisi/女")).andExpect(status().isOk()).andExpect(jsonPath("$.id").value(2)).andDo(print()).andReturn();Object handler = result.getHandler();System.out.println(handler);}@Testpublic void test1() throws Exception {mockMvc.perform(MockMvcRequestBuilders.get("/hello")).andExpect(view().name("hello123123"));}@Testpublic void test2() throws Exception {//{id:1,sex:"女"}mockMvc.perform(get("/json/findUserById2").param("name", "1")).andExpect(jsonPath("$.sex").value("n"));}}

@WebAppConfiguration:用于声明一个ApplicationContext集成测试加载WebApplicationContext

拦截器应用

SpringMVC的拦截器主要是针对特定处理器进行拦截的。

拦截器介绍

SpringMVC拦截器(Interceptor)实现对每一个请求处理前后,进行相关的业务处理,类似于Servlet中的Filter。SpringMVC中的Intercepter拦截请求是通过HandlerInterceptor接口实现的。在SpringMVC中定义一个Interceptor非常简单,主要有4种方式: 实现SpringMVC的HandlerInterceptor接口;继承实现了HandlerInterceptor接口的类,比如SpringMVC已经提供的实现HandlerInterceptor接口的抽象类HandlerInterceptorAdapter;实现SpringMVC的WebRequestInterceptor接口;继承实现了WebRequestInterceptor的类。

定义拦截器

实现HandlerInterceptor接口

public class MyHandlerIntercepter implements HandlerInterceptor {// Handler执行前调用// 应用场景:登陆认证、身份授权// 返回值为true则是放行,为false则不放行@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//return HandlerInterceptor.super.preHandle(request, response, handler);return false;}// 进入Handler开始执行,并且在返回ModelAndView之前调用// 应用场景:对ModelAndView对象操作,可以把公共模型数据传到前台,统一统一指定视图@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {//HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);}// 执行完Handler之后调用// 应用场景:统一异常处理、统一日志处理@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception {//HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}}

配置拦截器

SpringMVC拦截器是绑定在HandlerMapping中的,即:如果某个HandlerMapping中配置拦截,则该HandlerMapping映射成功的Handler会使用该拦截器。SpringMVC的全局拦截器配置,其实是把配置的拦截器注入到每个已初始化的HandlerMapping中了。

<!-- 配置全局mapping的拦截器 --><mvc:interceptors><!-- 公共拦截器可以拦截所有请求,而且可以有多个 --><bean class="com.yw.springmvc_ssm.interceptor.MyHandlerInterceptor"/><bean class="com.yw.springmvc_ssm.interceptor.MyHandlerInterceptor2"/><!-- 如果有针对特定URL的拦截器,则进行以下配置 --><mvc:interceptor><!-- /** 表示所有URL和子URL路径 --><mvc:mapping path="/orders/**"><!-- 特定请求的拦截器只能有一个 --><bean class="com.yw.springmvc_ssm.interceptor.MyHandlerInterceptor3"/></mvc:interceptor></mvc:interceptors>

多拦截器拦截规则

如果有多个拦截器,那么配置到springmvc.xml中最上面的拦截器,拦截优先级最高。

拦截器应用(实现登录认证)

需求:拦截器对访问的请求URL进行登录拦截校验 如果请求的URL是公开的地址(无需登录就可以访问的URL,具体指的就是保护login字段的请求URL),采取放行;如果用户session存在,则放行;不放行时,都要跳转到登录页面。 登陆页面:

<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>登录页面</title></head><body><form action="${pageContext.request.contextPath }/login" method="post"><table align="center" border="1" cellspacing="0" ><tr><td>用户名:<input type="text" name="username"/></td></tr><tr><td>密 码:<input type="text" name="password"/></td></tr><tr><td><input type="submit" value="登录"/></td></tr></table></form></body></html>

Controller类:

@Controllerpublic class LoginController {// 登录@RequestMapping("/login")public String login(HttpSession session, String username, String password) {// Service进行用户身份验证if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password))return "redirect:/login.jsp";// 把用户信息保存到session中session.setAttribute("username", username);// 重定向到另一个URLreturn "redirect:/item/findItem";}// 退出@RequestMapping("/logout")public String logout(HttpSession session) {// 清空sessionsession.invalidate();// 重定向到登录页面return "redirect:/login.jsp";}}

HandlerInterceptor类:

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {// 获取请求的URIString requestURI = request.getRequestURI();// 直接去查询白名单System.out.println(requestURI);// 1、 如果请求的URL是公开地址(无需登录就可以访问的URL),采取放行。if (requestURI.indexOf("login") > -1)return true;// 2、 如果用户session存在,则放行。String username = (String) request.getSession().getAttribute("username");if (username != null && !username.equals(""))return true;// 3、 如果用户session中不存在,则跳转到登录页面。response.sendRedirect("/springmvc-demo/login.jsp");return false;}}

HandlerInterceptor配置:

<mvc:interceptors><bean class="com.yw.springmvc.example.interceptor.LoginInterceptor" /></mvc:interceptors>

CORS跨域解决方案

什么是JS跨域?

浏览器因为安全考虑,所以设置了同源策略。同源策略简单理解就是协议、DNS域名、端口号完全相同就称为同源。同源下的页面之间才能进行js的dom操作。如果不同源,任何跨文档dom访问都是被阻止的。不同源下的访问可以称为跨域访问。非同源情况下,以下操作是被限制的: js的DOM操作JavaScript的请求cookie的获取 下表里的 a.js 是无法获取 b.js 的内容的

为什么要解决跨域问题?

因为目前互联网项目开发大多数都是采取【前后端分离】的方式,前端是通过去访问的html页面,而后端是部署在另一台服务器,肯定不是这台服务器。所以从这中前端页面发起的ajax异步请求,获取后端另一台服务器的数据,肯定是跨域的。

为什么要有同源策略?

安全性考虑

如何解决跨域?

解决跨域主要考虑两方面:一个是避开Ajax请求方式;一个是解决同源限制问题解决跨域的方式有多种: 基于JavaScript标签的src方式(了解)基于Jquery的JSONP方式(了解)基于CPRS的方式(解决同源的问题)JSONP和CORS的区别:JSONP只能解决GET方式提交,CORS不仅支持GET方式,同时也支持POST提交方式。

什么是CORS?

CORS是一个W3C标准,全称是“跨域资源共享(Cross-origin resource sharing)”。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。CORS需要浏览器和服务器的支持,目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

CORS原理

客户端自动向请求头header中注入 Origin;服务器端需要向响应头header中注入 Access-Control-Allow-Origin;浏览器检测到header中的 Access-Control-Allow-Origin,则就可以跨域操作了。

客户端跨域处理(了解)

请求分类标准

浏览器将CORS请求分为两类:简单请求和非简单请求。只要同时满足以下两大条件,就属于简单请求。凡是不同时满足上面两个条件,就属于非简单请求。浏览器对这两种请求的处理,是不一样的。

(1)请求方法是以下三种方式之一:* HEAD* GET* POST(2)HTTP的头信息不超过以下几种字段* Accept* Accept-Language* Content-Language* Last-Event-ID* Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

简单请求

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段请求信息: 响应信息: 字段说明:

* Access-Control-Allow-Origin:该字段是必须的,它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求* Access-Control-Allow-Credentials:该字段可选,它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。如果设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可

非简单请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为“预检”请求(preflight)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHTTPRequest请求,否则就报错。请求信息:HTTP请求方法是PUT,并且发送一个自定义信息X-Custom-Header,浏览器发现这是一个非简单请求,就自动发出一个预检请求,要求服务器确认可以这样请求: 预检请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。除了Origin字段,预检请求的头信息包括两个特殊字段:

* Accept-Control-Request-Method:该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT* Accept-Control-Request-Headers:该字段是一个逗号的分割字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header

服务器端跨域处理

CORS1

springmvc4.x以下,使用springmvc的拦截器实现。方式一:跨域不提交Cookie

public class AllowOriginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if(request.getHeader("Origin") != null){response.setContentType("text/html;charset=UTF-8");// 允许哪一个URLresponse.setHeader("Access-Control-Allow-Origin", "*");// 允许那种请求方法response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");response.setHeader("XDomainRequestAllowed", "1");System.out.println("正在跨域");}return true;}}// 拦截器配置代码,请自行补充

方式二:跨域提交Cookie

注意事项:Access-Control-Allow-Credentials为true时,Access-Control-Allow-Origin一定不能设置为“*”,否则报错。如果有多个拦截器,一定要把处理跨域请求的拦截器放到首位。

// jQuery Ajax$.ajax({url: 'xxx'method: 'POST',xhrFields: {withCredentials: true},success: function(data){},error: function(){}});// angularJS// 全局 在模块配置中添加app.config(['$[Math processing Error]httpProvider', function($httpProvider){$httpProvider.defaults.withCredentials = true}]);// 单个请求$http.get(url, {withCredentials: true});$http.post(url, data, {withCredentials: true});$httpProvider.defaults.withCredentials = true;

public class AllowOriginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if(request.getHeader("Origin") != null){response.setContentType("text/html;charset=UTF-8");// 允许哪一个URLresponse.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));// 允许那种请求方法response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");response.setHeader("Accept-Control-Max-Age", "0");// 允许请求头里的参数列表response.setHeader("Accept-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, iF-Modified-Since, Pragma, LAst-Modified, Cache-Control, Expires, Content-Type, X-E4M-With, userID token");// 允许对方带Cookie访问response.setHeader("Access-Control-Allow-Credentials", "true");response.setHeader("XDomainRequestAllowed", "1");System.out.println("正在跨域");}return true;}}

CORS2

springmvc4.x以上处理CORS跨域方式:某个方法可以跨域访问,在某个方法上使用@CrossOrigin;某个Controller类都可以跨域访问,在类上使用@CrossOrigin。全局访问,在springmvc.xml中配置

<mvc:cors><mvc:mapping path="/**" /></mvc:cors>

父子容器

问题1:在子容器声明一个Bean,然后父容器中使用@Autowired注解注入,验证注入是否成功。问题2:@Autowired注解的开启方式有哪些?如果父容器中不开启@Autowired注解,是如何操作?如果对于父子容器理解不到位的话,会在使用spring管理bean的时候,出现bean取不到的问题。那么怎么解决?最简单的方案就是消除父子容器的情况,只使用DispatcherServlet加载所有的spring配置文件。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。