什么是Spring MVC?简单介绍下你对Spring MVC的理解?
SpringMVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架,把复杂的web应用分成逻辑清晰的表示层、控制层、业务层(服务层)、持久层,简化开发,减少出错,方便组内开发人员之间的配合。
Spring MVC的优点
1、可以支持各种视图技术,例如JSP、template等等;
2、与Spring框架集成
3、清晰的角色分配:
前端控制器(dispatcherServlet) ,
处理器映射器(handlerMapping)
处理器适配器(HandlerAdapter),
视图解析器(ViewResolver)。
5、 解耦合
6、支持Restful风格
Spring MVC的主要组件?
前端控制器【中央控制器】(DispatcherServlet):主要负责捕获来自客户端的请求和调度各个组件。
处理器映射器(HandlerMapping):根据url查找后端控制器Handler。
处理器适配器(HandlerAdapter):执行后端控制器(Handler),拿到后端控制器返回的结果ModelAndView后将结果返回给前端控制器DispatcherServlet。
后端控制器(处理器)(Handler):主要负责处理前端请求,完成业务逻辑,生成ModelAndView对象返回给HandlerAdapter。
视图解析器(ViewResolver):主要负责将从DispatcherServlet中拿到的ModelAndView对象进行解析,生成View对象返回给DispatcherServlet。
什么是DispatcherServlet
DispatcherServlet是前端控制器,它用来捕获所有的HTTP请求并分发到对应的后端控制器,这靠前端控制器一个组件是完成不了的,所以它还要调度处理器映射器来找到请求url对应的后端控制器,然后调度处理器适配器来调用对应的后端控制器来处理这个请求,所以前端控制器还有一个作用是调度其他组件来相互配合。这个控制器是不需要程序员写的
什么是Spring MVC框架的控制器?
SpringMVC的控制器分为前端控制器和后端控制器
DispatcherServlet是前端控制器,它用来捕获所有的HTTP请求并分发到对应的后端控制器,这靠前端控制器一个组件是完成不了的,所以它还要调度处理器映射器来找到请求url对应的后端控制器,然后调度处理器适配器来调用对应的后端控制器来处理这个请求,所以前端控制器还有一个作用是调度其他组件来相互配合。这个控制器是不需要程序员写的
后端控制器就是自己定义的控制层里的类(带有@Controller的类)
Spring MVC的控制器是不是单例模式?如果是,有什么问题?怎么解决?
答:是单例模式,所以在多线程访问的时候有线程安全问题
如果要保证Controller的线程安全,有以下解决办法:
1、尽量不要在 Controller 中定义成员变量;
2、如果必须要定义一个非静态成员变量,那么可以通过注解 @Scope(“prototype”),将Controller设置为多例模式。
Controller @Scope(value="prototype") public class TestController { private int num = 0; @RequestMapping("/addNum") public void addNum() { System.out.println(++num); } }
简而言之就是最好不要在控制器里定义成员变量,因为它是线程不安全的,如果非要定义那就把单例模式改成多例模式
请描述Spring MVC的工作流程?描述一下 DispatcherServlet 的工作流程?
1、用户点击某个请求路径,发起一个request请求,此请求会被前端控制器处理。
2、前端控制器请求处理器映射器去查找Handler。可以依据注解或者XML配置去查找。
3、处理器映射器根据配置找到相应的Handler(可能包含若干个Interceptor拦截器),返回给前端控制器。
4、前端控制器请求处理器适配器去执行相应的Handler处理器(常称为Controller)。
5、处理器适配器执行Handler处理器。
6、Handler处理器执行完毕之后会返回给处理器适配器一个ModelAndView对象(SpringMVC底层对象,包括Model数据模型和View视图信息)。
7、处理器适配器接收到Handler处理器返回的ModelAndView后,将其返回给前端控制器。
8、前端控制器接收到ModelAndView后,会请求视图解析器(ViewResolver)对视图进行解析。
9、视图解析器根据View信息匹配到相应的视图结果,反馈给前端控制器。
10、前端控制器收到View具体视图后,进行视图渲染,将Model中的模型数据填充到View视图中的request域,生成最终的视图(View)。
11、前端控制器向用户返回请求结果。
组件说明:
以下组件通常使用框架提供实现:
DispatcherServlet:作为前端控制器,整个流程控制的中心,控制其它组件执行,统一调度,降低组件之间的耦合性,提高每个组件的扩展性。HandlerMapping:通过扩展处理器映射器实现不同的映射方式,例如:配置文件方式,实现接口 方式,注解方式等。
HandlAdapter:通过扩展处理器适配器,支持更多类型的处理器。
ViewResolver:通过扩展视图解析器,支持更多类型的视图解析,例如:jsp、freemarker、pdf、excel等。
组件:
1、前端控制器DispatcherServlet(不需要工程师开发),由框架提供 作用:接收请求,响
应结果,相当于转发器,中央处理器。有了dispatcherServlet减少了其它组件之间的耦合度。 用户 请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet是整个流程控制的中心,由它 调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。
2、处理器映射器HandlerMapping(不需要工程师开发),由框架提供 作用:根据请求的url查找 Handler HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射 器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
3、处理器适配器HandlerAdapter 作用:按照特定规则(HandlerAdapter要求的规则)去执行 Handler 通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
4、处理器Handler(需要工程师开发) 注意:编写Handler时按照HandlerAdapter的要求去做, 这样适配器才可以去正确执行Handler Handler 是继DispatcherServlet前端控制器的后端控制器, 在DispatcherServlet的控制下Handler对具体的用户请求进行处理。 由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发Handler。
5、视图解析器View resolver(不需要工程师开发),由框架提供 作用:进行视图解析,根据逻辑视图名解析成真正的视图(view) View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jstlView、 freemarkerView、pdfView等。 一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由工程师根据业务需求开发具体的页面。
6、视图View(需要工程师开发jsp...) View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf...)
核心架构的具体流程步骤如下:
1、首先用户发送请求——>DispatcherServlet,前端控制器收到 请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控
制;
2、DispatcherServlet——>HandlerMapping, HandlerMapping 将会把请求映射为
HandlerExecutionChain 对象(包含一个Handler 处理器(页面控制器)对象、多个
HandlerInterceptor 拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
3、 DispatcherServlet——>HandlerAdapter,HandlerAdapter 将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
4、 HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView 对象(包含模型数据、逻辑视图名);
5、ModelAndView的逻辑视图名——> ViewResolver, ViewResolver 将把逻辑视图
名解析为具体的View,通过这种策略模式,很容易更换其他视图技术; 6、View——>渲染,View 会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支 持其他视图技术;
7、返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到
此一个流程结束。
看到这些步骤我相信大家很感觉非常的乱,这是正常的,但是这里主要是要大家理解springMVC中的几个组件:
前端控制器(DispatcherServlet):接收请求,响应结果,相当于电脑的CPU。
处理器映射器(HandlerMapping):根据URL去查找处理器。
处理器(Handler):需要程序员去写代码处理逻辑的。
处理器适配器(HandlerAdapter):会把处理器包装成适配器,这样就可以支持多种类型的处理器,类比笔记本的适配器(适配器模式的应用)。
视图解析器(ViewResovler):进行视图解析,多返回的字符串,进行处理,可以解析成对应的页面。
MVC是什么?MVC设计模式的好处有哪些
mvc是一种设计模式,mvc分别是模型(model)-视图(view)-控制器(controller)三层架构的设计模式。它实现前端页面的展现与后端业务数据处理的分离。
mvc设计模式的好处
通过MVC模式可以把这些对象、显示、控制分离来提高软件的的灵活性和复用性,这样使得后期维护更加容易,同时重用性高,多个视图共享一个模型,用一个模型就可以处理,这样最大化重用代码(简单来说就是解耦合,便于后期的维护和扩展)
注解原理是什么
注解本质是一个继承了Annotation接口的特殊接口,当你用注解修饰某个元素,编译器将在编译期扫描每个类或者方法上的注解,会做一个基本的检查,你的这个注解是否允许作用在当前位置,如果允许的话就会将注解信息写入元素的属性表。当通过反射获取注解时,虚拟机将所有生命周期在 RUNTIME 的注解取出来放到memberValues这个map 中,并创建一个 AnnotationInvocationHandler 实例,把这个 map 传递给它。最后,虚拟机将采用 JDK 动态代理机制生成一个目标注解的代理类对象,并初始化好处理器。通过代理对象调用注解的方法时,最终调用AnnotationInvocationHandler 的invoke方法。该方法会从memberValues 这个Map 中索引出对应的值并设置进使用这个注解的元素中去(memberValues,它是一个 Map 键值对,键是我们注解属性名称,值就是该属性当初被赋上的值,memberValues 的来源是Java 常量池),一句话概括就是,通过方法名返回注解属性值。
扩展
AnnotationInvocationHandler 是 JAVA 中专门用于处理注解的 Handler, 这个类的设计也非常有意思。
这里有一个 memberValues,它是一个 Map 键值对,键是我们注解属性名称,值就是该属性当初被赋上的值。
而这个 invoke 方法就很有意思了,大家注意看,我们的代理类代理了 Hello 接口中所有的方法,所以对于代理类中任何方法的调用都会被转到这里来。
var2 指向被调用的方法实例,而这里首先用变量 var4 获取该方法的简明名称,接着 switch 结构判断当前的调用方法是谁,如果是 Annotation 中的四大方法,将 var7 赋上特定的值。如果当前调用的方法是 toString,equals,hashCode,annotationType 的话,AnnotationInvocationHandler 实例中已经预定义好了这些方法的实现,直接调用即可。
那么假如 var7 没有匹配上这四种方法,说明当前的方法调用的是自定义注解字节声明的方法,这种情况下,将从我们的注解 map 中获取这个注解属性对应的值。一句话概括就是,通过方法名返回注解属性值。
Spring MVC常用的注解有哪些?
@RequestMapping
RequestMapping是一个用来处理请求地址映射的注解,即指明处理器可以处理哪些URL请求,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。
@RequestBody
注解实现接收http请求的json数据,将json转换为java对象。
@ResponseBody
将方法的返回值,以特定的格式写入到response的body区域,进而将数据返回给客户端。
当方法上面没有写ResponseBody,底层会将方法的返回值封装为ModelAndView对象。
如果返回值是字符串,那么直接将字符串写到客户端;
如果是一个对象,会将对象转化为json串,然后写到客户端。
SpingMVC中的控制器的注解一般用哪个?有没有别的注解可以替代?
答:一般用@Controller注解,也可以使用@RestController,@RestController注解相当于@ResponseBody + @Controller,表示是表现层,除此之外,一般不用别的注解代替。
@Controller注解的作用
在一个类上添加@Controller注解,表明了这个类是一个控制器类,可以支持同时处理多个请求。Spring使用扫描机制查找应用程序中所有用了这个注解的控制器类。分发处理器会扫描使用了该注解的类的方法,并检测该方法是否使用了@RequestMapping注解,而使用@RequestMapping注解的方法才是真正处理请求的处理器
注意:这里注意@Controller 只是定义了一个控制器类,而使用@RequestMapping 注解的方法才是真正处理请求的处理器。单单使用@Controller 标记在一个类上还不能真正意义上的说它就是Spring MVC 的一个控制器类,因为这个时候Spring 还不认识它。那么要如何做Spring 才能认识它呢?这个时候就需要我们把这个控制器类交给Spring 来管理。
有两种方式:
1、在spring的配置文件中创建该类的实例
<bean class="test.controller.MyController" />
上述这种方式是在spring容器中注入单个bean,当项目比较大,控制器类比较多时,用这种方式向Spring容器中注入bean非常的让人苦恼,索性有第二种方式。
2、在Spring MVC的配置文件中,使用<context:component-scan base-scan=""/>元素,该元素的功能为:启动包扫描功能,以便注册带有@Controller,@Service,@repository,@Component等注解的类成为Spring的Bean。
<context:component-scan base-scan="test.controller" />
这种方式会扫描指定包中的所有类,并生成相应的bean注入到spring容器中。使用这种方式当然能够极大提高我们的开发效率,但是有时候我们不想某一类型的类注入到spring容器中。可以通过下面的方法解决
<context:component-scan base-package="test" > <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/> </context:component-scan>
上述代码表示扫描test包中除有@Service注解之外的类。
@RequestMapping注解的作用
RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
RequestMapping注解有六个属性如下所示
value: 指定请求的实际地址,指定的地址可以是URI Template 模式(后面将会说明);
method: 指定请求的method类型,GET、POST、PUT、DELETE等 ;
@RequestMapping(value="/toLogin",method = RequestMethod.GET)
consumes: 指定处理request请求中Content-Type为某种类型的请求,例如application/json, text/html;
produces: 指定处理request请求中Accept头中包含了了某种类型的请求,并且表明返回的内容类型是这种类型,例如application/json, text/html;
/** * consumes 标识处理request请求中Content-Type为“application/json”类型的请求. * produces标识处理request请求中Accept头中包含了"application/json"的请求. * 同时暗示了返回的内容类型为application/json; */ @ApiOperation(value = "保存用户") @PostMapping(value = "/execute",produces = MediaType.APPLICATION_JSON_VALUE,consumes = MediaType.APPLICATION_JSON_VALUE) public String saveUser(@RequestBody User userl){ //TO DO return "保存成功"; }
params: 限定要处理请求的参数,只有匹配该参数的请求,才会被该方法处理;
@GetMapping(value = "list",params="version=1") public Object list() { return "ok"; }
说白了就是请求url中必须带指定参数才会被这个方法处理
例如 localhost:8080/api/v1/books2/12?version=1这样的请求才会被这个方法处理
headers: 指定request中必须包含某些指定的header值,才能让该方法处理请求。
其实就是用来匹配特定的请求头
@RequestMapping( value = "/testParamsAndHeaders", params = {"username","password!=123456"}, headers = {"Host=localhost:8080"} ) public String testParamsAndHeaders(){ return "success"; }
@ResponseBody注解的作用和原理
将方法的返回值,以特定的格式写入到response的body区域,进而将数据返回给客户端。
当方法上面没有写ResponseBody,底层会将方法的返回值封装为ModelAndView对象。
当方法上面写ResponseBody,如果返回值是字符串,那么直接将字符串写到客户端;
当方法上面写ResponseBody,如果是一个对象,会将对象转化为json串,然后写到客户端。
这里需要注意的是,如果返回对象,按utf-8编码。
如果返回String,默认按iso8859-1编码,页面可能出现乱码。因此在注解中我们可以手动修改编码格式,例如@RequestMapping(value="/cat/query",produces="text/html;charset=utf-8"),前面是请求的路径,后面是编码格式。
原理
其实是通过HttpMessageConverter中的方法实现的,它本是一个接口,在其实现类完成转换。【Converter是转换器】
如果是bean对象,会调用对象的getXXX()方法获取属性值并且以键值对的形式进行封装,进而转化为json串。
@PathVariable和@RequestParam(pei4 rui3 嗯)的区别
1、用法上的不同: 从名字上可以看出来,PathVariable只能用于接收url路径上的参数,而RequestParam只能用于接收请求参数(params )看下面一个例子:
package com.lrm.springbootdemo.web; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @RestController @RequestMapping("/api/v1") public class HelloController { @GetMapping("/books/{username}") public Object testPathVariable(@PathVariable String username){ Map<String,Object> map = new HashMap<>(); map.put("username",username); return map; } @PostMapping("/books2") public Object testRequestParam(@RequestParam("name") String name, @RequestParam("author") String author, @RequestParam("isbn") String isbn) { Map<String, Object> book = new HashMap<String, Object>(); book.put("name", name); book.put("author", author); book.put("isbn", isbn); return book; } @PostMapping("/books2/{id}") public Object test(@PathVariable("id") long id,@RequestParam("name") String name, @RequestParam("author") String author, @RequestParam("isbn") String isbn) { Map<String, Object> book = new HashMap<String, Object>(); book.put("id",id); book.put("name", name); book.put("author", author); book.put("isbn", isbn); return book; } }
其中testPathVariable这个方法中的username参数只能使用@PathVariable来接收,因为username参数是url的path上携带的参数。username是无法使用RequestParam来接受的。
testRequestParam这个方法只能用于 localhost:8080/api/v1/books2/12?name=java in action&author=ric&isbn=dsdas2334 这种模式的请求,因为RequestParam只能用于接收请求上带的params,testPathVariable是无法接收上面的name、author、isbn参数的。
骚戴理解
localhost:8080/api/v1/books2/12?name=java in action&author=ric&isbn=dsdas2334
这个请求路径中?后面带的参数就用@RequestParam注解接收, localhost:8080/api/v1/books2/12的这个12其实就是id,也就是后端的/books2/{id}这个中的id,所以这个请求路径的参数就是用@PathVariable来接收
2、内部参数不同 PathVariable有value,name,required这三个参数,而RequestParam也有这三个参数,并且比PathVariable多一个参数defaultValue(该参数用于当请求体中不包含对应的参数变量时,参数变量使用defaultValue指定的默认值)
3、PathVariable一般用于get和delete请求,RequestParam一般用于post请求。
Spring MVC中函数的返回值是什么?
SpringBoot并不能处理以及响应客户端的请求,最终还是要依赖SpringMVC框架,所以接下来介绍SpringMVC Controller方法的返回值类型,涵盖所有返回值类型。总共有四种返回值
1. ModelAndView
我们在使用SpringMVC的时候,经常返回ModelAndView类型,现在前后端分离后,后端都是返回JSON格式数据为主。
返回 ModelAndView类型,我们可以在ModelAndView对象中指定视图名称,然后也可以绑定数据,如下面代码:
@RequestMapping("/userList") public ModelAndView getAllUser(ModelAndView mv) { List<User> users= userService.getAllUser(); //添加数据模型到request域中 mv.addObject("users", users); mv.setViewName("userList");//指定视图名 return mv; }
2.void
如果返回值为void的话,并不是真正没有返回值,而是会出现以下几种情况:
(1)如果方法内真的没有返回值,那么SpringMVC默认把deleteUser(映射的URL)当成视图名称来解析,如果存在该视图,就返回给客户端;如果不存在该视图,就会报视图找不到异常。
@RequestMapping("/deleteUser") public void deleteUser() { //删除操作 }
通过加@ResponseBody来修改默认行为,加上该注解表示返回JSON数据,这里返回空JSON数据,而不是把URL当成视图名称来解析
@RequestMapping("/deleteUser") @ResponseBody public void deleteUser() { //删除操作 }
(2)请求转发
@GetMapping("/") public void root(HttpServletRequest req,HttpServletResponse resp) { req.getRequestDispatcher("/WEB-INF/jsp/index.jsp").forward(req,resp); }
req.getRequestDispatcher("/WEB-INF/jsp/index.jsp").forward(req,resp);
(3)重定向
@RequestMapping("/") @ResponseBody public void root(HttpServletResponse resp){ resp.setStatus(302); resp.addHeader("Location","/WEB-INF/jsp/index.jsp"); } @RequestMapping("/") @ResponseBody public void root(HttpServletResponse resp){ resp.sendRedirect("/WEB-INF/jsp/index.jsp"); }
resp.sendRedirect("/WEB-INF/jsp/index.jsp");
3. String
当方法的返回值为String的时候,也会出现下面几种情况:
(1)逻辑视图名
返回String最常见的是逻辑视图名,这种时候一般利用默认的参数Model来传递数据
@RequestMapping("/deleteUser") //方法返回JSON数据 @ResponseBody public String deleteUser(Model model) { model.addAttribute("msg","删除成功"); return "userList"; }
(2)重定向
登录失败的时候重定向到登录页面。
@RequestParam("/login") public String redirect(User user){ if{ //登录成功... }else{ //登录失败,重定向到登录页面 return "redirect:tologin"; } }
return "redirect:tologin";
(3)请求转发
登录失败的时候请求转发到登录页面。
@RequestParam("/login") public String redirect(User user){ if{ //登录成功... }else{ //登录失败,转发到登录页面 return "forward:tologin"; } }
return "forward:tologin";
(4)真的返回String,相当于JSON格式的数据
@RequestMapping("/deleteUser") @ResponseBody public String deleteUser() { try{ //删除成功 return "删除成功"; }catch(Exception e){ return "删除失败"; } }
4.JSON
现在前后端分离的情况下,大部分后端只需要返回JSON数据即可,List 集合、Map集合,实体类等都可以返回,这些数据由 HttpMessageConverter自动转为JSON ,如果使用了Jackson或者Gson,不需要额外配置就可以自动返回JSON了,因为框架帮我们提供了对应的HttpMessageConverter ,如果使用了Alibaba的Fastjson的话,则需要自己手动提供一个相应的 HttpMessageConverter的实例,方法的返回值如下面代码:
@GetMapping("/getUser") @ResponseBody public User getUser() { User user = userService.getUser(); return user; } @RequestMapping("/userList") @ResponseBody public List<User> getAllUser() { List<User> users = userService.getAllUser(); return users; }





