SpringMVC

SpringMVC是spring为展现层提供的基于MVC设计理念的优秀WEB框架

SpringMVC通过一套注解,可以让普通的JAVA类成为contrllor控制器,无需继承Servlet

实现了控制层和Servlet之间的解耦。
SpringMVC支持Rest风格的URL写法
SpringMVC采用了松耦合,可热插的主键结构,比其他的框架更具扩展性和灵活性。

框架执行流程

image-20251123235925402

1DispatcherServlet:前端控制器
用户请求到达前端控制器,它就相当于 mvc 模式中的 c,dispatcherServlet 是整个流程控制的中心,由 它调用其它组件处理用户的请求,dispatcherServlet 的存在降低了组件之间的耦合性。

2 HandlerMapping:处理器映射器
HandlerMapping 负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的 映射方式,例如:配置文件方式,实现接口方式,注解方式等。

3 Handler:处理器 (自己定义的Controller处理单元)
它就是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler。由 Handler 对具体的用户请求进行处理。

4 HandlAdapter:处理器适配器
通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行

5 View Resolver:视图解析器
View Resolver 负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名 即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。

6 View:视图
SpringMVC 框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView等。我们最常用的视图就是 jsp。 一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开 发具体的页面。

7 在配置里面写 <mvc:annotation-driven>

组件 作用 注解驱动下的实现类
HandlerMapping(处理器映射器) 根据 URL 找到对应的 Controller 方法 RequestMappingHandlerMapping
HandlerAdapter(处理器适配器) 调用目标方法,处理参数、返回值 RequestMappingHandlerAdapter
ViewResolver(视图解析器) 把逻辑视图名转成实际视图(如 JSP) InternalResourceViewResolver

🔔 注意:<mvc:annotation-driven /> 只负责前两个(映射器 + 适配器),不包含视图解析器!所以你仍需手动配置 <bean class="InternalResourceViewResolver">

当配置了这个就相当于已经配置了前两个,然后框架就会自动拥有通过注解来获得

  • 注册 RequestMappingHandlerMapping:扫描所有 @Controller 中的 @RequestMapping,建立 URL → 方法的映射表
  • 注册 RequestMappingHandlerAdapter:负责调用这些方法,自动处理:
    • @RequestParam, @PathVariable, @RequestBody
    • 参数类型转换(如 String → Integer)
    • 返回值处理(如 String → 视图名,Object + @ResponseBody → JSON)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 1. 扫描 @Controller 等组件 -->
<context:component-scan base-package="com.xxx"/>

<!-- 2. 启用注解驱动(关键!) -->
<mvc:annotation-driven/>

<!-- 3. 配置视图解析器(用于返回 JSP 页面) -->
<bean class="InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"/>
<property name="suffix" value=".jsp"/>
</bean>

<!-- 4. 放行静态资源 -->
<mvc:resources mapping="/static/**" location="/static/"/>

@RequestMapping注解

1@RequestMapping控制请求方式
method属性可以控制请求的方式,值为RequestMethod的枚举值

1
@RequestMapping( value = "/***" ,method = RequestMethod.GET)

2@RequestMapping控制请求参数params和请求头headers

1
2
3
4
5
6
7
param:表示请求中必须包含名为param的参数
!param:表示请求中不能包含名为param的参数
param = value 表示请求中包含名为param的参数,但是值必须是value
param != value 表示请求中包含名为param的参数,但是值不能是value
{"param1","param2=value"},可以将对于多个参数的要求写入数组
@RequestMapping( value = "/***" ,params = {"username!=root","password"})
@RequestMapping( value = "/***",headers = {"Accept-Encoding=gzip, deflate"})

3@PathVariable注解和RESTful风格的支持
普通形式的url

1
2
3
4
5
6
*****/contextPath/aaa.do
*****/contextPath/aaa.jsp
*****/contextPath/aaa.html
*****/contextPath/css/aaa.css
*****/contextPath/js/aaa.js
*****/contextPath/aaa.do?id=10&username=root

restFul风格的url

1
2
*****/contextPath/aaa/10/root
*****/contextPath/aaa

controller 处理单元

1
2
3
4
5
6
7
8
9
10
@Controller
public class PathController {
@RequestMapping("/testPathVariable/{id}/{username}")
public String testPathVariable(@PathVariable("id") Integer id, @PathVariable("username") String username){
System.out.println("id:"+id);
System.out.println("username:"+username);
System.out.println("testPathVariable1");
return "success";
}
}

Http协议中,四个表示操作方式的动词”GET POST PUT DELETE”,他们对应四种基本操作,GET用来获取资源,POST用来新建资源,PUT用来更新资源,DELETE用来删除资源

简单的说,就是我们在访问资源时,可以通过这四个状态来表示对于资源的不同操作,这四个状态表现为我们请求的四种方式
/controller/1 HTTP GET :得到id为1 的资源
/controller/1 HTTP DELETE :删除id为1的资源
/controller/1 HTTP PUT :更新id为1 的资源
/controller/1 HTTP POST :增加id为1 的资源

  • GET(获取资源)
  • POST(提交数据)
  • PUT(全量更新)
  • PATCH(部分更新)
  • DELETE(删除资源)
  • HEADOPTIONSTRACE

在访问同一个url的时候,通过不同的请求方式,对应到不同的controller处理单元

但是HTML标准,只支持GET和POST,当然如果有可以直接发送比如Axios可以直接发送put . delete就不需要转换。

那么这时候就需要用一个过滤器来优先处理路径,也就是说请求过来会先经过过滤器,然后转换请求方法,再到DispatcherServlet。那么过滤器会拦截所有Post请求,然后检查是否有参数,如果有参数就会进行请求方法的转换。

Spring 提供了一个“变通方案”:

  • 前端用 POST 表单提交
  • 但在表单里加一个隐藏字段:<input name="_method" value="DELETE">
  • 后端通过 HiddenHttpMethodFilter 识别这个字段,并把请求“伪装”成 DELETE

1 配置hiddenHttpMethodFilter

1
2
3
4
5
6
7
8
9
<!--配置hiddenHttpMethodFilter ,将POST请求转换为PUT或者DELETE请求-->
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

转换原理

1
2
3
4
5
6
7
8
9
10
11
12
13
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
String paramValue = request.getParameter(this.methodParam);// "_method"
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter((ServletRequest)requestToUse, response);
}

image-20251124004310275

Controller层代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@RequestMapping(value = "/myController")
@RestController
public class MyController {
@RequestMapping(value = "/testRest/{id}",method = RequestMethod.PUT)
public String testPut(@PathVariable(value = "id") Integer id){
System.out.println("testPut, id:"+id);
return "show";
}
@RequestMapping(value = "/testRest/{id}",method = RequestMethod.DELETE)
public String testDelete(@PathVariable(value = "id") Integer id){
System.out.println("testDelete, id:"+id);
return "show";
}
@RequestMapping(value = "/testRest/{id}",method = RequestMethod.POST)
public String testPOST(@PathVariable(value = "id") Integer id){
System.out.println("testPOST, id:"+id);
return "show";
}
@RequestMapping(value = "/testRest/{id}",method = RequestMethod.GET)
public String testGET(@PathVariable(value = "id") Integer id){
System.out.println("testGET, id:"+id);
return "show";
}
}

获取请求参数

在 Spring MVC 中:

  • DispatcherServlet 接收请求
  • 通过 HandlerMapping 找到对应的 Controller 方法
  • 通过 HandlerAdapter(如 RequestMappingHandlerAdapter动态调用该方法
  • 调用前,需要准备方法参数(比如从 request 中取值)

那么这个准备方法参数,就有两种方式

1、紧耦合方式

Controller 方法直接接收 HttpServletRequest 对象:

1
2
3
4
5
6
7
8
@Controller
public class UserController {
public String handle(HttpServletRequest request) {
String username = request.getParameter("username");
// ... 处理逻辑
return "success";
}
}

特点

  • 强依赖 Servlet APIHttpServletRequest 是 Java EE / Jakarta EE 的接口)
  • Controller 和 Web 容器 耦合紧密
  • 单元测试困难(必须 mock HttpServletRequest
  • 代码可读性差(业务逻辑混杂了底层 API 调用)

💡 这就是所谓的“紧耦合”:你的业务代码直接绑定了 Web 层的具体实现。

2、解耦合方式

Controller 方法只声明业务需要的数据,不碰 HttpServletRequest

1
2
3
4
5
6
7
@Controller
public class UserController {
public String handle(@RequestParam("username") String username) {
// 直接使用 username,无需手动解析 request
return "success";
}
}

或者更简洁(参数名一致时可省略 @RequestParam):

1
public String handle(String username) { ... } // 要求请求参数名也是 "username"

核心机制

“在单元方法上声明形参来接收请求数据时,形参名必须和请求数据的键名一致”

这句话的意思是:

  • 当你写 public String handle(String username)
  • Spring MVC 会自动从 request 中查找名为 username 的参数(即 request.getParameter("username")
  • 然后把这个值注入给方法的 username 形参

    这个过程叫 “参数解析”(Argument Resolution),由 HandlerMethodArgumentResolver 体系完成。

⚠️ 注意:这只是默认行为。你可以用注解显式指定:

1
public String handle(@RequestParam("user_name") String username)

这样即使形参叫 username,也会去取 user_name 参数。

使用POJO实体类来接受表单数据

前端提交一个包含多个字段的表单(姓名、年龄、爱好等),后端用一个 Java 对象(POJO)自动接收。

要求 说明
表单 name 属性 = POJO 属性名 <input name="pname">private String pname;
POJO 必须有 setter 方法 Spring 通过反射调用 setPname() 赋值
支持数组/集合 <input name="hobby"> 多个 → String[] hobbyList<String>

接受日期类型

前端传的是字符串(如 "2025-11-24"),后端想直接得到 DateLocalDate

@DateTimeFormat(推荐)

1
2
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthdate;

接受List集合

1
2
<input name="pets[0].petName">
<input name="pets[1].petType">
1
private List<Pet> pets;

接受Map集合

1
2
<input name="petMap['a'].petName">
<input name="petMap['b'].petType">
1
private Map<String, Pet> petMap;

中文乱码问题

POST 用 Filter,GET 改服务器配置,这是经典组合。

POST 乱码:也是通过在过滤器层统一编码

1
2
3
4
5
6
7
<filter>
<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>

GET乱码:在server.xml中的Connector中更改Tomcat的配置

1
2
<Connector port="8080" protocol="HTTP/1.1"
URIEncoding="UTF-8" />

常见注解

1、@RequestMapping
作用:用于建立请求 URL 和处理请求方法之间的对应关系
出现位置:

类上: 请求 URL 的第一级访问目录。此处不写的话,就相当于应用的根目录。写的话需要以/开头
方法上: 请求 URL 的第二级访问目录
属性:
value:用于指定请求的 URL。它和 path 属性的作用是一样的。
method:用于指定请求的方式。
params(了解):用于指定限制请求参数的条件。它支持简单的表达式。要求请求参数的 key 和 value 必须和 配置的一模一样。
headers(了解):用于指定限制请求消息头的条件。

2 @RequestParam

作用:把请求中指定名称的参数给控制器中的形参赋值。

属性:

value:请求参数中的名称。

required:请求参数中是否必须提供此参数。默认值:true。表示必须提供,如果不提供将报错。

1
2
3
4
5
@RequestMapping("/getRequestParam") 
public String getRequestParam(@RequestParam("name")String uname, @RequestParam(value="age",required=false)Integer age){
System.out.println(username+","+age);
return "success";
}

3、@PathVariable

Restful的简介 :

REST(英文:Representational State Transfer,简称 REST)restful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

restful 的优点

它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。

作用:

用于绑定 url 中的占位符。例如:请求 url 中 /delete/{id},这个{id}就是 url 占位符。 url 支持占位符是 spring3.0 之后加入的。是 springmvc 支持 rest 风格 URL 的一个重要标志。

属性:

value:用于指定 url 中占位符名称。

required:是否必须提供占位符。

1
2
3
4
5
6
7
8
9
10
@Controller
public class PathController {
@RequestMapping("/testPathVariable/{id}/{username}")
public String testPathVariable(@PathVariable("id") Integer id, @PathVariable("username") String username){
System.out.println("id:"+id);
System.out.println("username:"+username);
System.out.println("testPathVariable1");
return "success";
}
}

4、@RequestHeader(了解)

作用:

用于获取请求消息头。

属性:

value:提供消息头名称

required:是否必须有此消息头

1
2
3
4
5
@RequestMapping("/getRequestHeader") 
public String getRequestHeader(@RequestHeader(value="Accept", required=false)String requestHeader){
System.out.println(requestHeader);
return "success";
}

5、@CookieValue(了解)

作用:

用于把指定 cookie 名称的值传入控制器方法参数。

属性:

value:指定 cookie 的名称。

required:是否必须有此 cookie

实现

1
2
3
4
5
@RequestMapping("/getCookie") 
public String getCookie(@CookieValue(value="JSESSIONID",required=false) String cookieValue){
System.out.println(cookieValue);
return "success";
}

响应处理

以前我们是自己在Servlet中使用response对象来完成响应的,那么在SpringMVC中如何响应请求的处理结果呢?

image-20251124010634553

在SpringMVC中如果对于当前的控制单元,没有写对应的返回值,这个时候SpringMVC就会找和自己控制单元名称一致的页面展示,如果没有配置视图解析器的前缀和后缀是没有产生404,需要注意控制单元仍然可以进。

0单元方法返回值为void

1
2
3
4
@RequestMapping("/testReturnVoid") 
public void testReturnVoid() throws Exception {
System.out.println("AccountController 的 testForward 方法执行了。。。。");
}

1转发和重定向ServletAPI 实现

1
2
3
4
5
6
7
@RequestMapping("demo1")
public void testDemo1(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 请求转发
//request.getRequestDispatcher("/forward.jsp").forward(request,response);
// 响应重定向
response.sendRedirect(request.getContextPath()+"/redirect.jsp");
}

单元方法的返回值类型设置void。因为使用response对象在单元方法中直接对此次请求进行了响应,不再通过DispatcherServlet了,既然已经响应了,就不需要再给DispatcherServlet返回值了。在单元方法上声明HttpServletResponse形参,来接收此次请求的response对象。

2使用forward关键字完成响应

1
2
3
4
5
6
7
8
9
10
 /*
* 返回字符串告诉DispatcherServlet跳转的路径
* 在路径之前放上一个forward: 关键字,就是请求转发
* 如果路径前的关键字是forward,那么可以省略不写
* */
@RequestMapping("demo2")
public String testDemo2() throws Exception {
//return "forward:/forwardPage.jsp";
return "/forwardPage.jsp";
}

使用通过单元方法的返回值来告诉DispatcherServlet请求转发指定的资源,如果是请求转发,forward关键字可以省略不写的

也就是说转发只是内部的转发共享同一个 request 和 response 对象,如果目标是另一个 Controller,仍然由同一个 DispatcherServlet 处理(不会重新进入 Servlet 容器的入口),而重定向是服务器返回一个 302 响应并在 Location 头中告诉浏览器:“请去 /target”,浏览器收到后,自动发起一个新的 GET 请求/target,这个新请求 重新进入整个 Web 应用流程,回到了DispatcherServlet重新发送

3使用redirect关键字完成响应

1
2
3
4
5
6
7
8
9
10
 /*
* 返回字符串告诉DispatcherServlet跳转的路径
* 在路径之前放上一个redirect: 关键字,就是重定向
* 如果路径前的关键字是redirect,那么不可以省略
* /表示当前项目下.这里不需要项目的上下文路径
* */
@RequestMapping("demo3")
public String testDemo3() throws Exception {
return "redirect:/redirectPage.jsp";
}

使用通过单元方法的返回值来告诉DispatcherServlet重定向指定的资源,注意这个redirect关键字不可以省去

4使用View视图转发和重定向

1
2
3
4
5
6
7
8
9
@RequestMapping("demo4")
public View testDemo4(HttpServletRequest req) {
View view =null;
// 请求转发
//view =new InternalResourceView("/forwardPage.jsp");
// 重定向
view=new RedirectView(req.getContextPath()+"/redirectPage.jsp");
return view;
}

RedirectView中所做的操作,最终的实现是在renderMergedOutputModel中完成实现的,简单来说RedirectView实现了链接的重定向,并且将数据保存到FlashMap中,这样在跳转后的链接中可以获取一些数据.

5使用ModelAndView转发重定向

1
2
3
4
5
6
7
8
9
10
11
 @RequestMapping("demo5")
public ModelAndView testDemo5(HttpServletRequest req) {
ModelAndView mv=new ModelAndView();
// 请求转发
//mv.setViewName("forward:/forwardPage.jsp");
//mv.setView(new InternalResourceView("/forwardPage.jsp"));
// 重定向
//mv.setViewName("redirect:/redirectPage.jsp");
mv.setView(new RedirectView(req.getContextPath()+"/redirectPage.jsp"));
return mv;
}

ModelAndView中的Model代表模型,View代表视图,这个名字就很好地解释了该类的作用。业务处理器调用模型层处理完用户请求后,把结果数据存储在该类的model属性中,把要返回的视图信息存储在该类的view属性中,然后让该ModelAndView返回该Spring MVC框架。

6ResponseBody 响应 json 数据

当浏览器发起一个ajax请求给服务器,服务器调用对应的单元方法处理ajax请求。而ajax的请求在被处理完成后,其处理结果需要直接响应。而目前我们在单元方 法中响应ajax请求,使用的是response对象,需要我们自己将要响应的数据转换 为json字符串响应,比较麻烦,而我们一直希望在单元方法中无论是否是ajax请求,都使用return语句来完成资源的响应,怎么办?

既然我们希望使用单元方法的返回值来响应ajax请求的处理结果,而目前DispatcherServlet的底层会将单元方法的返回值按照请求转发或者重定向来处理,所以就需要我们告诉DispatcherServlet,单元方法的返回值不要按照请求转发或者重定向处理,而是按照直接响应处理,将单元方法的返回值直接响应给浏览器。

那么看到这里会思考Controller方法的返回值是干什么的?

是告诉框架:

  • 要跳转到哪个视图?
  • 是转发(forward)还是重定向(redirect)?
  • 要携带哪些数据(Model)给视图?
返回类型 示例 用途
String "user/profile" "redirect:/login" 最常用,简洁
ModelAndView new ModelAndView("user/profile", "user", user) 同时指定视图 + 模型数据
View new RedirectView("/login") 直接返回视图对象(较少用)

返回 String(最常见)

1
2
3
4
5
@GetMapping("/test")
public String test(Model model) {
model.addAttribute("msg", "Hello");
return "result"; // 默认是 forward 到 /WEB-INF/views/result.jsp(取决于 ViewResolver)
}
  • 本质:返回的是逻辑视图名(logical view name)

  • Spring 会交给 ViewResolverInternalResourceViewResolver)解析成实际路径

    1
    2
    // 比如配置了 prefix="/WEB-INF/views/", suffix=".jsp"
    // "result" → /WEB-INF/views/result.jsp

举个完整对比例子

假设要显示用户信息:

方式1:String + Model

1
2
3
4
5
6
@GetMapping("/user/{id}")
public String getUser(@PathVariable Long id, Model model) {
User user = userService.findById(id);
model.addAttribute("user", user);
return "user/detail"; // forward to /WEB-INF/views/user/detail.jsp
}

方式2:ModelAndView

1
2
3
4
5
@GetMapping("/user/{id}")
public ModelAndView getUser(@PathVariable Long id) {
User user = userService.findById(id);
return new ModelAndView("user/detail", "user", user);
}

方式3:View(不推荐)

1
2
3
4
5
6
@GetMapping("/user/{id}")
public View getUser(@PathVariable Long id, ModelMap model) {
User user = userService.findById(id);
model.addAttribute("user", user);
return new InternalResourceView("/WEB-INF/views/user/detail.jsp");
}

效果完全一样!但方式1最清晰、最主流。

那么Model是什么?

  • Model 是后端(Controller)用来向前端(视图)传递数据的容器
  • 它是一个 Map 的封装(底层就是 ModelMap

举个例子:

1
2
3
4
5
6
@GetMapping("/user")
public String getUser(Model model) { // 注意:Model 是方法参数
User user = new User("张三", 25);
model.addAttribute("userInfo", user); // 把 user 放进 Model
return "userPage";
}

在 JSP 中:

1
<h1>欢迎:${userInfo.name}</h1> <!-- 从 Model 中取数据 -->

那么View是什么

View 只负责 “怎么渲染”不负责“传什么数据”

View是一个接口,代表“渲染策略”

  • InternalResourceView → 转发到 JSP
  • RedirectView → 重定向
  • ThymeleafView → 用 Thymeleaf 渲染

作用域传参

image-20251124154411295

PageContext对象

作用域范围:当前jsp页面内有效

request对象

作用域范围:一次请求内。

作用: 解决了一次请求内的资源的数据共享问题

session对象

作用域范围:一次会话内有效。

说明:浏览器不关闭,并且后台的session不失效,在任意请求中都可以获取到同一个session对象。

作用:解决了一个用户不同请求的数据共享问题。

application(ServletContext)对象

作用域范围:整个项目内有效。

特点:一个项目只有一个,在服务器启动的时候即完成初始化创建无论如何获取都是同一个项目。

作用:解决了不同用户的数据共享问题。

概念 本质 存储位置 生命周期 使用场景
Model(Spring) Spring 封装的数据容器 最终存入 request 作用域 一次请求 向视图(JSP/Thymeleaf)传递数据
request Servlet 原生作用域 HttpServletRequest 一次请求 转发时共享数据
session Servlet 原生作用域 HttpSession 一次会话 登录用户信息、购物车
application Servlet 原生作用域 ServletContext 整个应用 全局配置、在线人数统计
PageContext JSP 特有作用域 JSP 页面内部 一个 JSP 执行期间 JSP 内部临时变量(很少用)

上传文件

1如何在页面中显示一个按钮
用户可以点击该按钮后选择本地要上传的文件在页面中使用input标签,type值设置为”file”即可

2确定上传请求的发送方式
上传成功后的响应结果在当前页面显示,使用ajax请求来完成资源的发送

3上传请求的请求数据及其数据格式
请求数据:
上传的文件本身普通数据:用户名,Id,密码等,建议上传功能中不携带除上传资源以外的数据
数据格式:
传统的请求中,请求数据是以键值对的格式来发送给后台服务器的,但是在上传请求中,没有任何一个键可以描述上次的数据,因为数据本身是非常大的键就相当于一个变量,我们使用一个变量存储一个10g的电影显然是不可能 的。在上传请求中,将请求数据以二进制流的方式发送给服务器。

4在ajax中如何发送二进制流数据给服务器
① 创建FormData的对象,将请求数据存储到该对象中发送
② 将processData属性的值设置为false,告诉浏览器发送对象请求数据
③ 将contentType属性的值设置为false,设置请求数据的类型为二进制类型。
④ 正常发送ajax即可

5上传成功后后台服务器应该响应什么结果给浏览器
并且浏览器如何处理后台服务器处理完成后,响应一个json对象给浏览器,示例格式如下:
{ state:true,msg:“服务器繁忙”,url:”上传成功的资源的请求地址”}

6 文件上传依赖的jar

1
2
3
4
5
6
7
8
9
10
11
<!--文件上传依赖-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.8.0</version>
</dependency>

7 配置文件上传组件

1
2
3
4
5
6
7
8
<!--文件上传解析组件
id必须为multipartResolver
springmvc默认使用该id找该组件
-->
<bean
id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>

前端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>

<head>
<title>Title</title>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
$("#uploadFile").click(function(){
// 获取要上传的文件
var photoFile =$("#photo")[0].files[0]
if(photoFile==undefined){
alert("您还未选中文件")
return;
}
// 将文件装入FormData对象
var formData =new FormData();
formData.append("headPhoto",photoFile)
// ajax向后台发送文件
$.ajax({
type:"post",
data:formData,
url:"fileUpload.do",
processData:false,
contentType:false,
success:function(result){
// 接收后台响应的信息
console.log(result)
// 图片回显
}
})
})
})
</script>
</head>


<body>

<form action="addPlayer" method="get">
<p>账号<input type="text" name="name"></p>
<p>密码<input type="text" name="password"></p>
<p>昵称<input type="text" name="nickname"></p>
<p>头像:
<br/>
<input id="photo" type="file">
<a id="uploadFile" href="javascript:void(0)">立即上传</a>
</p>
<p><input type="submit" value="注册"></p>
</form>

</body>
</html>

controller代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Controller
public class FileUploadController {
@ResponseBody
@RequestMapping("fileUpload.do")
public String fileUpload(MultipartFile headPhoto) throws IOException {
// 指定文件存储目录
File dir = new File("d:/imgs");
// 获取文件名
String originalFilename = headPhoto.getOriginalFilename();
// 文件存储位置
File file =new File(dir,originalFilename);
// 文件保存
headPhoto.transferTo(file);
return "OK";
}
}

文件上传中的几个问题

1 中文文件名编码问题:
已经通过过滤器解决

2 文件位置存储问题
放在当前项目下,作为静态资源,这样可以通过URL访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Controller
public class FileUploadController {
@ResponseBody
@RequestMapping("fileUpload.do")
public String fileUpload(MultipartFile headPhoto, HttpServletRequest req) throws IOException {
// 指定文件存储目录为我们项目部署环境下的upload目录
String realPath = req.getServletContext().getRealPath("/upload");
File dir = new File(realPath);
// 如果不存在则创建目录
if(!dir.exists()){
dir.mkdirs();
}
// 获取文件名
String originalFilename = headPhoto.getOriginalFilename();
// 文件存储位置
File file =new File(dir,originalFilename);
// 文件保存
headPhoto.transferTo(file);
return "OK";
}
}

在SpringMVC中配置静态资源放行

1
<mvc:resources mapping="/upload/**" location="/upload/"></mvc:resources>

3 文件名冲突问题
使用UUID对文件名进行重命名

对比维度 UUID 自增 ID(传统 ID)
唯一性范围 全局唯一(跨机器、跨数据库) 仅表内唯一
生成方式 本地生成(无需数据库) 依赖数据库自增或序列
长度 36 字符(字符串)或 16 字节(二进制) 通常 4~8 字节(int/bigint)
可读性 差(一串乱码) 好(1, 2, 3…)
有序性 ❌ 无序(v4 是随机的) ✅ 严格递增
性能(数据库) 插入慢(主键无序,B+树频繁分裂) 插入快(顺序写入)
安全性 ✅ 不暴露业务信息 ❌ 暴露数据量、增长速度
分布式支持 ✅ 天然支持(各节点独立生成) ❌ 需要分布式 ID 算法(如 Snowflake)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Controller
public class FileUploadController {
@ResponseBody
@RequestMapping("fileUpload.do")
public String fileUpload(MultipartFile headPhoto, HttpServletRequest req) throws IOException {
// 指定文件存储目录为我们项目部署环境下的upload目录
String realPath = req.getServletContext().getRealPath("/upload");
File dir = new File(realPath);
// 如果不存在则创建目录
if(!dir.exists()){
dir.mkdirs();
}
// 获取文件名
String originalFilename = headPhoto.getOriginalFilename();
// 避免文件名冲突,使用UUID替换文件名
String uuid = UUID.randomUUID().toString();
// 获取拓展名
String extendsName = originalFilename.substring(originalFilename.lastIndexOf("."));
// 新的文件名
String newFileName=uuid.concat(extendsName);
// 文件存储位置
File file =new File(dir,newFileName);
// 文件保存
headPhoto.transferTo(file);
return "OK";
}
}

4 控制文件类型

5 控制文件大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Controller
public class FileUploadController {
@ResponseBody
@RequestMapping("fileUpload.do")
public Map<String,String> fileUpload(MultipartFile headPhoto, HttpServletRequest req) throws IOException {
Map<String,String> map=new HashMap<>();
// 控制文件大小
if(headPhoto.getSize()>1024*1024*5){
map.put("message", "文件大小不能超过5M");
return map;
}
// 指定文件存储目录为我们项目部署环境下的upload目录
String realPath = req.getServletContext().getRealPath("/upload");
File dir = new File(realPath);
// 如果不存在则创建目录
if(!dir.exists()){
dir.mkdirs();
}
// 获取文件名
String originalFilename = headPhoto.getOriginalFilename();
// 避免文件名冲突,使用UUID替换文件名
String uuid = UUID.randomUUID().toString();
// 获取拓展名
String extendsName = originalFilename.substring(originalFilename.lastIndexOf("."));
// 控制文件类型
if(!extendsName.equals(".jpg")){
map.put("message", "文件类型必须是.jpg");
return map;
}
// 新的文件名
String newFileName=uuid.concat(extendsName);
// 文件存储位置
File file =new File(dir,newFileName);
// 文件保存
headPhoto.transferTo(file);
// 上传成功之后,把文件的名字和文件的类型返回给浏览器
map.put("message", "上传成功");
map.put("newFileName", newFileName);
map.put("filetype", headPhoto.getContentType());
return map;
}
}

6 上传图片回显问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
$("#uploadFile").click(function(){
// 获取要上传的文件
var photoFile =$("#photo")[0].files[0]
if(photoFile==undefined){
alert("您还未选中文件")
return;
}
// 将文件装入FormData对象
var formData =new FormData();
formData.append("headPhoto",photoFile)
// ajax向后台发送文件
$.ajax({
type:"post",
data:formData,
url:"fileUpload.do",
processData:false,
contentType:false,
success:function(result){
// 接收后台响应的信息
alert(result.message)
// 图片回显
$("#headImg").attr("src","upload/"+result.newFileName);
}
})
})
})
</script>
</head>
<body>
<form action="addPlayer" method="get">
<p>账号<input type="text" name="name"></p>
<p>密码<input type="text" name="password"></p>
<p>昵称<input type="text" name="nickname"></p>
<p>头像:
<br/>
<input id="photo" type="file">
<br/>
<img id="headImg" style="width: 200px;height: 200px" alt="你还未上传图片">
<br/>
<a id="uploadFile" href="javascript:void(0)">立即上传</a>
</p>
<p><input type="submit" value="注册"></p>
</form>
</body>
</html>

7 进度条问题

https://www.cnblogs.com/wuyu1787/p/8919588.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>

<head>
<title>Title</title>
<style>
.progress {
width: 200px;
height: 10px;
border: 1px solid #ccc;
border-radius: 10px;
margin: 10px 0px;
overflow: hidden;
}
/* 初始状态设置进度条宽度为0px */
.progress > div {
width: 0px;
height: 100%;
background-color: yellowgreen;
transition: all .3s ease;
}
</style>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
$("#uploadFile").click(function(){
// 获取要上传的文件
var photoFile =$("#photo")[0].files[0]
if(photoFile==undefined){
alert("您还未选中文件")
return;
}
// 将文件装入FormData对象
var formData =new FormData();
formData.append("headPhoto",photoFile)
// ajax向后台发送文件
$.ajax({
type:"post",
data:formData,
url:"fileUpload.do",
processData:false,
contentType:false,
success:function(result){
// 接收后台响应的信息
alert(result.message)
// 图片回显
$("#headImg").attr("src","upload/"+result.newFileName);
},
xhr: function() {
var xhr = new XMLHttpRequest();
//使用XMLHttpRequest.upload监听上传过程,注册progress事件,打印回调函数中的event事件
xhr.upload.addEventListener('progress', function (e) {
//loaded代表上传了多少
//total代表总数为多少
var progressRate = (e.loaded / e.total) * 100 + '%';
//通过设置进度条的宽度达到效果
$('.progress > div').css('width', progressRate);
})
return xhr;
}
})
})
})
</script>
</head>

<body>

<form action="addPlayer" method="get">
<p>账号<input type="text" name="name"></p>
<p>密码<input type="text" name="password"></p>
<p>昵称<input type="text" name="nickname"></p>
<p>头像:
<br/>
<input id="photo" type="file">
<%--图片回显--%>
<br/>
<img id="headImg" style="width: 200px;height: 200px" alt="你还未上传图片">
<br/>
<%--进度条--%>
<div class="progress">
<div></div>
</div>
<a id="uploadFile" href="javascript:void(0)">立即上传</a>
</p>
<p><input type="submit" value="注册"></p>
</form>

</body>
</html>

8 单独准备文件存储服务器
分服务器上传作用

数据库服务器:运行我们的数据库

缓存和消息服务器:负责处理大并发访问的缓存和消息

文件服务器:负责存储用户上传文件的服务器。

应用服务器:负责部署我们的应用

在实际开发中,我们会有很多处理不同功能的服务器。(注意:此处说的不是服务器集群)

总结:分服务器处理的目的是让服务器各司其职,从而提高我们项目的运行效率。

文件下载

1、下载的基本流程
文件的上传是将用户本地的资源发送到服务器,让服务器存储到其硬盘中的过程。而下载和上传正好是相反的过程。下载是用户发起请求,请求要下载的资源。服务器根据请求,将其硬盘中的文件资源发送给浏览器的过程。
2、下载的请求数据
用户通过浏览器发起下载请求,服务器在接收到请求后,根据当前请求的用户信息,去数据库中获取当前用户要下载的资源的文件路径,然后服务器再去其硬盘中读取对应的文件,将文件响应给浏览器,基于此过程,下载请求的请求数据为:简单的下载:文件的路径直接作为一个字段存储在用户信息表中用户的ID。
[1] 下载的后台实现

   1. 创建单元方法处理下载请求
   2. 根据请求获取要下载的资源的流对象
   3. 读取文件并将资源响应给浏览器
   4. 下载的示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
用户点击下载链接

浏览器发送 GET /download?fileId=123

服务器 Controller 接收请求

【查数据库】获取文件元信息 + 权限校验

根据存储路径读取文件(本地/OSS)

设置响应头:
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="xxx"

将文件内容流式写入 Response Body

浏览器弹出下载框,保存文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping("fileDownload.do")
public void fileDownLoad(String photo, String filetype, HttpServletResponse response) throws IOException {
// 设置响应头
// 告诉浏览器要将数据保存到磁盘上,不在浏览器上直接解析
response.setHeader("Content-Disposition", "attachment;filename="+photo);
// 告诉浏览下载的文件类型
response.setContentType(filetype);
// 获取一个文件的输入流
InputStream inputStream = new URL(FILESERVER + photo).openStream();
// 获取一个指向浏览器的输出流
ServletOutputStream outputStream = response.getOutputStream();
// 向浏览器响应文件即可
IOUtils.copy(inputStream, outputStream);
}

拦截器

在之前学习JAVAWEB 的时候,我们学习了过滤器的知识。过滤器的作用是保护请求的服务器资源,在请求资源被执行之前,如果请求地址符合拦截范围,则会先执行过滤器。过滤器的执行时机,是在Servlet之前执行的。但是在使用了SpringMVC后,Servlet只有一个了,也就是DisptcherServlet。那么,如果我们仍然使用过滤器来完成请求的拦截,因为过滤器是在Servlet之前执行的,就会造成,过滤器会拦截DispatcherServlet所有的请求。那么,如果我们有部分请求不想被拦截,怎么办?

Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。
要使用Spring MVC中的拦截器,就需要对拦截器类进行定义和配置。通常拦截器类可以通过两种方式来定义。
1.通过实现HandlerInterceptor接口,或继承HandlerInterceptor接口的实现类(如HandlerInterceptorAdapter)来定义。
2.通过实现WebRequestInterceptor接口,或继承WebRequestInterceptor接口的实现类来定义。

拦截器和过滤器的区别

1拦截器SpringMVC的,而过滤器是servlet的。

  2拦截器不依赖与servlet容器,由spring容器初始化,过滤器依赖与servlet容器,由servlet容器初始化。
  3拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
  4拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。
  5在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。
6拦截器可以获取IOC容器中的各个bean,而过滤器就不太方便,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。

定义一个拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
/*在请求到达我们定义的handler之前工作的*/
System.out.println("MyInterceptor preHandle");
/*返回的是true,代表放行,可以继续到达handler*/
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor postHandle");
/*handler 处理单元返回ModelAndView 时候进行 拦截*/
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
/*
页面渲染完毕,但是还没有给浏览器响应数据的时候
*/
System.out.println("MyInterceptor afterCompletion");
}
}

springmvc.xml中注册拦截器

1
2
3
4
5
6
7
<!--注册拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/login.action"/>
<bean id="myInterceptor" class="com.msb.interceptor.MyInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>

拦截器内容详解

1、preHandle方法

执行时机
再进入控制单元方法之前执行

如何调用

按拦截器定义顺序调用

具体作用

如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去 进行处理,则返回 true。 如果程序员决定不需要再调用其他的组件去处理请求,则返回 false。

参数详解

HttpServletRequest arg0,拦截的请求的request对象

HttpServletResponse arg1, 拦截的请求的response对象

Object arg2       封存了单元方法对象的HandleMethod对象 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
*
* @param request 请求对象
* @param response 响应对象
* @param handler 目标要调用的Handler
* @return 返回true放行,返回false拦截
* @throws Exception
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
/*在请求到达我们定义的handler之前工作的*/
System.out.println("MyInterceptor preHandle");
/*设置请求和响应的乱码 */
/* request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");*/
// 判断是否登录
/*User user =(User) request.getSession().getAttribute("user");
if(null == user)
response.sendRedirect("index.jsp");
return false;*/
// 用户权限控制
return true;
}

2、postHandle方法

执行时机
在进行数据处理和做出响应之间进行这个方法的调用

如何调用

在拦截器链内所有拦截器返成功调用

具体作用

在业务处理器处理完请求后,但是 DispatcherServlet 向客户端返回响应前被调用,

在该方法中对用户请求 request域数据进行处理。

参数详解

    HttpServletRequest arg0, 拦截的请求的request对象

    HttpServletResponse arg1, 拦截的请求的response对象

    Object arg2, 封存了单元方法对象的HandleMethod对象

    ModelAndView arg3 封存了单元方法的返回值资源路径和请求转到的Map数据 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
*
* @param request
* @param response
* @param handler
* @param modelAndView controller响应的结果,视图和数据
* @throws Exception
*/
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor postHandle");
/*控制数据*/
/*Map<String, Object> map = modelAndView.getModel();
String msg = (String)map.get("msg");
String newMsg = msg.replaceAll("脏话", "**");
map.put("msg", newMsg);*/
/*控制视图*/
/*modelAndView.setViewName("/testDemo1.jsp");*/
}

3、afterCompletion方法

执行时机
在进行页面渲染的时候执行
如何调用

按拦截器定义逆序调用

具体作用

在DispatcherServlet 完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。

参数详解

HttpServletRequest arg0, 拦截的请求的request对象

HttpServletResponsearg1, 拦截的请求的response对象

Object arg2, 封存了单元方法对象的HandleMethod对象

Exception arg3 存储了责任链的异常信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 无论controller是否出现异常,都会执行的方法
* 一般来说都做一些资源释放工作
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
/*页面渲染完毕,但是还没有给浏览器响应数据的时候*/
System.out.println("MyInterceptor afterCompletion");
System.out.println(ex);
}

多个拦截器执行顺序

多个拦截器同时存在时,执行的顺序由配置顺序决定. 先配置谁, 谁就先执行.多个拦截器可以理解为拦截器栈, 先进后出(后进先出), 如图所示:

1
2
3
4
5
6
7
8
9
10
11
<!--注册拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/login.action"/>
<bean id="myInterceptor1" class="com.msb.interceptor.MyInterceptor"></bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/login.action"/>
<bean id="myInterceptor2" class="com.msb.interceptor.MyInterceptor2"></bean>
</mvc:interceptor>
</mvc:interceptors>

MyInterceptor preHandle
MyInterceptor2 preHandle
login.action
MyInterceptor2 postHandle
MyInterceptor postHandle
success.jsp
MyInterceptor2 afterCompletion
MyInterceptor afterCompletion

异常处理

SpringMVC异常简介
系统中异常包括两类:预期异常(检查型异常)和运行时异常 RuntimeException,前者通过捕获异常从而获取异常信息, 后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。
系统的 dao、service、controller 出现都通过 throws Exception 向上抛出,最后由 springmvc 前端控制器交由异常处理器进行异常处理,如下图

image-20251125000019822

异常处理具体实现

1使用@ExceptionHandler注解处理异常

缺点:只能处理当前Controller中的异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Controller
public class ControllerDemo1 {
@RequestMapping("test1.action")
public String test1(){
int i = 1/0;
return "success.jsp";
}
@RequestMapping("test2.action")
public String test2(){
String s =null;
System.out.println(s.length());
return "success.jsp";
}
@ExceptionHandler(value ={ArithmeticException.class,NullPointerException.class} )
public ModelAndView handelException(){
ModelAndView mv =new ModelAndView();
mv.setViewName("error1.jsp");
return mv;
}
}

2使用:@ControllerAdvice+@ExceptionHandler

此处优先级低于局部异常处理器

  • ```java
    package com.msb.exceptionhandler;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.servlet.ModelAndView;
    /**

    • @Author: Ma HaiYang
    • @Description: MircoMessage:Mark_7001
      */
      @ControllerAdvice
      public class GloableExceptionHandler1 {
      @ExceptionHandler(value ={ArithmeticException.class,NullPointerException.class} )
      public ModelAndView handelException(){
      ModelAndView mv =new ModelAndView();
      mv.setViewName(“error1.jsp”);
      return mv;
      }
      }
      1
      2
      3

      配置包扫描

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16

      3使用:SimpleMappingExceptionResolver


      xml配置

      ```xml
      <!--自定义异常解析器-->
      <bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
      <property name="exceptionMappings">
      <props>
      <prop key="java.lang.ArithmeticException">redirect:/error.jsp</prop>
      <prop key="java.lang.NullPointerException">redirect:/error2.jsp</prop>
      </props>
      </property>
      </bean>

配置类配置

  • ```java
    /**

    • 全局异常
      */
      @Configuration
      public class GloableException2 {
      
      @Bean
      public SimpleMappingExceptionResolver getSimpleMappingExceptionResolver(){
        SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
        Properties prop = new Properties();
        prop.put("java.lang.NullPointerException","error1.jsp");
        prop.put("java.lang.ArithmeticException","error2.jsp");
      
      resolver.setExceptionMappings(prop);
      return resolver;
      }
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23

      4自定义的HandlerExceptionResolver

      * ```java
      /**

      * 全局异常
      * HandlerExceptionResolve
      */
      @Configuration
      public class GloableException3 implements HandlerExceptionResolver {
      @Override
      public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
      ModelAndView mv = new ModelAndView();
      if(e instanceof NullPointerException){
      mv.setViewName("error1");
      }
      if(e instanceof ArithmeticException){
      mv.setViewName("error2");
      }
      mv.addObject("msg",e);
      return mv;
      }}

总结

当 Controller 中抛出异常时,Spring MVC 不会直接让异常“冒泡”到 Servlet 容器(那样会返回 500 错误页),而是:

  1. 捕获异常
  2. 交给异常解析器(HandlerExceptionResolver)链处理
  3. 由解析器决定:返回错误页面?JSON?重定向?

💡 所有异常处理机制,底层都基于 HandlerExceptionResolver 接口

方式1:@ExceptionHandler —— 控制器局部异常处理

作用:

  • 只在当前 Controller 类内生效
  • 捕获该 Controller 方法抛出的指定异常
  • 不会继续向上抛出(即被“消费”掉了)

方式2:@ControllerAdvice + @ExceptionHandler —— 全局异常处理

作用:

  • 对所有 Controller 生效
  • 相当于把 @ExceptionHandler 提取到一个公共类中
  • 作用安慰,仅Controller层

方式3:SimpleMappingExceptionResolver —— 基于异常类名映射视图

作用:

  • 传统 XML 配置时代常用的全局异常处理器
  • 根据异常类型 → 映射到指定逻辑视图名

特点:

  • 只能返回视图(JSP/Thymeleaf 页面)
  • 不能返回 JSON 或自定义 HTTP 状态码
  • 适用于纯服务端渲染项目

⚠️ 在 RESTful API(返回 JSON)场景下,基本不用它

方式4:自定义 HandlerExceptionResolver

🔹 作用:

  • 完全掌控异常处理逻辑
  • 实现 HandlerExceptionResolver 接口,加入 Spring 容器即可

执行顺序:

Spring 会按优先级调用多个 HandlerExceptionResolver,直到有一个返回 非 null 的 ModelAndView

内置解析器顺序(可通过 setOrder() 调整):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Controller 抛出异常

DispatcherServlet 捕获异常

依次调用 HandlerExceptionResolver 链:
1. ExceptionHandlerExceptionResolver ← 处理 @ExceptionHandler
2. ResponseStatusExceptionResolver ← 处理 @ResponseStatus
3. DefaultHandlerExceptionResolver ← 处理 404/405 等
4. SimpleMappingExceptionResolver ← 你配置的异常→视图映射
5. YourCustomHandlerExceptionResolver ← 你自定义的

只要某个 resolver 返回 **非 null 的 ModelAndView**,就停止

根据 ModelAndView 渲染视图(或你已直接写入 response)

SpringMVC 其他注解

1、@PostMapping

作用:

指定当前发送请求的方式只可以是post请求

属性:

和@RequestMapping中属性一致

代码实现

1
2
3
4
5
6
7
@PostMapping("/userControllerA")

public String userControllerA(){

return "forward:/success.jsp";

}

2、@GetMapping

作用:

指定当前发送请求的方式只可以是get请求

属性:

和@RequestMapping中属性一致

代码实现:

1
2
3
4
5
6
7
8
9
@GetMapping("/userControllerA")

public String userControllerA(){



return "forward:/success.jsp";

}

3、@RestController

作用:

  书写到类上,代表该类中所有控制单元方法均是ajax响应 相当于@ResponseBody+@Controller

属性:

  其中的属性和@Controller中一样

代码实现:

1
2
3
4
@RestController
public class UserController {

}

4、@JsonFormat

作用:

 处理响应json 数据的处理

属性:

pattern :指定响应时间日期的格式

Timezone:指定响应的时区,否则会有8个小时的时间差

代码实现:

@DateTimeFormat(pattern = “yyyy-MM-dd”)
@JsonFormat(pattern = “yyyy-MM-dd” ,timezone=”GMT+8”)
private Date birth;

5、@RequestBody

作用:

用于获取请求体json格式的字符串内容。直接使用得到是 key=value&key=value…结构的数据,get 请求方式不适用。

属性:

required:是否必须有请求体。默认值是:true。当取值为 true 时,get 请求方式会报错。如果取值 为 false,get 请求得到是null。

实现:

1
2
3
4
5
6
7
8
9
10
11
 $(function () { 
var jsonObj ={name:"zs",pwd:"123"};
var str =JSON.stringify(jsonObj);
$.ajax({
type:"post",
url:"testController",
/*data:'{"name":"zs","password":"123"}',*/
data:str,
contentType:"application/json",
})
})
1
2
3
4
5
6
7
8
9
@RequestMapping("/useRequestBody")
//真正对应的是:JSON 的字段名 ↔ User 类的属性名
public String useRequestBody(@RequestBody(required=false) User user){

System.out.println(body);

return "msb";

}

6、@CrossOrigin

什么是跨域

出于浏览器的同源策略限制。同源策略(SameOriginPolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)
http://127.0.0.1:8080/msb/index.jsp基础
https://127.0.0.1:8080/msb/index.jsp 协议不一样
http://192.168.24.11:8080/msb/index.jsp IP不一致
http://127.0.0.1:8888/msb/index.jsp 端口不一致
http://localhost:8080/msb/index.jsp IP不一致
作用:

解决ajax请求之间的跨域问题

属性:

origins : 允许可访问的域列表IP

maxAge:准备响应前的缓存持续的最大时间(以秒为单位)。

代码实现:

1
2
3
4
5
6
7
@CrossOrigin(origins = "http://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@GetMapping("/{id}")
public Account receive(@PathVariable Long id) { }
}