一、如何理解 MVC 、三层架构?

MVC三个字母分别代表 Model View Controller。在 JavaEE 的三层架构中,三层架构分别是 web 层、service 层、dao 层,它们的职责及调用逻辑:


  • web表现层:接收客户端请求,分发请求
  • service层:处理复杂业务逻辑、返回处理结果、与持久层交互
  • dao持久层:数据化持久

二、Servlet 整合 Spring

1、利用 web.xml 整合 Spring

如果是基于 web.xml 来整合 Spring,我们需要利用 SpringFramework 中提供的 ContextLoaderListener 监听器,给 web.xml 中提供 IOC 容器初始化时机;它默认会去找 WEB-INF 目录下的 applicationContext.xml 文件,如果我们是自定义配置文件,需要在 web.xml 中加入一个初始化参数:

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-web.xml</param-value>
</context-param>

如何在 Servlet 中获取上面 IOC 容器中的组件。

重写 HttpServlet 的父类 GenericServlet的init方法:

@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
ServletContext servletContext = config.getServletContext();
// 获取IOC容器
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
// 利用 WebApplicationContext 获取Bean 实例
}

注解依赖注入:

@Autowired
private UserService userService;

@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
ServletContext servletContext = config.getServletContext();
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this, servletContext);
}

2、基于 Servlet3.0 规范整合 Spring

2.1 Servlet 3.0 提出的 ServletContainerInitializer 有什么作用?

借助 Java 的 SPI 技术,可以从项目或者项目依赖的 jar 包中,找到一个 ​​/META-INF/services/javax.servlet.ServletContainerInitializer​​ 的文件,并加载项目中所有它的实现类。目的是为了代替 web.xml 的,所以它的加载时机也是在项目的初始化阶段就触发的。

ServletContainerInitializer 这个接口通常会配合 @HandlesTypes 注解一起使用,这个注解中可以传入一些我们所需要的接口/抽象类,支持 Servlet 3.0 规范的 Web 容器会在容器启动项目初始化时,把这些接口 / 抽象类的实现类全部都找出来,整合为一个 Set ,传入 ServletContainerInitializer 的 onStartup 方法参数中,这样我们就可以在 onStartUp 中拿到这些实现类,随机反射创建调用等等的动作。

Servlet 3.0 规范设计这个用法,目的之一是为了做到组件的可插拔,有了组件的可插拔,就可以在导入一个带 ServletContainerInitializer 的实现类的 jar 包时,自动加载对应的实现类。

2.2 Spring 支持 Servlet3.0 规范

在 Spring-web 的 jar 包中,可以找到​​/META-INF/services/javax.servlet.ServletContainerInitializer​​文件,内容如下:

org.springframework.web.SpringServletContainerInitializer

翻开它的源码,可以发现它需要的实现类的接口类型是 WebApplicationInitializer :

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer

但是我们不需要直接实现 WebApplicationInitializer 接口,因为 Spring 帮我们封装了一个 AbstractContextLoaderInitializer 的抽象类,我们只需要编写一个 AbstractContextLoaderInitializer 的实现类即可。如下

public class DemoWebApplicationInitializer extends AbstractContextLoaderInitializer {

@Override
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(UserConfiguration.class);
return ctx;
}
}

如果是使用 Servlet3.0 来完成 Spring 的整合,记得干掉 web.xml 文件,不然会产生两个 IOC 容器。

三、Servlet 整合 Spring mvc

1、基于 Web.xml

Spring MVC 的核心控制器是一个 Servlet,所以我们需要在 Web.xml 配置一个 DispatcherServlet,

servlet-mapping可以设置为/,表示拦截所有请求、静态资源。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">

<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

但是我们还需要让DispatcherServlet知道他要对哪些@Controlelr进行转发,所以我们需要给DispatcherServlet增加一个初始化参数,叫contextConfigLocation:

<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
</servlet>

2、基于Servlet3.0规范

基于Servlet3.0规范整合SpringMVC和整合Spring基本一致,首先我们需要干掉 web.xml,避免出现两个IOC容器。

上面提到,Spring是利用 WebApplicationInitializer 的抽象类 AbstractContextLoaderInitializer 来完成 Spring 的整合;而整合 SpringWebMvc 实现注解驱动开发时,我们通常是用 AbstractAnnotationConfigDispatcherServletInitializer 这个类来搞定,如果要 xml 配置文件与注解驱动混合使用,则用 AbstractDispatcherServletInitializer 即可。

AbstractAnnotationConfigDispatcherServletInitializer 我们需要重写三主要的方法:


  • getRootconfigClasses:返回根容器的配置类
  • getServletConfigClasses:返回Servlet子容器配置类
  • getServletMappings:返回DispatcherServlet拦截的路径

看到官方文档的截图,我们可以发现:


  • 根容器包含的是:Service层、Dao层deng等组件
  • Servlet子容器包含的是:Controlles、视图解析器、Handler映射器。
    2021-08-23阅读小笔记:Spring MVC 介绍_Servlet整合

3、使用 web.xml 的方式和基于 Servlet 3.0 的方式,各自都有哪些要注意的?

web.xml:


  1. DispatcherServlet 需要配置对应的servlet-mapping,作为拦截信息
  2. DispatcherServlet还需要配置contextConfigLocation初始化参数,配置的是处理器,即Controllers;如果不配的话,它会用一种特殊的规则去找 xml 配置文件:classpath:{servlet-name}-servlet.xml ,中间的花括号替换为上面配置的 servlet-name。

Servlet3.0:


  1. 使用 Servlet3.0规范整合Spring或者SpringMVC时,记得干掉Web.xml,否则会有两个IOC容器。
  2. 整合Spring,需要实现AbstractContextLoaderInitializer;而整合SpringMVC需要实现AbstractAnnotationConfigDispatcherServletInitializer或AbstractDispatcherServletInitializer

四、如何理解 SpringWebMvc 的工程形成的父子容器?

Web.xml形成父子容器:父容器是利用 ContextLoaderListener,而子容器是利用 DisptcherServlet

Servlet3.0形成父子容器:父容器是利用 getRootconfigClasses 方法,而子容器是利用 getServletConfigClasses 方法。

这种父子容器的设计,好处有两个:第一,形成层级关系后,Controller 可以拿到 Service ,而 Service 拿不到 Controller ,可以以此形成一道隔离;第二,如果真的出现特殊情况,需要注册多个 DispatcherServlet 的时候,不必注册多套 Service 和 Dao ,每个 Servlet 子容器都从这一个根容器中取 Service 和 Dao 即可。

五、拦截器和过滤器对比

拦截器是 SpringWebMvc 的概念,而过滤器是 Servlet 的概念。Servlet 、Filter 、Listener 共同称为 Servlet 三大组件,它们都需要依赖 Servlet 的 API 。而拦截器不同,拦截器是 SpringWebMvc 提出,它只是 SpringWebMvc 框架中的一个 API 设计罢了。所以我们可以总结出第一个区别:拦截器是框架的概念,而过滤器是 Servlet 的概念。

过滤器 Filter 是在 web.xml 或者借助 Servlet 3.0 规范来注册,任何来自于 Servlet 容器(Tomcat)的请求都会走这些过滤器;拦截器既然是框架的概念,而 SpringWebMvc 的核心是一个 DispatcherServlet ,所以拦截器实际上是在 DispatcherServlet 接收到请求后才有机会工作的,对于 DispatcherServlet 没有接收到的请求,拦截器只能干瞪眼。所以接下来总结出的第二个区别:过滤器可以拦截几乎所有请求,而拦截器只能拦截到被 DispatcherServlet 接收处理的请求。

过滤器由 Servlet 容器创建,与 SpringFramework 的 IOC 没有任何关系,所以无法借助依赖注入,给过滤器中注入属性;而拦截器是被 SpringFramework 的 IOC 统一管理的,它就是一个一个的普通的 bean ,所以可以任意注入需要的 bean 。所以第三条区别:拦截器可以借助依赖注入获取所需要的 bean ,而过滤器无法使用正常手段获取。

1、拦截器的拦截时机

一共有三个拦截时机:


  • preHandle :在执行 Controller 的方法之前触发,可用于编码、权限校验拦截等
  • postHandle :在执行完 Controller 方法后,跳转页面 / 返回 json 数据之前触发
  • afterCompletion :在完全执行完 Controller 方法后触发,可用于异常处理、性能监控等

2、多拦截器的执行机制

只有 preHandle 方法返回 true 时,afterCompletion 方法才会调用

只有所有 preHandle 方法的返回值全部为 true 时,Controller 方法和 postHandle 方法才会调用

六、什么是跨域?为什么会出现跨域现象?如何解决?

跨域是一种现象,是由浏览器的同源策略引起的。

同源策略,是在浏览器上对资源的一种保护策略,最初同源策略只是用于保护网页的 cookie ,后演变的越来越严格,下面会提到。同源策略规定了三个相同:协议相同、域名 / 主机相同、端口相同。当这三个因素都相同时,则浏览器会认为访问的资源是同一个来源。

同源策略的保护主要是**保护 cookie **,试想如果你在一个银行的网站上访问,当登录银行电子账户之后,同时你又在这个浏览器上开了一个新的页签,访问了别的网站,如果此时浏览器没有同源策略的保护,则浏览器中存放的银行网站的 cookie 会一并发送给这个别的网站。万一这个网站有啥危险的想法,那你的银行账户就岌岌可危了。因此,同源策略要保护我们的上网安全。

只要触发以下三种情况之一,都会引起跨域问题:


  • http 访问 https ,或者 https 访问 http
  • 不同域名 / 服务器主机之间的访问
  • 不同端口之间的访问

目前来讲,浏览器的同源策略,在处理跨域问题时的态度如下:


  • 非同源的 cookie 、localstorage 、indexedDB 无法访问
  • 非同源的 iframe 无法访问(防止加载其他网站的页面元素)
  • 非同源的 ajax 请求可以访问,但浏览器拒绝接收响应

七、SpringWebMvc

1、整体工作流程是怎样的?


  1. DispatcherServlet 接收客户端请求、委托HandlerMapping找打对应的Handler
  2. HandlerMapping 根据uri返回对应的 HandlerExecutionChain
  3. DispatcherServlet 接收到 HandlerExecutionChain,委托HandlerAdapter执行
  4. HandlerAdapter 根据 HandlerExecutionChain 找到对应的 Handler 进行执行,最后返回对应的 ModelAndView
  5. DispatcherServlet接收到 ModelAndView,委托 ViewResolver 执行
  6. ViewResolver 根据 ModelAndView 返回对应的 View响应或Json响应
  7. DispatcherServlet 将响应返回给客户端

2、DispatcherServlet 委托的三大核心组件分别都具备哪些职责?

HandlerMapping:

HandlerMapping 是处理器映射器,使用它,可以根据请求的 uri ,找到能处理该请求的一个 Handler ,并组合可能匹配的拦截器,包装为一个 HandlerExecutionChain 对象返回出去。


  • RequestMappingHandlerMapping :支持 @RequestMapping 注解的处理器映射器【最最常用】
    • 这种 HandlerMapping 就是我们前面用的,不需要显式注册也可以。
  • BeanNameUrlHandlerMapping :使用 bean 的名称作为 Handler 接收请求路径的处理器映射器
    • 使用该方式,需要我们编写的 Controller 类实现 Controller 接口(对,WebMvc 中有一个名为 Controller 的接口)。下面是一个简单的示例:
  • public class DepartmentListController implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { return null; }}​因为使用该方式编写的 Controller ,一个类只能接收一个请求路径,已被淘汰开发中不使用。
  • SimpleUrlHandlerMapping :集中配置请求路径与 Controller 的对应关系的处理器映射器
    • 这种 HandlerMapping 可以配置请求路径,与 Controller 的映射关系,例如下面是一个简单的 SimpleUrlHandlerMapping 配置:
  • <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/department/list">departmentListController</prop> <prop key="/department/save">departmentSaveController</prop> </props> </property></bean>​使用该方式,依然需要编写的 Controller 类实现接口,并且还需要额外配置 uri 与 Controller 的映射关系,因此也被淘汰了。

HandlerAdapter:

HandlerAdapter ,处理器适配器,从接口名上就能得知,它蕴含着一个适配器模式。

SpringWebMvc 中,对于 HandlerAdapter 的核心实现主要有以下几种:


  • RequestMappingHandlerAdapter :基于 @RequestMapping 的处理器适配器,底层使用反射调用 Handler 的方法【最最常见】
  • SimpleControllerHandlerAdapter :基于 Controller 接口的处理器适配器,底层会将 Handler 强转为 Controller ,调用其 handleRequest 方法
  • SimpleServletHandlerAdapter :基于 Servlet 的处理器适配器,底层会将 Handler 强转为 Servlet ,调用其 service 方法

ViewResolver:

ViewResolver ,视图解析器,顾名思义它是解析和生成视图用的。

可处理视图响应和Json响应。

八、异步请求

1、Servlet 3.0 规范的异步请求支持是如何操作的?


  1. @WebServlet 添加一个属性声明:asyncSupported = true ,代表让 Servlet 支持异步请求
  2. 利用 HttpServletRequest 的 AsyncContext 来完成异步调用

2、SpringWebMvc 如何支持异步请求?

  1. 利用返回值 Callable 完成,响应类型也为 Json 响应
@GetMapping("/async")
public Callable<String> async() {
System.out.println("AsyncController async ......" + Thread.currentThread().getName());
return () -> {
System.out.println("AsyncController Callable ......" + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(5);
return "AsyncController async ......";
};
}
  1. 利用 DeferredResult 返回值完成
private DeferredResult<String> deferredResult = null;

@GetMapping("/deferred")
public DeferredResult<String> deferred() {
// 5000L表示5秒没有传值,则认定超时
DeferredResult<String> deferredResult = new DeferredResult<>(5000L);
this.deferredResult = deferredResult;
return deferredResult;
}

@GetMapping("/addData")
public void addData() {
if (this.deferredResult != null) {
this.deferredResult.setResult("AsyncController deferredResult setResult");
this.deferredResult = null;
}
}

九、DispatcherServlet 初始化:

1、基于web.xml初始化

使用 web.xml 配置 DispatcherServlet 的时候,即便我们不配置任何内置组件,SpringWebMvc 可以通过预先定义好的内置组件,在 IOC 容器已经初始化接近完毕的时候,借助事件监听(ContextRefreshListener),回调 DispatcherServlet 使其初始化默认组件的落地实现,从而达到兜底配置。


在 spring-webmvc jar包下有一个 properties 文件,叫 DispatcherServlet.properties 文件,里面包含所有 WebMVC的核心组件的落地实现。


# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
org.springframework.web.servlet.function.support.RouterFunctionMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
org.springframework.web.servlet.function.support.HandlerFunctionAdapter


org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

2、基于 Servlet3.0 规范初始化

基于 Servlet 3.0 规范的方式,通过编写继承 AbstractAnnotationConfigDispatcherServletInitializer 的子类,分别提供根容器与 WebMvc 子容器的配置类,底层会帮我们初始化对应的容器,并且借助 ServletContext 注册 DispatcherServlet 。后续的处理逻辑与基于 web.xml 的配置方式一致。