【JavaLearn】#(31)SpringMVC原理图及使用、单元方法、restful请求、编码过滤器、静态资源放行、响应、SSM框架、作用域对象、处理Ajax请求、自定义视图解析器、上传、拦截器

1. 使用SpringMVC的原因

通过之前的学习,已经可以使用 Mybatis将数据库替换;使用 Spring将各层之间进行解耦。目前请求的入口还是 Controller层中的 Servlet,tomcat服务器在接收到请求后,会根据请求地址自动调用对应的 servlet中的 service方法,但此流程存在以下问题:

  • 每个功能都要声明对应的 Servlet,很麻烦
  • 在 Servlet中获取请求数据比较麻烦
  • 响应时,其实只想声明对应的响应数据

解决

  • 只声明一个 servlet,作为项目请求的公共入口,参考之前的文章,合并Servlet
  • 将同类型的逻辑方法统一放到对应的逻辑类中,以免结构不清晰
  • 在该servlet中存在逻辑处理,可根据请求地址动态调用相关逻辑类的逻辑方法
    • 如何在 servlet中获取逻辑类对象 — 使用 Spring容器的子容器,在子容器中存储所有的 Controller实例化对象(在 init方法中实现一次性获取)
    • 如何根据请求动态调用对象的逻辑方法 — 使用反射 + 注解

2. 原理图

MyServlet中的逻辑,框架已经实现了

image-20240626231632496

本质:将 Servlet进行了封装,提供一个公共的 Servlet,可以根据请求,动态的调用对应的逻辑方法完成处理。

基本流程:tomcat服务器启动;根据web.xml中的配置,创建对应的Servlet类;然后init()方法开始初始化,加载配置文件;根据配置文件中的内容,扫描注解,形成beans

3. SpringMVC的基本实现

  • 配置 SpringMVC的依赖

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>6.1.8</version>
    </dependency>
    
  • 在 web.xml中配置 Servlet

    • Servlet的访问路径
    • SpringMVC容器对象的配置文件路径
    <!DOCTYPE web-app PUBLIC
     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
      <!-- 配置 SpringMVC提供的 Servlet的访问路径 -->
      <servlet>
        <servlet-name>mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    
        <!-- 配置 SpringMVC容器对象的配置文件的加载路径 -->
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:springmvc.xml</param-value>
        </init-param>
    
        <!--服务器启动即完成DispatcherServlet的初始化创建-->
        <load-on-startup>1</load-on-startup>
      </servlet>
    
      <servlet-mapping>
        <servlet-name>mvc</servlet-name>
        <url-pattern>/</url-pattern> <!-- 拦截除JSP之外的所有请求 -->
      </servlet-mapping>
    </web-app>
    
  • 在resources下创建 SpringMVC配置文件 springmvc.xml

    • 配置注解扫描路径
    • 配置 SpringMVC的驱动解析器
    <?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
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/mvc
           http://www.springframework.org/schema/mvc/spring-mvc.xsd
    ">
    
        <!-- 配置注解扫描路径 -->
        <context:component-scan base-package="com.lwclick" />
    
        <!-- 配置注解驱动解析器 -->
        <mvc:annotation-driven/>
    </beans>
    
  • 创建对应的类,并在类中声明单元方法进行逻辑处理

    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    /**
     * @Author: LW
     * @Date: 2024
     * @Description:
     */
    @Controller
    public class SpringMvcFirstController {
    
        // 声明单元方法:给 DispatcherServlet使用,必须按照 SpringMVC的方式来声明逻辑方法
        @RequestMapping("demo")
        public String demo() {
            System.out.println("我是控制器的第一个单元方法");
            return "a";
        }
    }
    
  • 浏览器中,输入地址 localhost:8080/demo,即可请求到对应的单元方法(此处的 demo为 @RequestMapping注解中的值)

4. 单元方法获取请求数据

通过上面 SpringMVC的基本使用流程,发现 DispatcherServlet可根据请求,动态的调用对应的单元方法进行处理;

那么在单元方法中,如何获取请求数据呢?

解决:tomcat接收请求,然后封装到 request对象内,将其作为实参传递给 DispatcherServlet的 service方法,而 service方法会根据请求动态的调用单元方法,因此只需在 service方法中将 request作为实参传递给单元方法即可(单元方法必须声明对应的形参接收数据

实现方法:

  • 紧耦方式

    • DispatcherServlet的 service方法,直接将此次请求的 request对象传递给单元方法

      然后在单元方法上声明形参 HttpServletRequest来接收

  • 解耦方式

    • DispatcherServlet的 service方法,根据需求从 request对象中取出数据,然后将数据传递给单元方法

      然后在单元方法上声明对应的形参进行接收

4.1 紧耦方式-request对象

@Controller
public class SpringMvcFirstController {

    /**
     * 紧耦合方式获取请求数据
     * 使用:
     *      在单元方法上声明形参,类型为 HttpServletRequest
     *
     * 注意:
     *      单元方法都是由 DispatcherServlet根据请求调用的,因此是由它来传入实参
     */
    @RequestMapping("reqData")
    public String getDataByRequest(HttpServletRequest request) {
        // 获取数据
        String username = request.getParameter("username");

        // 处理逻辑
        System.out.println(username);

        // 响应
        return "abc";
    }
}

4.2 解耦方式-形参名即请求键名

在单元方法上,声明除了 request(HttpServletRequest类型)之外的形参时,DispatcherServlet会通过形参名作为请求数据的键名来获取数据(相当于 request.getParameter(“形参名”) 由 DispatcherServlet帮我们做了)

  • 若形参名和请求数据的键名不一致,则形参接到的数据为 null

    • 如果形参的数据类型为基本类型,则会出现数据类型转换异常(比如将 null转换为 int)

      因此建议将形参的数据类型都声明为包装类

  • 若请求数据的数据类型和形参的数据类型不匹配,会报400异常

当需要获取同键不同值的请求数据时(比如爱好-多选框),也可以在后台声明对应的形参名(如 String[] hobbies)

@Controller
public class SpringMvcFirstController {

    /**
     * 解耦方式获取请求数据——形参名即请求键名
     * 使用:
     *      声明除 request(HttpServletRequest类型)之外的形参,
     *      DispatcherServlet会通过形参名作为键名获取数据
     * @return
     */
    @RequestMapping("reqDataByArgName")
    public String reqDataByArgName(String username) {
        // 处理逻辑
        System.out.println(username);

        // 响应
        return "abc";
    }
}

4.3 解耦方式-@RequestParam

当形参名与请求数据的键名无法做到一致,又想获取数据时,可以使用 @RequestParam注解来实现

@RequestParam的属性:

  • value:获取请求数据的键名(只有 value时,可以省略不写)
  • required:表示请求中是否必须携带当前键名
  • defaultValue:默认值,不可以和 required一起使用
@Controller
public class SpringMvcFirstController {

    /**
     * 解耦方式获取请求数据——使用 @RequestParam
     *
     * @return
     */
    @RequestMapping("reqDataByAnno")
    public String reqDataByAnno(@RequestParam("uname") String username) {
        // 处理逻辑
        System.out.println(username);

        // 响应
        return "abc";
    }
}

4.4 解耦方式-实体类

当请求数据过多时,可以将请求封装到实体类对象中进行使用,保证数据的完整性

  • 实体类的属性名和请求数据的键名需要一致
    • 属性的数据类型使用包装类,避免出现类型转换异常
  • 必须提供对应属性名的 get/set方法
@Controller
public class SpringMvcFirstController {

    /**
     * 解耦方式获取请求数据——使用实体类对象
     *
     * @return
     */
    @RequestMapping("reqDataByObject")
    public String reqDataByObject(User user) {
        // 处理逻辑
        System.out.println(user);

        // 响应
        return "abc";
    }
}

4.5 紧耦和解耦方式混用

形参处可以使用 request,包装类类型的形参名,实体类等

@Controller
public class SpringMvcFirstController {

    /**
     * 解耦方式获取请求数据——使用实体类对象
     *
     * @return
     */
    @RequestMapping("reqDataMixture")
    public String reqDataMixture(User user, @RequestParam("uname") String name, 
                                 Integer age, HttpServletRequest request) {
        // 处理逻辑
        System.out.println(user);

        // 响应
        return "abc";
    }
}

5. restful格式请求

5.1 问题

目前浏览器发起请求给服务器时,不管用 GET还是 POST方式,请求数据的格式都是键值对

GET请求:localhost:8080/project/getUser?uname=zhangsan&age=18

POST请求:localhost:8080/project/addUser,请求体数据: uname=zhangsan age=18

这就要求后台获取请求数据的代码,必须按照指定的键名来获取 ----> 耦合性过高

5.2 实现

直接将数据本身作为请求地址的一部分发送给后台,后台使用 restful格式接收数据

restful格式:localhost:8080/project/getUser/zhangsan/18,可以是 GET,也可以是 POST

5.3 后台获取数据-{}占位符、@PathVariable

从服务器的角度看,服务器接收请求后,会根据请求地址调用对应的 Servlet来处理请求

因此针对restful格式的请求:localhost:8080/project/getUser/zhangsan/18,服务器在接收到该请求后,调用DispatcherServlet(后面简称为DS),DS底层再根据请求的 URL地址信息,调用对应的单元方法。

前面内容,在 web.xml中配置的拦截路径为 ‘/’,因此DS认为请求地址中项目名后的信息为要调用的单元方法的名称(即@RequestMapping注解中的值),所以DS会调用值为 "getUser/zhangsan/18"的方法; 若要获取 lisi的信息,则地址变为了 getUser/lisi/20

可以看到restful格式,获取不同的数据,地址就不同,不可能在后台声明如此多的单元方法,因此需要声明一个公共的单元方法 ----> SpringMVC对 restful格式的支持

后台在 @RequestMapping中使用 {}占位符进行模糊匹配,使用 @PathVariable获取请求数据`

@Controller
public class SpringMvcFirstController {

    /**
     * 处理 restful请求格式
     *      SpringMVC的单元方法支持模糊匹配的声明,使用 {} 占位符
     *
     *
     * @RequestMapping("getUser/{param1}/{param2}")
     *      代表以 getUser开头,后面可携带2个参数
     *      如: getUser/zhangsan/18    getUser/lisi/20
     *
     *
     * 形参列表中,使用 @PathVariable 接收数据
     *      如果占位符中的名字和形参名一致,可以不写
     *      如果不一致,格式为:@PathVariable("占位符名")
     * @return
     */
    @RequestMapping("getUser/{param1}/{obj2}")
    public String getUser(@PathVariable String param1,          // 占位符中的名称和形参名一样
                          @PathVariable("obj2") String param2,  // 占位符中的名称和形参名不一样
                          String fav) {    // 不加 @PathVariable注解,依旧以fav作为请求键名的方式获取数据
        // 处理逻辑
        System.out.println(param1);
        System.out.println(param2);

        // 响应
        return "abc";
    }
}

后台处理,就是从请求地址中,截取一部分数据来使用

restful 只是一种请求数据携带格式,并且可以和正常键值对数据的方式混用

6. 编码过滤器配置

前后台数据交互中,存在中文,浏览器的默认编码为 ISO-8859-1,但是 tomcat服务器默认的编码为 UTF-8,彼此之间对中文的编码形式不一致,就会导致乱码的产生。

image-20240706120413864

解决:使用 SpringMVC的编码过滤器配置 ---- 在 web.xml中进行配置即可

<web-app>
  <!-- 配置编码过滤器    ====== 要放在最上面!!! ====== -->
  <filter>
    <filter-name>charsetFilter</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>
    <!-- 设置编码格式的生效范围, true代表对"请求"和"响应"都有效 -->
    <init-param>
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>charsetFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!-- 其他配置:servlet、 servletMapping -->
</web-app>

image-20240706122336945

7. 静态资源放行配置

前面内容,在 web.xml中配置的拦截范围是 ‘/’,表示拦截除 JSP请求之外的所有请求,那么JS、CSS、图片等静态资源也会被拦截 ---- 需要配置静态资源的放行

在 springmvc.xml中进行配置

<!-- 配置静态资源放行
        mapping 代表的是请求的地址格式,即项目地址后直接带 js,  localhost:8080/js/ajax.js
        location 代表要映射到的目录位置,/js/ 即项目根目录下的 js文件夹
 -->
<mvc:resources mapping="/js/**" location="/js/" />
<mvc:resources mapping="/css/**" location="/css/" />
<mvc:resources mapping="/img/**" location="/img/" />

8. SpringMVC的响应

在单元方法中,接收数据,完成逻辑处理后,如何进行响应呢?

**解决:**使用了 SpringMVC之后,servlet只有一个 DispatcherServlet了;单元方法是由 DS调用的,那么只需要在单元方法执行完毕后,通过 return的内容,告诉 DS如何响应就可以了。

原始方式:使用紧耦方式,即原生的 response进行响应(了解即可:response方式,不经过 servlet,直接响应,所以单元方法的返回值为 void)

8.1 forward转发

通过单元方法的返回值,告诉 DispatcherServlet请求转发指定的资源:JSP资源、其他单元方法等(直接 return “字符串”,就是 forward方式)

转发的方式,request是同一个

@Controller
public class MyController {

    /**
     * 转发 JSP资源
     *
     * @return jsp资源的绝对路径
     */
    @RequestMapping("forwardJsp")
    public String forwardJsp(String uname, Integer age) {
        System.out.println("使用 forward关键字请求转发 JSP资源!");

        // jsp页面的路径, /index.jsp即项目根目录下的(从 webapp下开始找)
        return "/index.jsp";
    }

    /**
     * 转发其他单元方法
     *
     * @return 其他单元方法的请求路径
     */
    @RequestMapping("forwardMethod")
    public String forwardMethod(String uname, Integer age) {
        System.out.println("使用 forward关键字请求其他单元方法!");

        // 其他单元方法的请求地址
        return "/forwardJsp";
    }
}

8.2 redirect重定向

redirect是两次请求,因此 request不是同一个,使用格式: return "redirect:/xxxx"

如果想要传递数据,可以使用 HttpSession session,将数据放到 session中

@Controller
public class MyController {

    /**
     * 重定向到 JSP资源
     *
     * @return jsp资源的绝对路径
     */
    @RequestMapping("redirectJsp")
    public String redirectJsp(String uname, Integer age) {
        System.out.println("使用 redirect关键字重定向到 JSP资源!");

        // jsp页面的路径, /index.jsp即项目根目录下的(从 webapp下开始找)
        return "redirect:/index.jsp";
    }

    /**
     * 重定向到其他单元方法
     *
     * @return 其他单元方法的请求路径
     */
    @RequestMapping("redirectMethod")
    public String redirectMethod(String uname, Integer age) {
        System.out.println("使用 redirect关键字重定向到其他单元方法!");

        // 其他单元方法的请求地址      ====== 其他单元方法收到的 request不是从浏览器直接过来的 =====
        return "redirect:/forwardJsp";
    }
}

9. SSM框架搭建

  • 创建 maven项目,并补齐目录结构

    image-20240706221753276

    image-20240706222006296

  • 在 pom.xml中配置相关依赖(注意版本的对照关系)

    <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/maven-v4_0_0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>com.lwclick</groupId>
      <artifactId>SSM</artifactId>
      <packaging>war</packaging>
      <version>1.0-SNAPSHOT</version>
      <name>SSM Maven Webapp</name>
    
      <!-- 配置版本号 -->
      <properties>
        <spring-version>6.1.6</spring-version>
        <commons-logging-version>1.3.1</commons-logging-version>
        <mybatis-spring-version>3.0.3</mybatis-spring-version>  <!-- 需要注意JDK版本 -->
        <mybatis-version>3.5.16</mybatis-version>
        <slf4j-log4j12-version>2.0.12</slf4j-log4j12-version>
        <log4j-version>2.23.1</log4j-version>
        <mysql-version>8.0.33</mysql-version>
      </properties>
    
      <dependencies>
        <!-- 配置 SpringMVC的依赖 -->
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>${spring-version}</version>
        </dependency>
    
        <!-- 配置 Spring的依赖 -->
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-tx</artifactId>
          <version>${spring-version}</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-aspects</artifactId>
          <version>${spring-version}</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>${spring-version}</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-web</artifactId>
          <version>${spring-version}</version>
        </dependency>
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-jdbc</artifactId>
          <version>${spring-version}</version>
        </dependency>
        <dependency>
          <groupId>commons-logging</groupId>
          <artifactId>commons-logging</artifactId>
          <version>${commons-logging-version}</version>
        </dependency>
    
        <!-- 配置 Spring整合 Mybatis的依赖 -->
        <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis-spring</artifactId>
          <version>${mybatis-spring-version}</version>
        </dependency>
    
        <!-- 配置 Mybatis的依赖 -->
        <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis</artifactId>
          <version>${mybatis-version}</version>
        </dependency>
    
        <!-- 配置日志依赖 -->
        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-log4j12</artifactId>
          <version>${slf4j-log4j12-version}</version>
        </dependency>
        <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-api</artifactId>
          <version>${log4j-version}</version>
        </dependency>
        <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-core</artifactId>
          <version>${log4j-version}</version>
        </dependency>
    
        <!-- 配置 JDBC依赖 -->
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>${mysql-version}</version>
        </dependency>
    
        <!-- 配置 web相关依赖 -->
        <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>4.0.1</version>
        </dependency>
        <dependency>
          <groupId>javax.servlet.jsp</groupId>
          <artifactId>jsp-api</artifactId>
          <version>2.2</version>
        </dependency>
        <dependency>
          <groupId>jstl</groupId>
          <artifactId>jstl</artifactId>
          <version>1.2</version>
        </dependency>
      </dependencies>
    
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.2</version>
            <configuration>
              <port>8080</port>
              <path>/ssm</path>
            </configuration>
          </plugin>
        </plugins>
      </build>
    </project>
    

    image-20240716225608494

  • 在 "java"目录下,新建controller、mapper、pojo、service等目录

  • 在 "resources"目录下,新建 applicationcontext.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:aop="http://www.springframework.org/schema/aop"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd"
    
           default-autowire="byName">
    
        <!--属性文件扫描-->
        <context:property-placeholder location="classpath:db.properties" />
        
        <!--配置注解扫描, service层 -->
        <context:component-scan base-package="com.lwclick.service.impl" />
        
        <!--配置数据源bean-->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="${mysql.driver}" />
            <property name="url" value="${mysql.url}" />
            <property name="username" value="${mysql.username}" />
            <property name="password" value="${mysql.password}" />
        </bean>
        
        <!--配置工厂bean-->
        <bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean" />
        
        <!--配置Mapper扫描bean,mapper层-->
        <bean id="mapper" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="sqlSessionFactoryBeanName" value="factory" />
            <property name="basePackage" value="com.lwclick.mapper" />
        </bean>
        
        <!--配置事务管理bean-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" />
        
        <!--配置事务管理方法-->
        <tx:advice id="advice" transaction-manager="transactionManager">
            <tx:attributes>
                <tx:method name="sel*" read-only="true"/>
                <tx:method name="ins*"/>
                <tx:method name="upd*"/>
                <tx:method name="del*"/>
            </tx:attributes>
        </tx:advice>
        <!--配置事务管理切面-->
        <aop:config>
            <aop:pointcut id="mp" expression="execution(* com.lwclick.service.impl.*.*(..))"/>
            <aop:advisor advice-ref="advice" pointcut-ref="mp" />
        </aop:config>
    </beans>
    
  • 在 "resources"目录下,新建 db.properties

    mysql.driver=com.mysql.cj.jdbc.Driver
    mysql.url=jdbc:mysql://localhost:3306/ssm
    mysql.username=root
    mysql.password=root
    
  • 在 "resources"目录下,新建 log4j.properties

    log4j.rootCategory=info
    log4j.logger.com.lwclick.mapper=debug, CONSOLE,LOGFILE
    log4j.logger.com.lwclick.advice=debug, CONSOLE,LOGFILE
    log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
    log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
    log4j.appender.CONSOLE.layout.ConversionPattern=- %c-%d-%m%n
    log4j.appender.LOGFILE=org.apache.log4j.FileAppender
    log4j.appender.LOGFILE.File=D:/ssm.log
    log4j.appender.LOGFILE.Append=true
    log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
    log4j.appender.LOGFILE.layout.ConversionPattern=- %c-%d-%m%n
    
  • 在 "resources"目录下,新建 springmvc.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:mvc="http://www.springframework.org/schema/mvc"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/mvc
           http://www.springframework.org/schema/mvc/spring-mvc.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!--配置注解扫描, controller层-->
        <context:component-scan base-package="com.lwclick.controller" />
    
        <!--配置注解解析器-->
        <mvc:annotation-driven />
    
        <!--配置静态资源放行-->
        <mvc:resources mapping="/js/**" location="/js/" />
        <mvc:resources mapping="/css/**" location="/css/" />
        <mvc:resources mapping="/images/**" location="/images/" />
    </beans>
    
  • 配置 webapp目录下 WEB-INF目录下的 web.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://java.sun.com/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
            http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
             version="3.0">
    
        <!--配置Spring容器的配置文件路径-->
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationcontext.xml</param-value>
        </context-param>
    
        <!--配置Spring的监听器-->
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
    
        <!--配置SpringMVC的Servlet-->
        <servlet>
            <servlet-name>mvc</servlet-name>
            <!-- 'org.springframework.web.filter.CharacterEncodingFilter' is not assignable to 'javax.servlet.Filter,javax.servlet.Filter'   可以回退spring及springmvc的版本 -->
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <!--配置SpringMVC容器的配置文件路径-->
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:springmvc.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>mvc</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
        <!--配置编码过滤器-->
        <filter>
            <filter-name>code</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>forceEncoding</param-name>
                <param-value>true</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>code</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    </web-app>
    
  • 在 "controller"包下新建控制器类,书写逻辑

    通过依赖注入的方式,在controller层声明 service层对象,在service层声明 mapper层对象

    (前提是在 applicationcontext.xml文件内配置了service和 mapper层的注解扫描,从前台能访问的前提是在 springmvc.xml中配置了 controller层的注解扫描)

    需要注意的是,如果声明了事务,在 controller层,声明 service层的对象,用的是 private UserService userService,即接口,而不是接口的实现类 xxxImpl

10. 使用作用域对象完成数据的流转

10.1 作用域对象

  • PageContext对象
    • 作用域:当前 jsp页面内有效
  • request对象
    • 作用域:一次请求内
    • 解决了一次请求内的资源的数据共享问题
  • session对象
    • 作用域:一次会话内有效
    • 浏览器不关闭,且后台的 session不失效,则在同一个用户的任意请求中都可以获取到同一个 session对象
  • application (ServletContext)对象
    • 作用域:整个项目内有效
    • 一个项目只有一个,在服务器启动的时候就完成初始化创建,解决了不同用户的数据共享问题

10.2 使用 request对象

/**
 * 使用 request对象,作为请求转发的数据流转的载体
 *      需要在单元方法上,声明类型为 HttpServletRequest的 request对象
 */
@RequestMapping("testRequest")
public String testReq(String uname, HttpServletRequest request) {
    System.out.println("使用 request对象作为请求转发数据流转的载体:" + uname);

    // 在 req.jsp中,可以获取键值为 str的数据
    request.setAttribute("str", "我是request对象");
    return "/req.jsp";
}

10.3 使用 session对象

使用 session对象作为同一个用户,不同请求的数据流转的载体(用在重定向中,可以避免无法传递数据)

/**
 * 使用 session对象,作为同一个用户,不同请求的数据流转的载体
 *      在单元方法上,声明类型为 HttpSession的 session对象
 */
@RequestMapping("testSession")
public String testSession(String uname, HttpSession session) {
    System.out.println("使用session作为数据流转的载体:" + uname);

    // 通过某一请求,重定向到 session.jsp中后,在 session.jsp中仍然可以获取键值为 str的数据
    session.setAttribute("str", "我是session对象");
    return "redirect:/session.jsp";
}

10.4 使用 application对象

使用 application对象,作为项目公共数据的载体,可以让不同的用户共享数据(application对象的获取,只能在单元方法中通过 request获取,无法使用形参的方式)

/**
 * 使用 application对象,作为项目公共数据的载体,可以让不同的用户共享数据
 *      在单元方法中,使用类型为 HttpServletRequest的 request对象获取 servletContext
 */
@RequestMapping("testApplication")
public String testApplication(String uname, HttpServletRequest request) {
    System.out.println("使用session作为数据流转的载体:" + uname);

    ServletContext servletContext = request.getServletContext();
    servletContext.setAttribute("str", "我是 ServletContext对象");
    return "redirect:/session.jsp";
}

10.5 使用 SpringMVC的 Model对象

Model对象也可作为数据流转的载体,是 SpringMVC提供的方法

使用:在单元方法上,声明 Model类型的形参即可(是由DispatcherServlet创建,并作为实参传递给单元方法)

  • 请求转发方式:

    • model对象中存储的数据,相当于存储到了 request对象中
    /**
     * 使用 Model对象作为数据流转的载体  --- 请求转发方式
     *    在单元方法上声明 Model类型的形参
     */
    @RequestMapping("testModelForward")
    public String testModelForward(String uname, Model model) {
        System.out.println("使用 Model对象作为数据流转的载体:" + uname);
    
        model.addAttribute("str", "我是 Model对象作为请求转发数据流转的载体!");
        return "/model.jsp";
    }
    
  • 重定向方式:

    • 会将第一次请求中的 model数据,作为第二次请求的请求数据,第一次请求的 model对象销毁(只能携带基本类型的数据)
    /**
     * 使用 Model对象作为数据流转的载体  --- 重定向方式
     */
    @RequestMapping("testModelRedirect")
    public String testModelRedirect(String uname, Model model) {
        System.out.println("使用 model作为数据流转的载体:" + uname);
    
        model.addAttribute("str", "我是 Model对象作为 重定向 数据流转的载体!");
        // ==== 重定向到 model.jsp后,会携带参数 str,值为 '我是 Model对象作为 重定向 数据流转的载体!' ===
        return "redirect:/model.jsp";
    }
    

11. 对 Ajax请求的处理

目前 DispatcherServlet的底层会将单元方法的返回值按照请求转发或者重定向来处理,但是 ajax的请求在被处理完后,需要直接响应,因此需要告诉 DS直接进行响应 — @ResponseBody

  • 配置 jackson依赖

    SpringMVC的底层会自动调用该依赖中的资源,将响应数据转换为 json格式

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.17.1</version>
    </dependency>
    
  • 在单元方法上增加 @ResponseBody注解,将响应数据直接 return即可(返回类型为要 return的数据类型)

    /**
     * 处理 ajax请求
     */
    @RequestMapping("testAjax")
    @ResponseBody
    public User testAjax(String uname, Integer age) {
        System.out.println("单元方法,处理ajax请求:" + uname);
    
        return new User(uname, age);
    }
    
  • 在 ajax回调函数中,直接使用返回的 json对象即可,无需再用 eval函数将响应数据转换为 json对象

12. 自定义视图解析器

12.1 默认视图解析器-ModelAndView

DispatcherServlet 底层是如何通过单元方法 return值中的关键字来区分是请求转发还是重定向呢? — 视图解析器

SpringMVC官方封装好了 请求转发(InternalResourceView)、重定向(RedirectView)、请求转发和重定向(ModelAndView) 这几个视图解析器,DispatcherServlet目前默认用的就是 ModelAndView

image-20240707180102324

12.2 自定义视图解析器

可以让开发者根据需求配置一些内容,同时还具备 ModelAndView的逻辑 ---- InternalResourceViewResolver

它在接收到单元方法的返回值后,会给返回值增加一个前缀和后缀常量,然后返回拼接的内容

  • 在 springmvc.xml中进行配置

    <!-- 配置视图解析器 -->
    <bean id="resourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/packageA/page" />   <!-- 前缀内容 -->
        <property name="suffix" value=".jsp" />   <!-- 后缀内容 -->
    </bean>
    

配置好后,如果单元方法的返回值中,不带任何关键词(forward: 、redirect:)就会触发自定义的视图解析器,否则还是使用 ModelAndView来完成资源解析

而且自定义视图解析器只做资源的请求转发

12.3 使用场景

目前项目相关的页面资源及静态资源都直接声明在了 web目录下,并且在 springmvc.xml中配置了静态资源放行(<mvc:resources mapping="/js/**" location="/js/" />)因此只要知道某个资源的URL地址,任何人都可以访问到,非常不安全

解决:WEB-INF文件夹,浏览器无法直接访问它下面的资源,必须通过内部请求转发才能访问;

@RequestMapping("getJsp")
public String getJsp() {
	// 逻辑代码
    
    return "/WEB-INF/jsp/index.jsp";
}

代码优化

  • 为避免资源过多,每次都要在单元方法中写前缀及后缀内容,结合自定义视图解析器 InternalResourceViewResolver进行优化

    • 在 springmvc.xml中进行配置

      <!-- 配置视图解析器 -->
      <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
          <property name="prefix" value="/WEB-IFN/jsp/" />      <!-- 前缀内容 -->
          <property name="suffix" value=".jsp" />               <!-- 后缀内容 -->
      </bean>
      
  • 为避免资源过多,需要声明很多的单元方法,使用 restful声明公共的单元方法,转发 WEB-INF下的资源

    • 声明单元方法

      /**
       * 使用自定义视图解析器 + restful
       */
      @RequestMapping("{uri}")
      public String getJsp(@PathVariable String uri) {
          System.out.println("使用自定义视图解析器,保证资源的安全");
      
          System.out.println("使用restful,声明公共单元方法,完成 WEB-INF下的资源的转发");
      
          return uri;  // 直接写资源的名称即可,对应的为 /WEB-INF/jsp/xxx.jsp
      }
      

因此可以将 JS、CSS、image等资源也都放到 /WEB-INF目录下,同时需要修改 springmvc.xml文件中的资源放行配置

<!--配置静态资源放行-->
<mvc:resources mapping="/js/**" location="/WEB-INF/js/" />    <!-- 增加 /WEB-INF目录 -->
<mvc:resources mapping="/css/**" location="/WEB-INF/css/" />
<mvc:resources mapping="/images/**" location="/WEB-INF/images/" />

13. 上传功能

需求:将用户本地的资源,上传到服务器

image-20240708223730554

前台 ajax代码示例:

// 获取请求数据(要上传的文件)
var file = $("#photo")[0].files[0];  // 存储了上传的文件信息的js对象 <input type="file" id="photo">

// 创建 FormData对象存储要上传的资源 ajax
var formData = new FormData();
formData.append("photo", file);      // 后台获取数据的键名

// 发起ajax请求,上传请求数据
$.ajax({
    url: "regUpload",   // 处理上传请求的单元方法路径,后台 servlet地址
    type: "post",       // 请求方式,设置为post
    processData: false, // 设置false
    contentType: false, // 设置false,将请求数据类型设置为 multipart/form-data
    data: formData,     // 请求数据,用户要上传的文件的二进制数据
    success: function (data) {   // 上传成功后的回调函数,响应数据为json对象
        if(data.state) {
            alert("上传成功")
        } else {
            alert("上传失败")
        }
    }
})

在 springmvc.xml中配置上传解析器

<!-- 配置上传解析bean      注意!! id 必须为multipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>

<!-- 注意:
        从 Spring3.1开始,Spring提供了两个 MultipartResolver的实现用于处理 multiparty请求
            CommonsMultipartResolver: 使用commons fileupload,需要引入对应 pom依赖
            StandardServletMultipartResolver: 基于 Servlet3.0 (即Tomcat 7.0.x之后的版本)
     -->

在 web.xml中配置上传解析器的初始化配置

<servlet>
    <servlet-name>mvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    
    <!-- 配置上传解析器的初始化配置 -->
    <multipart-config>
        <!-- 临时文件的目录 -->
        <location>d:/</location>
        <!-- 上传单个文件的最大限制,单位 字节 -->
        <max-file-size>2097152</max-file-size>
        <!-- 上传整个请求的最大限制,单位 字节-->
        <max-request-size>4194304</max-request-size>
    </multipart-config>
</servlet>

在 controller中书写单元方法进行处理

@Controller
public class MyController {

    /**
     * 在单元方法上直接声明形参接收数据(通过 photo键名获取数据:前台设置的名字)
     * @param photo
     */
    @RequestMapping("regUpload")
    public void regUpload(MultipartFile photo) {
        System.out.println(photo.getSize());
        System.out.println("收到文件");
        // 其他逻辑处理
    }
}

14. 拦截器

在 DispatcherServlet之后,单元方法之前使用拦截器预处理请求

image-20240717224002763

  • 创建Java类,实现 HandlerInterceptor接口

    public class MyInter implements HandlerInterceptor {
    
        /**
         * 拦截请求,   在单元方法之前执行
         *
         * @param request  当次的请求对象
         * @param response 当次的响应对象
         * @param handler  实参实际是 HandlerMethod的对象, 由 DispatcherServlet传过来的
         *                 HandlerMethod的对象中,存储了当次请求的单元方法的方法对象
         *                 ========= 因此,可以通过反射调用请求的单元方法 =========
         * @return true表示放行, false表示拦截
         * @throws Exception
         */
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            HandlerMethod hm = (HandlerMethod) handler;
            Method method = hm.getMethod();
    
            return HandlerInterceptor.super.preHandle(request, response, handler);
        }
    
        /**
         * 在单元方法执行完,准备跳转资源之前执行
         *
         * @param request      当次的请求对象
         * @param response     当次的响应对象
         * @param handler      实参实际是 HandlerMethod的对象, 由 DispatcherServlet传过来的
         * @param modelAndView 当次请求的单元方法的返回值的对象(单元方法执行后的视图解析器对象)
         *                     ---- 可以对跳转的资源进行重置  或者对 model对象中流转的数据进行修改
         * @throws Exception
         */
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            // modelAndView.setViewName("重置的资源的地址");
    
            modelAndView.getModel().put("键名", "要修改的值");
            HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
        }
    
        /**
         * 处理在当次请求的任意位置出现的异常
         *
         * @param request  当次的请求对象
         * @param response 当次的响应对象
         * @param handler  实参实际是 HandlerMethod的对象, 由 DispatcherServlet传过来的
         * @param ex       处理过程中的异常信息
         * @throws Exception
         */
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            // 打印异常堆栈信息
            System.out.println(ex.getMessage());
            HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
        }
    }
    
  • 在 springmvc.xml中配置拦截器 bean

    <!-- 配置拦截器的bean -->
    <mvc:interceptors>
        <!-- ======全局拦截器====== 直接配置bean -->
        <bean id="myInterAll" class="com.lwclick.interceptor.MyInter" />
    
        <!-- ======局部拦截器====== mvc:interceptor 标签下-->
        <!-- 配置对应的实现bean,以及相应的拦截范围 -->
        <mvc:interceptor>
            <!-- 拦截范围:拦截单元方法的路径 -->
            <mvc:mapping path="/demo/"/>      <!-- 可以配置多个标签,表示拦截多个路径 -->
            <bean id="myInter" class="com.lwclick.interceptor.MyInter" />
        </mvc:interceptor>
    </mvc:interceptors>
    

如果存在多个拦截器,按照在 springmvc.xml中的位置进行触发: preHandle(配置靠上) --> preHandle(配置靠下) --> 单元方法 --> postHandle(配置靠下) --> postHandle(配置靠上) --> afterCompletion(配置靠下) --> afterCompletion(配置靠上)

15. SpringMVC运行原理

核心组件:

  • DispatchServlet:Servlet分发器,整个SPringMVC框架入口
  • HandlerMapping:寻找 URL所请求的 HandlerMethod,找 @RequestMapping()
    • 实现类 DefaultAnnotationHandlerMapping实际起作用
  • HandlerAdapter:实际调用HandlerMethod的组件
    • 实现类 AnnotationMethodHandlerAdapter实际起作用
  • ViewResovler:视图解析器,解析 HandlerMethod返回值,把逻辑视图转换为需要调用的物理视图
    • 自定义时:InternalResourceViewResolver

当在 springmvc.xml中配置了 <mvc:annotation-driven/>时,实际创建了以上 4个组件的 bean

image-20240717234111032

  • 9
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
SSM框架中,静态资源放行可以通过以下步骤实现: 1.在WebContent目录下创建一个名为Static-Resources的文件夹,并在该文件夹下创建css、images和js三个文件夹,用于存放静态资源文件。 2.在web.xml文件中配置DispatcherServlet,将其拦截所有请求。 3.在springmvc.xml文件中添加静态资源放行语句,将Static-Resources文件夹下的css、images和js文件夹中的静态资源文件放行。 具体实现步骤如下: 1.在WebContent目录下创建Static-Resources文件夹,并在该文件夹下创建css、images和js三个文件夹,用于存放静态资源文件。 2.在web.xml文件中配置DispatcherServlet,将其拦截所有请求。 ```xml <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> ``` 3.在springmvc.xml文件中添加静态资源放行语句,将Static-Resources文件夹下的css、images和js文件夹中的静态资源文件放行。 ```xml <!-- 对静态资源放行 --> <mvc:resources location="/Static-Resources/css/" mapping="/css/**"/> <mvc:resources location="/Static-Resources/images/" mapping="/images/**"/> <mvc:resources location="/Static-Resources/js/" mapping="/js/**"/> ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LRcoding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值