一、如何理解 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映射器。
3、使用 web.xml 的方式和基于 Servlet 3.0 的方式,各自都有哪些要注意的?
web.xml:
- DispatcherServlet 需要配置对应的servlet-mapping,作为拦截信息
- DispatcherServlet还需要配置contextConfigLocation初始化参数,配置的是处理器,即Controllers;如果不配的话,它会用一种特殊的规则去找 xml 配置文件:classpath:{servlet-name}-servlet.xml ,中间的花括号替换为上面配置的 servlet-name。
Servlet3.0:
- 使用 Servlet3.0规范整合Spring或者SpringMVC时,记得干掉Web.xml,否则会有两个IOC容器。
- 整合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、整体工作流程是怎样的?
- DispatcherServlet 接收客户端请求、委托HandlerMapping找打对应的Handler
- HandlerMapping 根据uri返回对应的 HandlerExecutionChain
- DispatcherServlet 接收到 HandlerExecutionChain,委托HandlerAdapter执行
- HandlerAdapter 根据 HandlerExecutionChain 找到对应的 Handler 进行执行,最后返回对应的 ModelAndView
- DispatcherServlet接收到 ModelAndView,委托 ViewResolver 执行
- ViewResolver 根据 ModelAndView 返回对应的 View响应或Json响应
- 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 规范的异步请求支持是如何操作的?
- @WebServlet 添加一个属性声明:asyncSupported = true ,代表让 Servlet 支持异步请求
- 利用 HttpServletRequest 的 AsyncContext 来完成异步调用
2、SpringWebMvc 如何支持异步请求?
- 利用返回值 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 ......";
};
}
- 利用 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 的配置方式一致。