SpringMVC学习

SpringMVC、MVC的基础知识

说明:关于该知识的项目可以在ubuntu--eclipse--SpringMVC文件夹中查看

mvc的概念

环境搭建
[SpringMVC 核心组件和三大组件](#SpringMVC 核心组件和三大组件)
我们开发的MVC业务代码
[SpringMVC 其他组件 ](#SpringMVC 组件 )
注解

请求相关

请求参数的绑定
解决POST表单中文乱码
自定义类型转换

得到原生的Request、Response

常用注解

响应相关

@RequestMapping方法返回值分类:
Json相关
Springmvc实现文件上传

异常处理

springMVC的异常处理
springmvc拦截器

SSM整合

其他细节

AJAX的处理
@RequestMapping方法对应的url
[RESTful 风格知识](#RESTful 风格)
EL表达式
WEB-INF目录及重定向

webapp下的各种信息映射

错误收集


SpringMVC、MVC的基础知识

讲的很好的课程:黑马张阳老师

MVC的概念

说明:MVC是一种模型概念。在java中,SpringMVC是表现层框架:

  • Model:模型,指的就是 javaBean
  • View:视图,一般指返回的静态或动态页面。如将结果封装到 jsp中显示。
  • Controller:控制器,通常指Servlet(可访问)或Filter(不可访问),还有我们编写的实现了@RequestMapping的类和方法。

Model1:早期开发的设计模型图示:(只有JSP和JavaBean)

4.jpg

说明

由于 JSP 的可重用性差,不同项目的共享代码,每次都需要再在 JSP 中写一遍,而且 JSP 还要翻译成.java再编译成.class,现在几乎没有用这种方式开发web应用的了。

优点:结构简单。当用于小型项目时,效率高。

缺点

①职责:JSP 的职责兼顾:[ 展示数据 ] + [ 处理数据 ]。(也就是干了视图、控制器的事)

②开发时:开发效率低。因为所有逻辑代码都写在 JSP 中,导致代码重用性很低。

③运行维护:维护性差。因为展示数据的代码和部分的业务代码交织在了一起,维护非常不方便。

结论:此种模型已被淘汰,无人使用。

Model2:针对上述的缺点进行改进得到的模型

针对每个缺点:

①:让JSP的职责为仅展示数据,而不处理数据。(仅作为View)处理数据交由控制器(Servlet)来实现。

②:抽取原 JSP 中重复代码,提取并封装,让其成为可重用代码。

③:如①所述,其可以带来维护的便利。

图示:

5.jpg

延续拓展

根据Model2模型,由许多开发人员共同努力,提出MVC的理念。

MVC模型的优势

其优势也就是解决了上述Model1的缺点,概括如下:

①职责清晰:清晰而又单一的职责划分。

②组件功能独立:每个组件提供不同的功能,有利于代码的重用。

③后期维护方便。

④任何项目都适用,包括中小项目的表现层也可以采用该模型来开发,并且后期易于拓展(二次开发)

MVC 模型的弊端

任何模型都有两面性,MVC也并非全方面优秀的设计模型,弊端如下:

①运行速度慢:展示数据响应速度慢(主要是因为用 JSP显示数据。JSP 如上述要经过翻译、编译成.class文件,然后才能展示。

②开发人员要求高:需要对组件进行合理设计,构造严谨的架构。

③异步交互不方便。(因为AJAX处理数据时,如服务器响应后进行更新的操作,其逻辑中包含了视图和处理数据,交织在了一起,又形成了职责模糊。

针对这些弊端,如今又提出了基于异步请求的设计模式:

基于异步请求的MVVM 模式

Model-View-ViewModel,模型-视图-视图模型

改善:功能分离。其主要将异步操作中,脚本里的服务器响应后视图更新的View提取出来放到html页面中,让脚本只专注于逻辑,而写html页面只专注于页面的布局、结构、样式等等。

如下,将${"#mydiv"}.html(data);这个用于服务器异步响应之后的更新动作,放到了页面布局中:

6.jpg

实现MVVM的应用框架:VUE

springmvc和struts2的区别

共同点:
  • 他们都是表现层框架,且都是基于MVC模型。(从轮廓)
  • 他们的底层都是ServletAPI。(从底层所用接口)
  • 他们处理请求的机制都是一个核心控制器。(从工作机制)
区别:
  • SpringMVC的入口是Servlet;而Struts2是Filter。

  • SpringMVC是基于方法设计的(单例模式);而Struts2是基于类,其每次执行都会创建一个动作类。(前者会稍微比后者快)

  • SpringMVC使用更简单,同时支持 JSR303,处理ajax的请求更方便(异步)

    (JSR303 是一套JavaBean 参数校验的标准,他定义了很多常用的校验注解,我们可以直接将这些注解加在我们JavaBean 的属性上面,就可以在需要校验时进行校验)

  • Struts2的OGNL表达式 使页面开发效率相比 SpringMVC更高些,但执行效率并没有比JSTI提升,尤其是Struts2 的表单标签,远没有html执行效率高。

环境搭建

小技巧

  • maven配置pom.xml文件中,可以使用<properties>映射字符串:如版本<properties><spring.version>5.2.5.RELEASE<...,后面的version即可用${spring.version}代替。

maven项目:ubuntu--eclipse--springmvc01

​ ①建立maven项目,archetype选择webapp。

​ ②补全java目录:

​ 因为main下面没有自动生成javatestresources目录,需手动添加。

​ ②-1:java下新建一个Hello类,并将其作为spring Bean,且创建一个方法,用于返回一个字符串如“success”。

​ 这个字符串将会被解析成要返回的 success.jsp。

​ ③配置pom.xml:

​ 导入依赖:

 <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.2.1-b03</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

​ ④配置web.xml:

​ 要将springMVC的Servlet类添加到过滤所有请求

<display-name>Archetype Created Web Application</display-name>

  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
       <!-- 当tomcat启动时,自动读取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>

    <!-- 注意,映射后某些.jsp、.html、js脚本文件需要映射配置:[webapp下的各种信息映射]-->
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

​ ⑤resources下新建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:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        https://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    
    <context:component-scan base-package="hello"></context:component-scan>
    
    <!-- 其可以自动加载springmvc的处理器映射器(HandlerMapping)、处理器适配器(handlerAdaptor) -->
    <mvc:annotation-driven/>
    
    <!-- 配置视图解析器,解析hello返回的值"success",并打开success.jsp -->
    <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 前缀为success.jsp所在目录 -->
        <property name="prefix" value="/WEB-INF/pages/"/>
        <!-- 后缀名:.jsp -->
        <property name="suffix" value=".jsp"/>
    </bean>
    
    <!-- 配置index.jsp中索引js库的路径:配置js的映射 -->
    <mvc:resources mapping="/js/**" location="/js/"/>
    <mvc:resources mapping="/web/**" location="/web/"/>

</beans>

注意:视图解析器中,value的值要在WEB-INF前加'/',如果不加,则该方法返回的字符串解析后的路径是: /springmvc01/user/WEB-INF/pages/success.jsp,是错误的页面。

⑥配置服务器,并开始。


流程图

说明:DispatcherServlet是核心,其对每个request请求都会进行拦截,并处理。

概括流程图:

  • ①拦截“hello”( 点了入门程序),并将其与标注有@RequestMapping的方法进行匹配
  • ②匹配到时,该DispatcherServlet就会调用sayHello方法,并返回一个值。
  • ③这个值,又被DispatcherServlet拦截,并发送给视图解析器对象,由其生成返回的seccess.jsp。
  • ④DispatcherServlet将该jsp发送给服务器,服务器发送给浏览器。
7.jpg
截图未命名.jpg

具体流程图:

8.jpg
9.jpg

MVC的分析:

10.jpg

由于前面的大图中,其他组件是我们没使用mvc之前就有的,所以后四个就是我们的mvc。

  • DispathcerServlet:其是控制器C,但helloControl也是C,不同之处在于它处理的是公共数据(用封装的可重用代码),如将参数进行转换,传给helloControl的方法中。
  • helloController:其本身可以看成CDispatcherController是spring提供的通用控制器,而实际处理程序业务的控制器是我们自己写的这个helloController
  • helloController内部我们自己写的方法:其里面封装的我们自己写的组件,可以看成JavaBean,即M
  • success.jspV,用于展示数据。

说明:spring不仅提供了 MVC,还对C:Controller进行了更细致的划分。

如在我们@RequestMApping定义的方法中,传入的参数,其类型并不是我们自己转换的,而是DispatcherServlet帮我们转换的。我们自己写的控制器helloController类,只用着重业务层面的逻辑,而不需要考虑转换请求参数的问题,springmvc都帮我们封装好了。

SpringMVC将控制器独立出来,分成了两部分。

好处:各司其职、清晰高效、维护拓展性强。DispatcherController处理公共部分,而我们自己写的helloController只用写纯的业务逻辑。

springmvc流程图

说明:springmvc框架基于组件的方式执行流程:

1.jpg

SpringMVC 核心组件和三大组件

DispatcherServlet

说明:继承关系:DispatcherServlet <--- 派生 --- FrameworkServlet < -------- HttpServletBean <------

HttpServlet(原始Servlet)

所以DispatcherServlet也是一个Servlet,不过由springmvc精心设计之后,其提供了强大的功能。

源码分析思路

  • 查看 initStrategies 方法:主要是为了查看三大组件的初始化过程。
  • 查看 doService 方法:主要是为了查看执行流程。

内部执行过程

doService():原始HttpServlet中的Service方法,其会选择调用doGetdoPost等,而doService()会调用doDispatch.

doDispatch():其随后调用了如下类:

  • RequsetMappingHandlerAdapter:调用了该类的handle()方法,实际方法是:handleInternal()

    ​ 随后,经过一系列赋值、判断,调用了doInvoke()中的this.getBridgeMethod().innvoke()方法,其反射创建了一个proxy对象,即我们自己定义的@RequestMapping()方法所在类。

    ​ 最后,进入到我们自己编写的@RequestMapping()代码中。

incovableHandlerMethod类中,其的一个方法,反射调用并执行了我们自己编写的业务逻辑代码,即helloController里面的注释了@RequestMapping的方法。

调用结束并得到返回值(ModelAndView,其包含了请求域和view名),然后在该类中,再执行处理返回值,即调用视图解析器处理 view名 来得到实际的 jsp地址,并返回给浏览器。

拦截后的映射:webapp下的各种信息映射


三大组件

HandlerMapping(处理器映射器)

职责:如其名,职责是负责映射。其中一个职责是:将@RequestMapping注释的方法进行映射,如:"/hello" = com.controller.Hello.myHello()。还包含了很多映射。

说明:我是在DispatcherServletinitStrategies()中,找到的initHandlerMappings(),并跟进去看了。

​ 其有一个Map<String, HandlerMapping> matchingBeans哈希表,里面存放了各种映射。包含上面提到的注释方法的映射。


HandlerAdapter(处理器适配器)

职责:其是一个接口,职责仅仅是提供一个统一的适配器接口,供DispatcherServlet使用。(主要是三种Controller创建方法)

​ 其实现的接口包括三种自定义控制器的适配器,用于在DispatcherServlet的doDispatch中,三种实现方法适配器的接口整合,doDispatch中可以用接口就处理三种适配器,清晰简单。


InternalResourceViewResolver(视图解析器)

说在前面:其实现在视图解析器比较尴尬了,在以前传统开发中其是很有作用的,但现在的互联网开发中,不使用 .jsp 来显示页面了,而是使用 html 配合AJAX异步请求,浏览器可以直接解析,速度快很多。

传统开发项目:某个企业的办公自动化、某个电商库存。需要用户、密码才能访问,面向群体小。

互联网开发:如现在的淘宝,其不需要用户密码也能访问,而且面向的是所有互联网用户,基量很大。

springmvc 视图:

11.jpg
12.jpg
不需要视图解析器的场景:

​ 不需要页面跳转,即ajax的异步请求,用json数据交互时,即可不配置任何视图解析器。


我们开发的MVC业务代码

说明:springmvc帮我们实现了很多可重用代码的封装,极大程度低提高了我们的开发效率,那我们到底需要编写哪些东西呢?

M

位置:一般位于.../model/下。

职责:关于model模型的设计,其应该在controller之后,其提取出业务代码中的可重用代码,去重并封装成可重用组件JavaBeann,供controller使用。

V

位置:如果是eclipse中,一般位于webapp/ 、webapp/WEB-INF/pages/这两个位置。

职责:提供最终返回并显示的html页面。

C

位置:一般位于.../controller/下。

职责:是url映射的方法入口,当请求一个url,就会到Controller,然后其会调用M、和一些独立代码,来处理请求,并最终返回一个ModelAndView(可能是直接返回、或者是字符串由springmvc封装后返回)。

三种实现方式

说明:后两种方法都需要在springmvc.xml中配置。

如:

<!-- 为其他两种自定义Controller类进行配置,而非使用@RequestMapping生成。 -->
<!-- implements HttpRequestHandler -->
<bean name="/implHttpRequestHandler" class="com.controller.implHttpRequestHandler"/>
<bean id="httpRequestHandlerApater" class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"/>

<!-- implements Controller -->
<bean name="/implController" class="com.controller.implController"></bean>
<bean id="simpleControllerHandlerAdapter" class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>
  • @RequestMapping注解:

    ​ 其是最简单的,不需要配置springmvc.xml,直接在我们写的Controller类、方法,进行注解即可。不需要继承或者实现其他接口。

    适配器:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

  • implements Controller:

    ​ 其是实现了org.springframework.web.servlet.mvc.Controller接口的,我们自定义的Controller类。我们的自定义代码写在一个必须实现的方法:ModelAndView handleRequest(HttpServletRequest request,HttpServletResponse response) 中。

    ​ 但是因为其没有对url和方法的映射,我们需要在springmvc.xml 中配置映射。

    适配器:org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter

  • implements HttpRequestHandler:

    ​ 其是实现了org.springframework.web.HttpRequestHandler接口的,我们自定义类。我们的自定义代码也是要写在一个void handleRequest(HttpServletRequest request,HttpServletResponse response)方法中。

    ​ 同第二种方式,也需要配置映射。

    适配器:org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter



SpringMVC 组件

说明:springmvc提供了许多组件,用来支持表现层框架,如上图中的各个组件。HandlerMappingHandlerApatorViewResolver称为SpringMVC三大组件。

配置HandlerMappingHandlerApator应该是已经被springMVC框架作为组件进行注解注入了,所以在springmvc.xml中,<mvc:annotation-drivern/>可以加载这两个适配器。

注解

@RequestMapping:

作用:建立起浏览器请求url和后端服务器方法的联系。通过@RequestMapping(path="value"),确定该方法用来执行对应path(项目名+包名+类名+value:之类,就是一个url)

当方法所在类也被该注解作用时,索引该方法时:<a href="类path的value/value">..</a>。(<a>在同一项目的html中)

所在类没注解时,索引该方法:<a href="value">..</a>

源码

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping{...}

属性

  • path:指定调用该方法的url。

    ​ 我猜想其实现的效果是将方法变成了一个拟Servlet。

  • value:功能和path一样,是别名。

    ​ 所以只有一个值时,path是可以省略的。如:@RequestMapping("hello")

  • method:用于规定请求方式。

    ​ value为:RequestMethod的枚举类型数组。当被值指定时,只能符合请求方式,如method="RequestMethod.POST",则只有POST请求才能调用该方法。

  • params:用于规定必要参数

    ​ value为:String[]。用于指定该请求的必要参数,当没有传入这些参数时,不能调用该方法。

    如某页面中:<a href="hello">想要索引某方法,该方法的@RequestMapping(value="hello",params={"username","password=123"})

    则必须要为<a href="hello?username=任意&password=123">才可以成功调用执行该方法。

  • header:用于规定包含请求头

    ​ value为:String[]。当客户端http请求没有包含该请求头时,则不可以调用。


请求相关

请求参数的绑定

说明:也可以说请求参数的获取。如servlet中getParameter()得到的参数。

1-方法参数对应请求参数

说明:当某方法是注解了@RequestMapping后,当其有参数时,且与请求的参数同名,调用该方法时,会将请求参数的value,作为实参传入同名参数。

@RequestMapping("/testParams")
    public String testParams(String username,String password) {...}
2-对象属性对应请求参数:

说明:首先,该对象应作为实现请求映射注解的方法参数;然后,请求参数应该与该对象的属性名(成员变量名)相同。(如果该对象包含一个对象obj引用,则对应请求参数名应该是obj.paramName)

索引jsp中:

<h3>参数对象绑定测试</h3>
    <form action="params/obj2Params" method="post">
        用户名:<input type="text" name="username"/><br/>
        密码:<input type="text" name="password"/><br/>
        其他:<input type="text" name="other"/><br/>
        用户名字:<input type="text" name="user.name"/><br/>
        用户年龄:<input type="text" name="user.age"/><br/>
        提交:<input type="submit"/><br/>
    </form>

请求映射类中:(Account包含User引用,其有两个参数name、age)

@RequestMapping("/obj2Params")
    public String testParams (Account act) {
        System.out.println("testParams调用了");    
        System.out.println(act);
        return "success";
    }
2-2对象含有集合变量:

说明:请求映射方法如上;有集合变量也蛮简单,就是将参数按索引得到对象,然后传入参数。

索引 jsp中:

<h3>参数--对象含集合:绑定测试</h3>
    <form action="params/obj2Params" method="post">
        用户名:<input type="text" name="username"/>
        密码:<input type="text" name="password"/>
        其他:<input type="text" name="ohter"/>
        <p>集合信息:</p>
        名字:<input type="text" name="list[0].name"/>
        年龄:<input type="text" name="list[0].age"/><br/>
        名字:<input type="text" name="list[0].name"/>
        年龄:<input type="text" name="list[0].age"/><br/>
        名字:<input type="text" name="map['first'].name"/>
        年龄:<input type="text" name="map['first'].age"/><br/>
        提交:<input type="submit"/>
    </form>

解决POST表单中文乱码:

说明:使用Springmvc框架提供的一个过滤器组件:characterEncodingFilter

步骤:

​ 在web.xml中,添加一个全局过滤器,然后初始化参数,设置encoding为utf-8.

 <!-- 配置过滤器,处理POST请求中文乱码 -->
  <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>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

自定义类型转换

说明:spring框架为我们提供了很多的类型转换(http参数--请求映射方法参数),当我们需要拓展进行自定义时,可以使用自定义类型转换。

步骤

①创建一个类,其实现了 org.springframework.lang.Nullable.Converter接口

②在该类中,实现convert方法,并进行自定义类型转换。源值作为参数source,对其进行处理并将结果值进行返回。

③在springmvc.xml配置文件中,添加转换服务工厂组件,并将我们的自定义类作为其参数传入。

<bean id="conversionServiceFactoryBean" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="hello.util.String2Date"/>
            </set>
        </property>
</bean>

④将该工厂组件启用:

<mvc:annotation-driven conversion-service="conversionServiceFactoryBean"/>

得到原生的Request、Response

说明:也是使用了springmvc提供的请求参数绑定的方式,在请求映射方法的参数中输入即可,如:

@RequestMapping("originalServlet")
public void originalServlet(HttpServletRequest requset,HttpServletRespond respond){
    ... 
}

常用注解

@RequestBody

@PathVariable

@RequestHeader

@CookieValue

@ModelAttribute

@SessionAttributes

@RestController

@RequestParam

作用:用于将http请求参数的name与@RequestMapping方法参数的名字进行映射,如下。

@RequestMapping("annoParam")

public String annoParam(@RequestParam("name") String username) {..}

注意:该注解有一个参数为required,默认为true。意味着当调用该方法,http参数名不匹配缺失 时,会抛异常。

@RequestBody

作用:用于获取请求体内容。

类型:String;

值:name=value&name=value... 结构的数据。所以中文的的话会乱码,因为其是模拟url。多个参数时时,顺序可能会有错乱。

注意:不适用于GET,因为GET请求的参数直接封装在了url中,并没有封装请求体。

@RequestMapping("requestBody")
public String requestBody(@RequestBody String body) {
    System.out.println(body);
    return "success";
}
@ResponseBody

作用:将Controller返回值:对象、基础数据类型等,封装成JSON数据格式,传递给html,通常用于script、AJAX

注意:使用该注解后,不会使用视图解析器,即不会跳转到.jsp页面,其返回的值就是浏览器页面显示的值。所以只适用于异步更新等请求。

@PathVariable

作用:用于RESTfule编程风格的代码。 解析路径的变量,并赋值给请求映射方法的参数。

如:

    @RequestMapping("/restful/{id}")
    public String restfulProgramming(@PathVariable("id") int id) {
        System.out.println(id);
        return "success";
    }
    
    @RequestMapping("/restful")
    public String restfulProgramming(){
        System.out.println("没有参数的同路径restful运行了。");
        return "success";
    }

    @RequestMapping("/restful/{id}/{age}")
    public String restfulProgramming(@PathVariable("id") int id,int age) {
        System.out.println(id);
        return "success";
    }

→ [RESTful 风格](#RESTful 风格)

说明:浏览器如果没有插件,是无法实现put、delete等请求方式的,如果需要模拟,则需要浏览器插件、或者自己用spring提供的组件:HiddenHttpMethodFilter类来进行处理。并且表单中,应该要有一个<input type=“hidden" name="_method" value="any">的表项。

@RequestHeader

作用:用于获取请求头的值,并将某个请求头映射到方法参数,如:

    @RequestMapping("/requestHeader")
    public String requestHeader(@RequestHeader("Accept") String header){
        System.out.println("requestHeader运行了。");
        System.out.println(header);
        return "success";
    }
@CookieValue

作用:用于获取Cookie的值,将指定cookie名的值映射到方法参数,如:

    @RequestMapping("/getCookie")
    public String getCookie(@CookieValue("JSESSIONID") String cookie){
        System.out.println("getCookie运行了。");
        System.out.println(cookie);
        return "success";
    }
@ModelAttribute

作用:用于方法或类注解,另其首先执行。若是方法时,则调用该方法所在类的其他方法前,会先执行该方法。

​ 其一个用处是:表单中元素不全时(如封装到一个User对象),则该方法将对User的其他值进行定义。

两种完善方法

  • 方法有返回值:

      @ModelAttribute
      public User modelAttribute(String name,int age) {
          System.out.println("modelAttribute运行了。");
          //模拟数据库进行提取
          User user = new User();
          user.setAge(age);
          user.setName(name);
          user.setDate(new Date());
          return user;
      }
    
  • 方法无返回值:使用Map

      @ModelAttribute
      public void modelAttribute(String name,int age,Map<String, User> map) {
          System.out.println("modelAttribute运行了。");
          //模拟数据库进行提取
          User user = new User();
          user.setAge(age);
          user.setName(name);
          user.setDate(new Date());
          map.put("姓孙", user);
      }
    -----------同时在实际调用方法中--------------------
        @RequestMapping("testModelAttribute")
      public String testModelAttribute(@ModelAttribute("姓孙") User user) {...}
    
@SessionAttributes

作用:将请求域扩展为会话域,这样同一个浏览器页面以后进行访问时,可以通过${sessionScope}得到该属性的值。因为请求域只是本次请求。

只能是类的注解,其值为:String[]类型。用于将requestScope中的属性,添加到sessionScope中。

如:

@Controller
@RequestMapping("/annoTest")
@SessionAttributes(value= {"keyWords"})
public class AnnoTest {

@RequestMapping("/testSessionAttribute")
    public String testSessionAttribute(Model model) {
        System.out.println("testSessionAttribute运行了hh。");
        model.addAttribute("keyWords", "i m a good boy");
        return "success";
}

返回常用注解首部

@RestController

作用:相当于配置了@RequestBody和@ResponseBody。写在类前,方法可以直接返回字符串。


响应相关

项目:springmvc/springmvcResponse

大致行为

①当@RequestMapping方法被调用时,给方法添加参数(Model model),用该model对象的addAttribute(key,value)方法加入响应页面的数据(如数据库中的数据)。

②该方法返回到字符串可以指定逻辑视图(view)名,通过视图解析器将其解析为实际返回页面。

③在实际显示页面如respond.jsp中,用EL表达式方法可以获得之前输入model中的数据,并进行封装显示。

@RequestMapping方法返回值分类:

1-字符串:(String)

​ 使用试图解析器,设置其prefixsuffix属性,其分别对应:返回页面路径、返回页面的后缀 (如.jsp)。

2-void

3种方法实现响应:

  • 用请求调度器进行转发:

    HttpServletRequest.getRequestDispatcher("/WEB_INF/pages/success.jsp").forward(request,response);

​ 路径为:实际路径(视图解析器将不会进行解析)。若不想继续执行该方法后文,则return。

  • 用重定向(需要浏览器两次请求):

    HttpServletResponse.sendRedirect(request.getContextPath()+"/WEB_INF/pages/xxx.jsp");

  • 用respond在方法内直接输出页面:

    response.getWriter().print("..."),详细可以看Servlet

@RequestMapping("/voidTest")
    public void voidTest(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
        System.out.println("voidTest执行了.");
        /*这是第一种,传入request、response。并用request的调度器进行转发。*/
        //request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request, response);
        
        /*第二种方式:用response的sendRedirect进行重定向*/
        //response.sendRedirect("/springmvcResponse/success.jsp");
        
        /*第三种方式:直接用response返回页面,输出的内容会被封装到body里面*/
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().println("<h2>成功</h2>");
        return;
    }
3-ModelAndView对象:

​ 其和String类似,只是会返回一个ModelAndView的对象。在方法中创建该对象,并且设置View,返回后会由视图解析器进行解析得到实际返回页面的路径。

    @RequestMapping("/mvTest")
    public ModelAndView mvTest() {
        ModelAndView mv= new ModelAndView();
        System.out.println("mvTest执行了.");
        User user=new User();
        user.setName("老孙");
        user.setAge(24);
        mv.addObject("user1",user);
        mv.setViewName("success");
        return mv;
    }
4-String+关键字:
    /**
     * 用关键字实现转发,返回的String前置固定的关键字.
     * 转发:"forward:..."
     * 重定向:"redirect:"  (底层会进行项目名前缀封装,可以不用写,但自己写的重定向要写)
     * @return
     */
    @RequestMapping("/keywordsTest")
    public String keywordsTest() {
        System.out.println("keywordsTest执行了.");
        //关键字实现转发:forward
        //return "forward:/WEB-INF/pages/success.jsp";
        
        //关键字实现重定向:redirect
        return "redirect:/indexSuccess.jsp";
    }

Json相关

需要知识:javaScript

过滤静态资源:

  • Controller中:

    使用@RequestBody注解方法参数,如:[异步请求参数为Json 的处理](#异步请求参数为Json 的处理)

    使用@ResponseBody注解返回值。

  • View中:(或者说在索引页,调用我们编写的Controller中的JS里)

    脚本中使用jQuery,并且用ajax进行异步处理。

  • 添加依赖:jackson,功能如下图:

13.jpg
 <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>${jackson.version}</version>
   </dependency>
  
  <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>${jackson.version}</version>
   </dependency>

  
  <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>${jackson.version}</version>
   </dependency>
  </dependencies>

说明

当未用`jackson`依赖时,我们需要自己得到一个response.write(),然后将从数据库得到的对象User,手动输出。使用jackson后,就可以让直接返回User对象,传入时也是。只需要在返回值前加注解@ResponseBody
@RequestMapping("handler3")
    public @ResponseBody User handler(@RequestBody User user) throws IOException {
        System.out.println("handler3执行了.");
        user.setName("小孙");
        System.out.println(user);
        return user;        
    }

详细:AJAX的处理


Springmvc实现文件上传

传统文件上传方法

Springmvc实现的文件上传

跨服务器的文件上传

传统文件上传

前提**:

14.jpg

依赖

15.jpg
始--终

:文件表单编写。

​ 页面的点击,即VIew中的索引页,其应该提供一个文件上传表单,并配置enctype(encoded type):multipart/form-data。

<!-- 文件上传测试 -->
<form action="fileHandler/fileUpload" method="POST" enctype="multipart/form-data">
    文件上传:<input type="file" name="fileUpload"/>
    <input type="submit" value="上传文件"/>
</form>

:将上传的文件,存储在磁盘上。

​ 首先是导入文件上传依赖包,commons-fileuploadcommons-io。然后在Controller中,请求映射方法的参数为HttpServletRequest

​ 编写逻辑:从request中得到项目目录、新建一个磁盘文件、将文件放到该文件中。(可以先给文件名加入前缀或后缀以去重。)

    @RequestMapping("/fileUpload")
    public String fileUpload(HttpServletRequest request) {
        System.out.println("fileUpload执行了.");
        // 创建一个磁盘文件,用于存放
        /*  下面getRealPath默认是项目的目录,其位于  /home/ubuntu/soft/eclipse/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/springmvc01/uploads  */
        String path = request.getSession().getServletContext().getRealPath("/uploads/");
        File uploadDir = new File(path);
        if (!uploadDir.exists()) {
            uploadDir.mkdirs();
        }
        // 得到请求中的文件表单
        // 使用了依赖。
        DiskFileItemFactory factory = new DiskFileItemFactory();
        ServletFileUpload upload = new ServletFileUpload(factory);
        //解析request
        List<FileItem> items;
        try {
            items = upload.parseRequest(request);
        
        for(FileItem i:items) {
            //判断是否是Item项
            if(i.isFormField()){
                continue;
            }
            else {
                // 处理文件名、避免重复上传出现同名文件
                String fileName =i.getName()+ Math.random();
                // 将文件放在磁盘中
                i.write(new File(path, fileName));
                // 删除临时文件
                i.delete();
            }
        }
        
        } catch (FileUploadException e) {
            //  TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println("上传文件解析失败!");
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println("文件写入磁盘失败!");
        }
        return "success";
    }

Springmvc实现的文件上传

原理图:
16.jpg

Springmvc文件解析器:multipartResolver

职责:可以解析request中的文件,并与@requestMapping方法参数进行绑定。

注意

  • 参数名称与表单中文件的名称必须相同。
  • 配置文件中,文件解析器的id,必须是:multipartResolver
**
     * 处理springmvc文件上传操作,依赖了两个jar包。commons-fileupload
     * 和commons-io,还需要配置springmvc.xml的视图解析器。
     * 
     * @throws IOException
     */
    @RequestMapping("/fileUpload2")
    public void fileUpload2(HttpServletRequest request, HttpServletResponse response, MultipartFile upload)
            throws IOException {
        System.out.println("fileUpload执行了.");
        // 创建一个磁盘文件,用于存放
        /*
         * 下面getRealPath默认是项目的目录,其位于
         * /home/ubuntu/soft/eclipse/workspace/.metadata/.plugins/org.eclipse.wst.server
         * .core/ tmp0/wtpwebapps/springmvc01/uploads
         */
        String path = request.getSession().getServletContext().getRealPath("/uploads/");
        File uploadDir = new File(path);
        if (!uploadDir.exists()) {
            uploadDir.mkdirs();
        }
        // 使用MultipartFile得到请求中的文件表单
        // 处理文件名、避免重复上传出现同名文件
        String fileName = Math.random()+upload.getOriginalFilename() ;
        // 将文件放在磁盘中
        upload.transferTo(new File(path, fileName));

        response.setContentType("text/plain;charset=utf-8");
        response.getWriter().write("成功了");
        return;
    }

跨服务器的文件上传

为何需要跨服务器上传:

17.jpg
18.jpg

始--终

起始:

​ 想测试跨服务器文件上传,首先需要两个服务器。

  • 服务器配置:(eclipse)

    ①window--preference--servers里面,新建一个tomcat即可,但http:port 和 jxm:port要修改。

    ②新建一个项目,用于启动该服务器。项目不用任何配置,只需要在webapp下新建一个uploads文件夹即可。还可以弄个index.jsp,确认创建成功。

    ③在项目浏览器中的servers里,找到文件服务器,在其web.xml中添加下述到servlet

          <init-param>
              <param-name>readonly</param-name>
              <param-value>false</param-value>
          </init-param>
    

    ④启动该文件服务器对应项目。

  • jar依赖

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>1.18.1</version>
    </dependency>
    
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>1.18.1</version>
    </dependency>
    
  • Controller

    @RequestMapping("/fileUpload3")
      public void fileUpload3(HttpServletResponse response, MultipartFile upload)
              throws IOException {
          
          System.out.println("跨服务器文件上传执行了.");
          
          // 使用MultipartFile得到请求中的文件表单
          // 处理文件名、避免重复上传出现同名文件
          String fileName = Math.random()+upload.getOriginalFilename() ;
          
          //文件服务器路径,记得添加项目名。
          String path = "http://localhost:8081/fileServer/uploads/";
          //建立一个文件服务器客户端,连接服务器
          Client client = Client.create();
          //传输文件
          WebResource webResource = client.resource(path + fileName);
          webResource.put(upload.getBytes());
                
          response.setContentType("text/plain;charset=utf-8");
          response.getWriter().write("成功了");
          return;
      }
    
    

springMVC的异常处理

处理原因

​ 当出现异常时,如很多网站公司,都会出现一个自定义的比较和谐的报错页面,而不是纯错误信息栈的不友好页面。

​ 如下图是springMVC的异常处理。而当前端控制器:DispatcherServlet不使用异常处理器组件,而是直接抛出异常到浏览器时,浏览器只会打印如同控制台的错误信息。

​ 我们可以通过配置异常处理器组件,来自定义错误返回页面。

19.jpg

流程:

  • 首先创建一个自定义的异常类,其继承自Exception,内部至少应该要有一个String msg,用于存储信息。

    public class CustomException extends Exception {
      
      String msg;
    
      public CustomException(String msg) {
          super();
          this.msg = msg;
      }
    
      public String getMsg() {
          return msg;
      }
    
      public void setMsg(String msg) {
          this.msg = msg;
      }   
    }
    
  • 然后创建一个异常处理类,其继承自HandlerExceptionResolver,实现其方法,如下:

    public class CustomExceptionResolver implements HandlerExceptionResolver {
    
      public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
              Exception ex) {
          //始:传入一个异常参数ex。 ——  终:最终跳转到一个我们定义的页面。
          
          //判断ex是否为我们自定义的异常类
          CustomException custExce =null;
          if(ex instanceof CustomException ) {
              //如果是,则强转ex为我们自定义异常类
              custExce = (CustomException)ex;
          }
          else {
              //如果不是,则创建我们自定义异常类,并将ex的错误信息传入。
              custExce = new CustomException("系统维护~");
          }
      
          //设置ModelAndView,将错误信息传入请求域,以让提示页面jsp中,得到该信息。
          ModelAndView mv = new ModelAndView();
          mv.addObject("errorMsg",custExce.getMsg());
          //设置返回的view名
          mv.setViewName("errorPrompt");
          return mv;
      }
    }
    
  • springmvc.xml中配置异常处理类。(注释注入不知为何出错了)


springmvc拦截器

拦截器说明:其类似于servlet中的过滤器Filter,但其不能拦截所有资源,仅拦截我们编写的Controller,而不会去拦截View的请求,如静态html、jsp等资源。

20.jpg

其是AOP思想的具体应用。

多个拦截器

说明:按照配置文件中的前后顺序,确定其拦截顺序。

运行流程示意图:

21.jpg

拦截器创建

  • 自定义一个类,其实现了handlerInterceptor.( 该接口没有必须实现的方法)
  • 按需重载HandlerInterceptor的方法。(preHandler、postHandler、afterCompletion)
  • 配置spingmvc.xml:
    • 将拦截器加入容器
    • 配置拦截器:<mvc:interceptor>
/**
 * 自定义拦截器,实现了HandlerInterceptor。
 * 如下重载方法,其返回值为boolean,代表是否放行
 * true为放行,可能会传递给下一个拦截器,或直接到Controller。
 * false为不放行,可以用request、response来进行返回页面跳转。
 * @author ubuntu
 *
 */
public class MyHandlerInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println("myHandlerInterceptor拦截器执行了。");
        return false;
    }   
}

springmvc.xml

<mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean id="myHandlerInterceptor" 
                  class="com.interceptor.MyHandlerInterceptor"/>
        </mvc:interceptor>
        
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean id="myHandlerInterceptor2" 
                  class="com.interceptor.MyHandlerInterceptor2"/>
        </mvc:interceptor>
    </mvc:interceptors>

三个重载函数

boolean preHandler(...)

职责:此方法执行在控制器方法执行之前,所以我们可以使用其对请求部分进行增强,同时因结果视图没创建,所以我们可以指定响应的视图。如访问页面,如果没有登录,则跳转到登录页面。

​ 执行时机:

​ Controller执行前。

void postHandler(...)

职责:此方法因在视图结果创建生成之前执行,所以通常使用该方法对响应部分进行增强。如压缩视图等待方法。如:将service对象转成JSON数据格式等。

​ 执行时机:

​ Controller执行后,success.jsp执行前。

void afterCompletion(...)

职责:该方法在结果视图创建省城后,展示到浏览器之前执行。我们可以利用其进行清理操作,释放一些资源。

​ 执行时机:

​ success.jsp执行后,展示到浏览器之前。

console控制台输出:

​ 说明:在配置文件中,拦截器1在拦截器2之前编写。

myHandlerInterceptor拦截器执行了。。。前111
myHandlerInterceptor拦截器执行了。。。前222
myHello调用了
myHandlerInterceptor拦截器执行了。。。后222
myHandlerInterceptor拦截器执行了。。。后111
success执行了.
myHandlerInterceptor拦截器执行了。。。完成222
myHandlerInterceptor拦截器执行了。。。完成111

DispatcherServlet中的AOP

说明:以ha.handle作为切点,在其前后加入通知。

拦截器数组是在initStrategies()的时候加入的。

22.jpg

SSM整合

环境搭建

项目:ubuntu--eclipse---ssm

思路:Spring框架来整合表现层(springmvc)和持久层(mybatis)

​ ssm中,每个都应该单独尝试是否成功,再两两进行整合,如spring和springmvc都测试成功,再进行整合,如此当发生错误时,容易及时定位解决。

spring

  • 先搭建好Spring的环境及相关类(Service类和DAO类):spring.md

  • 在tomcat的配置文件web.xml中,设置启动时就创建spring的核心容器,原因在于核心容器只需要有一份。

    • 原理

      根据ServletContext这个最大的域对象,其生命周期与服务器相同,仅会创建一次,我们建立一个监听它的监听器,然后在监听到其创建,即服务器启动时,就导入spring的配置文件。如下的对象是spring-web为我们提供的API。

    • ContextLoaderListener

      通过该spring提供的ServletContext监听器,我们可以很简单的在服务器启动时导入spring配置文件。

SpringMVC

  • 搭建好环境、springmvc.xml、web.xml
  • 测试无误后,进行与spring的整合。所谓的整合,即Spring由服务器启动时,自动创建核心容器,然后可以在Controller中,注入Service类。

mybatis

  • 搭建好环境:SqlMapConfig.xml,及dao接口的配置映射文件。(可以是注解、可以是同路径xml配置):

    mybatis

  • 测试无误后,进行与spring整合。整合即是将dao接口代理类,添加到核心容器中作为bean,并在Service中注入该dao代理对象。

    方法

    • applicationContext.xml中:

      <!--配置连接池-->
      <!--SqlSessionFactory工厂对象-->
      <!--配置接口所在包-->
      

AJAX的处理

jQuery

始-终

  • 浏览器(准确说是索引文件):在脚本中,设置AJAX,如:

    ...
    <scrpit src="...">
      $(function(){
          $("#btn").click(function(){
              $.ajax({
                  url:"demo_test.txt",
                  data:null,
                  datatype:"json",
                  success:function(result){
                      alert("成功接收");
              }});
          });
      });
    </script>
    <button id="btn">
    ...
    
  • Controller:要有一个控制器专门来处理,其不应该有返回值。

    ​ 在方法中,应该传入参数response,用以直接返回json格式的数据,以供浏览器中 JS AJAX处理,用来刷新数据。

    @Controller
    @RequestMapping("/ajax0")
    public class AjaxHandler {
      
      @RequestMapping("/handler")
      public void handler(HttpServletResponse response) throws IOException {
          System.out.println("handler执行了.");
          response.setContentType("text/html;charset=utf-8");
          
          response.getWriter().write("{\"username\"=\"老孙\",\"age\"=18}");
          return;
      }
    }
    

配置:

​ 如果是用我们放在/js/下面的 JS库,此时应该要配置springmvc.xml,即js的路径映射。

如:

<mvc:resources mapping="/js/**" location="/js/"/>

异步请求参数为Json 的处理

  • 索引文件中:$.ajax(){}中,配置:contentType:dapplication/json"

  • Controller中:配置注解@RequestBody

  • springmvc.xml配置文件中:若jQuery库存放在本地,则需要配置映射。webapp下的各种信息映射


@RequestMapping方法对应的url

说明:如下代码,该方法的url为:localhost:8080/项目名/annoTest/annoParam

若方法的注解为("annoParam"),则其路径为localhost:8080/项目名/annoTestannoParam

@RequestMapping("/annoTest")
public class AnnoTest {
    
    @RequestMapping("/testSessionAttribute")
    public String testSessionAttribute(Model model) {
        System.out.println("testSessionAttribute运行了hh。");
        model.addAttribute("keyWords", "i m a good boy");
        return "success";
    }
}
------显示页面.jsp的头中,应该取消EL表达式忽略------------
<%@ page language="java" contentType="text/html; charset=UTF-8" isELIgnored="false"
    pageEncoding="UTF-8"%>

<a>中的索引url

说明:如下代码,其索引的url是:localhost:8080/项目名/annoTest/annoParam

若加上'/':href="/annoTest/annoParam",则索引变为:localhost:8080/annoTest/annoParam

<a href="annoTest/annoParam">RequestParam</a>

RESTful 风格

全称:Representational State Tranfer

编程风格

说明:如下图,restfule的前三个方法都可以使用同一个url路径,但他们的请求方式是不同的,按照请求方式就可以调用指定方法。

findallfindById可以通过是否传入参数(格式即user后+占位符:user/{id})来判断调用哪个方法。

优势:restful风格的代码只需要一份缓存,节约空间,而且利于管理。

2.jpg

requestScope

说明:是EL表达式定义的隐式对象,使用方法都是和${}一起。如:${requestScope};值为requestScope(请求域)中的所有属性的键值对。(属性:Attribute指对象,可以是基本对数据对象、自定义类对象)

设已经存储了一个属性:addAttribute("keyWords","i m a handsome boy!");则:

${requesScope.keyWords}可以在客户端浏览器页面输出"i m a handsome boy"


EL表达式

------显示页面.jsp的头中,应该取消EL表达式忽略------------
<%@ page language="java" contentType="text/html; charset=UTF-8" isELIgnored="false"
    pageEncoding="UTF-8"%>

结构:${...}

如:${requestScope},输出request域中的所有属性及其值。

${requestScope.user},输出request域中user属性的值。

四种变量作用域:(都使用EL表达式)

下列的对应,都指的是jsp内置对象。其作用就是为了读取值(getAttribute()),读取的是该对象的setAttribute()方法,已设定的属性(对象)的数值。

  • pageScope:表示变量只能在本页面使用。

    对应:pageContext

  • requestScope:表示变量能在本次请求中使用。

    对应:request

  • sessionScope:表示变量能在本次会话中使用。

    对应:session

  • applicationScope:表示变量能在整个应用程序中使用。

    对应:application

隐式对象

3.jpg

WEB-INF目录及重定向

大概说明

WEB-INF里面的内容只能由服务器级别访问,客户端并不能访问。所以当重定向时,不能直接重定向到/WEB-INF/..之类的页面。可以使用:

request.getRequestDispatcher("/WEB-INF/.../").forward(request,respond);进行跳转

两种方式

一: 转发模式

​ 借助于request范围(请求-->request-->转发-->request),------------>forward()

二: 重定向模式

​ 借助于session范围(请求-->request-->重定向-->新request)------>sendRedirect()


webapp下的各种信息映射

说明:由于我们配置了所有路径的拦截器DispatcherServlet,所以其会进行拦截处理,我们定义的索引.html、js文件夹等都需要需要如下处理:

<!-- 针对ajax异步请求处理,不需要试图解析器,但是要配置index.jsp中索引js库的路径:配置js的映射 -->
    <mvc:resources mapping="/js/**" location="/js/"/>
    <mvc:resources mapping="/web/**" location="/web/"/>

错误收集

No mapping for GET

说明:

  • 检查web.xml文件中的<url-pattern>/</url-pattern>

  • 检查springmvc.xml文件中的<context:component-scan base-package="".."是否正确,检查java文件下的目录。

  • 如果不行,关掉eclipse重启试一下!我这样试就好了

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。