SpringMVC小结

neweastsun 2019-11-07

SpringMVC

官方文档https://docs.spring.io/spring/docs/5.2.1.RELEASE/spring-framework-reference/web.html#spring-web

执行流程

SpringMVC小结

  • 发起请求到前端控制器(DispatcherServlet)
  • 前端控制器请求 HandlerMapping 查找 Handler,可以根据 xml 配置、注解进行查找
  • 处理器映射器 HandlerMapping 向前端控制器返回 Handler
  • 前端控制器调用处理器适配器去执行 Handler
  • 处理器适配器去执行 Handler
  • Handler 执行完成给适配器返回 ModelAndView
  • 处理器适配器向前端控制器返回 ModelAndViewModelAndViewSpringMVC 框架的一个底层对象,包括 ModelView
  • 前端控制器请求视图解析器去进行视图解析,根据逻辑视图名解析成真正的视图(JSP)
  • 视图解析器向前端控制器返回 View
  • 前端控制器进行视图渲染,视图渲染将模型数据(在 ModelAndView 对象中)填充到 request
  • 前端控制器向用户响应结果

优点

MVC 是一个非常好的协作模式,能够有效降低代码的耦合度,从架构上能够让开发者明白代码应该写在哪里。为了让 View 更纯粹,还可以使用 Thymeleaf、Freemarker 等模板引擎,使模板里无法写入 Java 代码,让前后端分工更加清晰。

转发和重定向

无需视图解析器

转发

return "/index.jsp";
return "forward:/index.jsp";

重定向

return "redirect:/index.jsp";

有视图解析器

转发

return "index";

重定向

return "redirect:hello.do";

接收参数

提交的域名称和处理方法的参数名一致

同名参数直接接收

提交的域名称与处理方法的参数名不一致

参数加注解@RequestParam("别名")

RestFul风格

参数加注解@PathVariable("别名")

乱码

xml配置SpringMVC提供的过滤器

<filter>
    <filter-name>encoding</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>encoding</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

/和/*的区别

  • <url-pattern>/</url-pattern>
    会匹配到/springmvc这样的路径型url,不会匹配到模式为*.jsp这样的后缀型url。

  • <url-pattern>/*</url-pattern>
    会匹配所有的url:路径型的和后缀型的url(包括/springmvc,.jsp,.js和*.html等)。

自定义过滤器

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Map;

/**
* 解决get和post请求 全部乱码的过滤器
*/
public class GenericEncodingFilter implements Filter {

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //处理response的字符编码
        HttpServletResponse myResponse=(HttpServletResponse) response;
        myResponse.setContentType("text/html;charset=UTF-8");

        // 转型为与协议相关对象
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        // 对request包装增强
        HttpServletRequest myrequest = new MyRequest(httpServletRequest);
        chain.doFilter(myrequest, response);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

}

//自定义request对象,HttpServletRequest的包装类
class MyRequest extends HttpServletRequestWrapper {

    private HttpServletRequest request;
    //是否编码的标记
    private boolean hasEncode;
    //定义一个可以传入HttpServletRequest对象的构造函数,以便对其进行装饰
    public MyRequest(HttpServletRequest request) {
        super(request);// super必须写
        this.request = request;
    }

    // 对需要增强方法 进行覆盖
    @Override
    public Map getParameterMap() {
        // 先获得请求方式
        String method = request.getMethod();
        if (method.equalsIgnoreCase("post")) {
            // post请求
            try {
                // 处理post乱码
                request.setCharacterEncoding("utf-8");
                return request.getParameterMap();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        } else if (method.equalsIgnoreCase("get")) {
            // get请求
            Map<String, String[]> parameterMap = request.getParameterMap();
            if (!hasEncode) { // 确保get手动编码逻辑只运行一次
                for (String parameterName : parameterMap.keySet()) {
                    String[] values = parameterMap.get(parameterName);
                    if (values != null) {
                        for (int i = 0; i < values.length; i++) {
                            try {
                                // 处理get乱码
                                values[i] = new String(values[i]
                                        .getBytes("ISO-8859-1"), "utf-8");
                            } catch (UnsupportedEncodingException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
                hasEncode = true;
            }
            return parameterMap;
        }
        return super.getParameterMap();
    }

    //取一个值
    @Override
    public String getParameter(String name) {
        Map<String, String[]> parameterMap = getParameterMap();
        String[] values = parameterMap.get(name);
        if (values == null) {
            return null;
        }
        return values[0]; // 取回参数的第一个值
    }

    //取所有值
    @Override
    public String[] getParameterValues(String name) {
        Map<String, String[]> parameterMap = getParameterMap();
        String[] values = parameterMap.get(name);
        return values;
    }
}

JSON

JSON 和 JavaScript 对象互转

  • 要实现从JSON字符串转换为JavaScript 对象,使用 JSON.parse() 方法:

    var obj = JSON.parse('{"a": "Hello", "b": "World"}'); 
      //结果是 {a: 'Hello', b: 'World'}
  • 要实现从JavaScript 对象转换为JSON字符串,使用 JSON.stringify() 方法:

    var json = JSON.stringify({a: 'Hello', b: 'World'});
      //结果是 '{"a": "Hello", "b": "World"}'

Controller返回JSON数据

Jackson

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.8</version>
</dependency>

使用

@RequestMapping("/json")
@ResponseBody
public String json() throws JsonProcessingException {
    //创建一个jackson的对象映射器,用来解析数据
    ObjectMapper mapper = new ObjectMapper();
    //创建一个对象
    User user = new User("小明", 3, "男");
    //将我们的对象解析成为json格式
    String str = mapper.writeValueAsString(user);
    //由于@ResponseBody注解,这里会将str转成json格式返回;十分方便
    return str;
}

转换时间

Jackson 默认是会把时间转成时间戳timestamps形式

解决方案:取消timestamps形式 , 自定义时间格式

@RequestMapping("/json")
public String json() throws JsonProcessingException {

    ObjectMapper mapper = new ObjectMapper();

    //不使用时间戳的方式
    mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    //自定义日期格式对象
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    //指定日期格式
    mapper.setDateFormat(sdf);

    Date date = new Date();
    String str = mapper.writeValueAsString(date);

    return str;
}

fastjson

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.60</version>
</dependency>

fastjson 三个主要的类

  • 【JSONObject 代表 json 对象 】

    • JSONObject实现了Map接口, 猜想 JSONObject底层操作是由Map实现的。
    • JSONObject对应json对象,通过各种形式的get()方法可以获取json对象中的数据,也可利用诸如size(),isEmpty()等方法获取"键:值"对的个数和判断是否为空。其本质是通过实现Map接口并调用接口中的方法完成的。
  • 【JSONArray 代表 json 对象数组】

    • 内部是有List接口中的方法来完成操作的。
  • 【JSON 代表 JSONObject和JSONArray的转化】

    • JSON类源码分析与使用

测试

public static void main(String[] args) {
    //创建一个对象
    User user1 = new User("小明", 3, "男");
    User user2 = new User("小王", 3, "男");
    List<User> list = new ArrayList<User>();
    list.add(user1);
    list.add(user2);

    System.out.println("*******Java对象 转 JSON字符串*******");
    String str1 = JSON.toJSONString(list);
    System.out.println("JSON.toJSONString(list)==>"+str1);
    String str2 = JSON.toJSONString(user1);
    System.out.println("JSON.toJSONString(user1)==>"+str2);

    System.out.println("\n****** JSON字符串 转 Java对象*******");
    User jp_user1=JSON.parseObject(str2,User.class);
    System.out.println("JSON.parseObject(str2,User.class)==>"+jp_user1);

    System.out.println("\n****** Java对象 转 JSON对象 ******");
    JSONObject jsonObject1 = (JSONObject) JSON.toJSON(user2);
    System.out.println("(JSONObject) JSON.toJSON(user2)==>"+jsonObject1.getString("name"));

    System.out.println("\n****** JSON对象 转 Java对象 ******");
    User to_java_user = JSON.toJavaObject(jsonObject1, User.class);
    System.out.println("JSON.toJavaObject(jsonObject1, User.class)==>"+to_java_user);
}

JSON乱码解决

  • 通过@RequestMaping的produces属性来实现

    //produces:指定响应体返回类型和编码
      @RequestMapping(value = "/json1",produces = "application/json;charset=utf-8")
  • 乱码统一解决:在springmvc的配置文件上添加一段消息StringHttpMessageConverter转换配置

    <mvc:annotation-driven>
          <mvc:message-converters register-defaults="true">
              <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                  <constructor-arg value="UTF-8"/>
              </bean>
              <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                  <property name="objectMapper">
                      <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                          <property name="failOnEmptyBeans" value="false"/>
                      </bean>
                  </property>
              </bean>
          </mvc:message-converters>
      </mvc:annotation-driven>

Ajax

参数

jQuery.ajax(...)
      部分参数:
          url:请求地址
         type:请求方式,GET、POST(1.9.0之后用method)
      headers:请求头
         data:要发送的数据
  contentType:即将发送信息至服务器的内容编码类型(默认: "application/x-www-form-urlencoded; charset=UTF-8")
        async:是否异步
      timeout:设置请求超时时间(毫秒)
   beforeSend:发送请求前执行的函数(全局)
     complete:完成之后执行的回调函数(全局)
      success:成功之后执行的回调函数(全局)
        error:失败之后执行的回调函数(全局)
      accepts:通过请求头发送给服务器,告诉服务器当前客户端课接受的数据类型
     dataType:将服务器端返回的数据转换成指定类型
        "xml": 将服务器端返回的内容转换成xml格式
       "text": 将服务器端返回的内容转换成普通文本格式
       "html": 将服务器端返回的内容转换成普通文本格式,在插入DOM中时,如果包含JavaScript标签,则会尝试去执行。
     "script": 尝试将返回值当作JavaScript去执行,然后再将服务器端返回的内容转换成普通文本格式
       "json": 将服务器端返回的内容转换成相应的JavaScript对象
      "jsonp": JSONP 格式使用 JSONP 形式调用函数时,如 "myurl?callback=?" jQuery 将自动替换 ? 为正确的函数名,以执行回调函数

获取baidu接口Demo

<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>JSONP百度搜索</title>
    <style>
        #q{
            width: 500px;
            height: 30px;
            border:1px solid #ddd;
            line-height: 30px;
            display: block;
            margin: 0 auto;
            padding: 0 10px;
            font-size: 14px;
        }
        #ul{
            width: 520px;
            list-style: none;
            margin: 0 auto;
            padding: 0;
            border:1px solid #ddd;
            margin-top: -1px;
            display: none;
        }
        #ul li{
            line-height: 30px;
            padding: 0 10px;
        }
        #ul li:hover{
            background-color: #f60;
            color: #fff;
        }
    </style>
    <script>

        // 2.步骤二
        // 定义demo函数 (分析接口、数据)
        function demo(data){
            var Ul = document.getElementById('ul');
            var html = '';
            // 如果搜索数据存在 把内容添加进去
            if (data.s.length) {
                // 隐藏掉的ul显示出来
                Ul.style.display = 'block';
                // 搜索到的数据循环追加到li里
                for(var i = 0;i<data.s.length;i++){
                    html += '<li>'+data.s[i]+'</li>';
                }
                // 循环的li写入ul
                Ul.innerHTML = html;
            }
        }

        // 1.步骤一
        window.onload = function(){
            // 获取输入框和ul
            var Q = document.getElementById('q');
            var Ul = document.getElementById('ul');

            // 事件鼠标抬起时候
            Q.onkeyup = function(){
                // 如果输入框不等于空
                if (this.value != '') {
                    // ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆JSONPz重点☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
                    // 创建标签
                    var script = document.createElement('script');
                    //给定要跨域的地址 赋值给src
                    //这里是要请求的跨域的地址 我写的是百度搜索的跨域地址
                    script.src = 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd='+this.value+'&cb=demo';
                    // 将组合好的带src的script标签追加到body里
                    document.body.appendChild(script);
                }
            }
        }
    </script>
</head>

<body>
<input type="text" id="q" />
<ul id="ul">

</ul>
</body>
</html>

文件上传和下载

前端表单要求

为了能上传文件,必须将表单的method设置为POST,并将enctype设置为multipart/form-data。只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器;

对表单中的 enctype 属性做个详细的说明:

  • application/x-www=form-urlencoded:默认方式,只处理表单域中的 value 属性值,采用这种编码方式的表单会将表单域中的值处理成 URL 编码方式。
  • multipart/form-data:这种编码方式会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数中,不会对字符编码。
  • text/plain:除了把空格转换为 "+" 号外,其他字符都不做编码处理,这种方式适用直接通过表单发送邮件。

文件上传

导入文件上传的jar包,commons-fileupload , Maven会自动帮我们导入他的依赖包 commons-io包;

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

配置bean:multipartResolver

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

CommonsMultipartFile 的 常用方法:

  • String getOriginalFilename():获取上传文件的原名
  • InputStream getInputStream():获取文件流
  • void transferTo(File dest):将上传文件保存到一个目录文件中

示例

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.*;

@Controller
public class FileController {
    //@RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile 对象
    //批量上传CommonsMultipartFile则为数组即可
    @RequestMapping("/upload")
    public String fileUpload(@RequestParam("file") CommonsMultipartFile file , HttpServletRequest request) throws IOException {

        //获取文件名 : file.getOriginalFilename();
        String uploadFileName = file.getOriginalFilename();

        //如果文件名为空,直接回到首页!
        if ("".equals(uploadFileName)){
            return "redirect:/index.jsp";
        }
        System.out.println("上传文件名 : "+uploadFileName);

        //上传路径保存设置
        String path = request.getServletContext().getRealPath("/upload");
        //如果路径不存在,创建一个
        File realPath = new File(path);
        if (!realPath.exists()){
            realPath.mkdir();
        }
        System.out.println("上传文件保存地址:"+realPath);

        InputStream is = file.getInputStream(); //文件输入流
        OutputStream os = new FileOutputStream(new File(realPath,uploadFileName)); //文件输出流

        //读取写出
        int len=0;
        byte[] buffer = new byte[1024];
        while ((len=is.read(buffer))!=-1){
            os.write(buffer,0,len);
            os.flush();
        }
        os.close();
        is.close();
        return "redirect:/index.jsp";
    }
}

采用file.Transto 来保存上传的文件

/*
 * 采用file.Transto 来保存上传的文件
 */
@RequestMapping("/upload2")
public String  fileUpload2(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {

    //上传路径保存设置
    String path = request.getServletContext().getRealPath("/upload");
    File realPath = new File(path);
    if (!realPath.exists()){
        realPath.mkdir();
    }
    //上传文件地址
    System.out.println("上传文件保存地址:"+realPath);

    //通过CommonsMultipartFile的方法直接写文件(注意这个时候)
    file.transferTo(new File(realPath +"/"+ file.getOriginalFilename()));

    return "redirect:/index.jsp";
}

文件下载

  1. 设置 response 响应头
  2. 读取文件 -- InputStream
  3. 写出文件 -- OutputStream
  4. 执行操作
  5. 关闭流 (先开后关)

代码实现

@RequestMapping(value="/download")
public String downloads(HttpServletResponse response ,HttpServletRequest request) throws Exception{
    //要下载的图片地址
    String  path = request.getServletContext().getRealPath("/upload");
    String  fileName = "基础语法.jpg";

    //1、设置response 响应头
    response.reset(); //设置页面不缓存,清空buffer
    response.setCharacterEncoding("UTF-8"); //字符编码
    response.setContentType("multipart/form-data"); //二进制传输数据
    //设置响应头
    response.setHeader("Content-Disposition",
            "attachment;fileName="+URLEncoder.encode(fileName, "UTF-8"));

    File file = new File(path,fileName);
    //2、 读取文件--输入流
    InputStream input=new FileInputStream(file);
    //3、 写出文件--输出流
    OutputStream out = response.getOutputStream();

    byte[] buff =new byte[1024];
    int index=0;
    //4、执行 写出操作
    while((index= input.read(buff))!= -1){
        out.write(buff, 0, index);
        out.flush();
    }
    out.close();
    input.close();
    return null;
}

前端

<a href="/download">点击下载</a>

知识点

SprigMVC的三大组件

  • 处理器映射器HandlerMapping
  • 处理器适配器HandlerAdapter
  • 视图解析器ViewResolver

SprigMVC中的拦截器类的方法

HandlerInterceptor接口包含三个方法,分别是:

  1. preHandle():执行handler之前执行此方法

  2. postHandle():handler执行之后,返回ModelAndView之前

  3. afterHandle():完成处理,返回ModelAndView之后

SprigMVC的控制器是不是单例模式,如果是,有什么问题,怎么解决?

是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能的,解决方案是在控制器里面不能写字段。

相关推荐