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="/pathvar/{str};produces=“application/json;charset=UTF-8”)
    method1(@PathVariable String str,HttpServletRequest request) //接受路径参数,并在方法参数前结合@PathVariable使用,访问路径是 /anno/pathvar/xx
  • @RequestMapping(value="/requestParam;produces=“application/json;charset=UTF-8”)
    method2(Long id,HttpServletRequest request) //访问路径是**/anno/requestParam?id=1**
  • @RequestMapping(value="/obj;produces=“application/json;charset=UTF-8”)
    method2(DemoObj obj,HttpServletRequest request) //解释参数到对象 访问路径是 /anno/obj?id=1&name=xx
  • @RequestMapping(value={"/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的位置加上如下内容:
 <filter>
     <filter-name>encodingFilterfilter-name>
     <filter-class>
         org.springframework.web.filter.CharacterEncodingFilter
     filter-class>
     <init-param>
         <param-name>encodingparam-name>
         <param-value>UTF-8param-value>
     init-param>
     <init-param>
         <param-name>forceEncodingparam-name>
         <param-value>trueparam-value>
     init-param>
 filter>
 <filter-mapping>
     <filter-name>encodingFilterfilter-name>
     <url-pattern>*url-pattern>
 filter-mapping>    

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="/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、发送请求工具

  • 1、advanced REST client
    是chrome浏览器下的一个插件,通过它能够发送http,https,websocket请求,这样就不必通过写一个jsp页面来实现请求,节省时间

  • 2、HttpClient工具类
    模拟了浏览器的行为,如果我们需要在后端向某一地址提交数据获取结果,就可以使用HttpClient

  • 3、我们公司常使用的工具
    restlet插件 postman插件 用于模拟浏览器的请求

7、更新资源时,需要加过滤器,解决无法提交表单的问题

@responsebody(method=requestMethod.put)
public responseBodyEntity<void> 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中添加过滤器解决

<filter>
    <filter-name>httpmethodfilter
    <filter-class>org.springframework.web.filter.httpputformContentFilter
filter>

删除资源时,将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 = Exception.class)

ExceptionHandler的作用是用来捕获指定的异常。
使用 RestControllerAdvice 来捕获全局异常Demo:

public class CallResultMsg<T> {
    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<T> {

    @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=xx.class) 用于全局处理控制器里的异常,更人性化的将异常输出给用户
例如:

@ExceptionHandler(value=Exception.class)   //value为拦截所有的异常
public ModelAndView exception(Exception exception, webRequest  request){
     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
<div class="upload">
    <form action="upload" enctype="multipart/form-data" method="post">
        <input type="file" name="file"/>br>
        <input type="submit" value="上传"/>
    form>
div>
  • (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="/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
发现并不能访问成功,会抛出异常
在这里插入图片描述
因为传入的参数是 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;
    }
}

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

你可能感兴趣的:(深入理解Spring生态,spring,java,后端,SpringMVC)