1、概述
在了解SpringMVC之前,我们需要知道MVC架构设计模式以及J2EE的三层架构设计,MVC模式如下图所示:
三层结构如下图所示:
SpringMVC则是基于 MVC 设计理念的 Web 框架,集成于SpringFramework中。SpringMVC也就是我们熟知的'SSM'中的'S',它通过一套 MVC 注解,让 POJO 成为处理请求的控制器,而无须实现任何接口,并且支持REST风格URL,整体采用松散耦合、可插拔组件结构,比其他 MVC 框架更具扩展性和灵活性。
SpringMVC 基于 Servlet 规范实现,所以基于 SpringMVC 的项目,需要 Servlet 容器服务器支撑,比如常用的tomcat。
2、快速上手
1)环境准备
首先需要导入项目所需要的maven依赖,包括spring-web、spring-webmvc、servlet-api、jsp相关依赖、IoC相关依赖。基本环境为Spring5.1.3.RELEASE,tomcat8。
2)逻辑代码
①控制器类
首先需要创建一个用于分发请求的控制器类,在SpringMVC中只要给类标上@Controller
注解即表示这是个控制器类,之后使用@RequestMapping
注解在方法上标明请求路径,方法的返回值即是对应的视图页面(这里以jsp为例)。
package top.jtszt.controller;
@Controller
public class HelloController {
@RequestMapping("/hello")
public String hello(){
return "/WEB-INF/pages/hello.jsp";
}
}
②配置相关
有了@Controller
注解标注的控制器类之后,我们还需要将组件扫描加入IoC容器中:
<!-- SpringMVC-config.xml -->
<beans ...>
<context:component-scan base-package="top.jtszt"/>
</beans>
SpringMVC 的核心控制器是一个 Servlet 叫 DispatcherServlet
。所以我们需要在 web.xml中配置它,并且需要配置对应的Spring配置文件指向与启动级别:
<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:SpringMVC-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
这里如果不显式配置Spring配置文件路径,默认会按照classpath:{servlet-name}-servlet.xml
去查找配置文件,并且需要注意的是这里servlet的拦截路径为/,如果拦截为/*则表示连同jsp页面都拦截,而jsp是我们用来展示的,不可以进行拦截。
之后是配置tomcat以及jsp页面
③测试
配置完毕,我们可以启动项目,在浏览器查看效果。
3、视图解析器
1)概述
SpringMVC中的视图解析器(ViewResolver)是前端控制器中九大组件之一,用于将逻辑视图转化为物理视图。SpringMVC会先将控制器类的 String/ModelAndView/View
类型的返回值都转化为ModelAndView
类型,之后视图解析器把它解析为具体的View
类型的视图对象。
ViewResolver接口中有几个主要的实现类,可以实现视图解析功能:
•BeanNameViewResolver:将视图名解析为一个Bean•InternalResourceViewResolver:将视图名解析为一个URL文件•jasperReportsViewResolver:将视图名解析为报表文件对应的URL
我们可以选择一种视图解析器或混用多种视图解析器,并且每个视图解析器都实现了 Ordered 接口并开放出一个 order 属性,可以通过 order 属性指定解析器的优先顺序,order 越小优先级越高。
2)解析前后缀
在对jsp页面的解析中一般使用InternalResourceViewResolver
,最常见的是配置视图解析的前缀和后缀,对于xml配置来说直接配置bean即可:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
其中 prefix
代表解析器拼串开头,suffix
代表解析器拼串结尾,这样配置之后,我们在控制器类的返回只需要返回hello即可。
/WEB-INF/pages/hello.jsp ---> hello
3)拼串与否?
这里还涉及到一个返回值拼串与否的问题,正常来说返回值都是按照设定的前缀和后缀进行拼串之后返回视图,而如果出现以下两种情况,那么视图解析器将不会拼前后缀:
•返回值以forward:
开头,代表的是直接转发•返回值以redirect:
开头,代表的是重定向
除了以上两种情况,其他的都会进行拼串处理。
4、数据传递与参数绑定
现在已经可以实现请求分发与响应功能了,那么如果页面的数据需要在请求时才渲染要怎么做,换句话说,数据传递要怎么办?这时可以将数据放置在request域中,在页面去取出对应的数据。传统的引入HttpServletRequest传递的方式就不介绍了,这里主要介绍SpringMVC中提供了数据传递功能。
1)ModelAndView
ModelAndView
能封装数据和返回跳转的视图信息,在其中存储的数据实际上是存储在request域中。可以在参数位置传入ModelAndView
对象,之后调用 setViewName
设置视图名,调用addObject
设置放置的数据信息(键值对) 。
@RequestMapping("/hello")
public ModelAndView hello(ModelAndView mv) {
mv.addObject("msg", "this is msg from modelAndView");
mv.setViewName("hello");
return mv;
}
也可以手动创建一个ModelAndView
对象,传入的参数为要跳转的视图名,之后调用该对象的addObject
方法设置数据信息。
@RequestMapping("/hello")
public ModelAndView hello(){
ModelAndView modelAndView = new ModelAndView("hello");
modelAndView.addObject("msg", "this is msg from modelAndView");
return modelAndView;
}
之后在jsp页面 ${msg}
取出即可。
当然这里可传入的不只是简单信息,也可以传入Map、List等,在jsp页面通过c:foreach取出即可。
2)Map与Model
Spring MVC 在调用方法前会创建一个隐含的模型对象作为模型数据的存储容器,如果方法的入参为 Map 或 Model 类型,Spring MVC 会将隐含模型的引用传递给这些入参。
在方法体内,我们可以通过入参对象访问到模型中的所有数据,也可以向模型中添加新的属性数据。
实际上传入无论是Model、Map还是ModelMap最终都会被转化为BindingAwareModelMap对象。
代码语言:javascript复制@RequestMapping("/hello")
public String hello(ModelMap modelMap,
Map<String,Object> map,
Model model){
modelMap.addAttribute("msg","hello modelMap");
map.put("msg2", "hello map");
model.addAttribute("msg3","hello model");
return "hello";
}
3)参数收集
① 原生数据类型
这种类型的数据除了可以在入参位置声明 HttpServletRequest
,之后通过request.getParameter()
获取参数之外,还可以直接在入参位置传入需要获取的参数。
@RequestMapping("/hello")
public String hello(HttpServletRequest request) {
String name = request.getParameter("name");
System.out.println("name is " name);
return "result";
}
代码语言:javascript复制@RequestMapping("/hello")
public String hello(String name) {
System.out.println("name is " name);
return "result";
}
当我们访问 /hello?name="test"
时就可以在控制器中成功打印结果。
②模型类型
假设有一个Employee对象,包含id和name属性,现在我们需要将参数中的name与id获取之后包装成Employee对象,这时我们不需要手动进行包装。在SpringMVC中,如果请求的参数名称,与模型类中的属性一一对应,那么SpringMVC 会按请求参数名和 POJO 属性名进行自动匹配,自动为该对象填充属性值,支持级联属性。
代码语言:javascript复制@RequestMapping("/hello")
public String hello(Employee employee) {
System.out.println("Employee is " employee);
return "result";
}
当我们访问 /hello?id=1&name="test"
时就可以在控制器中成功打印结果。
③使用注解
在SpringMVC中还可以通过 @RequestParam
注解获取参数,这种获取方式不要求用户传参与规定参数一一对应,可以指定参数名获取。
@RequestMapping("/hello")
public String hello(@RequestParam("username")String name) {
System.out.println("name is " name);
return "result";
}
这时访问 /hello?username="test"
时也可以在控制器中成功打印结果。
现在我们解决了参数名不一致的获取问题,那么如果不带参数访问呢?实测会抛异常,因为默认是需要传入参数的。为此该注解还有一个属性:required
,它表示请求参数中是否必须携带指定的参数。默认值是 true 。还有一个属性 defaultValue
,它可以指定参数不传递时的默认值。
@RequestMapping("/hello")
public String hello(@RequestParam(value = "username",required = false,defaultValue = "default")String name) {
System.out.println("name is " name);
return "result";
}
这时访问 /hello
也不会抛异常,并且会打印default。
④其他注解
除了获取参数的注解,SpringMVC中还有用于获取header的某个属性值的注解 @RequestHeader
,以及获取cookie中的某个属性值的注解 @CookieValue
。
@RequestMapping("/hello")
public String hello(@RequestHeader("User-Agent")String userAgent,
@CookieValue("JSESSIONID")String jid){}
4)乱码问题
在接收POST请求参数时,可能会出现中文乱码的问题,这是编码集不一致导致的。SpringMVC中提供了CharacterEncodingFilter
用于解决乱码问题,它有三个属性,encoding
代表编码格式, forceRequestEncoding
代表对请求设置编码, forceResponseEncoding
代表对响应设置编码。这里我们只需要在web.xml
中配置好这个filter,并且设置拦截路径为 /*
即可。
<web-app>
<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>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
如果是GET请求出现乱码问题,那应该考虑的是Tomcat的编码设置问题。
5、请求映射
SpringMVC中使用 @RequestMapping
注解为控制器指定可以处理哪些 URL 请求,这个注解可以标注在类上,也可以标注在方法上。如果@RequestMapping("/admin")
标注在类上,那么该类下的所有方法都须以 /admin
为前缀才可以访问。
//如:
@Controller
@RequestMapping("/admin")
public class MyController {}
代码语言:javascript复制@RequestMapping("/hello")
public String hello() {return "hello"}
那么如果要访问到hello.jsp页面必须通过 /admin/hello
访问。
此外 @RequestMapping
中也提供了对于请求头的一些限制属性,其中 value 表示请求URL,method 表示请求方法,params 代表请求参数,heads 表示请求头,他们之间是与的关系,联合使用多个条件可让请求映射更加精确化。
此外SpringMVC也提供了几个限定请求方式的注解:
•@GetMapping("xx") :限定get方式•@PostMapping("xx") :限定post方式•@PutMapping("xx"):限定put方式•@DeleteMapping("xx"):限定delete方式
6、RESTful风格
REST即Representational State Transfer(表现层状态转化),是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。
每发出一个请求,就代表了客户端和服务器的一次交互过程。HTTP协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生“状态转化”。而这种转化是建立在表现层之上的,所以就是 “表现层状态转化”。
具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。而RESTful风格就是将这四种方式与资源的操作联系起来。
举个例子,假设现在要实现员工信息的增删改查,那RESTful风格的url就可以是下面这样的:
•员工列表:/emps get方法•员工添加:/emp post方法•员工修改:/emp/{id} put方法•员工删除:/emp/{id} delete方法•员工查询:/emp/{id} get方法
这里的{id}代表的是动态的id传入,在SpringMVC中可以使用@PathVariable("id")
标注在Controller的参数位置来获取URL中的指定的值。
@RequestMapping(value = "/emp/{id}")
public String getEmp(@PathVariable("id")Integer id){...}
这样当我们以GET方式访问/emp/1
时,在getEmp方法中就可以拿到具体的id。
7、返回json数据
1)导包、配置
SpringMVC中整合了主流的json转换工具,默认使用 jackson 进行 json 格式转换。使用jackson需要先导入maven依赖,包括:jackson-core、jackson-annotations、jackson-databind
。导入依赖之后还需要配置json转换器,一种比较简单的方法是在SpringMVC的配置文件中加入注解驱动配置,也可以手动配置。
<!-- 开启注解驱动 -->
<mvc:annotation-driven/>
2)@ResponseBody
此外还需要在Controller对应方法上标注 @ResponseBody
,表明方法返回信息作为响应体返回(由jackson包封装为json格式)。
//假数据做示例
Employee emp = new Employee(1, "test01", "123@163.com");
代码语言:javascript复制@ResponseBody
@RequestMapping("/getinfo")
public Employee getInfo(){
return emp;
}
还可以将返回值类型设置成ResponseEntity<T>
,之后在方法中new该对象,以此方法可以设置响应体、响应头以及响应状态码。
@RequestMapping("/getinfo2")
public ResponseEntity<Employee> getInfo2(){
return new ResponseEntity<>(emp,HttpStatus.OK);
}
3)@RestController
当一个Controller类中的所有映射方法都要返回json数据,那么在每个方法上都标注上@ResponseBody
注解就不太现实了。这时可以直接在类上标注@ResponseBody
,也可以使用@RestController
注解标注在Controller类上,其相当于@Controller
与 @ResponseBody
。
4)@RequestBody
与 @ResponseBody
相对的还有 @RequestBody
,它可以把请求体中json格式的数据封装成指定的对象。
@PostMapping("/saveinfo")
@ResponseBody
public void saveInfo(@RequestBody Employee employee) {
System.out.println(employee);
}
当我们POST此路径并带上json格式的数据时,其会将请求体中的数据(如果存在配对)封装成Employee对象。
除此之外还可以将参数声明为 HttpEntity<T>
,这样声明的参数除了可以获得请求体还可以获得请求头。
@PostMapping("/saveinfo2")
@ResponseBody
public void saveInfo(HttpEntity<Employee> httpEntity) {
Employee emp = httpEntity.getBody();
System.out.println(emp);
}
8、静态资源配置
1)默认处理器
如果我们尝试引入项目路径下的js或者css,会发现并不能访问到。这个问题源于我们将SpringMVC中的DispatcherServlet
请求映射配置为 /,这时静态资源的请求也会被当成一个普通请求处理,因找不到对应处理器而导致错误。
这时可以在 SpringMVC 的配置文件中配置默认处理器解决静态资源的问题:
代码语言:javascript复制<mvc:default-servlet-handler/>
起作用的是 SpringMVC 上下文中定义的 DefaultServletHttpRequestHandler
,它会对进入 DispatcherServlet
的请求进行筛查,如果发现是没有经过映射的请求,就将该请求交由 WEB 应用服务器默认的 Servlet 处理;如果不是静态资源的请求,才由 DispatcherServlet 继续处理 一般 WEB 应用服务器默认的 Servlet 的名称都是 default。若所使用的 WEB 服务器的默认 Servlet 名称不是 default,则需要通过 default-servlet-name
属性显式指定
2)解析配置
除了使用默认处理器解析之外,还可以显式地配置上对于静态资源的解析。通过xml<mvc:resources>
标签进行配置,其中cache-period
表示缓存时长。
<mvc:resources mapping="/js/**" location="/scripts/" />
<mvc:resources mapping="/css/**" location="/css/" cache-period="2592000" />
这时js文件的访问会解析到项目的scripts目录,css会解析到css目录。
9、文件上传与下载
1)文件上传
①导依赖
首先需要导入文件上传所需的依赖,包括commons-fileupload,commons-io,以及IoC、MVC相关依赖。
②写配置
在基于xml的配置中,需要先配置一个文件上传处理器(CommonsMultipartResolver),其中的defaultEncoding
可以用于设置文件的编码, maxUploadSize
可以用于设置文件大小的最大值,要注意的是配置的bean的id必须为multipartResolver
,否则无法识别到。
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"/>
<property name="maxUploadSize" value="#{1024*1024*20}"/>
</bean>
③接收文件
现在写一个jsp页面加上包含上传文件的表单,访问路径为项目下的/upload:
代码语言:javascript复制<form action="${ctp}/upload" method="post" enctype="multipart/form-data">
文件上传:<input type="file" name="uploadFile"/>
<input type="submit" value="提交"/>
</form>
在Controller层我们写一个用于处理/upload请求的方法,这里传入一个 MultipartFile
类型的参数用于接收上传的文件:
@Controller
public class UploadController {
@PostMapping("/upload")
public String upload(@RequestParam("uploadFile")MultipartFile file){...}
}
④保存文件
MultipartFile有一个 getOriginalFilename
方法可以用于获取文件原始文件名,如果将文件保存到本地可调用MultipartFile的 transferTo()
,传入new File(xx) 之后即可持久化到本地。如果想保存到数据库,可获取文件的字节码存入。
file.transferTo(new File("I:\upload\" filename));
byte[] bytes = file.getBytes();
2)文件下载
文件的下载需要借助之前写到的ResponseEntity<>
将文件的byte[] 数据放置在其中,之后设置响应头然后返回这个对象,这里演示的是图片的展示。
@Controller
public class DownloadController {
@RequestMapping("/getImg")
public ResponseEntity<byte[]> getImg() throws IOException {
String filename = "I:\upload\mvc.png";
byte[] bytes = Files.readAllBytes(Paths.get(filename));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", filename);
return new ResponseEntity<>(bytes, headers, HttpStatus.CREATED);
}
}
在jsp页面用一个img标签接收即可。
代码语言:javascript复制<img src="${pageContext.request.contextPath}/getImg">
10、异常处理器
到目前为止,我们所有的异常都是直接抛出去,当我们用get方法访问一个限制POST方法访问的路径时,得到的是Tomcat的异常页面,这对于用户体验十分不友好并且存在代码安全性问题。因此我们需要让DispatcherServlet(前端控制器),去对异常进行处理。
Spring MVC 通过 HandlerExceptionResolver
处理程序的异常,包括 Handler 映射、数据绑定以及目标方法执行时发生的异常,它有四个主要的实现类可以对异常进行处理:
•ExceptionHandlerExceptionResolver•ResponseStatusExceptionResolver•DefaultHandlerExceptionResolver•SimpleMappingExceptionResolver
1)ExceptionHandlerExceptionResolver
这个异常解析器主要是处理标明 @ExceptionHandle
注解的异常,而@ExceptionHandler
是SpringMVC提供的注解,用于声明式地捕获指定的异常。该注解的标注可分为两种类型:一种是在标明@controller的类中配置,也称为本类配置;一种是在单独的类中配置,也称为全局配置。
①本类配置
在类中创建一个方法标注上@ExceptionHandle(value="xx")
注解,value属性指明需要捕获的异常,该方法的返回值会被视图解析器解析,如果想获取异常信息可以在入参位置传入异常类型。
@ExceptionHandler(value = {NullPointerException.class} )
public ModelAndView exceptionHandle(NullPointerException npe){
npe.printStackTrace();
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception",npe.getMessage());
return mv;
}
②全局配置
创建一个用于处理异常的类,在类上标注 @ControllerAdvice
,表明这个类用来集中处理异常;接下在定义异常处理的方法,步骤与本类配置一致。
@ControllerAdvice
public class ExceptionController {
@ExceptionHandler(value = {NullPointerException.class})
public ModelAndView exceptionHandle(NullPointerException npe){
npe.printStackTrace();
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception",npe.getMessage());
return mv;
}
}
如果全局和本类都有配置异常处理,那么本类的优先;如果捕获异常的处理器有多个,那么精确的优先。
2)ResponseStatusExceptionResolver
其用于处理标明 @ResponseStatus
注解的异常,可以自定义错误代码和错误原因,其中reason属性为错误原因,value属性与code为HttpStatus类型的错误代码。该注解主要是标注在自定义异常类上,之后在代码逻辑异常处理处抛出该错误类型,SpringMVC捕获之后就会调用到这个处理器。
//自定义一个异常类
@ResponseStatus(reason = "用户访问异常", code = HttpStatus.NOT_ACCEPTABLE)
class UserNotFoundInAdminException extends RuntimeException{
static final long serialVersionUID = 1L;
}
代码语言:javascript复制@RequestMapping("/admin")
public String loginAdmin(@RequestParam("user")String user){
if(user!="admin"){
throw new UserNotFoundInAdminException();
}
....
}
3)SimpleMappingExceptionResolver
用于配置简单的异常解析,可以在SpringMVC的配置文件中配置。其中class为class为SimpleMappingExceptionResolver
的全类名,exceptionMappings
属性的key为要处理的异常全类名,value为异常后跳转的视图名;exceptionAttribute
属性的value值为异常信息存储在域的key。
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.NullPointerException">error</prop>
</props>
</property>
<property name="exceptionAttribute" value="ex"/>
</bean>
4)DefaultHandlerExceptionResolver
用于处理SpringMVC自带的异常,当没有找到其他的异常解析器时,就会来到这个异常解析器解析。
11、拦截器
1)概述
首先需要区分一些拦截器和过滤器的概念。拦截器是SpringMVC中的一个API设计,而过滤器是Servlet中的一个组件。所有经过Servlet容器的请求都可以被过滤器截获到,而拦截器只对经过DispatcherServlet 处理的请求有效。此外,拦截器通过IoC统一管理,所以可以进行任意注入。
2)配置
拦截器的核心接口是HandleInterceptor
,自定义的拦截规则要实现该接口,该接口中有三个主要方法,代表着三个执行时机:
•preHandle:目标方法运行前,返回true则代表放行;•postHandle:目标方法运行后;•afterCompletion:视图响应之后;
代码语言:javascript复制public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("preHandle.....");
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("postHandle.....");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
System.out.println("afterCompletion.....");
}
}
接着我们需要将拦截器注册到IoC容器中,使用的是<mvc:interceptors>
标签,这也说明可以注册多个拦截器;<mvc:interceptor>
标签体内有一个 <mvc:mapping>
用于配置拦截路径, <bean>
用于配置拦截器:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/"/>
<bean class="top.jtszt.interceptor.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
3)拦截流程
从上面我们可以看到拦截器一共有三个运行的时机:目标方法运行前、目标方法运行后、视图响应之后。在配置了拦截器的请求流程大致为:
1.preHandle2.目标方法运行3.postHandle4.视图响应5.afterCompletion
4)多拦截器
假设配置了两个拦截器,按照配置顺序第一个记为interceptor1,第二个记为interceptor2。
12、跨域问题
跨域指的是当前发起请求的域与该请求指向的资源所在的域不一样,这里的域概念表示的是协议、域名、端口(同源策略)。只有当这三者都相同才属于同一个域。当这三个任一个不相同则会引发跨域问题。
SpringMVC中可以在允许跨域的方法上标注 @CrossOrigin
注解,该注解上可以指定跨域范围。这个注解实际上是给响应头中添加了一个 Access-Control-Allow-Origin:
。
参考资料:
•Spring Framework 5.1.3.RELEASE文档[1]•从 0 开始深入学习 Spring-掘金小册[2]•浅谈 MVC与三层架构-CSDN[3]•SpringMVC之视图解析器-CSDN[4]•Spring5 系统架构-CSDN[5]•雷丰阳Spring、Spring MVC、MyBatis课程-bilibili[6]
相关链接
[1]
Spring Framework 5.1.3.RELEASE文档: https://docs.spring.io/spring-framework/docs/5.1.3.RELEASE/spring-framework-reference/
[2]
从 0 开始深入学习 Spring-掘金小册: https://juejin.cn/book/6857911863016390663/section
[3]
浅谈 MVC与三层架构-CSDN: https://blog.csdn.net/weixin_42153410/article/details/90753696
[4]
SpringMVC之视图解析器-CSDN: https://blog.csdn.net/u012369373/article/details/77529595?utm_source=tuicool&utm_medium=referral
[5]
Spring5 系统架构-CSDN: https://blog.csdn.net/lj1314ailj/article/details/80118372
[6]
雷丰阳Spring、Spring MVC、MyBatis课程-bilibili: https://www.bilibili.com/video/BV1d4411g7tv?p=256