【Spring】SpringMVC 常见面试题
目录
2.2 SpringMVC 如何处理线程并发问题?是否是线程安全的?如何保证性能?
2.3.5 SpringMVC 和 Spring 父子容器的扫描配置
2.7 Filter(过滤器) 、Interceptor(拦截器)、Listener(监听器)
2.7.2 Filter 与 Interceptor 的区别
一、SpringMVC 概念
1.1 什么是MVC
MVC 是一种使用 MVC(Model View Controller 模型-视图-控制器)设计创建 Web 应用程序的模式
● Model(模型):处理应用程序数据逻辑的部分
● View(视图):处理数据显示的部分
● Controller(控制器):处理与用户交互的部分
示例流程图如下:
1.2 SpringMVC 设计模式
1.2.1 改进
SpringMVC 将传统的 Model 层再次进行细化,拆分为 Service 层和 Dao层
● Service(业务层):处理业务逻辑代码
● Dao(数据访问层):处理数据库操作
1.2.2 设计模型图
可以看到,Spring 是 SpringMVC 的父容器,SpringMVC 为子容器
1.2.3 SpringMVC 组件解析
组件 | 组件名 | 作用 |
前端适配器 | DispatcherServlet | 相当于mvc模式中的c。它是整个流程控制的中心,由它调用其他组件处理用户的请求,相当于组件的控制中枢。它的存在降低了组件之间的耦合性。 |
处理器映射器 | HandlerMapping | 负责根据用户请求找到对应的处理器Handler(处理类和方法)。Spring提供了不同的映射器实现不同的映射方式,如:配置文件方式、实现接口方式、注解方式等。 |
处理器 | Handler | 即具体业务控制器,当 DispatcherServlet 将用户的请求转发到 Handler, 由 Handler 对具体的用户请求进行处理 |
处理器适配器 | HandlerAdapter | 通过 HandlerAdapter 对处理器进行执行,可以通过扩展适配器实现对更多类型的处理器的执行 |
视图解析器 | View Resolver | 负责将 Handler 处理的结果生成 View 视图,View Resolver 先根据逻辑视图名解析成物理视图名(即具体的页面地址),再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。 |
视图 | View | 展现给用户的界面 |
1.2.4 SpringMVC 模型图和流程分析
模型图如下:
流程为:
- 用户先进行 http 请求,请求被 DispatcherServlet(前端控制器) 拦截
- DispatcherServlet(前端控制器) 根据实际配置,选择将请求发送到 HandlerMapping(处理器映射器) 或是直接放过(静态资源)
- HandlerMapping(处理器映射器) HandlerMapping 根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet
- DispatcherServlet 获取到映射器的返回数据,发送给 HandlerAdapter(处理器适配器),让他调用对应的 Handler 处理业务
- HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView 对象(包含模型数据、逻辑视图名)
- Handler 处理完将结果 ModelAndView 返回给 HandlerAdapter,HandlerAdapter 将 ModelAndView 返回给 DispatcherServlet
- DispatcherServlet 将 ModelAndView 发送给 ViewResolver(视图解析器)进行解析
- ViewResolver 解析后返回具体的 View 给DispatcherServlet
- DispatcherServlet 对 View 进行渲染(即填充数据到视图中),并响应给用户
从上面的流程分析,有几个关键点:
1. 拦截器什么时候生效?
拦截器生效是在HandlerAdapter 找到真正的处理器,即将对 Handler 处理时,先执行拦截器
2. HandlerAdapter 有什么作用?
在 HandlerMapping 时就已经找到了请求对应的执行方法,那么为什么还要 HandlerAdapter呢?
这是由于不同的 Handler 对应了不同的处理器,处理器类型分别为:
HandleAdapter实现类 | 请求处理器类型 | 介绍 |
RequestMappingHandlerAdapter | HandlerMethod | 每个HandlerMethod对应一个@RequestMapping注解的控制器方法 |
HttpRequestHandlerAdapter | HttpRequestHandler |
HttpRequestHandler的例子比如静态资源请求处理器
|
ResourceHttpRequestHandler | Controller | 请求处理器是实现了接口 Controller 的某个对象 |
1.2.5 SpringMVC 有什么优势
- 支持各种视图技术,不仅仅局限于jsp
- 与 Spring 框架无缝结合,能够使用 IOC、AOP等
- 支持各种请求的映射策略(RequestMapper)
- 代码结构更加清晰,利于多人共同开发、功能拓展和维护
1.2.6 SpringMVC 与 Strust2 比较
共同点:
● 框架
SpringMVC 和 Strust2 都是基于 MVC 的表现层框架
● 请求处理
SpringMVC 和 Strust2 都是一个核心控制器
● 线程安全
SpringMVC 默认通过单例模式创建 Controller Bean,即并发请求下用的是同一个实例,当没有共享属性时是线程安全的,也不应该在 Controller、Service、Dao 内创建共享属性!
而 Strust2 对每一个请求分配一个新的 action 实例处理,故也是线程安全的
区别:
● 入口
SpringMVC 的入口是 Servlet,Strust2 的入口是 Filter。但Filter在容器启动后就初始化,服务停止后销毁,晚于Servlet;Servlet在是在调用时初始化,先于Filter调用,服务停止后销毁。
● 拦截机制
SpringMVC 是方法级别的拦截,一个方法对应一个 Request 上下文,所以方法都是独立的,独享 request、response 数据。且每个方法和一个url对应,传递的参数是直接注入到方法中的,是方法独有的。
Strust2 是类级别的拦截。每一次请求都会创建一个 Action,通过 ActionBean 的 setter 和 getter 方法将数据注入到属性中。由于是通过属性接收的,故属性参数是让类中的多个方法共享的。
● 性能
由于 SpringMVC 中使用了单例模式,没有重复创建 Controller Bean 的消耗,而 Strust2 的每个 Action 都需要重新创建对象,即多例模式,故 SpringMVC 的性能略优于 Strust2
● 配置
由于 SpringMVC 与 Spring 的配置是无缝的,基本使用注解就可以完成配置,而 Strust2 需要通过 xml 文件配置
二、Spring 面试题
2.1 SpringMVC 常用注解
● @Controller
包位置:org.springframework.stereotype.Controller
作用:将当前类标记为控制器对象,SpringMVC 使用扫描机制查找应用程序中所有基于注解的控制类,控制类中用 @RequestMapping 标注了不同url对应的方法,分发处理器会扫描并记录url对应的方法处理器。
相关注解: @RequestMapping
● @RequestMapping
包位置:org.springframework.web.bind.annotation
作用:将特定url的请求与处理方法绑定
● @PathVariable
包位置:org.springframework.web.bind.annotation
作用:获得请求url中的动态参数
示例:
@RequestMapping("/login/{account}/{password}")
@ResponseBody
public String userProfile(@PathVariable String username, @PathVariable String password){
return "user" + username + " pass = " + password;
}
● @RequestParam
包位置:org.springframework.web.bind.annotation
作用:从请求体中读出对应的数据
示例:
@RequestMapping("/user")
@ResponseBody
public String getUserBlog(@RequestParam("id") int blogId) {
return "blogId = " + blogId;
}
与 @PathVariable 的区别:
@PathVariable 是通过占位符映射的方式获取参数的,即 http://xxxx.com/login/tom/123456,而 @RequestParam 是将对应请求路径下的请求参数值进行匹配映射
例子:都是做登录请求,区别主要在于请求方式,以及 @RequestMappding 书写方式
请求 | 处理 | |
@PathVariable | http://xxxx.com/login/tom/123456 | @RequestMapping("/login/{account}/{password}") @ResponseBody public String userProfile(@PathVariable String username, @PathVariable String password){ return "user" + username + " pass = " + password; } |
@PathParam | http://xxxx.com/login?account=tom&password=123456 | @RequestMapping("/login") @ResponseBody public String getUserBlog(@RequestParam("account") String account, @RequestParam("password") String password) { return "account= " + account+ " password = " + password; } |
● @RequestBody
包位置:org.springframework.web.bind.annotation
作用:将请求体中的 JSON 字符串绑定到相应的 bean 上,也可以绑定到对应的字符串上。要求请求者以 application/json 方式请求
示例:
# 直接转成对象
@requestMapping("/login")
@requestBody
public void login(User user){
System.out.println(userName+" :"+user);
}
# 获取参数
@requestMapping("/login")
public void login(@requestBody String userName,@requestBody String pwd){
System.out.println(userName+" :"+ userName;
}
● @ResponseBody
包位置:org.springframework.web.bind.annotation
作用:将 Controller 对象返回的方法,通过适当的转换器转换为指定格式后,写入到 response 的 body 区中,长用来返回 JSON 数据或是 XML 数据。使用此注解后不会再走视图解析器
示例:
@RequestMapping("/login")
@ResponseBody
public User login(User user){
return user;
}
# User字段:userName pwd
# 那么在前台接收到的数据为:'{"userName":"xxx","pwd":"xxx"}'
# 效果等同于如下代码:
@RequestMapping("/login")
public void login(User user, HttpServletResponse response){
response.getWriter.write(JSONObject.fromObject(user).toString());
}
● 其他注解
@RestController:等同于 @Controller + @ResponseBody
@SessionAttribute:声明将什么模型数据存入session
@CookieValue:获取cookie值
@ModelAttribute:将方法返回值存入model中
@HeaderValue:获取请求头中的值
2.2 SpringMVC 如何处理线程并发问题?是否是线程安全的?如何保证性能?
2.2.1 无状态对象
无状态的对象即是自身没有状态的对象,它们只是用来执行某些操作的,不会因为多个线程的交替调度而破坏自身状态导致线程安全问题
2.2.2 SpringMVC 如何处理线程并发问题?是否是线程安全?
当 SpringMVC 接收到一个请求时,都会新开一个线程去处理。由于 Spring 中多线程环境下共享的对象都是无状态对象,无状态对象无论是单例模式还是多例模式都不存在线程安全问题。
同时,Spring 对 Bean 中非线程安全状态采用的是 ThreadLocal 进行处理,解决了线程安全问题。
2.2.3 常见的处理并发问题的机制
常见的解决多线程中相同变量的访问冲突,有两种方式:线程同步机制(锁) 或 ThreadLocal
线程同步机制:同步机制采用了"时间换空间"的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队等待
ThreadLocal: 采用了"空间换时间"的方式,为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。由于每一个线程都拥有了自己的变量副本,就无需锁了。在编写代码时,应该将不安全的变量封装进 ThreadLocal
2.2.4 SpringMVC 如何保证性能?
- SpringMVC 会对每一个请求创建独有的一个线程,保证了处理效率
- SpringMVC 默认是使用单例模式的无状态对象,省去了对象创建的 GC 的时间,效率略优于 Strust2
2.3 父子容器
2.3.1 什么是父子容器
在 Spring 的框架核心中,容器是核心思想,用来管理 Bean 的整个生命周期的。在项目中,容器不一定只有一个,Spring 中可以包括多个容器,且容器之间有上下层关系。最常见的就是 SpringMVC 和 Spring,其中 Spring 为父容器,SpringMVC 为子容器。整个结构可以想象成一棵树,Spring 是主干, SpringMVC 是分支
2.3.2 父子容器的特点
- 父容器和子容器之间是相互隔离的,各自内部可以存在名称相同的 bean
- 子容器对父容器不可见,父容器对子容器可见(子容器可以访问父容器的 bean,父容器不能访问子容器的 bean)
- 调用子容器的 getBean 方法获取 bean 的时候,会沿着当前容器开始向上查找,知道找到对应的 bean 为止
- 子容器可以注入父容器的 bean,而父容器无法注入子容器的 bean
2.3.3 SpringMVC 中只使用一个容器是否可行?
可以在 SpringMVC 的配置中管理全部对象,但不能在 Spring 的配置中管理 controller(向上不可见)
2.3.4 为什么SpringMVC中要使用父子容器?
SpringMVC 中的父子容器:
在 SpringMVC 中,将 controller 层交给 SpringMVC 容器加载,其他的 service 和 dao 层则是交给 Spring 容器加载。在 controller 层中注入 service 层的 bean
作用:
- 避免模块混乱:避免在 service 层或 dao 层注入 controller 层的 bean,导致依赖层次混乱
- 明确各自需求:父子容器的需求不同。在 Sring 容器中需要使用到事务,而 SpringMVC 中就完全用不到。将互不相关的东西隔开,让视图层相关代码和业务层相关代码分离开,也有利于代码维护和容器加载速度
- 拓展性更强:使用Spring 可以兼容更多的模块,如 mybatis、redis 等
2.3.5 SpringMVC 和 Spring 父子容器的扫描配置
先增加 springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 容器扫描时,只扫描 Controller -->
<context:component-scan base-package="xyz.tom">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 视图解析器对象 -->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 文件目录 -->
<property name="prefix" value="/WEB-INF/pages/"></property>
<!-- 文件后缀名 -->
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 告诉前端控制器,哪些静态资源不拦截 -->
<mvc:resources mapping="/js/**" location="/js/**"></mvc:resources>
<mvc:resources mapping="/css/**" location="/css/**"></mvc:resources>
<mvc:resources mapping="/images/**" location="/images/**"></mvc:resources>
<!-- 开启SpringMvc框架注解的支持 -->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
增加 applicationContext.xml 文件,里面为 spring 配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="xyz.tom">
<!-- 配置Controller注解不扫描 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>
在 web.xml 文件中添加监听器
ContextLoaderListener 的作用就是在启动 web 容器时,自动装配 ApplicationContext 的配置信息
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定加载 springmvc.xml -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 配置Spring的监听器,默认只加载WEB-INF目录下的applicationContext.xml配置文件 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
</web-app>
2.4 Springmvc 统一异常处理
SpringMVC 支持通过统一异常处理,实现友好的前后端交互。主要是通过继承 HandlerExceptionResolver 接口。
构建 exception 文件夹,和自定义异常处理类 SysException.java
public class SysException extends Exception{
private String message;
public SysException(String message) {
this.message = message;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
构建异常处理类 SysExceptionResolver.java
public class SysExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception ex) {
SysException e = null;
if (ex instanceof SysException) {
e = (SysException) ex;
} else {
e = new SysException("系统正在维护");
}
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("errorMsg", e.getMessage());
modelAndView.setViewName("error");
return modelAndView;
}
}
修改 springmvc.xml 文件,配置异常处理器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="xyz.tom.www"></context:component-scan>
<!-- 视图解析器对象 -->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 文件目录 -->
<property name="prefix" value="/WEB-INF/pages/"></property>
<!-- 文件后缀名 -->
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 告诉前端控制器,哪些静态资源不拦截 -->
<mvc:resources mapping="/js/**" location="/js/**"></mvc:resources>
<mvc:resources mapping="/css/**" location="/css/**"></mvc:resources>
<mvc:resources mapping="/images/**" location="/images/**"></mvc:resources>
<!-- 配置异常处理器 -->
<bean id="sysExceptionResolver" class="xyz.tom.www.exception.SysExceptionResolver"></bean>
<!-- 开启SpringMvc框架注解的支持 -->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
2.5 SpringMVC 中文乱码的解决方式
2.5.1 Post 请求
通过在 web.xml 中配置过滤器,指定编码格式来解决
<!-- 配置解决中文乱码的过滤器 -->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2.5.2 Get 请求
get请求中文参数出现乱码解决方法有两个:
①修改tomcat配置文件添加编码与工程编码一致,如下:
<ConnectorURIEncoding="utf-8" connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443"/>
②另外一种方法对参数进行重新编码:
String userName = new String(request.getParamter("userName").getBytes("ISO8859-1"),"utf-8")
ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码。
2.6 请求带参转发,和页面重定向
使用 forward 转发请求,使用 redirect 重定向页面
@RequestMapping("/testVoid")
public void testVoid(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
System.out.println("testVoid方法执行了...");
// 请求转发
req.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(req, res);
// 重定向,不能跳转 WEB-INF 中的
res.sendRedirect(req.getContextPath() +"/index.jsp");
// 设置中文乱码
res.setCharacterEncoding("UTF-8");
res.setContentType("text/html;charset=UTF-8");
res.getWriter().print("你好");
return;
}
2.7 Filter(过滤器) 、Interceptor(拦截器)、Listener(监听器)
2.7.1 三者的区别
Filter | Interceptor | Listener | |
实现机制 | 依赖于 servlet 框架,基于 Java 回调机制实现 | 依赖于 spring 容器,基于 Java 的反射机制实现 | web监听器是Servlet中的特殊的类,用于监听web的特定事件 |
拦截时间 | 拦截方法的请求和响应,并对请求和响应做处理 | 在方法处理之前和方法处理之后进行操作 | 当监听的对象的方法被调用,或是属性改变时 |
调用次数 | 只能在容器初始化时被调用一次 | 在 controller 中可以多次触发 | 有变化时触发一次 |
用途 | 当有一堆东西,只希望选择出符合的东西 | 在一个请求进行中的时候,你想干预它的进展,甚至控制是否终止 | 专门用来监听另一个java对象的方法调用或属性改变,当被监听对象发生变动时,监听器某个方法立即被执行 |
使用场景 | 设置字符编码,登录验证、鉴权操作 | 用于处理页面提交的请求响应并进行处理,例如做国际化,做主题更换,过滤等 | 统计网站的访问量、统计在线人数和在线用户、特定功能定制 |
生命周期 | 随应用启动而启动,随应用消亡而销毁 | 随应用启动而启动,随应用消亡而销毁 | 随应用启动而启动,随应用消亡而销毁 |
启动顺序 | 监听器 > 过滤器 > 拦截器 > AOP |
2.7.2 Filter 与 Interceptor 的区别
过滤器和拦截器的作用看起来非常相似,都是对客户端发来的请求进行处理,区别如下:
● 作用域不同
过滤器依赖于 servlet 容器,只能在 servlet 容器, web 环境下使用
拦截器依赖于 spring 容器,可以在 spring 容器中使用,无论此时 Spring 是什么环境
● 细粒度不同
过滤器的控制比较粗,只能在请求进来时进行处理,对请求和响应进行包装
拦截器支持更精细的控制,可以在 controller 对请求处理之前或之后被调用,也可以在渲染视图呈现给用户后被调用
● 中断链执行的难易程度不同
过滤器较为复杂,需要处理请求和响应对象来引发中断,如将用户重定向到错误界面
拦截器只需要在 preHandle 方法内返回 false 进行中断
● 总结
拦截器相比过滤器有更细粒度的控制,依赖于Spring容器,可以在请求之前或之后启动,过滤器主要依赖于servlet,过滤器能做的,拦截器基本上都能做
2.7.3 实现方式
https://blog.csdn.net/qq_34416331/article/details/107532076
本文地址:https://blog.csdn.net/qq_34416331/article/details/107489618
上一篇: composer拓展包开发
下一篇: Android 自定义对话框Dialog