深入理解Spring生态|Spring第三讲(SpringMVC 从入门到精通)

本文是Spring第三讲:SpringMVC 从入门到精通

文章目录
      • 1、SpringMVC 执行流程及工作原理 喜闻乐见的面试题
      • 2、SpringMVC常用注解都有哪些?
      • 3、如何解决get和post乱码问题?
      • 4、 参数绑定(从请求中接收参数) 重点
      • 5、Rest和Http什么关系? 大家都说Rest很轻量,你对Rest风格如何理解?
      • 6、 SpringMVC或Struts处理请求流程区别?
      • 7、SpringMVC全局异常处理/整个系统只有一个
      • 8、SpringMvc 拦截器用过吗?什么场景会用到,过滤器,拦截器,监听器有什么区别?
      • 9、SpringMVC的定制配置
      • 10、@DateTimeFormat 和 @JsonFormat 注解

1、SpringMVC 执行流程及工作原理 喜闻乐见的面试题
1、springMvc是什么?(市场占有率40%,web开发当之无愧的霸主)
  • 一个表现层框架,就是从请求中接收传入的参数,然后将处理后的结果数据返回给页面展示
2、springMvc执行流程(已经滚瓜乱熟了)
  • a. 用户向服务器发送请求,请求被springMVC前端控制器DispatchServlet捕获;
  • b. DispatcherServle对请求URL进行解析,得到请求资源标识符(URL),然后根据该URL调用HandlerMapping将请求映射到处理器HandlerExcutionChain;
  • c. DispatchServlet根据获得Handler选择一个合适的HandlerAdapter适配器处理;
  • d. Handler对数据处理完成以后将返回一个ModelAndView对象给DispatchServlet;
  • e. Handler返回的 ModelAndView只是一个逻辑视图并不是一个正式的视图,DispatcherSevlet 通过ViewResolver视图解析器将逻辑视图转化为真正的视图View;
  • h. DispatcherServle通过model解析出ModelAndView中的参数进行解析最终展现出完整的view并返回给客户端;
SpringMVC启动流程?
待补充
启动流程和运行流程有何区别?20181222
待补充
2、SpringMVC常用注解都有哪些?
1、 @RequestMapping 用来处理请求地址映射的注解,可用于类或方法上
注解在方法上的@RequestMapping路径会继承注解在类上的路径
  • 示例: @RequestMapping("/anno") 映射此类的访问路径是**/anno**
  • @RequestMapping(produces=“application/json; charset=UTF-8”) 返回值是json对象,字符集是UTF-8
  • @RequestMapping(value="https://www.it610.com/pathvar/{str}; produces=“application/json; charset=UTF-8”)
    method1(@PathVariable String str,HttpServletRequest request) //接受路径参数,并在方法参数前结合@PathVariable使用,访问路径是 /anno/pathvar/xx
  • @RequestMapping(value="https://www.it610.com/requestParam; produces=“application/json; charset=UTF-8”)
    method2(Long id,HttpServletRequest request) //访问路径是**/anno/requestParam?id=1**
  • @RequestMapping(value="https://www.it610.com/obj; produces=“application/json; charset=UTF-8”)
    method2(DemoObj obj,HttpServletRequest request) //解释参数到对象 访问路径是 /anno/obj?id=1&name=xx
  • @RequestMapping(value=https://www.it610.com/article/{"/name1","/name2"}; produces=“application/json; charset=UTF-8”) //不同路径到相同的方法
2、@RequestBody 注解实现接收 http 请求的 json 数据,将 json 数据转换为 java 对象
  • @ResponseBody 注解实现将 controller 方法返回对象转化为 json 响应给客户 可以放置在方法上方或返回值类型之前
  • 返回值放在response体内
3、@Controller 它标记的类就是一个控制层对象
  • Dispatcher Servlet会自动扫描注解了此注解的类,并将Web请求映射到注解了@RequestMapping的方法上
4、@Resource 和@Autowired 都是做 bean 的注入时使用
  • 相同点:两者都可以写在字段和 setter 方法上。两者如果都写在字段上,那么就不需要再写 setter 方法
  • 不同点:@Autowired 为 Spring 提供的注解,需要导入包(.annotation.Autowired)
  • @Autowired 注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许 null值,可以设置它的 required 属性为 false。
    如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用
  • @Resource 不是Spring的注解,默认按照 ByName 自动注入,需要导入包 javax.annotation.Resource。 @Resource有两个重要的属性:name 和 type,而 Spring 将@Resource 注解的 name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型。所以,如果使用 name 属性,则使用 byName 的自动注入策略
  • 推荐使用@Autowired注解,因为按照类型装配依赖对象不会有任何问题出现。
5、@PathVariable 用于将请求 URL 中的模板变量映射到功能处理方法的参数上,即取出uri模板中的变量作为参数。
  • 如/news/001,可接受001作为参数,此注解放置在参数前
6、@requestParam
@requestParam 主要用于在 SpringMVC 后台控制层获取参数,类似一种是 request.getParameter(“name”),他有三个常用参数 defaultValue,required,value
7、@Component 相当于通用的注解,当不知道一些类归到哪个层时使用,但是不建议
8、@RestController (组合注解@Controller和@ResponseBody)
  • 注意:1、jackson对象和json做转换时一定需要此空参构造
如何开启注解处理器和适配器?
  • 我们在项目中一般会在 springMvc.xml 中通过开启 < mvc:annotation-driven>来实现注解处理器和适配器的开启。
  • 使用@EnableWebMvc注解会开启一些默认的配置,如viewResolver或MessageConverter
3、如何解决get和post乱码问题?
1、解决post请求乱码:
  • 我们可以在web.xml里边配置一个CharacterEncodingFilter过滤器。设置为utf-8
    在web.xml文件中filter的位置加上如下内容:
encodingFilter org.springframework.web.filter.CharacterEncodingFilter encoding UTF-8 forceEncoding true encodingFilter *

4、 参数绑定(从请求中接收参数) 重点
1、默认类型:
  • 在controller方法中可以有也可以没有,看自己需求随意添加. httpservletRqeust, httpServletResponse, httpSession, Model(ModelMap其实就是Mode的一个子类,一般用的不多)
2、基本类型:string, double, float, integer, long. boolean
3、pojo类型:页面上input框的name属性值必须要等于pojo的属性名称
4、vo类型(view object 用于表现层):页面上input框的name属性值必须要等于vo中的属性.属性.属性…
5、自定义转换器converter:
  • 作用:由于springMvc无法将string自动转换成date,所以需要自己手动编写类型转换器,需要编写一个类实现Converter接口
  • 在springMvc.xml中配置自定义转换器
  • 在springMvc.xml中将自定义转换器配置到注解驱动上
5、Rest和Http什么关系? 大家都说Rest很轻量,你对Rest风格如何理解?
1、Rest是一种web服务实现方式,Http接口按照Rest风格设计就是 restful http
  • rest风格,一种更为简洁良好的设计风格,可以遵守,也可不必
  • rest这个词,是由fielding的博士论文中提出的,翻译成中文是表现层状态转换。表现层指的是资源(图片、歌曲、文本等)的表现层,可以使用url来对其进行访问。
  • restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格,是对http协议的诠释。
要求url中没有动词,只有名词,没有参数
例如:@RequestMapping(value="https://www.it610.com/viewItems/{id}")
  • {xxx}是占位符,在方法中使用@pathVariable可以获取{xxx}中的变量
  • restful风格其中的一个思想是:通过http请求对应的post、get、put、delete方法,来完成对应的curd操作。
2、REST架构的主要原则:
  1. 网络上的所有事物都可以被抽象化为资源(根据请求头信息,返回xml或json)
  2. 每个资源有唯一的资源标识符
  3. 同一资源具有多种表现形式
  4. 对资源的各种操作不会改变资源标识符
  5. 所有的操作都是无状态
3、资源操作
http://example.com/users/
GET: 获取一个资源
POST:创建一个新的资源
PUT:修改一个资源的状态
DELETE:删除一个资源
之前的操作 RESTful的用法 幂等 安全
http://127.0.0.1/user/query/1 GET查询 http://127.0.0.1/user/1 GET
http://127.0.0.1/user/save POST增加 http://127.0.0.1/user POST
http://127.0.0.1/user/update POST修改 http://127.0.0.1/user PUT
http://127.0.0.1/user/delete GET/POST删 http://127.0.0.1/user DELETE
1、如何理解 RESTful API 的幂等性?
  • 对于同一REST接口的多次访问,得到的资源状态是相同的
2、如何保证接口的幂等性?****
有些接口可天然实现幂等性,比如查询接口:增加、更新、删除都要保证幂等性。那么如何来保证幂等性呢?
  • 1、全局唯一ID 在执行操作前先根据这个全局唯一ID是否存在,来判断这个操作是否已经执行。如果存在则表示该方法已经执行;否则,把全局ID,存储到存储系统弄中,比如数据库,redis等
  • 2、去重表 //适用于有唯一标识场景 如订单ID
  • 3、多版本控制 //适合在更新的场景中,在更新的接口中增加一个版本号,来做幂等
  • 4、状态机控制 //适合在有状态机流转的情况 在设计状态字段时,使用int类型,并且通过值类型的大小来做幂等
    比如订单的创建为0,付款成功为100。付款失败为99
4、最佳实践
  • url组成 网络协议http/https 服务器地址 接口名称 ?参数列表
  • url定义限定 不要使用过大写字母 使用中线-代替下划线_ 参数列表应该被encode过
5、springMVC实现RESTful服务
  • springmvc原生态的支持rest风格的架构
  • 涉及到的注解:@requestMapping 请求的url
    @pathVariable 参数
    @responseBody 响应的json数据
6、发送请求工具
  • 【深入理解Spring生态|Spring第三讲(SpringMVC 从入门到精通)】1、advanced REST client
    是chrome浏览器下的一个插件,通过它能够发送http,https,websocket请求,这样就不必通过写一个jsp页面来实现请求,节省时间
  • 2、HttpClient工具类
    模拟了浏览器的行为,如果我们需要在后端向某一地址提交数据获取结果,就可以使用HttpClient
  • 3、我们公司常使用的工具
    restlet插件 postman插件 用于模拟浏览器的请求
7、更新资源时,需要加过滤器,解决无法提交表单的问题
@responsebody(method=requestMethod.put) public responseBodyEntity updateUser(User user){ try{ integer count = this.userservice.updateUser(user); if(count.intValue() == 1){ //响应 204 return responseEntity.status(HttpStatus.NO_CONTENT).build(); }catch(exception e){e.printStackTrace} //新增失败500 return responseEntity.status(HttpStatus.INTERNAL_SERVERR_ERROR).build(); } }

默认情况下,put请求时无法提交表单数据的,需要在web.xml中添加过滤器解决
httpmethodfilter org.springframework.web.filter.httpputformContentFilter

删除资源时,将post请求转化为DELETE或者是put要用-method指定真正的请求方法
6、 SpringMVC或Struts处理请求流程区别?
SpringMVC通过ModelAndView,把结果集拿到以后,从配置文件里获取对应的bean,类反射
区别:
  • springMvc是方法级别的拦截,一个方法对应一个request上下文,而方法同时又跟一个url对应,参数的传递是直接注入到方法中的,是该方法独有的
  • struts2是类级别的拦截,一个类对应一个request上下文,struts是在接受参数的时候,可以用属性来接受参数,这就说明参数是让多个方法共享的,这也就无法用注解或其他方式标识其所属方法了。
  • 1、SpringMvc的入口是一个servlet即前端控制器,而Struts2入口是一个filter过滤器;
  • 2、SpringMvc是基于方法开发(一个url对应一个方法),请求参数传递到方法的形参,可以设计为单例或多例(建议多例),struts2是基于类开发,传递参数是通过类的属性,只能设计为多例;
  • 3、Struts2 采用值栈存储请求和响应的数据,通过OGNL存取数据,Springmvc通过参数解析器是将request请求内容解析,并给方法形参赋值,将数据和视图封装成ModelAndView对象, 最后又将ModelAndView中的模型数据通过reques域传输到页面。Jsp视图解析器默认使用JSTL表达式
备注:如今的开发基本不使用struts2了,基本都是sprigboot这一条脚手架,里面集成了springMvc。 2021010
7、SpringMVC全局异常处理/整个系统只有一个
使用场景:
  • 在做前后端分离的项目时,后端通常都会拆分成多个独立的微服务,这时候就会涉及每个服务返回给前端的数据格式问题了。通过SpringMVC实现一个比较常用的数据格式,统一所有服务的返回值格式。
数据格式
  • 第一部分: 请求处理是否成功,
  • 第二部分:服务处理结果编码,
  • 第三部分:编码对应的文本信息,
  • 第四部分:返回值
解决了日常开发中的痛点
  • 如果我们在controller中通过try catch来处理异常的话,会出现一个问题就是每个函数里都加一个Try catch,代码会变的很
    乱。
使用方法:
  • 1)需要实现一个接口 HandlerExceptionResolver TODO 补充demo
  • 2)需要在springMvc中配置。
    处理逻辑:捕获整个系统中发生的异常。
  • 1、异常写入日志文件 ?
  • 2、及时通知开发人员。发邮件、短信。?
  • 3、展示一个错误页面,例如:您的网络故障,请重试。
@RestControllerAdvice都是对Controller进行增强的,可以全局捕获spring mvc抛的异常。
  • @ExceptionHandler(value = https://www.it610.com/article/Exception.class)
ExceptionHandler的作用是用来捕获指定的异常。
使用 RestControllerAdvice 来捕获全局异常Demo:
public class CallResultMsg { private boolean result; private int code; private String message; private T data; }public enum CodeAndMsg { SUCCESS(1000, "SUCCESS"), METHODFAIL(2000, "ENCOUNTER AN ERROR WHEN EXECUTE METHOD"), UNKNOWEXCEPTION(3000, "THIS IS AN UNKNOW EXCEPTION"); private int code; private String msg; CodeAndMsg(int code, String msg){ this.code = code; this.msg = msg; } }@RestControllerAdvice(basePackages = { "cn.gov.zcy.service.item.web.controller", "cn.gov.zcy.service.warehouse.web.controller"}) public class ControllerErrorHandler {@ResponseStatus(HttpStatus.OK) public CallResultMsg sendSuccessResponse(){ return new CallResultMsg(true, CodeAndMsg.SUCCESS, null); }@ResponseStatus(HttpStatus.OK) public CallResultMsg sendSuccessResponse(T data) { return new CallResultMsg(true, CodeAndMsg.SUCCESS, data); }@ExceptionHandler(UserDefinedException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public CallResultMsg sendErrorResponse_UserDefined(Exception exception){ return new CallResultMsg(false, ((UserDefinedException)exception).getException(), null); }@ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public CallResultMsg sendErrorResponse_System(Exception exception){ if (exception instanceof UserDefinedException) { return this.sendErrorResponse_UserDefined(exception); }return new CallResultMsg(false, CodeAndMsg.UNKNOWEXCEPTION, null); } }

通过上面的一波操作,我们的controller中就不需要再去写大量的try-catch了,RestControllerAdvice会自动帮助catch,并匹配相应的ExceptionHandler,然后重新封装异常信息,返回值,统一格式返回给前端。
8、SpringMvc 拦截器用过吗?什么场景会用到,过滤器,拦截器,监听器有什么区别?
1、拦截器interceptor:
  • 是指通过统一拦截,从浏览器发往服务器的请求来完成功能的增强
  • 在面向切面编程中应用
  • 使用场景:解决请求的共性问题(乱码问题,权限验证问题
2、过滤器:filter
  • 实现了javax.servlet.Filter接口
  • 主要的用途是过滤字符编码、做一些业务逻辑判断
  • 原理:在web.xml文件配置好要拦截的客户端请求,它都会帮你拦截到请求,此时你就可以对请求或响应(Request、Response)统一设置编码,简化操作
  • 生命周期:随web应用启动而启动的,只初始化一次,以后就可以拦截相关请求,只有当web应用停止或重新部署的时候才销毁
3、监视器:listener
  • 实现了javax.servlet.ServletContextListener 接口
  • 主要作用是:做一些初始化的内容添加工作、设置一些基本的内容、比如一些参数或者是一些固定的对象等
  • 生命周期:它也是随web应用的启动而启动,只初始化一次,随web应用的停止而销毁
4、区别:
  • 1.拦截器是基于java反射机制的,而过滤器是基于函数回调的;
  • 2.过滤器依赖于servlet容器,而拦截器不依赖于servlet容器;
  • 3.拦截器只能对Action请求起作用,而过滤器则可以对几乎所有请求起作用;
  • 4.拦截器可以访问Action上下文、值栈里的对象,而过滤器不能;
  • 5.在Action的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时被调用一次
9、SpringMVC的定制配置
我们定义一个配置类MyMvcConfig,继承WebMVCConfigurerAdapter,并在此类中使用@EnableWebMvc注解,开启对springMVC的配置支持
1、静态资源映射 在配置里重写addResourceHandlers方法来实现
@override public void addResourceHandlers(ResourceHandlerRegistry registry){ registry.addResourceHandler("/asserts/**").addResourceLocation("classpath:/asserts/"); //前者是对外暴露的访问路径后者是文件放置的目录 }

2、拦截器设置Interceptor
  • 实现对每一个请求处理前后进行相关的业务处理,类似servlet中的Filter
  • 先让普通的Bean实现HandlerInterceptor接口或是继承HandlerInterceptorAdapter类来实现自定义拦截器,然后重写WebMvcConfigurerAdapter(配置类)的addInterceptors方法来注册自定义的拦截器
3、@ControllerAdvice 将对控制器的全局配置放置在同一个位置
  • 注解了@ControllerAdvice的类的方法可使用@ExceptionHandler @InitBinder @ModelAttribute注解到方法上
(1)定制ControllerAdvice
@ExceptionHandler(value=https://www.it610.com/article/xx.class) 用于全局处理控制器里的异常,更人性化的将异常输出给用户
例如:
@ExceptionHandler(value=https://www.it610.com/article/Exception.class)//value为拦截所有的异常 public ModelAndView exception(Exception exception, webRequestrequest){ ModelAndView modelAndView =new ModelAndView("error"); modelAndView.addObject("errorMessage",exception.getMessage()); return modelAndView; }

  • @InitBinder 用于设置WebDataBinder,自动绑定前台请求参数到Model中
    例如:
@InitBinder public void initBinder(WebDataBinder webDataBinder){ webDataBinder.setDisallowedFields("id"); }

  • @ModelAttribute 让全局的@RequestMapping都能拿在此处设置的键值对
    例如:
@ModelAttribute public void addAttributes(Model model){ model.addAttributes("msg","额外信息"); }

(2)那么,在使用控制器时:
@Controller public class AdviceController{ @RequestMapping("/advice") public String getSomething(@ModelAttribute("msg") String msg,DemoObj obj){ throw new IllegalArgumentException("参数有误/"+"来自@ModelAttribute:"+msg); } }

(3)异常展示页面 在src/main/resources/views下,新建error.jsp
${errorMessage} //运行时发现 id被过滤掉了,且获得了@ModelAttribute的msg信息
4、其他配置
  • 快捷的ViewController //配置页面转向 registry.addViewController("/index").setViewName("/index"); //前面是路径,后面是页面
  • 路径匹配参数设置 使用configurePathMatch方法不可忽视"."后面的参数 Configurer。setUseSuffixPatternMatch(false);
5、文件上传配置(必备)springMvc通过配置MultipartResolver来上传文件,在spring的控制器中,通过MultipartFile file来接受文件
  • (1)添加文件上传依赖 commons-fileupload/commons-io
  • (2)上传页面,在src/main/resources/views下新建upload.jsp


  • (3)添加转向到upload页面的ViewController
public void addViewController(ViewControllerRegistry registry){ registry.addViewController("/index").setViewName("/index"); registry.addViewController("/toUpload").setViewName("/upload"); }

  • (4)MultipartResolver配置(配置类中)
@Bean public MultipartResolver multipartResolver(){ CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(); multipartResolver.setMaxUploadSize(1000000); return multipartResolver; }

  • (5)控制器
@Controller public class UploadController{ @RequestMapping(value="https://www.it610.com/upload",method="RequestMethod.POST") public @ResponseBody String upload(MultipartFile file){ try{ FileUtils.writeByteArrayToFile(new File("e:/upload/"+file.getOriginalFileName()),file.getBytes()); //快速写文件到磁盘 return "ok"; }catch(IOException e){ e.prrintStackTrace(); return "wrong"; } } }

6、自定义HttpMessageConvertor 用于处理request和response里的数据
10、@DateTimeFormat 和 @JsonFormat 注解
定义一个pojo,它有一个 java.util.Date 类型的属性 date。
import java.util.Date; public class DateVo { private Date date; public void setDate(Date date){ this.date = date; } public Date getDate(){ return date; } }

定义一个Controller
@RestController @RequestMapping("/date/") public class DateController { @RequestMapping("test") public DateVo getDate(DateVo vo){ System.out.println("date1:"+vo.getDate()); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String date = sdf.format(vo.getDate()); System.out.println("date2:"+date); DateVo vo2 = new DateVo(); vo2.setDate(new Date()); return vo2; } }

访问 /date/test ,并传入参数:2018-08-02 22:05:55
发现并不能访问成功,会抛出异常
深入理解Spring生态|Spring第三讲(SpringMVC 从入门到精通)
文章图片

因为传入的参数是 String 类型的,而用来接收参数的 DateVo 的 date 属性是 java.util.Date 类型的,类型无法转换。
  1. 入参格式化
    这时,就可以使用 Spring 的 @DateTimeFormat 注解格式化参数,来解决上述问题
public class DateVo { @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") private Date date; public void setDate(Date date){ this.date = date; } public Date getDate(){ return date; } }

再像上面一样访问 /date/test ,并传入参数:2018-08-02 22:05:55,将在控制台上打印:
  • date1:Thu Aug 02 22:05:55 CST 2018
  • date2:2018-08-02 22:05:55
可以看到,加入 @DateTimeFormat 注解后参数可以被接收到了,但日期时间的格式还是需要自己再手动转换一下。
因为 @DateTimeFormat 注解的 pattern 属性值指定的日期时间格式并不是将要转换成的日期格式,这个指定的格式是和传入的参数对应的,假如注解为:
@DateTimeFormat(pattern=“yyyy/MM/dd HH:mm:ss”)
则传入的参数应该是这样的:2018/08/02 22:05:55,否则会抛出异常。
  1. 出参格式化
    在上述示例中,调用接口的返回结果为:
  • “date”: “2018-08-01T14:25:31.296+0000”
    这个格式并不是我们想要的,那么如何将其进行格式化?这时就需要用到 jackson 的@JsonFormat 注解。
改造 DateVo:
public class DateVo { @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") @JsonFormat( pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8" ) private Date date; public void setDate(Date date){ this.date = date; } public Date getDate(){ return date; } }

这样,就能返回正确的结果了。

    推荐阅读