SpringMVC学习

前言

很久之前学习过一点php里的MVC(模型-视图-控制器)架构。在学习java时遇到了SpringMVC框架,这里做一个记录。

Servlet

首先idea创建一个Servlet项目。创建一个空的maven工程,在pom.xml中加入依赖。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>springmvc-01</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
    </dependencies>

</project>

添加成功后,在项目添加web Framework support。使其成为一个web项目。

image.png

编写一个Servlet类,用来处理用户的请求。

public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获得参数
        String method = req.getParameter("method");
        if (method.equals("add")&&method!=null){
            req.getSession().setAttribute("msg","执行了add方法");
        }
        //视图跳转部分
        req.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
}

代码会在当前会话中设置了一个名为 "msg" 的属性,值为 "执行了add方法"。然后跳转到/WEB-INF/jsp/test.jsp文件。
在web.xml里面配置servlet如下

<?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_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>com.kuang.servlet.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

    <session-config>
        <!--配置session30分钟超市-->
        <session-timeout>30</session-timeout>
    </session-config>
    <!--配置欢迎页-->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

</web-app>

当访问hello这个servlet时,代码就会走到HelloServlet,触发doGet方法。通过传入method方法,跳转到/WEB-INF/jsp/test.jsp页面。

image.png

我们会发现用 Servlet 处理用户的请求会非常麻烦。每个 Servlet 都要继承 HttpServlet,会产生非常多的重复性代码。所以SpringMVC就产生了。

初始SpringMVC

首先还是创建一个maven项目,导入依赖

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
    </dependencies>

在web.xml 中创建前置控制器 DispatcherServlet。

<?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_4_0.xsd"
         version="4.0">

    <!--1.注册DispathcerServlet-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--2. 绑定spring配置文件-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <!--3. 启动级别-->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- / 只匹配所有的请求,不会匹配jsp页面
    /* 匹配所有的请求,包括jsp页面
    -->
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

创建一个controller请求

@Controller
public class HelloController{
    @RequestMapping("/hello")
    public ModelAndView hello(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        // ModelAndView 模型和视图
        ModelAndView mv = new ModelAndView();
        //封装对象,放在ModelAndView中,Model
        mv.addObject("msg","HelloSpringMVC!");
        //封装要跳转的视图,放在ModelAndView中
        mv.setViewName("test"); //WEB-INF/jsp/test.jsp
        return mv;
    }
}

然后在src/resources 资源目录下创建 SpringMVC的配置文件springmvc-servlet.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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--注册组件扫描器-->
    <context:component-scan base-package="com.kuang.controller"/>
    <!-- 这个标签启用了基于注解的 Spring MVC 支持-->
    <mvc:annotation-driven/>


    <!-- 
        配置视图解析器
        作用:将prefix + 视图名称 + suffix 确定最终要跳转的页面
    -->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <property name="suffix" value=".jsp"/>
        </bean>
    
    </beans>

在/WEB-INF/jsp/下创建test.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>springmvc</title>
</head>
<body>
${msg}
</body>
</html>

配置tomcat启动即可。
需要注意的是在发布的时候将maven依赖也要导入进去。

image.png
image.png

当访问hello这个Servlet时,就会执行HelloController里的hello方法,将数据HelloSpringMVC返回到视图test.jsp。

SpringMVC流程分析

image.png

根据SpringMV执行的流程图,我们简要梳理一下。

  • 1、用户在浏览器提交请求到前置的中央处理器控制器DispatcherServlet进行处理。
  • 2、前置控制器DispatcherServlet收到请求后,将请求转给处理器映射器HandlerMapping。
  • 3、处理器映射器HandlerMapping根据request请求的URL等信息查找能够进行处理的Handler,并构造HandlerExecutionChain执行链,然后返回给前置控制器DispatcherServlet,执行链包含一个处理器对象和一或多个拦截器。
  • 4、前置控制器DispatcherServlet根据处理器执行链的处理器,能够找到其对应的处理器适配器HandlerAdapter。
  • 5、处理器适配器HandlerAdapter调用相应的处理器Handler,即具体的Controller执行方法。
  • 6、Controller处理完后返回ModelAndView给HandlerAdapter
  • 7、处理器适配器HandlerAdapter将Controller执行结果ModelAndView返回给前置控制器DispatcherServlet。
  • 8、前置控制器DispatcherServlet调用视图解析器ViewReslover处理ModelAndView。
  • 9、视图解析器ViewReslover解析后根据逻辑视图名解析成具体的页面地址,生成并返回具体对象View。
  • 10、前置控制器DispatcherServlet根据对象View进行视图渲染。
  • 11、返回渲染的视图结果到前置控制器DispatcherServlet。
  • 12、最后前置控制器DispatcherServlet向用户返回响应,至此就全部完成了。

注解开发SpringMVC

在刚才的HelloController存在以下注解

  • @Controller 注解用来标识一个控制器类
  • @RequestMapping("/hello") 注解用来指定处理哪些 URL 请求
    @RequestMapping 还存在以下属性
@RequestMapping(value = "/hello",method = RequestMethod.GET)

value 表示请求的url,method表示请求方法。
那如何接受参数呢?

@RequestParam 注解可以把请求参数传递给请求方法。

修改一下HelloController,测试看看

@Controller
public class HelloController{
    @RequestMapping(value = "/hello",method = RequestMethod.GET)
    public String test3(@RequestParam("username") int a, int b, Model model){
        int sum = a+b;
        model.addAttribute("msg","结果为:"+sum);
        return "test";
    }
}
image.png

当默认访问hello这个Servlet时,没有传递username参数值。

image.png

当传递username,而缺少b参数时,依然会告诉你b值可选择。

image.png

Spring MVC 会按请求参数名和属性值自动匹配。

@PathVariable 注解,可以将 URL 中占位符参数绑定到控制器处理方法的入参中

修改一下HelloController,测试看看

@Controller
public class HelloController{
    @RequestMapping("/add/{a}/{b}")
    public String test4(@PathVariable int a, @PathVariable int b, Model model){
        int sum = a+b;
        model.addAttribute("msg","结果为:"+sum);
        return "test";
    }
}

此时访问add这个serlvet,显示404。

image.png

当输入完整路径,可回显结果。

image.png

RestFul风格

RestFul是一种风格,可以更加简洁、更有层次、更易于实现缓存的机制。该风格被广泛应用,可以帮助我们理解平时遇到的地址和传参
在传统方式里,用的最多的是GET和POST。而使用RESTFul风格,可以通过不同的请求方式来实现不同的效果。
如刚才我们实验的
http://localhost:8080/springmvc_02_war_exploded/add/145/2
再次修改一下HelloController,测试看看

@Controller
public class HelloController{
    @RequestMapping(path = "/add/{a}/{b}",method= RequestMethod.HEAD)
    public String test4(@PathVariable int a, @PathVariable int b, Model model){
        int sum = a+b;
        model.addAttribute("msg","结果为:"+sum);
        return "test";
    }
}

method必须使用HEAD方法来请求,而在传统请求方式里基本用不到。
此时浏览器默认访问即405,方法不允许。

image.png

改用burpsuite可正常执行。

image.png

因为 Spring MVC 默认不会执行HEAD方法来计算结果,所以这里返回空值。
小结:在RestFul风格下,访问某个地址404,添加参数后200,是合理的;访问某个地址405,更改方法以后200,是合理的;使用PUT方法来更新数据是合理的;使用OPTIONS方法来获取数据也是合理的。

重定向和转发

修改一下HelloController,测试看看

@Controller
public class HelloController{
    @RequestMapping("/m1/t1")
    public String test1(Model model){
        //转发
        model.addAttribute("msg","m1_t1_springmvc");
        return "/WEB-INF/jsp/test.jsp";
    }

    @RequestMapping("/m2/t2")
    public String test2(Model model){
        //重定向
        model.addAttribute("msg","m2_t2_springmvc");
        return "redirect:/index.jsp";
    }
}
转发
重定向

很明显,

  • 转发是发生在服务器端,客户端无感知。且url地址不会有变化。转发后也可以获取原始请求信息。
  • 重定向是浏览器发起的新请求,url地址有明显变化。原始请求中的参数、属性等信息也不会被重定向后的请求获取到。

文件上传

首先导入依赖

<!--文件上传-->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.3</version>
        </dependency>
        <!--servlet-api导入包-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
        </dependency>

在springmvc-servlet.xml进行配置文件上传

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--请求的编码格式,必须和jsp的pageEncoding属性一致,以便正确读取表单的内容,默认是ISO-885901-->
        <property name="defaultEncoding" value="utf-8"/>
        <!--文件上传的大小上限,单位为字节(10485760=10M)-->
        <property name="maxUploadSize" value="10485760"/>
        <property name="maxInMemorySize" value="40960"/>
    </bean>

前端页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
    <%--上传文件表单--%>
    <form action="${pageContext.request.contextPath}/upload" enctype="multipart/form-data" method="post">
      <input type="file" name="file"/>
      <input type="submit" value="upload" />
    </form>
  </body>
</html>

编写Controller

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.io.File;
import java.io.IOException;

@Controller
public class FileController {

    @RequestMapping("/upload")
    public String upload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) throws IOException {
        /* 1. 获取文件名 */
        String uploadFileName = file.getOriginalFilename();
        if ("".equals(uploadFileName)) {
            redirectAttributes.addFlashAttribute("msg", "文件名为空!");
            return "redirect:/index.jsp";
        }
        System.out.println("上传文件名为:" + uploadFileName);

        /* 2. 设置上传文件保存路径 */
        String uploadPath = "/Users/cseroad/IdeaProjects/studykuangshen/springmvc-02/";
        File realPath = new File(uploadPath);
        if (!realPath.exists()) {
            realPath.mkdir();
        }
        System.out.println("上传文件保存地址: " + realPath);

        /* 3. 将上传文件保存到指定路径 */
        file.transferTo(new File(realPath, uploadFileName));
        redirectAttributes.addFlashAttribute("msg", "文件上传成功!");
        return "redirect:/index.jsp";
    }
}

不再需要自己写上传文件过程,而采用file.Transto 来保存上传的文件。该方法本身没有任何漏洞。

image.png

上传成功以后客户端只是一个302重定向。

文件下载

创建一个前端页面

<h1>测试下载</h1>
<a href="${pageContext.request.contextPath}/download">点击下载</a>

编写一个controller

 @RequestMapping("/download")
    public String downloads(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String fileName = "123.png";
        response.reset();
        response.setCharacterEncoding("utf-8");
        response.setContentType("multipart/form-data");
        response.setHeader("Content-Disposition","attachment;fileName="+ URLEncoder.encode(fileName,"UTF-8"));
        File file = new File("/Users/cseroad/IdeaProjects/studykuangshen/springmvc-02/", fileName);
        InputStream fin = new FileInputStream(file);
        OutputStream out = response.getOutputStream();

        byte[] buff = new byte[1024];
        int len =0;
        while ((len=fin.read(buff))!=-1){
            out.write(buff,0,len);
            out.flush();
        }

        out.close();
        fin.close();
        return null;
    }

点击下载的时候前端无感知。

总结

学习了SpringMVC的一些操作,忽然就理解了部分渗透时遇到的奇奇怪怪的现象。

参考资料

https://www.bilibili.com/video/BV1aE41167Tu?p=1&vd_source=0627d2723fb97773126096556cc98e0d
https://blog.csdn.net/m0_69305074/article/details/124619703
https://github.com/ljingen/kuangstudy/blob/main/%E7%8B%82%E7%A5%9EJAVA-16-SpringMVC%E5%AD%A6%E4%B9%A0.md

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容