springMVC学习

springMVC学习

三层架构

表现层

也就是我们常说的web层。它负责接收客户端请求,向客户端响应结果,通常客户端使用http协议请求web 层, web 需要接收 http 请求,完成 http 响应。

表现层包括展示层和控制层:控制层负责接收请求,展示层负责结果的展示。

表现层依赖业务层,接收到客户端请求一般会调用业务层进行业务处理,并将处理结果响应给客户端。

表现层的设计一般都使用 MVC 模型。(MVC 是表现层的设计模型,和其他层没有关系)

MVC模型:

  • M(model,模型):javabean
  • V(view,视图):jsp\html
  • C(controller,控制器):servlet

业务层

也就是我们常说的 service 层。它负责业务逻辑处理,和我们开发项目的需求息息相关。 web 层依赖业务层,但是业务层不依赖 web 层。

业务层在业务处理时可能会依赖持久层,如果要对数据持久化需要保证事务一致性。(也就是我们说的,事务应该放到业务层来控制

持久层

也就是我们是常说的 dao 层。负责数据持久化,包括数据层即数据库和数据访问层,数据库是对数据进行持久化的载体,数据访问层是业务层和持久层交互的接口,业务层需要通过数据访问层将数据持久化到数据库中。通俗的讲,持久层就是和数据库交互,对数据库表进行曾删改查的。

框架简介

Web项目总览:

image-20200803205806305

image-20200312210738734

如果直接用Servlet,就必须继承Servlet或其子对象并实现其的方法,但是对于SpringMVC却不需要,加一个注解即可

SpringMVC的优势

  1. 清晰的角色划分:
    前端控制器(DispatcherServlet)
    请求到处理器映射(HandlerMapping)
    处理器适配器(HandlerAdapter)
    视图解析器(ViewResolver)
    处理器或页面控制器(Controller)
    验证器( Validator)
    命令对象(Command 请求参数绑定到的对象就叫命令对象)
    表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象)。
  2. 分工明确,而且扩展点相当灵活,可以很容易扩展,虽然几乎不需要。
  3. 由于命令对象就是一个 POJO,无需继承框架特定 API,可以使用命令对象直接作为业务对象。
  4. 和 Spring 其他框架无缝集成,是其它 Web 框架所不具备的。
  5. 可适配,通过 HandlerAdapter 可以支持任意的类作为处理器。
  6. 可定制性, HandlerMapping、 ViewResolver 等能够非常简单的定制。(意思就是你要改的话很好改)
  7. 功能强大的数据验证、格式化、绑定机制。
  8. 利用 Spring 提供的 Mock 对象能够非常简单的进行 Web 层单元测试。
  9. 本地化、主题的解析的支持,使我们更容易进行国际化和主题的切换。
  10. 强大的 JSP 标签库,使 JSP 编写更容易。
    ………………还有比如RESTful风格的支持、简单的文件上传、约定大于配置的契约式编程支持、基于注解的零配置支持等等

image-20200312211104666

环境搭建和入门程序

Spring+SpringMVC+Mybatis的示例程序看这里:https://github.com/JiaDingCN/MyBBS

  1. 创建Maven项目,从webapp的骨架开始构建,创建java\resources等包

  2. 引入maven依赖:

    <?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/maven-v4_0_0.xsd">
    
        <modelVersion>4.0.0</modelVersion>
        //版本锁定
        <packaging>war</packaging>
    
        <name>springmvc-day01_01_start</name>
        <groupId>com.jiading</groupId>
        <artifactId>springmvc-day01_01_start</artifactId>
        <version>1.0-SNAPSHOT</version>
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>
            <spring.version>5.0.2.RELEASE</spring.version>
        </properties>
        <dependencies>
            <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-webmvc</artifactId>
                <version>${spring.version}</version>
            </dependency>
    
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>servlet-api</artifactId>
                <version>2.5</version>
                <scope>provided</scope>
            </dependency>
    
            <dependency>
                <groupId>javax.servlet.jsp</groupId>
                <artifactId>jsp-api</artifactId>
                <version>2.0</version>
                <scope>provided</scope>
            </dependency>
    
        </dependencies>
        <build>
            <finalName>springmvc_day01_01_start</finalName>
            <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
                <plugins>
                    <plugin>
                        <artifactId>maven-clean-plugin</artifactId>
                        <version>3.0.0</version>
                    </plugin>
                    <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
                    <plugin>
                        <artifactId>maven-resources-plugin</artifactId>
                        <version>3.0.2</version>
                    </plugin>
                    <plugin>
                        <artifactId>maven-compiler-plugin</artifactId>
                        <version>3.7.0</version>
                    </plugin>
                    <plugin>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <version>2.20.1</version>
                    </plugin>
                    <plugin>
                        <artifactId>maven-war-plugin</artifactId>
                        <version>3.2.0</version>
                    </plugin>
                    <plugin>
                        <artifactId>maven-install-plugin</artifactId>
                        <version>2.5.2</version>
                    </plugin>
                    <plugin>
                        <artifactId>maven-deploy-plugin</artifactId>
                        <version>2.8.2</version>
                    </plugin>
                </plugins>
            </pluginManagement>
        </build>
    
    
    </project>
    
  3. 在web.xml中配置前端控制器,其本质就是一个servlet,所以根据我们学习servlet的经验进行同样的配置

    完整的xml配置如下:

    <!DOCTYPE web-app PUBLIC
            "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
            "http://java.sun.com/dtd/web-app_2_3.dtd"
            >
    <!--全局配置
    web.xml:每个javaEE项目中都会有,web.xml文件是用来初始化配置信息-->
    <web-app>
        <display-name>Archetype Created Web Application</display-name>
        <welcome-file-list>
            <!--welcome file就是加载时默认加载的页面-->
            <welcome-file>/index.html</welcome-file>
        </welcome-file-list>
        <servlet-mapping>
            <!-- 让 tomcat 的默认 servlet 处理静态资源
            除了Jsp页面之外,都由它处理-->
            <servlet-name>default</servlet-name>
            <url-pattern>*.jpg</url-pattern>
            <url-pattern>*.png</url-pattern>
            <url-pattern>*.gif</url-pattern>
            <url-pattern>*.css</url-pattern>
            <url-pattern>*.js</url-pattern>
            <url-pattern>*.swf</url-pattern>
            <url-pattern>*.pdf</url-pattern>
            <url-pattern>*.java</url-pattern>
            <url-pattern>*.class</url-pattern>
            <url-pattern>*.xls</url-pattern>
            <url-pattern>*.xlsx</url-pattern>
            <url-pattern>*.et</url-pattern>
            <url-pattern>*.vm</url-pattern>
            <url-pattern>*.html</url-pattern>
        </servlet-mapping>
        <!--配置Spring的监听器,默认只加载WEB-INF目录下的applicationContext.xml配置文件
        ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。-->
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
        <!--设置配置文件的路径-->
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </context-param>
    
        <!--配置前端控制器-->
        <servlet>
            <servlet-name>dispatcherServlet</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <!--加载springmvc.xml配置文件-->
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:springmvc.xml</param-value>
            </init-param>
            <!--启动服务器,创建该servlet-->
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>dispatcherServlet</servlet-name>
            <!--
            <url-pattern>/ </ url-pattern > 不会匹配到*.jsp,由JspServlet(tomcat中默认的web.xml中配置的)拦截处理,即*.jsp不会进入spring的 DispatcherServlet类 。
            2、在配置了< url-pattern > /* </ url-pattern >的情况下自然会拦截所有请求(包括/login,*.jsp,*.js和*.html等)
            JspServlet:
            org.apache.jasper.servlet,Class JspServlet:
            The JSP engine (a.k.a Jasper).
            The servlet container is responsible for providing a URLClassLoader for the web application context Jasper is being used in.
            -->
            <url-pattern>/</url-pattern>
        </servlet-mapping>
        <!--解决中文乱码的过滤器-->
        <filter>
            <filter-name>characterEncodingFilter</filter-name>
            <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
            <init-param>
                <param-name>encoding</param-name>
                <param-value>UTF-8</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>characterEncodingFilter</filter-name>
            <!-- 拦截所有请求
            This filter can either apply its encoding if the request does not already specify an encoding, or enforce this filter's encoding in any case
            -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
    </web-app>
    
  4. 在resources中配置springmvc的配置文件,例如叫springmvc.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           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">
        <!--开启注解扫描-->
        <context:component-scan base-package="com.jiading"></context:component-scan>
    
        <!--配置视图解析器
        视图解析器也会被实例化为对象-->
        <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <!--视图解析器会去prefix下找视图,suffix是确定了要找的文件的后缀-->
            <property name="prefix" value="/WEB-INF/pages/"></property>
            <property name="suffix" value=".jsp"></property>
        </bean>
    
        <!--开启springmvc注解的支持
        开启后,同时默认配置处理器适配器和处理器映射器-->
        <mvc:annotation-driven></mvc:annotation-driven>
    </beans>
    

    关于<mvc:annotation_driven>注解:

    使 用 mvc:annotation-driven 自 动 加 载 RequestMappingHandlerMapping ( 处 理 映 射 器 ) 和RequestMappingHandlerAdapter ( 处 理 适 配 器 ) , 可 用 在 SpringMVC.xml 配 置 文 件 中 使 用
    mvc:annotation-driven替代注解处理器和适配器的配置。
    它就相当于在 xml 中配置了:

    <!-- 上面的标签相当于 如下配置-->
    <!-- Begin -->
    <!-- HandlerMapping -->
    <bean  
    class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerM
    apping"></bean>
    <bean
    class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>
    <!-- HandlerAdapter -->
    <bean
    class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerA
    dapter"></bean>
    <bean
    class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"></bean>
    <bean
    class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
    <!-- HadnlerExceptionResolvers -->
    <bean
    class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExcept
    ionResolver"></bean>
    <bean
    class="org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolv
    er"></bean>
    <bean
    class="org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver"
    ></bean>
    <!-- End -->
    

    一般开发中,我们都需要写上此标签(虽然从入门案例中看,我们不写也行,随着课程的深入,该标签还有具体的使用场景)。 它使得我们只需要编写处理具体业务的控制器以及视图 。

  5. 和spring结合后,控制器类这么写:

    package com.jiading.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    
    /**
     * 控制器的类
     * 如果类上加了RequestMapping注解,则调用方法时需要在path上先加上类的path,再加方法的path
     * RequestMapping的属性:
     * value:和value是等价的;既然和value等价,那么如果只写这一个属性的话,不写属性名也可以
     * method:当前请求方法接受的请求方式(get\post):使用RequestMethod这个枚举类就好
     * params:用于指定限制请求参数的条件。 它支持简单的表达式。 要求请求参数的 key 和 value 必须和配置的一模一样。
     * 例如:
     * params = {"accountName"},表示请求参数必须有 accountName(参数可以通过?之后key=value传递)
     * params = {"money!100"},表示请求参数中 money 不能是 100。
     * params = {"username=hehe"},表示请求参数username必须等于hehe
     * headers:指定发送的请求必须包含的请求头。例如:headers={"Accept"}
     * 注意:以上四个属性只要出现 2 个或以上时,他们的关系是与的关系。
     */
    @Controller
    @RequestMapping(path = "/user", method = {RequestMethod.POST})
    public class helloController {
        @RequestMapping(path = "/hello")
        public String sayHello() {
            System.out.println("hello,springMVC");
            return "success";//它会要求跳转到一个叫success的jsp中,这由视图解析器处理
        }
    
        @RequestMapping(path = "/testRequestMapping")
        public String testRequestMapping() {
            System.out.println("测试RequestMapping注解");
            return "success";
        }
    }
    

    当然其实也可以直接传入request和response,然后借助这两个对象进行跳转等操作,和servlet中一样:

        @RequestMapping(value = "/modifyPassword", method = RequestMethod.POST)
        public void modifyPassword(HttpServletRequest req, HttpServletResponse resp) throws IOException {
            String password = req.getParameter("password");
            User user = (User) req.getSession().getAttribute("user");
            user.setPassword(password);
            service.updateUser(user);
            writeValue(ResultInfo.getTrueResultInfo(),resp);
        }
    

SpringMVC步骤分析

image-20200803215752048

完整版如下:

image-20200803215841635

SpringMVC框架是基于组件的方式执行流程的

HandleMapping根据请求的url以及我们注解的RequestMapping来找到合适的handler,返回的是控制器的对应的方法,由处理器适配器去采用适配器模式封装对象并执行,返回的对象去视图解析器中解析来获取页面,从而返回页面给前端控制器

其实视图解析器也不一定需要,在Vue框架开发的前端中,其只需要与后端进行数据的交互,页面的跳转可以由前端完成,并且其页面从文件上来看只有一个Index页面,后端也没办法完成跳转任务,此时视图解析器配置不配置都没关系,因为根本用不上,在Controller层把数据封装到response中就完事了

组件分析

前端控制器

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

处理器映射器

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

处理器

它就是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler。由Handler 对具体的用户请求进行处理。

视图解析器

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

视图

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

请求参数的绑定

也就是从前端拿到数据的过程

常用的方法是直接在方法上加上传入的参数

public void sayHello(String username,String password)

要求方法的形参名称和传入的参数的名称完全一样,大小写也要统一,这样框架会自动给绑定到形参上

除了基本类型,也支持实体类和集合(list\map)

  • 如果是实体类,因为前端传入的只能是key-value形式的,不能是对象,所以只要求前端传入的key和bean中的属性名称完全对应,方法上的形参直接写对象即可。特别的,如果对象中有引用类型,例如包括了另外一个实体类,我们在前端提交的时候对应的数据要写成“该引用类型对象的名称.其属性”的方式,这样就会自动封装了。例如:

    image-20200803231637115

  • 集合的话,例如我们的对象里面包括这样的两个集合类型的属性:

    image-20200803231826566

    前端的name就应该这样写:

    image-20200803232215844

除了这种方法,也可以直接获取servlet的HttpServletRequest和HttpServletResponse对象,然后按servlet那样利用getParameter等方法来获取参数,也就是完全用serlvet原生的那一套

特殊情况:自定义类型转换器

这个问题主要是,因为前端传回来的所有的值都是字符串类型,而我们后端要将其转换为各种各样的类型,这个过程一般是不需要我们手动做的,SpringMVC会帮我们把常见的类型正确地转换掉。但是对于一些特殊的、容易出问题的类型,例如Date类,如果前端传回来的类型和这个类型在后端的要求的格式不一样的话,类型转换就会失败,这个时候我们可以通过自定义类型转换器来解决。

image-20200804114413134

上图是部分框架所提供的类型转换器

步骤如下:

  1. 定义一个类,实现Converter接口

  2. 在springmvc.xml中注册这个类型转换器

    image-20200804113814101

    image-20200804113841104

常用注解

RequestParam

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

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

它可以用于要求前端必须返回某种数据,也可以用于前端数据名称和形参名称不一样的情况

使用实例:

image-20200804222522293

RequestBody

image-20200804222737826

没有value属性

get方法不适用是因为get方法没有请求体,其参数都在url中

控制器方法应该这样写,这个注解也是加在形参前面的

image-20200804223035128

另外,如果RequestBody注解加在方法上,字符串类型的返回值会通过HTTP响应主体直接发送给浏览器。

@Controller

public class UserController{

	@ResponseBody

	@RequestMapping(“/login”)

	public String login(){

	return “hello”;

	}

}

img

对于非字符串类型的返回值,我们需要添加转换器将其转换为字符串类型,常用的就是将其转换为json格式

配置方法非常简单,我们只需要添加jackson-databind依赖:

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-databind</artifactId>

<version>2.8.7</version>

</dependency>

Jackson库可以实现Java对象和JSON的相互转换,引入Jackson-databind依赖后,SpringMVC会自动添加Jackson的转换器。

RequestHeader

和前面类似,是用来获取请求头的

value:提供消息头名称(因为请求头内容也很多,所以我们可以通过设置value来获取特定key的value而不是都拿过来)
required:是否必须有此消息头

image-20200804223307059

PathVariable

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

属性:
value: 用于指定 url 中占位符名称。
required:是否必须提供占位符

REST风格URL

什么是 rest:
REST(英文: Representational State Transfer,简称 REST)描述了一个架构样式的网络系统,
比如 web 应用程序。它首次出现在 2000 年 Roy Fielding 的博士论文中,他是 HTTP 规范的主要编写者之一。在目前主流的三种 Web 服务交互方案中, REST 相比于 SOAP(Simple Object Access protocol,简单对象访问协议)以及 XML-RPC 更加简单明了,无论是对 URL 的处理还是对 Payload 的编码, REST 都倾向于用更加简单轻量的方法设计和实现。值得注意的是 REST 并没有一个明确的标准,而更像是一种设计的风格。它本身并没有什么实用性,其核心价值在于如何设计出符合 REST 风格的网络接口。
restful 的优点
它结构清晰、符合标准、易于理解、 扩展方便,所以正得到越来越多网站的采用。
restful 的特性:

  • 资源(Resources) : 网络上的一个实体,或者说是网络上的一个具体信息。
    它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的存在。可以用一个 URI(统一资源定位符)指向它,每种资源对应一个特定的 URI 。要获取这个资源,访问它的 URI 就可以,因此 URI 即为每一个资源的独一无二的识别符。
  • 表现层(Representation) : 把资源具体呈现出来的形式,叫做它的表现层 (Representation)。
    比如,文本可以用 txt 格式表现,也可以用 HTML 格式、 XML 格式、 JSON 格式表现,甚至可以采用二进制格式。
  • 状态转化(State Transfer) : 每发出一个请求,就代表了客户端和服务器的一次交互过程。
    HTTP 协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段, 让服务器端发生“ 状态转化” (State Transfer)。而这种转化是建立在表现层之上的,所以就是 “ 表现层状态转化” 。具体说,就是 HTTP 协议里面,四个表示操作方式的动词: GET 、 POST 、 PUT、DELETE。它们分别对应四种基本操作: GET 用来获取资源, POST 用来新建资源, PUT 用来更新资源, DELETE 用来删除资源。
    restful 的示例:
/account/1 HTTP GET : 得到 id = 1 的 account
/account/1 HTTP DELETE: 删除 id = 1 的 account
/account/1 HTTP PUT: 更新 id = 1 的 account
/account HTTP POST: 新增 account

通过url和请求类型来确定资源和对应的操作,而不是请求不同的方法来实现

这个也是我们很常用的方法,就是比如每个页面或者每个人都有一个不同的url,但是显然不是为他们单独生成一个html页面的,而是在RequestMapping中使用了占位符,把我们url传入的一部分作为方法的参数获取了,来返回数据的,从而看起来就是每个人的url不一样,这个是很重要的功能

image-20200804231204305

更完整地,应该在RequestMapping里加上method属性,来指定请求方法

HiddentHttpMethodFilter过滤器

image-20200805095728896

这里只是了解即可,如果用到再查

image-20200805100213248

在表单中加一个隐藏域,来传递method参数

CookieValue

作用:
用于把指定 cookie 名称的值传入控制器方法参数。
属性:
value:指定 cookie 的名称。
required:是否必须有此 cookie。

image-20200805100450819

ModelAttribute

作用:
该注解是 SpringMVC4.3 版本以后新加入的。它可以用于修饰方法和参数。
出现在方法上,表示当前方法会在控制器的方法执行之前,先执行。它可以修饰没有返回值的方法,也可
以修饰有具体返回值的方法。
出现在参数上,获取指定的数据给参数赋值。

属性:
value:用于获取数据的 key。 key 可以是 POJO 的属性名称,也可以是 map 结构的 key。

应用场景:
当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据。
例如:
我们在编辑一个用户时,用户有一个创建信息字段,该字段的值是不允许被修改的。在提交表单数
据是肯定没有此字段的内容,一旦更新会把该字段内容置为 null,此时就可以使用此注解解决问题。

放在方法上:先于其他控制器方法执行该方法

image-20200805100700065

注意这个先执行的方法也同样可以获取前端传来的参数和对象,这就是这个方法为什么可以解决“当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据”的问题,因为它可以先拿到参数后在数据库里查询,把缺失的参数补上,然后把这个对象或者参数再正式传入方法中,相当于对方法进行了一个增强(注意要加返回值)

image-20200805103355936

也可以不用返回值,而是添加一个map类型的形参,用一个字符串作为key,然后再用该注解修饰控制器方法的形参来把这个对象拿出来

image-20200805105734536

SessionAttribute

作用:
用于多次执行控制器方法间的参数共享。
属性:
value:用于指定存入的属性名称
type:用于指定存入的数据类型

也就是往session里存数据,其实不用这个方法,如果用servlet原生api的话,从request里也能拿到,例如:

 @RequestMapping(value = "/modifyPassword", method = RequestMethod.POST)
    public void modifyPassword(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String password = req.getParameter("password");
        User user = (User) req.getSession().getAttribute("user");
        user.setPassword(password);
        service.updateUser(user);
        writeValue(ResultInfo.getTrueResultInfo(),resp);
    }

但是这里我们不用servlet原生api,所以用这个注解还是挺方便的

这个注解只能加在类,而不是方法上。我们获取值是从一个model对象中获取的,它其实就是包装了一个hashmap,key是字符串,value是我们定义的类型的数据,所以我们在注解中要定义有哪些key以及数据的类型

image-20200805110922245

之后就可以操作model对象了,注意要把Model加到形参中

image-20200805111008041

image-20200805111035933

响应数据和结果视图

返回值的类型

  • 字符串:由视图解析器进行解析,该字符串就是page的名称

    如果要传一些参数的话,可以使用sessionArrtibute的model对象

  • void类型:默认会去调用和控制器方法RequestMapping同名的jsp资源(包括加在类上的那部分),如果没有的话就会报404错误。或者可以直接用servlet的api,用servlet的方法,这个很熟悉了就不说了

     @RequestMapping(value = "/modifyPassword", method = RequestMethod.POST)
        public void modifyPassword(HttpServletRequest req, HttpServletResponse resp) throws IOException {
            String password = req.getParameter("password");
            User user = (User) req.getSession().getAttribute("user");
            user.setPassword(password);
            service.updateUser(user);
            writeValue(ResultInfo.getTrueResultInfo(),resp);
        }
    

    如果有页面跳转的话:

    image-20200805114307427

    注意因为这里就没有用到视图解析器,所以解析器里面的配置就不起作用了,所以要写完整的页面路径和类型

    也可以重定向、直接响应等等,这都是servlet里的方法了,详见这篇文章:https://www.cnblogs.com/jiading/p/12006879.html

  • ModelAndView对象

    是一个由SpringMVC提供的对象,用来调整具体的JSP视图,这个对象可以同时存储Java Bean对象以传参以及要跳转的目标视图

    这个对象直接new就行

    image-20200805151431582

    其实通用的就是这个类,即使我们直接返回字符串,底层用的其实也是这个类

ResponseBody响应json数据

该注解用于将 Controller 的方法返回的对象,通过 HttpMessageConverter 接口转换为指定格式的
数据如: json,xml 等,通过 Response 响应给客户端

Springmvc 默认用 MappingJacksonHttpMessageConverter 对 json 数据进行转换,需要加入jackson 的包

@Controller("jsonController")
public class JsonController {
/**
* 测试响应 json 数据
*/
@RequestMapping("/testResponseJson")
public @ResponseBody Account testResponseJson(@RequestBody Account account) {
System.out.println("异步请求: "+account);
return account;
}
}

注意我们在形参上使用了RequestBody注解,因为前段传的也是json封装好的对象,所以我们是从请求体中来获取该对象,而在返回值类型上我们使用了ResponseBody注解

前段传来的对象如下,是用json格式表示的对象

image-20200805152521587

加了这个注解,SpringMVC会帮我们自动将返回对象json化并传输

文件上传

SpringMVC为我们提供了一个文件上传的方式

如果要上传文件,对前端的要求如下:

  1. form 表单的 enctype 取值必须是: multipart/form-data
    (默认值是:application/x-www-form-urlencoded)
    enctype:是表单请求正文的类型

    将enctype换成multipart/form-data后,会把表单数据分割为几部分:

    image-20200805154844999

    image-20200805154855788

  2. method 属性取值必须是 Post

  3. 提供一个文件选择域<input type="file" />

解析上传的文件需要引入Commons-fileupload 组件 ,需要导入该组件相应的支撑 jar 包: Commons-fileupload 和commons-io

传统web文件上传方式

  1. 前端表单:

    <form action="/fileUpload" method="post" enctype="multipart/form-data">
    名称: <input type="text" name="picname"/><br/>
    图片: <input type="file" name="uploadFile"/><br/>
    <input type="submit" value="上传"/>
    </form>
    
  2. 控制器

    @RequestMapping(value="/fileupload")
    public String fileupload(HttpServletRequest request) throws Exception {
    // 先获取到要上传的文件目录的真实路径
    String path = request.getSession().getServletContext().getRealPath("/uploads");
    // 创建File对象,一会向该路径下上传文件
    File file = new File(path);
    // 判断路径是否存在,如果不存在,创建该路径
    if(!file.exists()) {
    file.mkdirs();
    } /
    / 创建磁盘文件项工厂
    DiskFileItemFactory factory = new DiskFileItemFactory();
    ServletFileUpload fileUpload = new ServletFileUpload(factory);
    // 解析request对象
    List<FileItem> list = fileUpload.parseRequest(request);
    // 遍历
    for (FileItem fileItem : list) {
    // 判断文件项是普通字段,还是上传的文件
    if(fileItem.isFormField()) {
    }else {
    // 上传文件项
    // 获取到上传文件的名称
    String filename = fileItem.getName();
    // 上传文件
    fileItem.write(new File(file, filename));
    // 删除临时文件
    fileItem.delete();
    }
    } 
        return "success";
    }
    

    也可以不使用原文件的文件名,而是生成一个唯一的文件名,例如用uuid来生成,以避免重名问题的产生

SpringMVC的上传方式

image-20200805195319049

upload就是解析后的文件,也就是文件解析这一步由SpringMVC完成了,我们配置完文件解析器之后只需要操作解析后的文件对象就好了

注意上图两处对id和name的要求

代码如下:

html部分:

<h3>跨服务器的文件上传</h3>
<form action="user/fileupload3" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="upload"/><br/>
<input type="submit" value="上传文件"/>
</form>

springmvc.xml:

maxUploadSize属性是限制了上传的最大文件的大小,单位是1byte

image-20200805195612481

控制器方法:

/**
* SpringMVC跨服务器方式的文件上传
* *
@param request
* @return
* @throws Exception
*/
@RequestMapping(value="/fileupload3")
public String fileupload3(MultipartFile upload) throws Exception {
System.out.println("SpringMVC跨服务器方式的文件上传...");
// 定义图片服务器的请求路径,uploads是我们创建的一个文件夹
String path = "http://localhost:9090/day02_springmvc5_02image/uploads/";
// 获取到上传文件的名称
String filename = upload.getOriginalFilename();
String uuid = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
// 把文件的名称唯一化
filename = uuid+"_"+filename;
// 向图片服务器上传文件
// 创建客户端对象
Client client = Client.create();
// 连接图片服务器,获得web资源
WebResource webResource = client.resource(path+filename);
// 上传文件,直接操作upload即可
webResource.put(upload.getBytes());
return "success";
}

上面的代码是跨服务器保存的,它除了之前我们说的fileupload jar包外,还需要两个jar包用来连接文件服务器:

image-20200805214619361

跨服务器上传

在实际开发中,我们会有很多处理不同功能的服务器。例如:
应用服务器:负责部署我们的应用
数据库服务器:运行我们的数据库
缓存和消息服务器:负责处理大并发访问的缓存和消息
文件服务器:负责存储用户上传文件的服务器

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

image-20200805200430782

应用服务器向文件服务器上传的代码就是上面的代码,而在文件服务器中我们其实不需要写什么代码,因为它的功能只是允许连接向特定的文件夹内写入文件,所以我们只需要在web.xml文件中配置,使得tomcat允许读写操作即可(默认是只能读不能写的):

image-20200805215223925

异常处理和拦截器

拦截器

Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。用户可以自己定义一些拦截器来实现特定的功能。

谈到拦截器,还要向大家提一个词——拦截器链(Interceptor Chain)。拦截器链就是将拦截器按一定的顺序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用

说到这里,可能大家脑海中有了一个疑问,这不是我们之前学的过滤器吗?是的它和过滤器是有几分相似,但
是也有区别,接下来我们就来说说他们的区别:

  • 过滤器是 servlet 规范中的一部分, 任何 java web 工程都可以使用

  • 拦截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用

  • 过滤器在 url-pattern 中配置了/*之后,可以对所有要访问的资源拦截。

  • 拦截器它只会拦截访问的控制器方法,如果访问的是 jsp, html,css,image 或者 js 是不会进行拦
    截的。

    它也是 AOP 思想的具体应用。
    我们要想自定义拦截器, 要求必须实现: HandlerInterceptor 接口

过滤器的例子(在web.xml中配置):

    <!--解决中文乱码的过滤器-->
    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <!-- 拦截所有请求
        This filter can either apply its encoding if the request does not already specify an encoding, or enforce this filter's encoding in any case
        -->
        <url-pattern>/*</url-pattern>
    </filter-mapping>

拦截器可以做的事情,过滤器全部都可以做,只是拦截器因为有框架的支持,实现起来很简单;但是拦截器能实现的功能过滤器不一定能实现

拦截器的实现

  1. 编写一个类实现HandlerInterceptor接口

    public class HandlerInterceptorDemo1 implements HandlerInterceptor {
        //这里的方法都有接口中的默认实现,所以不override也不报错,只需要override要覆写的方法就好了
        //实现预处理方法,返回值如果是true,表示放行,否则就拦截掉,就结束了,任何后续操作都需要在这个方法里利用servlet的api实现了
        //功能:用户登录等
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse
    response, Object handler)
    throws Exception {
    System.out.println("preHandle 拦截器拦截了");
    return true;
    }
        //实现后处理方法
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response,
    Object handler,ModelAndView modelAndView) throws Exception {
    System.out.println("postHandle 方法执行了");
    }
        //控制器方法执行后、页面由视图解析器跳转完成后再执行的方法,也就是最后的方法
        //功能:释放资源
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse
    response, Object handler, Exception ex)
    throws Exception {
    System.out.println("afterCompletion 方法执行了");
    }
    }
                           
    
  2. 在springmvc.xml中配置拦截器

    <!-- 配置拦截器 -->
    <mvc:interceptors>
    <mvc:interceptor>
        <!--要拦截的具体方法,/**表示在某一级下的url全部拦截-->
    <mvc:mapping path="/**"/>
    <bean id="handlerInterceptorDemo1"
    class="com.itheima.web.interceptor.HandlerInterceptorDemo1"></bean>
    </mvc:interceptor>
    </mvc:interceptors>
    
  3. 配置拦截器链

    <!-- 配置拦截器 -->
    <mvc:interceptors>
        
    <mvc:interceptor>
    <!-- 哪些方法进行拦截 -->
    <mvc:mapping path="/user/*"/>
    <!-- 哪些方法不进行拦截
    <mvc:exclude-mapping path=""/>
    -->
        <!-- 注册拦截器对象 -->
    <bean class="cn.itcast.demo1.MyInterceptor1"/>
    </mvc:interceptor>
        
    <mvc:interceptor>
    <!-- 哪些方法进行拦截 -->
    <mvc:mapping path="/**"/>
    <!-- 注册拦截器对象 -->
    <bean class="cn.itcast.demo1.MyInterceptor2"/>
    </mvc:interceptor>
        
    </mvc:interceptors>
    

异常处理

想实现的是如果出现了异常,能够跳转到某个用户友好的页面(例如404页面等).

如果不额外配置,异常处理的过程就是:

image-20200805235141296

这显然是很不友好的方式,而如果配置了异常处理器,就可以把流程改善为:

image-20200805235052563

我们可以在异常处理器中写各种方法,例如返回404页面给浏览器,这就好多了

步骤:

  1. 编写自定义异常类,以便于储存提示信息(默认的异常类不能完整地体现业务中异常出现的原因)

  2. 编写异常处理器

  3. 配置异常处理器

  4. 异常类

    public class CustomException extends Exception {
    private String message;
    public CustomException(String message) {
    this.message = message;
    }
    public String getMessage() {
    return message;
    }
    }
    

    在捕获异常的时候就这样写:

    image-20200806000005516

  5. 编写异常处理器,要实现HandlerExceptionResolver接口

    public class CustomExceptionResolver implements HandlerExceptionResolver {
        //处理异常业务逻辑
    @Override
        public ModelAndView resolveException(HttpServletRequest request,
    HttpServletResponse response, Object handler, Exception ex) {
            //ex就是我们抛出的异常对象
    ex.printStackTrace();
    CustomException customException = null;
    //如果抛出的是系统自定义异常则直接转换
    if(ex instanceof CustomException){
    customException = (CustomException)ex;
    }else{
    //如果抛出的不是系统自定义异常则重新构造一个系统错误异常。
    customException = new CustomException("系统错误,请与系统管理 员联系! ");
    }
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.addObject("message", customException.getMessage());
    modelAndView.setViewName("error");
    return modelAndView;
    }
    }
    
  6. 在springmvc.xml中配置异常处理器

    <!-- 配置自定义异常处理器 -->
    <bean id="handlerExceptionResolver"
    class="com.itheima.exception.CustomExceptionResolver"/>
    
posted @ 2020-08-06 00:06  别再闹了  阅读(101)  评论(0)    收藏  举报