【Java面试突击-SpringMVC】精选SpringMVC高频面试题及答案 ()

Spring MVC高频面试题 ()

Spring常见面试题总结 | JavaGuide(Java面试 + 学习指南)

MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。

想要真正理解 Spring MVC,我们先来看看 Model 1 和 Model 2 这两个没有 Spring MVC 的时代。

Model 1 时代

在 Model1 模式下,整个 Web 应用几乎全部用 JSP 页面组成,只用少量的 JavaBean 来处理数据库连接、访问等操作。

这个模式下 JSP 即是控制层(Controller)又是表现层(View)。显而易见,这种模式存在很多问题。比如控制逻辑和表现逻辑混杂在一起,导致代码重用率极低;再比如前端和后端相互依赖,难以进行测试维护并且开发效率极低。

mvc-mode1

Model 2 时代

学过 Servlet 并做过相关 Demo 的朋友应该了解“Java Bean(Model)+ JSP(View)+Servlet(Controller) ”这种开发模式,这就是早期的 JavaWeb MVC 开发模式。

  • Model:系统涉及的数据,也就是 dao 和 bean。
  • View:展示模型中的数据,只是用来展示。
  • Controller:接受用户请求,并将请求发送至 Model,最后返回数据给 JSP 并展示给用户

img

Model2 模式下还存在很多问题,Model2 的抽象和封装程度还远远不够,使用 Model2 进行开发时不可避免地会重复造轮子,这就大大降低了程序的可维护性和复用性。

于是,很多 JavaWeb 开发相关的 MVC 框架应运而生比如 Struts2,但是 Struts2 比较笨重。

Spring MVC 时代

随着 Spring 轻量级开发框架的流行,Spring 生态圈出现了 Spring MVC 框架, Spring MVC 是当前最优秀的 MVC 框架。相比于 Struts2 , Spring MVC 使用更加简单和方便,开发效率更高,并且 Spring MVC 运行速度更快。

MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架。Spring MVC 可以帮助我们进行更简洁的 Web 层的开发,并且它天生与 Spring 框架集成。Spring MVC 下我们一般把后端项目分为 Service 层(处理业务)、Dao 层(数据库操作)、Entity 层(实体类)、Controller 层(控制层,返回数据给前台页面)。

Spring MVC 的核心组件

记住了下面这些组件,也就记住了 SpringMVC 的工作原理。

  • DispatcherServlet核心的中央处理器,负责接收请求、分发,并给予客户端响应。
  • HandlerMapping处理器映射器,根据 uri 去匹配查找能处理的 Handler ,并会将请求涉及到的拦截器和 Handler 一起封装。
  • HandlerAdapter处理器适配器,根据 HandlerMapping 找到的 Handler ,适配执行对应的 Handler
  • Handler请求处理器,处理实际请求的处理器。
  • ViewResolver视图解析器,根据 Handler 返回的逻辑视图 / 视图,解析并渲染真正的视图,并传递给 DispatcherServlet 响应客户端

SpringMVC 工作原理

image-20230909164051170

具体流程如下所示:

1、用户发送出请求到前端控制器DispatcherServlet。

2、DispatcherServlet收到请求调用HandlerMapping(处理器映射器)。

3、HandlerMapping找到具体的处理器(可查找xml配置或注解配置),生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。

4、DispatcherServlet调用HandlerAdapter(处理器适配器)。

5、HandlerAdapter经过适配调用具体的处理器(Handler/Controller)。

6、Controller执行完成返回ModelAndView对象。

7、HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。Model 是返回的数据对象,View 是个逻辑上的 View

8、DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)。

9、ViewResolver会根据逻辑 View 查找实际的 View

10、DispaterServlet 把返回的 Model 传给 View(视图渲染)。

11、DispatcherServlet把 View 返回给请求者(浏览器)。

Spring MVC的拦截器

拦截器会对处理器进行拦截,这样通过拦截器就可以增强处理器的功能。Spring MVC中,所有的拦截器都需要实现HandlerInterceptor接口,该接口包含如下三个方法:preHandle()、postHandle()、afterCompletion()。

这些方法的执行流程如下图:

图片

通过上图可以看出,Spring MVC拦截器的执行流程如下:

  • 执行preHandle方法,它会返回一个布尔值。如果为false,则结束所有流程,如果为true,则执行下一步。
  • 执行处理器逻辑,它包含控制器的功能。
  • 执行postHandle方法。
  • 执行视图解析和视图渲染。
  • 执行afterCompletion方法。

Spring MVC拦截器的开发步骤如下:

  1. 开发拦截器:

    实现handlerInterceptor接口,从三个方法中选择合适的方法,实现拦截时要执行的具体业务逻辑。

  2. 注册拦截器:

    定义配置类,并让它实现WebMvcConfigurer接口,在接口的addInterceptors方法中,注册拦截器,并定义该拦截器匹配哪些请求路径。

SpringMVC中拦截器(Interceptor)和Servlet中过滤器(Filter)的区别

image-20230909173846131

(1)过滤器:

依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,获取我们想要获取的数据,比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等

(2)拦截器:

依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上基于Java的反射机制,属于面向切面编程(AOP)的一种运用。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理

怎么去做请求拦截?

  • 如果是对Controller进行拦截,则可以使用Spring MVC的拦截器。
  • 如果是对所有的请求(如访问静态资源的请求)进行拦截,则可以使用Filter。
  • 如果是对除了Controller之外的其他Bean的请求进行拦截,则可以使用Spring AOP。

Spring MVC的Controller线程安全?如何解决?

Controller 它一定是线程不安全的。首先 Controller 是一个单例,其次每一次请求也是一个线程,那么如果在 Controller 中有变量的话一定会导致数据不准确。

那么如何解决呢?

  • 首先尽量不要在 Controller 里面定义变量

  • 如果必须要定义一个非静态成员变量,那么可以通过注解 @Scope(“prototype”) ,将Controller设置为多例模式。

  • 如果是各自的计算逻辑直接放在 ThreadLocal 里面,保持线程独有的变量,这样既不影响性能,也保持数据准确。

  • 如果是通用的变量计算逻辑,使用 synchronized 等同步机制。更严格的做法是用AtomicInteger类型定义成员变量,对于成员变量的操作使用AtomicInteger的自增方法完成。

SpringMVC的全局异常处理怎么实现

特厉害的一个功能,全局异常处理『SpringMVC系列』 ()

可以直接使用Spring MVC中的全局异常处理器对异常进行统一处理,此时Contoller方法只需要编写业务逻辑代码,不用考虑异常处理代码。

开发一个全局异常处理器需要使用到两个注解:@ControllerAdvice 、@ ExceptionHandler

/**
 * 统一异常处理类
 */
@ControllerAdvice
public class GlobalExceptionHandle {

    /**
     * 此方法用来处理 NameException 类型的异常,
     * 当controller抛出NameException异常的时候,此方法会被调用
     *
     * @param e
     * @return
     */
    @ExceptionHandler({NameException.class})
    public ModelAndView doNameException(Exception e) {
        System.out.println("-----doNameException-----");
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("error");
        modelAndView.addObject("msg", "登录名有误!");
        modelAndView.addObject("e", e);
        return modelAndView;
    }
}

Spring MVC注解

SpringMVC常用注解那么多!你能记几个? ()

@RequestMapping

@RequestMapping的作用是建立请求URL和处理方法之间的对应关系

@RequestMapping可以作用在方法和类上

  • 作用在类上:第一级的访问目录
  • 作用在方法上:第二级的访问目录

@RequestMapping的属性

  • path:指定请求路径的URL
  • value:属性和path属性是一样的
  • method:指定该方法的请求方式
  • params:指定限制请求参数的条件
  • headers:发送的请求中必须包含的请求头

@RequestParam

作用:获取URL中?后面的参数。

语法:@RequestParam(value=”参数名”,required=”true/false”,defaultValue=””)

  • value:参数名

  • required:是否包含该参数,默认为true,表示该请求路径中必须包含该参数,如果不包含就报错。

  • defaultValue:默认参数值,如果设置了该值,required=true将失效,自动为false,如果没有传该参数,就使用默认值

@GetMapping(“/example”)
public String example(@RequestParam(“param”) String param) {
	// 处理请求参数
	return “result”;
}

@PathVaribale

作用:通过 @PathVariable("xxx") 可以将 URL 中的 {xxx} 占位符绑定到操作方法的入参。

在Spring MVC中,@PathVariable注解是从Spring 3.0版本开始引入的。在之前的版本中,可以使用@PathVariable注解的替代方式是使用@RequestAttribute注解或者直接从HttpServletRequest中获取参数值。

@GetMapping(“/example/{id}”)
public String example(@PathVariable(“id”) int id) {
	// 处理逻辑
	return “result”;
}

@RequestBody

@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的),所以只能发送POST请求。

@PostMapping(“/example”)
public String example(@RequestBody User user) {
	// 处理用户对象
	return “result”;
}

GET方式无请求体,所以使用@RequestBody接收数据时,前端不能使用GET方式提交数据,而是用POST方式进行提交。

在后端的同一个接收方法里,@RequestBody与@RequestParam()可以同时使用,@RequestBody最多只能有一个,而@RequestParam()可以有多个。

注:一个请求,只有一个RequestBody;一个请求,可以有多个RequestParam。

@ResponseBody

作用:@ResponseBody注解通常使用在控制层的方法上,用于将Controller的方法返回的对象,通过springmvc提供的HttpMessageConverter接口转换为指定格式的数据如:json、xml等,通过Response响应给客户端。

假如是字符串则直接将字符串写到客户端;假如是一个对象,此时会将对象转化为json串然后写到客户端。这里需要注意的是,如果返回对象,按utf-8编码。如果返回String,页面可能出现乱码。因此在注解中我们可以手动修改编码格式,例如@RequestMapping(value="/cat/query",produces="text/html;charset=utf-8"),前面是请求的路径,后面是编码格式。

@Controller
@RequestMapping("/user")
public class UserController {
    @autowire
	private UserService userService;
 
    @ResponseBody
	@RequestMapping("/addUser")
	public List<User> getUsers() {
		List<User> list = userService.getUserList();
    	return  list;
  	}
}

这里返回的一个JSON字符串数组并用于前端页面解析,@RequestBody只能修饰方法。

@ModelAttribute

1) 可以用在 方法的参数上:

注解在参数上,会将客户端传递过来的参数按名称注入到指定对象中,并且会将这个对象自动加入ModelMap中

<form action="../register" method="post">
姓名:<input type="text" name="name" id="name"><br/>
生日:<input type="text" id="birthday" name="birthday"><br>
<input id="submit" type="submit" value="提交"/>
</form>
@RequestMapping(value="/register")
public String register(@ModelAttribute User user,Model model) {
    System.out.println(user.getBirthday());
    model.addAttribute("user", user);
    return "success";
}

2)用在Controller的方法上:

注解在方法上,会在每一个@RequestMapping标注的方法前执行,如果有返回值,则自动将该返回值加入到ModelMap中

/*
   * 在控制器类的处理方法执行前执行指定的方法
   */
	@ModelAttribute
	public User getUser(Model model) {
    	User user=new User("atguigu","123",new Address("北京","海淀"));
    	model.addAttribute("user", user);
    	return user;
	}
  
	@RequestMapping("/modelAttribute")
	public String modelAttribute(User user) {
    	System.out.println(user);
    	return "success";
	}

@SessionAttributes

默认情况下Spring MVC将模型中的数据存储到request域中。当一个请求结束后,数据就失效了。如果要跨页面使用。那么需要使用到session。而@SessionAttributes注解就可以使得模型中的数据存储一份到session域中。

@SessionAttributes的参数:

 1)names:这是一个字符串数组。里面应写需要存储到session中数据的名称。

 2)types:根据指定参数的类型,将模型中对应类型的参数存储到session中。

​ 3)value:其实和names是一样的。

    @SessionAttributes(value = {"names"}, types = {Integer.class, User.class})
    @Controller
    public class Test {

        @RequestMapping("/test")
        public String test(Map<String, Object> map) {
            map.put("names", Arrays.asList("atguigu", "尚硅谷", "大厂学院"));
            map.put("age", 18);
            map.put("user", new User("1001", "admin", 18))
            return "hello";
        }
    }

这里可以将map中的names存放在session中, 也可以将age存放在session中,因为指定了存放Integer类型

还可以存放User对象。

【注意】:@SessionAttributes注解只能在类上使用,不能在方法上使用。

@RestController

这里的@RestController等价于 ==@Controller + @ResponseBody==。 表示所有的请求都支持对象格式(json格式)的数据返回。不再赘述。

@RequestHeader

作用:获取指定的请求头数据