线程数据共享和安全ThreadLocal-韩顺平2022笔记简

线程数据共享和安全ThreadLocal

什么是ThreadLocal

image-20231005224654355


threadLocal快速入门

image-20231013193613352


实现同一线程资源数据共享

基础类Pig,Dog;

public class Pig {
}

public class Dog {
}

T1类(主线程)
public class T1 {
  //创建ThreadLocal对象,做成public ,static ;
  public static ThreadLocal<Object> threadLocal1 = new ThreadLocal<>();

  //T1类中的静态内部类(线程类)
  public static class Task implements Runnable{

    @Override
    public void run() {
      //
      Dog dog = new Dog();
      Pig pig = new Pig();
      //给threadLocal1对象放置set dog ,隔山打牛
      System.out.println("Task中放入了dog="+dog);
   threadLocal1.set(dog);
      System.out.println("Task在run方法中线程="+Thread.currentThread().getName());
   new T1Service().update();
    }
  }

  public static void main(String[] args) {
    //启动线程
    new Thread(new Task()).start();//主线程启动一个新的线程;

  }
}

T2DAO类(取出同一线程数据)
public class T2DAO {
  public void  update(){
    //取出线程关联的thread Local1对象的数据
    Object o = T1.threadLocal1.get();
    //获取当前线程名
    String name = Thread.currentThread().getName();
    System.out.println("在T2DAO的update()线程是="+name+"取出dog="+o);
  }
}

T1Service类(取出同一线程类并调用T2DAO中方法)
public class T1Service {
  //取出threadLocal1对象关联的对象;
  public void update(){
    Object o = T1.threadLocal1.get();
    //获取当前线程名
    String name = Thread.currentThread().getName();
    System.out.println("在T1Service中的update() 线程name =  "
      +name+"dog="+o);
    //调用dao-update
    new T2DAO().update();
  }
}

同一线程数据共享效果图

image-20231010175410149


image-20231010184023588


threadLocal源码分析?

流程图set方法

image-20231012192152112


set(T value)

/*
    老韩解读
    public void set(T value) {
        //1. 获取当前线程, 关联到当前线程!
        Thread t = Thread.currentThread();
        //2. 通过线程对象, 获取到ThreadLocalMap
        //   ThreadLocalMap 类型 ThreadLocal.ThreadLocalMap
        ThreadLocalMap map = getMap(t); 
        //3. 如果map不为null, 将数据(dog,pig..) 放入map -key:threadLocal value:存放的数据
        //   从这个源码我们已然看出一个threadlocal只能关联一个数据,如果你set, 就会替换
        //4. 如果map为null, 就创建一个和当前线程关联的ThreadLocalMap, 并且该数据放入
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

 */
/**
 * 1,源代码
 *   public void set(T value) {
 *         Thread t = Thread.currentThread();
 *         ThreadLocalMap map = getMap(t);
 *         if (map != null) {
 *             map.set(this, value);
 *         } else {
 *             createMap(t, value);
 *         }
 *     }
 *     2,分析源代码执行过程,带入示例进行贯通
 *     3.01,当启动主线程时执行run(),set方法()->Thread t = Thread.currentThread();获取当前线程
 *     3.02,getMap(t)->源代码:    ThreadLocalMap getMap(Thread t) {
 *         return t.threadLocals;
 *
 *     }
 *     t.threadLocals->    ThreadLocal.ThreadLocalMap threadLocals = null;这里表达了
 *     //    getMap(thread t)根据当前线程,返回对应的threadLocal的静态内部类的threadLocalMap
 *     //   static class ThreadLocalMap {
 *      static class Entry extends WeakReference<ThreadLocal<?>> {
 *       Object value;
 *
 *             Entry(ThreadLocal<?> k, Object v) {
 *                 super(k);
 *                 value = v;
 *             }
 *         }
 *         }
 *         //后续执行:
 *         3.03,判断当前threadLocals(ThreadLocalMap是否为空),不为空
 *         *             map.set(this, value);this->ThreadLocal;value->Object
 *         3.04,不为空就创建,并添加进ThreadLocalMap
 *
 *
 */

get()

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
//    ThreadLocal.ThreadLocalMap threadLocals = null;
//	  ThreadLocalMpa是ThreadLocal的静态内部类;
//    getMap(thread t)根据当前线程,返回对应的threadLocal的静态内部类的threadLocalMap

/**
 * 老韩解读 get
 * public T get() {
 *          //1. 先得到当前的线程对象
 *         Thread t = Thread.currentThread();
 *         //2.通过线程获取到对应的ThrealLocalMap
 *         ThreadLocalMap map = getMap(t);
 
 *         if (map != null) {
 *              //3. 如果map不为空, 根据当前的 threadlocal对象,得到对应的Entry
 *             ThreadLocalMap.Entry e = map.getEntry(this);//Entry为ThreadLocalMap的静态内部类
 *             //4. 如果e 不为null
 *             if (e != null) {
 *                 @SuppressWarnings("unchecked")
 *                 //返回当前threadlocal关联的数据value
 *                 T result = (T)e.value;
 *                 return result;
 *             }
 *         }
 *         return setInitialValue();
 *     }
 *
 */

根据当前的 threadlocal对象,得到对应的Entry?


内部类/线程(review)

内部类分类

image-20231006094108708


局部内部类

image-20231006094421425


为什么外部类访问局部内部类的成员访问方式:创建对象,再访问?

鱼聪明解惑:

在 Java 中,外部类是无法直接访问局部内部类的成员的。这是因为局部内部类是定义在某个方法或作用域内的内部类,其作用域仅限于所定义的方法或作用域内部。

要访问局部内部类的成员,可以通过创建局部内部类的对象,然后通过该对象来访问其成员。**这是因为局部内部类的对象是在其所在的方法或作用域内创建的,**可以访问该方法或作用域内的变量和成员,包括外部类的成员。

以下是一个示例代码,演示了外部类访问局部内部类的成员的方法:

public class OuterClass {
    private int outerData = 10; // 外部类的成员变量

    public void accessLocalInnerClass() {
        class LocalInnerClass { // 局部内部类
            private int innerData = 20; // 局部内部类的成员变量
            public void printData() {
                System.out.println("Outer data: " + outerData); // 外部类成员的访问
                System.out.println("Inner data: " + innerData); // 内部类成员的访问
            }
        }

        LocalInnerClass inner = new LocalInnerClass(); // 创建局部内部类的对象
        inner.printData(); // 通过对象访问局部内部类的成员
    }
}

在上述代码中,accessLocalInnerClass() 方法创建了一个局部内部类 LocalInnerClass 的对象 inner,然后可以通过该对象访问局部内部类的成员变量和方法。

需要注意的是,由于局部内部类是在方法内部定义的,所以它的作用域也仅限于该方法内部。因此,在其他方法或作用域中是无法访问局部内部类的。


匿名内部类

image-20231006095234502


image-20231008152149564


匿名内部类示例Code
基于接口的局部内部类
public class AnonymousInnerClass {
  public static void main(String[] args) {
    Outer04 outer04 = new Outer04();
    outer04.method();
  }
}

interface IA {
  //  接口public
  void cry();
}

class Outer04 { //外部类
  private int n1 = 10;//属性

  public void method() {//方法
    //基于接口的局部内部类(带名字的)
//    class Outer04$1 implements IA {
//      @Override
//      public void cry() {
//        System.out.println("老虎叫唤...");
//      }
//    }
    //7. jdk 底层在创建匿名内部类 Outer04$1,立即马上就创建了 Outer04$1 实例,并且把地址
// 返回给 tiger
//8. 匿名内部类使用一次,就不能再使用
    IA tiger = new IA() {
      @Override
      public void cry() {
        System.out.println("老虎叫唤...");
      }
    };
    //注:匿名内部内也作用域也是只能在定义它的方法或代码块中
    /**
     * 需要注意的是,由于局部内部类或匿名内部类是在方法内部定义的,
     * 所以它的作用域也仅限于该方法内部。
     * 因此,在其他方法或作用域中是无法访问局部内部类(匿名内部类)的。
     * . 匿名内部类使用一次,就不能再使用!!!
     **/
    System.out.println("tiger 的运行类型=" + tiger.getClass());
    tiger.cry();
  }
}

基于类的匿名内部类
class Father{
  private String name ;

  public Father(String name) {
    this.name = name;
    System.out.println("接收到 name=" + name);
  }

  public Father() {
  }
  public void test() {//方法
  }

}

//在其他类的方法中的匿名内部类重写了father的test方法
 Father father = new Father("jack"){
      @Override
      public void test(){
        System.out.println("匿名内部类重写了test方法");
      }
    };
    System.out.println("father对象的运行类型="+father.getClass());
    father.test();

基于抽象类的匿名内部类
abstract class Animal{
  abstract void eat();
}

//
Animal animal = new Animal(){
      @Override
      void eat() {
        System.out.println("小狗吃骨头...");
      }
    };
    System.out.println("animal对象的运行类型="+animal.getClass());
    animal.eat();

  }

成员内部类

image-20231008152418963


如果成员内部类的成员和外部类的成员重名,会遵守就近原则.可以通过 外部类名.this.属性 来访问外部类的成员;

成员内部类Code
public class MemberInnerClass {
  public static void main(String[] args) {
    Outer outer = new Outer();
    Outer.Inner inner = outer.gerInnerClass();
    inner.say();
    //直接访问成员内部类
   Outer.Inner inner1 = outer.new Inner();//规则
    inner1.say();
  }
}
class Outer{
  private int n1 = 10;
  public String name = "张三";


  private void hi() {
    System.out.println("hi()方法...");
  }
  public class Inner{
    private double sal = 99.8; private int n1 = 66;
    public void say() {
//可以直接访问外部类的所有成员,包含私有的
//如果成员内部类的成员和外部类的成员重名,会遵守就近原则.
//,可以通过  外部类名.this.属性 来访问外部类的成员
      System.out.println("n1 = " + n1 + " name = " + name + " 外部类的 n1=" + Outer.this.n1);
      hi();
      hi();
    }
  }
  public Inner gerInnerClass(){
    return new Inner();
  }
}
静态内部类

image-20231009174522546

静态成员变量与非静态成员变量之间的互相访问的区别?

www.pind.com解惑:

java中呢

Java静态成员变量和非静态成员变量之间的互相访问区别

在Java中,静态成员变量和非静态成员变量之间的访问也存在一些区别。

首先,静态成员变量是所有类的实例共享的,所以调用的时候只需要通过 类名.属性名 就可以调用(也只能这样调用)。而非静态成员变量的值是各个实例私有的,所以调用的时候必须先有具体的类的实例,然后通过 实例名.属性名 来调用blog.csdn.net

其次,静态成员变量在类加载完成的时候就已经分配完空间,所以实际存在,可以被静态方法调用。而非静态成员变量只有在创建对象的时候才分配空间,对象被销毁的时候空间被回收blog.csdn.net

访问权限上,静态方法只能访问静态成员变量,而不能访问非静态成员变量。这是因为静态方法不需要实例化就可以调用,如果此时调用非静态的成员,不能确定类是否已被实例化,被调用的成员是否存在。非静态成员方法可以访问所有的成员变量,包括静态和非静态zhuanlan.zhihu.com

如果在静态方法中需要使用非静态成员,可以通过以下两种方式解决:

  1. 创建类的对象,然后通过对象来访问非静态成员juejin.cn
  2. 将非静态成员改为静态成员。如果某个成员不依赖于对象实例,并且在整个类中都是相同的值或行为,那么可以将其声明为静态成员。这样就可以在静态方法中直接访问和调用该成员。但需要注意的是,将非静态成员改为静态成员可能会影响程序的设计和逻辑,需要根据具体情况进行考虑juejin.cn

例如,在下面的代码中,静态方法 printInfo() 可以访问静态成员变量 name,但无法直接访问非静态成员变量 age

public class Person {
    public static String name;
    public int age;

    public static void printInfo() {
        System.out.println("Name: " + name);
        System.out.println("Age: " + age);  // 错误,无法从静态上下文中引用非静态变量 age
    }
}

如果需要在 printInfo() 方法中访问 age,可以通过创建 Person 类的对象来实现:

public class Person {
    public static String name;
    public int age;

    public static void printInfo() {
        Person p = new Person();
        System.out.println("Name: " + name);
        System.out.println("Age: " + p.age);  // 通过对象 p 来访问非静态变量 age
    }
}

静态内部类Code
public class StaticInnerClass {

  public static void main(String[] args) {
    Outer10 outer10 = new Outer10();
    //访问静态内部类
    //类.
    Outer10.Inner10 inner10 = new Outer10.Inner10();
    inner10.say();
    Outer10.Inner10 inner101 = outer10.getInner10();
    inner101.say();
  }


}

class Outer10 { //外部类private int n1 = 10;
  private static String name = "张三";

  private static void cry() {
  }

  //Inner10 就是静态内部类
//1. 放在外部类的成员位置
//2. 使用 static 修饰
//3. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
//4. 可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
//5. 作用域 :同其他的成员,为整个类体
  static class Inner10 {
    private static String name = "韩顺平教育";

    public void say() {
//如果外部类和静态内部类的成员重名时,静态内部类访问的时,
//默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.成员)
      System.out.println(name + " 外部类 name= " + Outer10.name);
      cry();
    }
  }


  public void m1() { //外部类---访问------>静态内部类 访问方式:创建对象,再访问
    Inner10 inner10 = new Inner10();
    inner10.say();
  }


  public Inner10 getInner10() {
    return new Inner10();
  }


  public static Inner10 getInner10_() {
    return new Inner10();
  }
}

线程

IO流

web应用常用功能文件上传

文件上传需要导入的包

链接:https://pan.baidu.com/s/1FapXjFcvZ4BzuYKIVODbvg?pwd=cbw9
提取码:cbw9

文件上传原理分析图(老韩)

image-20231014192437508


http请求头(File)

image-20231014193125051


服务器端处理

image-20231014193213218


文件上传入门示例

1,先导包

链接:https://pan.baidu.com/s/1FapXjFcvZ4BzuYKIVODbvg?pwd=cbw9
提取码:cbw9

2,表单->jsp页面
<%--
  Created by IntelliJ IDEA.
  User: 韩顺平
  Version: 1.0
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- 指定了base标签 -->
    <base href="<%=request.getContextPath()+"/"%>>">
    <style type="text/css">
        input[type="submit"] {
            outline: none;
            border-radius: 5px;
            cursor: pointer;
            background-color: #31B0D5;
            border: none;
            width: 70px;
            height: 35px;
            font-size: 20px;
        }

        img {
            border-radius: 50%;
        }

        form {
            position: relative;
            width: 200px;
            height: 200px;
        }

        input[type="file"] {
            position: absolute;
            left: 0;
            top: 0;
            height: 200px;
            opacity: 0;
            cursor: pointer;
        }
    </style>

    <script type="text/javascript">
        function prev(event) {
            //获取展示图片的区域
            var img = document.getElementById("prevView");
            //获取文件对象
            var file = event.files[0];
            //获取文件阅读器: Js的一个类,直接使用即可
            var reader = new FileReader();
            reader.readAsDataURL(file);
            reader.onload = function () {
                //给img的src设置图片url
                img.setAttribute("src", this.result);
            }
        }
    </script>

</head>
<body>
<!-- 表单的enctype属性要设置为multipart/form-data
    enctype="multipart/form-data" 表示提交的数据是多个部分构造,有文件和文本
 -->

<form action="fileUploadServlet" method="post" enctype="multipart/form-data">
    家居图: <img src="2.jpg" alt="" width="200" height="200" id="prevView">
<%--    小伙伴愿意完成自己测试--%>
    <input type="file" name="pic" id="" value="" οnchange="prev(this)"/>

    家居名: <input type="text" name="name"><br/>

    <input type="submit" value="上传"/>
</form>
</body>
</html>

enctype

enctype属性在HTML表单中定义了在将表单数据发送到服务器之前如何对其进行编码。这个属性只有在表单的method属性设置为post时才会使用runoob.com

enctype属性的默认值是application/x-www-form-urlencoded。这意味着,如果你没有明确指定enctype属性,那么表单数据将会以这种方式进行编码。这是一种标准的编码格式,适用于大多数情况,特别是当表单中没有文件上传控件时cnblogs.com

enctype属性的其他可选值有:

  • multipart/form-data:这个值用于上传非文本的内容,比如图片或者mp3等。只有这个值才能完整地传递文件数据cnblogs.com

  • text/plain:这个值用于纯文本传输,主要用于发送邮件时设置。如果在发送头文件时没有设置这种编码类型,可能会出现接收时编码混乱的问题cnblogs.com

这些编码类型都是MIME类型,用于告诉浏览器如何解析接收到的数据。MIME(Multipurpose Internet Mail Extensions)是一种多用途网际邮件扩充协议,最早应用于电子邮件系统,后来也应用到浏览器cnblogs.com

3,Servlet(配置)
package com.tom.servlet;

import org.apache.commons.fileupload.servlet.ServletFileUpload;

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

public class FileUploadServlet extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  doPost(request,response);
  }

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    if(ServletFileUpload.isMultipartContent(request)){
      System.out.println("OK");
    }else {
      System.out.println("不是表单文件");
    }
  }
}

4,web.xml配置(*)
<servlet>
      <servlet-name>FileUploadServlet</servlet-name>
      <servlet-class>com.tom.servlet.FileUploadServlet</servlet-class>
  </servlet>
<servlet-mapping>
  <servlet-name>FileUploadServlet</servlet-name>
  <url-pattern>/fileUploadServlet</url-pattern>
</servlet-mapping>

FileItem文件项中的文件上传到服务器的temp下的文件保存找指定的目录

核心代码(IO流/文件传输流/打印流)
           //否则就是一个文件
           String name = fileItem.getName();
           System.out.println("上传文件名="+name);
           //并且将这个上传到服务器的temp下的文件保存找指定的目录
          String filePath = "/upload/";
          String fileRealPath = request.getServletContext().getRealPath(filePath);
           System.out.println("fileRealPath="+fileRealPath);

           //创建上传目录,上传文件
           File fileRealPathDirectory = new File(fileRealPath);
           if(!fileRealPathDirectory.exists()){//如果文件不存在就创建
             fileRealPathDirectory.mkdirs();//创建

           }
//           //将文件拷贝到fileRealPathDirectory目录
//            String fileFullPath= fileRealPathDirectory+ name;,错误1

           String fileFullPath= fileRealPathDirectory+"/"+ name;

           fileItem.write(new File(fileFullPath));

           //判断是否拷贝成功
           response.setContentType("text/html;charset=utf-8");
           response.getWriter().write("上传成功~");

文件上传问题

中文编码
//解决接收到的文件中文乱码问题
servletFileUpload.setHeaderEncoding("utf-8");
分目录存放

编写工具类,获得当前日期作为分级目录

package com.tom.utlis;

import java.time.LocalDateTime;
import java.time.Month;

public class WebUtils {
  //获取当前时间用于在上传文件时的分目录存放
  public static String GetYearMonthDay(){
    LocalDateTime ldt = LocalDateTime.now();
    int year = ldt.getYear();
    Month month = ldt.getMonth();
    int day = ldt.getDayOfMonth();
    return year+"/"+month+"/"+day+"/";
  }

  public static void main(String[] args) {
    //测试工具类
    String s = WebUtils.GetYearMonthDay();
    System.out.println(s);
  }
}

使用工具类更改上传的文件路径

//创建上传目录,上传文件
File fileRealPathDirectory = new File(fileRealPath+ WebUtils.GetYearMonthDay());

文件覆盖问题
  //添加UUID(随机的字符串形式),保持前缀是唯一的
name = UUID.randomUUID().toString()+"_"+System.currentTimeMillis()+"_"+name;

多文件上传

将//ServletFileUpload对象可以把表单数据提交的数据text / 文件将其封装到FileItem文件项,经过判断后的文件上传方法封装到一个方法中

package com.tom.utlis;

import org.apache.commons.fileupload.FileItem;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.UUID;

public class UploadUtils {
  public static boolean UploadSuccess(HttpServletRequest request, HttpServletResponse response,FileItem fileItem) throws Exception {
    String name = fileItem.getName();
    System.out.println("上传文件名="+name);
    //并且将这个上传到服务器的temp下的文件保存找指定的目录
    String filePath = "/upload/";
    String fileRealPath = request.getServletContext().getRealPath(filePath);
    System.out.println("fileRealPath="+fileRealPath);
    //创建上传目录,上传文件
    File fileRealPathDirectory = new File(fileRealPath+ WebUtils.GetYearMonthDay());
    if(!fileRealPathDirectory.exists()){//如果文件不存在就创建
      fileRealPathDirectory.mkdirs();//创建
    }
//           //将文件拷贝到fileRealPathDirectory目录
//            String fileFullPath= fileRealPathDirectory+ name;,错误1
    //添加UUID(随机的字符串形式),保持前缀是唯一的
    name = UUID.randomUUID().toString()+"_"+System.currentTimeMillis()+"_"+name;
    String fileFullPath= fileRealPathDirectory+"/"+ name;

    fileItem.write(new File(fileFullPath));

    //判断是否拷贝成功
    response.setContentType("text/html;charset=utf-8");
    response.getWriter().write("上传成功~");
    return true ;

  }


}

多文件上传?
public static boolean UploadSuccess(HttpServletRequest request, HttpServletResponse response, MultipartFile[] files) throws Exception {
   for (MultipartFile file : files) {
       String name = file.getOriginalFilename();
       System.out.println("上传文件名="+name);
       String filePath = "/upload/";
       String fileRealPath = request.getServletContext().getRealPath(filePath);
       System.out.println("fileRealPath="+fileRealPath);
       File fileRealPathDirectory = new File(fileRealPath+ WebUtils.GetYearMonthDay());
       if(!fileRealPathDirectory.exists()){
           fileRealPathDirectory.mkdirs();
       }
       name = UUID.randomUUID().toString()+"_"+System.currentTimeMillis()+"_"+name;
       String fileFullPath= fileRealPathDirectory+"/"+ name;
       file.transferTo(new File(fileFullPath));
   }
   response.setContentType("text/html;charset=utf-8");
   response.getWriter().write("上传成功~");
   return true;
}

web应用常用功能文件下载

文件下载功能示意图

image-20231108233516931


web页面(简)

<%--
  Created by IntelliJ IDEA.
  User: Tom
  Date: 2023/11/8
  Time: 23:36
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
  <meta charset="UTF-8">
  <title>文件下载</title>
  <base href="<%=request.getContextPath()+"/"%>">
</head>
<body>
<h1>文件下载</h1>
<a href="fileDownLoad?name=1.jpg">点击下载小狗图片</a><br/>
<a href="fileDownLoad?name=韩顺平零基础Java笔记.pdf">点击下载 韩顺平零基础Java笔记.pdf</a><br/>
</body>
</html>

Servlet页面(简)

package com.tom.servlet;

import org.apache.commons.io.IOUtils;
import sun.misc.BASE64Encoder;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;

public class FileDownLoadServlet extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  doPost(request,response);
  }

  @Override
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//    System.out.println("FileDownLoadServlet被调用~");
    //获取所要下载文件的名字
    request.setCharacterEncoding("utf-8");
    String downLoadFileName = request.getParameter("name");
//    System.out.println("downLoadFileName="+downLoadFileName);


    //给http响应,设置响应头Content-Type,就是文件名MIME
    // 通过servletContext来获取
    ServletContext servletContext = request.getServletContext();
    String downLoadPath = "/DownLoad/";
    //servletContext.getMimeType()
   String downLoadFileFullPath =  downLoadPath+downLoadFileName;
    String mimeType = servletContext.getMimeType(downLoadFileFullPath);
//    System.out.println("mimeType="+mimeType);
    response.setContentType(mimeType);

    //给http响应,设置响应头Content-Disposition
    //不同浏览器写法不一样,考虑编码
    //火狐是文件名中文需要base64,而ie/chrome URL编码
    if(request.getHeader("User-Agent").contains("Firefox")){
      response.setHeader("Content-Disposition","attachment;filename==?UTF-8?B?"+new BASE64Encoder().encode(downLoadFileName.getBytes("UTF-8"))+"?=");

    }else {
      response.setHeader("Content-Disposition","attachment;filename="+ URLEncoder.encode(downLoadFileName,"UTF-8"));

    }

    //读取下载的文件数据,返回给客户端/浏览器
    //(1)创建一个和要下载的文件,关联的输入流
    InputStream resourceAsStream = servletContext.getResourceAsStream(downLoadFileFullPath);
    //(2)得到返回数据的输出流
    ServletOutputStream outputStream = response.getOutputStream();

    //(3)使用工具类,将输入流关联的文件,对拷到输出流,并返回给客户端/浏览器
    IOUtils.copy(resourceAsStream,outputStream);


  }
}

启动TomCat工程路径加载下载文件问题

image-20231109085151686


解决方法

image-20231109085800912


image-20231109085645531


文件下载注意和细节

image-20231109092935791


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值