SpringMVC:

1、MVC理论基础

2、配置环境&搭建项目

3、Controller控制器

4、Interceptor拦截器

5、异常处理

6、JSON数据格式与Axios请求

7、实现文件上传和下载

*8、解读DispatcherServlet源码

一、MVC理论基础

MVC详细解释如下:

  • M是指业务模型(Model):通俗的讲就是我们之前用于封装数据传递的实体类。
  • V是指用户界面(View):一般指的是前端页面。
  • C则是控制器(Controller):控制器就相当于Servlet的基本功能,处理请求,返回响应。

二、配置环境&搭建项目

Maven依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>6.0.10</version>
</dependency>

传统xml配置

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">
    <servlet>
        <servlet-name>mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <!--     指定创建在类路径下的application.xml配置文件       -->
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:application.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

application.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
          <!-- 需要先引入context命名空间,然后直接配置base-package属性就可以了 -->
    <context:component-scan base-package="com.example"/>
</beans>

Controller

@Controller
public class HelloController {
    @ResponseBody
    @RequestMapping("/")
    public String hello(){
        return "HelloWorld!";
    }
}

全注解配置

Tomcat会在类路径中查找实现ServletContainerInitializer 接口的类,如果发现的话,就用它来配置Servlet容器

public class MainInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{WebConfiguration.class};   //基本的Spring配置类,一般用于业务层配置
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[0];  //配置DispatcherServlet的配置类、主要用于Controller等配置,这里为了教学简单,就不分这么详细了,只使用上面的基本配置类
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};    //匹配路径,与上面一致
    }
}

配置类:

@Configuration
@EnableWebMvc   //快速配置SpringMvc注解
@ComponentScan("com.example.controller")
public class WebConfiguration {
    
}

日志依赖:

<dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.33</version>
</dependency>
<dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-jdk14</artifactId>
      <version>1.7.33</version>
</dependency>

三、Controller控制器

MVC请求处理流程

有了SpringMVC,不必再一个请求地址创建一个Servlet了,而是用DispatcherServlet代替Tomcat提供的默认的静态资源Servlet,它作为一个统一的访问点,所有的请求全部由他进行调度。

请求到达 Tomcat 服务器,由 Web 应用程序进行处理;DispatcherServlet 接收所有匹配其映射规则的请求;

HandlerMapping 根据请求 URL 找到对应的 Handler(Controller 方法)并包装成 HandlerExecutionChain(包含 Interceptors)。

依次执行 HandlerInterceptor 的 preHandle()

HandlerAdapter 适配并调用 Handler(执行 Controller 方法)。Controller 处理请求,返回结果(可能是 ModelAndViewString 视图名、@ResponseBody 等)。

依次执行 HandlerInterceptor 的 postHandle()(如果 Controller 未抛出异常)。

如果返回的是视图ViewResolver 将视图名解析为具体的 View 对象。View.render() 方法将模型数据渲染到响应(如生成 HTML)。

依次执行 HandlerInterceptor 的 afterCompletion()

DispatcherServlet 将渲染结果返回给客户端。

  • REST API 场景:如果使用 @RestController@ResponseBody,流程会在Controller 处理请求后直接通过 HttpMessageConverter 序列化数据为 JSON/XML,跳过 ViewResolverView 渲染。
  • 静态资源:可通过 <mvc:resources>ResourceHandlerRegistry 配置,由 Spring 直接处理,不经过 Controller。

配置视图解析器和控制器

Thymeleaf

<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring6</artifactId>
    <version>3.1.1.RELEASE</version>
</dependency>

配置视图解析器:

@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebConfiguration {
    //我们需要使用ThymeleafViewResolver作为视图解析器,并解析我们的HTML页面
    @Bean
    public ThymeleafViewResolver thymeleafViewResolver(SpringTemplateEngine springTemplateEngine){
        ThymeleafViewResolver resolver = new ThymeleafViewResolver();
        resolver.setOrder(1);   //可以存在多个视图解析器,并且可以为他们设定解析顺序
        resolver.setCharacterEncoding("UTF-8");   //编码格式是重中之重
        resolver.setTemplateEngine(springTemplateEngine);   //和之前JavaWeb阶段一样,需要使用模板引擎进行解析,所以这里也需要设定一下模板引擎
        return resolver;
    }

    //配置模板解析器
    @Bean
    public SpringResourceTemplateResolver templateResolver(){
        SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
        resolver.setSuffix(".html");   //需要解析的后缀名称
        resolver.setPrefix("/");   //需要解析的HTML页面文件存放的位置,默认是webapp目录下,如果是类路径下需要添加classpath:前缀
        return resolver;
    }

    //配置模板引擎Bean
    @Bean
    public SpringTemplateEngine springTemplateEngine(ITemplateResolver resolver){
        SpringTemplateEngine engine = new SpringTemplateEngine();
        engine.setTemplateResolver(resolver);   //模板解析器,默认即可
        return engine;
    }
}

控制器:

@Controller
public class HelloController {

    @RequestMapping("/index")
    public ModelAndView index() {
        ModelAndView modelAndView = new ModelAndView("index");
        modelAndView.getModel().put("name", "啊这");   //将name传递给Model
        return modelAndView;
          //返回后会经过视图解析器进行处理
    }
}

在类路径根目录下创建一个简单html文件:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="static/test.js"></script>
</head>
<body>
    HelloWorld!
    <div th:text="${name}"></div>
</body>
</html>

静态资源访问

我们的页面中可能还会包含一些静态资源,比如js、css,因此这里我们还需要配置一下,让静态资源通过Tomcat提供的默认Servlet进行解析,我们需要让配置类实现一下WebMvcConfigurer接口,并重写以下方法:

@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    configurer.enable();   //开启默认的Servlet
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/static/**").addResourceLocations("/static/");
    //配置静态资源的访问路径
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>测试</title>
    <!-- 引用静态资源,这里使用Thymeleaf的网址链接表达式,Thymeleaf会自动添加web应用程序的名称到链接前面 -->
    <script th:src="@{/static/test.js}"></script>
</head>
<body>
    <p>欢迎来到网站</p>
</body>
</html>

@RequestMapping

@RequestMapping({"/index", "/test"})
public ModelAndView index(){
    return new ModelAndView("index");
}

现在我们访问/index或是/test都会经过此方法进行处理。

添加路径前缀

@RequestMapping添加到类名上,表示为此类中的所有请求映射添加一个路径前缀:

@Controller
@RequestMapping("/yyds")
public class MainController {

    @RequestMapping({"/index", "/test"})
    public ModelAndView index(){
        return new ModelAndView("index");
    }
}

路径添加通配符

路径还支持使用通配符进行匹配:

  • ?:表示任意一个字符,比如@RequestMapping("/index/x?")可以匹配/index/xa、/index/xb等等。
  • *:表示任意0-n个字符,比如@RequestMapping("/index/*")可以匹配/index/lbwnb、/index/yyds等。
  • **:表示当前目录或基于当前目录的多级目录,比如@RequestMapping("/index/**")可以匹配/index、/index/xxx等。

Method属性

@RequestMapping(value = "/index", method = RequestMethod.POST)
public ModelAndView index(){
    return new ModelAndView("index");
}

使用衍生注解:

@PostMapping(value = "/index")
public ModelAndView index(){
    return new ModelAndView("index");
}
携带参数

比如这里我们要求请求中必须携带usernamepassword属性,否则无法访问:

@RequestMapping(value = "/index", params = {"username", "password"})
public ModelAndView index(){
    return new ModelAndView("index");
}

它还支持表达式:

@RequestMapping(value = "/index", params = {"!username", "password"}):在username之前添加一个感叹号表示请求的不允许携带此参数,否则无法访问

@RequestMapping(value = "/index", params = {"username!=test", "password=123"}) :请求参数username不允许为test,并且password必须为123,否则无法访问。

请求头

@RequestMapping(value = "/index", headers = "!Connection"):如果请求头中携带了Connection属性,将无法访问。

其他两个属性:

  • consumes: 指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;
  • produces: 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回;

@RequestParam和@RequestHeader

如何获取到请求中的参数?添加@RequestParam

注:如果参数名称与形式参数名称相同,即使不添加@RequestParam也能获取到参数值。

一旦添加@RequestParam,那么此请求必须携带指定参数,我们也可以将require属性设定为false来将属性设定为非必须;

我们还可以直接设定一个默认值,当请求参数缺失时,可以直接使用默认值

@RequestMapping(value = "/index")
public ModelAndView index(@RequestParam(value = "username", required = false, defaultValue = "伞兵一号") String username){
    System.out.println("接受到请求参数:"+username);
    return new ModelAndView("index");
}

如果需要使用Servlet原本的一些类,直接添加HttpServletRequest为形式参数即可,SpringMVC会自动传递该请求原本的HttpServletRequest对象;

@RequestMapping(value = "/index")
public ModelAndView index(HttpServletRequest request){
    System.out.println("接受到请求参数:"+request.getParameterMap().keySet());
    return new ModelAndView("index");
}

同理,我们也可以添加HttpServletResponse作为形式参数,甚至可以直接将HttpSession也作为参数传递:

@RequestMapping(value = "/index")
public ModelAndView index(HttpSession session){
    System.out.println(session.getAttribute("test"));
    session.setAttribute("test", "鸡你太美");
    return new ModelAndView("index");
}

@RequestHeader@RequestParam用法一致:

// 获取单个请求头
@GetMapping("/info")
public String getInfo(@RequestHeader("User-Agent") String userAgent) {
    return "User Agent: " + userAgent;
}

// 使用默认值
@GetMapping("/lang")
public String language(@RequestHeader(value = "Accept-Language", defaultValue = "en") String lang) {
    return "Language: " + lang;
}

// 获取所有请求头为Map
@GetMapping("/allHeaders")
public String allHeaders(@RequestHeader Map<String, String> headers) {
    return "Headers: " + headers;
}

// 获取特定类型的请求头
@GetMapping("/content")
public String content(@RequestHeader("Content-Type") String contentType) {
    return "Content Type: " + contentType;
}

// 获取多值请求头
@GetMapping("/accept")
public String accept(@RequestHeader("Accept") List<String> acceptHeaders) {
    return "Accept headers: " + acceptHeaders;
}

// 获取HTTP日期头
@GetMapping("/ifModified")
public String ifModified(@RequestHeader("If-Modified-Since") Date ifModifiedSince) {
    return "If-Modified-Since: " + ifModifiedSince;
}

@CookieValue和@SessionAttrbutie

通过使用@CookieValue注解,我们也可以快速获取请求携带的Cookie信息;

@RequestMapping(value = "/index")
public ModelAndView index(HttpServletResponse response,
                          @CookieValue(value = "test", required = false) String test){
    System.out.println("获取到cookie值为:"+test);
    response.addCookie(new Cookie("test", "lbwnb"));
    return new ModelAndView("index");
}

同样的,Session也能使用注解@SessionAttribute快速获取:

@RequestMapping(value = "/index")
public ModelAndView index(@SessionAttribute(value = "test", required = false) String test,
                          HttpSession session){
    session.setAttribute("test", "xxxx");
    System.out.println(test);
    return new ModelAndView("index");
}

重定向和请求转发

特性重定向(Redirect)请求转发(Forward)
本质两次独立的HTTP请求一次HTTP请求,服务器内部转发
客户端感知客户端知道URL变化(地址栏变化)客户端不知道(地址栏不变)

重定向和请求转发,我们只需要在视图名称前面添加一个前缀即可。

通过添加redirect:前缀,就可以很方便地实现重定向:

@RequestMapping("/index")
public String index(){
    return "redirect:home";
}

@RequestMapping("/home")
public String home(){
    return "home";
}

使用forward:前缀表示转发给其他请求映射:

@RequestMapping("/index")
public String index(){
    return "forward:home";
}

@RequestMapping("/home")
public String home(){
    return "home";
}

Bean的Web作用域

在学习Spring时我们讲解了Bean的作用域,包括singletonprototype,Bean分别会以单例和多例模式进行创建,而在SpringMVC中,它的作用域被继续细分:

  • request:对于每次HTTP请求,使用request作用域定义的Bean都将产生一个新实例,请求结束后Bean也消失。
  • session:对于每一个会话,使用session作用域定义的Bean都将产生一个新实例,会话过期后Bean也消失。
  • global session:不常用
@Bean
@RequestScope
public TestBean testBean(){
    return new TestBean();
}
@Controller
public class MainController {

    @Resource
    TestBean bean;

    @RequestMapping(value = "/index")
    public ModelAndView index(){
        System.out.println(bean);
        return new ModelAndView("index");
    }
}

我们发现,每次发起请求得到的Bean实例都不同。

我们将其作用域修改为@SessionScope,这样作用域就上升到Session,只要清理浏览器的Cookie,那么都会被认为是同一个会话,只要是同一个会话,那么Bean实例始终不变。

实际上,它也是通过代理实现的,我们调用Bean中的方法会被转发到真正的Bean对象去执行。

RestFul风格&@PathVariable

中文释义为“表现层状态转换”

RESTful风格的设计允许将参数通过URL拼接传到服务端,目的是让URL看起来更简洁实用;

并且我们可以充分使用多种HTTP请求方式(POST/GET/PUT/DELETE),来执行相同请求地址的不同类型操作。

我们可以按照不同功能进行划分:

我们分别编写四个请求映射:

@Controller
public class MainController {

    @RequestMapping(value = "/index/{id}", method = RequestMethod.GET)
    public String get(@PathVariable("id") String text){
        System.out.println("获取用户:"+text);
        return "index";
    }

    @RequestMapping(value = "/index", method = RequestMethod.POST)
    public String post(String username){
        System.out.println("添加用户:"+username);
        return "index";
    }

    @RequestMapping(value = "/index/{id}", method = RequestMethod.DELETE)
    public String delete(@PathVariable("id") String text){
        System.out.println("删除用户:"+text);
        return "index";
    }

    @RequestMapping(value = "/index", method = RequestMethod.PUT)
    public String put(String username){
        System.out.println("修改用户:"+username);
        return "index";
    }
}

四、Interceptor拦截器

过滤器是作用于Servlet之前,只有经过层层的过滤器才可以成功到达Servlet;

而拦截器是在Servlet与RequestMapping之间,相当于DispatcherServlet在将请求交给对应Controller中的方法之前进行拦截处理,它只会拦截所有Controller中定义的请求映射对应的请求(不会拦截静态资源)

创建拦截器

创建一个拦截器我们需要实现一个HandlerInterceptor接口

public class MainInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("我是处理之前!");
        return true;   //只有返回true才会继续,否则直接结束
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("我是处理之后!");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
          //在DispatcherServlet完全处理完请求后被调用
        System.out.println("我是完成之后!");
    }
}

接着我们需要在配置类中进行注册

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new MainInterceptor())
      .addPathPatterns("/**")    //添加拦截器的匹配路径,只要匹配一律拦截
      .excludePathPatterns("/home");   //拦截器不进行拦截的路径
}

如果处理过程中抛出异常,那么不会执行处理后postHandle方法,但是会执行afterCompletion方法,我们可以在此方法中获取到抛出的异常。

多级拦截器

@Override
public void addInterceptors(InterceptorRegistry registry) {
      //一号拦截器
    registry.addInterceptor(new MainInterceptor()).addPathPatterns("/**").excludePathPatterns("/home");
      //二号拦截器
    registry.addInterceptor(new SubInterceptor()).addPathPatterns("/**");
}

与单个拦截器的情况一样,一旦拦截器返回false,那么之后无论有无拦截器,都不再继续。

五、异常处理

SpringMVC为我们提供了默认的异常处理页面,当我们的请求映射方法中出现异常时,会直接展示在前端页面,当出现异常时,我们的请求会被直接转交给专门用于异常处理的控制器进行处理。

我们可以自定义一个异常处理控制器,一旦出现指定异常,就会转接到此控制器执行:

@ControllerAdvice
public class ErrorController {

    @ExceptionHandler(Exception.class)
    public String error(Exception e, Model model){  //可以直接添加形参来获取异常
        e.printStackTrace();
        model.addAttribute("e", e);
        return "500";
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  500 - 服务器出现了一个内部错误QAQ
  <div th:text="${e}"></div>
</body>
</html>

六、JSON数据格式与Axios请求

JSON数据格式

我们现在推崇的是前后端分离的开发模式,而不是所有的内容全部交给后端渲染再发送给浏览器,这就要用到Json。

JSON (JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。

JSON解析框架有很多种,比较常用的是Jackson和FastJSON。

<dependency>
      <groupId>com.alibaba.fastjson2</groupId>
      <artifactId>fastjson2</artifactId>
      <version>2.0.34</version>
</dependency>
let obj = JSON.parse('{"studentList": [{"name": "杰哥", "age": 18}, {"name": "阿伟", "age": 18}], "count": 2}')
//将JSON格式字符串转换为JS对象
obj.studentList[0].name   //直接访问第一个学生的名称
@RequestMapping(value = "/index")
public String index(){
    JSONObject object = new JSONObject();
    object.put("name", "杰哥");
    object.put("age", 18);
    JSONArray array = new JSONArray();
    array.add(object);
    System.out.println(array.toJSONString());
    return "index";
}

我们可以也直接创建一个实体类,将实体类转换为JSON格式的数据:

@RequestMapping(value = "/index", produces = "application/json")
@ResponseBody
public String data(){
    Student student = new Student();
    student.setName("杰哥");
    student.setAge(18);
    return JSON.toJSONString(student);
}

SpringMVC非常智能,我们可以直接返回一个对象类型,它会被自动转换为JSON字符串格式,注意需要在配置类中添加一下FastJSON转换器,这里需要先添加一个依赖:

<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2-extension-spring6</artifactId>
    <version>2.0.34</version>
</dependency>
@RequestMapping(value = "/data", produces = "application/json")
@ResponseBody
public Student data(){
    Student student = new Student();
    student.setName("杰哥");
    student.setAge(18);
    return student;
}

配置类实现WebMvcConfigurer接口,重写方法configureMessageConverters(),使用FastJsonHttpMessageConverter(),内容就会自动转换为JSON格式响应给客户端了。

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(new FastJsonHttpMessageConverter());
}

Axios异步请求

前端异步请求指的是在前端中发送请求至服务器或其他资源,并且不阻塞用户界面或其他操作。在传统的同步请求中,当发送请求时,浏览器会等待服务器响应,期间用户无法进行其他操作。而异步请求通过将请求发送到后台,在等待响应的同时,允许用户继续进行其他操作。这种机制能够提升用户体验,并且允许页面进行实时更新。

常见的前端异步请求方式包括使用XMLHttpRequest对象、Fetch API、以及使用jQuery库中的AJAX方法,以及目前最常用的Axios框架等。

Ajax(Asynchronous JavaScript and XML)技术:

Ajax工作流程

第一步:浏览器端发起请求

  1. 触发事件:浏览器中发生某个用户交互事件(如点击按钮、输入内容、页面滚动等)
  2. 创建请求对象:JavaScript创建XMLHttpRequest对象(现代浏览器也可使用Fetch API)
  3. 发送HTTP请求:通过XMLHttpRequest对象向服务器发送HTTP请求,此过程为异步操作,不会阻塞页面

第二步:服务器端处理

  1. 接收请求:服务器接收到浏览器发来的HTTP请求
  2. 处理请求:服务器根据请求内容执行相应的业务逻辑处理
  3. 返回数据:服务器创建响应,将处理结果以数据形式(通常是JSON或XML格式)返回给浏览器

第三步:浏览器端接收并处理

  1. 接收响应:浏览器接收到服务器返回的数据
  2. JavaScript处理:通过回调函数或Promise方式,使用JavaScript处理返回的数据
  3. 局部更新页面:根据处理结果,动态更新页面的部分内容(无需刷新整个页面)

Get

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试</title>
    <script src="https://unpkg.com/[email protected]/dist/axios.min.js"></script>
</head>
<body>
  <p>欢迎来到交友网站</p>
  <p>用户名: <span id="username"></span></p>
  <p>密码: <span id="password"></span></p>
</body>
</html>
<script>
    function getInfo() {
        axios.get('/mvc/test').then(({data}) => {
            document.getElementById('username').innerText = data.username
            document.getElementById('password').innerText = data.password
        })
    }
</script>

Post

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试</title>
    <script src="https://unpkg.com/[email protected]/dist/axios.min.js"></script>
</head>
<body>
  <p>欢迎来到交友网站</p>
  <button onclick="login()">立即登录</button>
</body>
</html>
form表单形式:
<script>
    function login() {
        axios.post('/mvc/test', {
            username: 'test',
            password: '123456'
        }, {
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        }).then(({data}) => {
            if(data.success) {
                alert('登录成功')
            } else {
                alert('登录失败')
            }
        })
    }
</script>
@ResponseBody
@PostMapping(value = "/test", produces = "application/json")
public String hello(String username, String password){
    boolean success = "test".equals(user.getUsername()) && "123456".equals(user.getPassword());
    JSONObject object = new JSONObject();
    object.put("success", success);
    return object.toString();
}
json形式:

我们也可以将js对象转换为JSON字符串的形式进行传输,这里需要使用ajax方法来处理:

<script>
    function login() {
        axios.post('/mvc/test', {
            username: 'test',
            password: '123456'
        }).then(({data}) => {
            if(data.success) {
                alert('登录成功')
            } else {
                alert('登录失败')
            }
        })
    }
</script>

如果我们需要读取前端发送给我们的JSON格式数据,那么这个时候就需要添加@RequestBody注解

@ResponseBody
@PostMapping(value = "/test", produces = "application/json")
public String hello(@RequestBody User user){
    boolean success = "test".equals(user.getUsername()) && "123456".equals(user.getPassword());
    JSONObject object = new JSONObject();
    object.put("success", success);
    return object.toString();
}

七、实现文件上传和下载

利用SpringMVC,我们可以很轻松地实现文件上传和下载,我们需要在MainInitializer中添加一个新的方法:

public class MainInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    ...

    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {
          // 直接通过registration配置Multipart相关配置,必须配置临时上传路径,建议选择方便打开的
        // 同样可以设置其他属性:maxFileSize, maxRequestSize, fileSizeThreshold
        registration.setMultipartConfig(new MultipartConfigElement("/Users/nagocoler/Download"));
    }
}

上传

@RequestMapping(value = "/upload", method = RequestMethod.POST)
@ResponseBody
public String upload(@RequestParam MultipartFile file) throws IOException {
    File fileObj = new File("test.png");
    file.transferTo(fileObj);
    System.out.println("用户上传的文件已保存到:"+fileObj.getAbsolutePath());
    return "文件上传成功!";
}
<div>
    <form action="upload" method="post" enctype="multipart/form-data">
        <input type="file" name="file">
        <input type="submit">
    </form>
</div>

下载

直接使用HttpServletResponse,并向输出流中传输数据即可:

@GetMapping("/download")
public void download(@RequestParam("filename") String filename, 
                     HttpServletResponse response) {
    
    // 1. 验证文件名(防止路径遍历攻击)
    if (!isValidFilename(filename)) {
        response.setStatus(HttpStatus.BAD_REQUEST.value());
        return;
    }
    
    // 2. 构建文件路径
    String filePath = "/path/to/files/" + filename;
    File file = new File(filePath);
    
    // 3. 检查文件是否存在
    if (!file.exists()) {
        response.setStatus(HttpStatus.NOT_FOUND.value());
        return;
    }
    
    // 4. 设置响应头
    response.setContentType("application/octet-stream");
    response.setHeader("Content-Disposition", 
        "attachment; filename=\"" + encodeFilename(filename) + "\"");
    response.setContentLength((int) file.length());
    
    // 5. 写入文件流
    try (InputStream is = new FileInputStream(file);
         OutputStream os = response.getOutputStream()) {
        IOUtils.copy(is, os);
    } catch (IOException e) {
        throw new RuntimeException("文件下载失败", e);
    }
}

// 中文文件名编码处理
private String encodeFilename(String filename) {
    try {
        return URLEncoder.encode(filename, "UTF-8").replaceAll("\\+", "%20");
    } catch (UnsupportedEncodingException e) {
        return filename;
    }
}

// 防止路径遍历攻击
private boolean isValidFilename(String filename) {
    // 检查是否包含路径分隔符
    return filename != null && !filename.contains("..") && !filename.contains("/");
}
<!-- 传递文件名参数 -->
<a href="/download?filename=test.png" download="test.png">下载test.png</a>
<a href="/download?filename=report.pdf" download="report.pdf">下载报告</a>
<a href="/download?filename=数据报表.xlsx" download="数据报表.xlsx">下载Excel</a>
分类: Java-Backend 标签: JavaSpring

评论

暂无评论数据

暂无评论数据

目录