最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring Boot文件上傳原理與實(shí)現(xiàn)詳解

 更新時(shí)間:2024年01月04日 08:54:26   作者:Splaying  
這篇文章主要介紹了Spring Boot 文件上傳原理與實(shí)現(xiàn)詳解,前端文件上傳是面向多用戶的,多用戶之間可能存在上傳同一個(gè)名稱、類型的文件;為了避免文件沖突導(dǎo)致的覆蓋問(wèn)題這些應(yīng)該在后臺(tái)進(jìn)行解決,需要的朋友可以參考下

1、文件上傳

文件上傳核心核心要點(diǎn):

  • 文件通過(guò)前端表單或者ajax提交,文件上傳應(yīng)該使用enctype="multipart/form-data"標(biāo)簽。
  • 前端文件上傳是面向多用戶的,多用戶之間可能存在上傳同一個(gè)名稱、類型的文件;為了避免文件沖突導(dǎo)致的覆蓋問(wèn)題這些應(yīng)該在后臺(tái)進(jìn)行解決!
  • 對(duì)于文件名稱采用UUID、雪花算法、MD5等一些哈希手段確保不會(huì)重復(fù);
  • 對(duì)于用戶上傳的文件不能讓用戶輕易的獲取到,應(yīng)該將上傳的文件放在一個(gè)相對(duì)隱秘的或者禁止的路徑中。
  • 針對(duì)不同場(chǎng)景應(yīng)該限制用戶上傳文件的類型、大小;
  • 后臺(tái)在處理文件上傳的時(shí)候應(yīng)該不應(yīng)該占用主線程,應(yīng)該使用異步的形式處理文件上傳;主線程繼續(xù)向下執(zhí)行代碼,異步的優(yōu)勢(shì)在于頁(yè)面不會(huì)白屏轉(zhuǎn)圈太久增強(qiáng)用戶體驗(yàn)!

2、文件上傳簡(jiǎn)單實(shí)現(xiàn)

2.1、編寫前端頁(yè)面

  1. 文件上傳請(qǐng)求類型必須是post請(qǐng)求
  2. 同時(shí)必須是enctype=“multipart/form-data”
  3. 可以通過(guò)accept設(shè)置上傳文件的類型
  4. 多文件可以使用ctrl多選,標(biāo)簽中攜帶上multiple
<!DOCTYPE html>
<html lang="en" xml>
<head>
    <meta charset="UTF-8">
    <title>文件上傳</title>
</head>
<body>
    <form method="post" action="/upload" enctype="multipart/form-data">
        單文件: <input type="file" name="headimg"><br/>
        <hr/>
        多文件: <input type="file" name="photos" multiple><br/>
        <input type="submit" value="上傳">
    </form>
</body>
</html>

2.2、Controller層

  • 依據(jù)上傳核心應(yīng)該使用異步的形式,因此Controller線程中不應(yīng)該直接對(duì)文件處理;而應(yīng)該將文件交由Service層進(jìn)行異步處理,Controller線程繼續(xù)向下執(zhí)行處理未執(zhí)行完畢的代碼!
  • @RequestPart注解用于標(biāo)注文件上傳參數(shù)
  • MultipartFile參數(shù)是一個(gè)封裝IO流的簡(jiǎn)易文件處理接口,StandardMultipartFile實(shí)現(xiàn)類。
@Controller
public class FileController {

    @Autowired
    FileUploadService service;

    @RequestMapping("/upload")
    @ResponseBody
    public String upload(@RequestPart MultipartFile headimg,
                         @RequestPart MultipartFile[] photos) throws IOException {
        System.out.println(" Controller線程: =============== "+Thread.currentThread().getName()+" ===========");
        System.out.println("頭像大小: " + headimg.getSize());
        System.out.println("照片數(shù)量: " + photos.length);
        service.upload(new MultipartFile[]{headimg});
        service.upload(photos);
        return "File Upload Success!";
    }
}

2.3、Service層異步

  • 針對(duì)用戶上傳的文件判斷文件是否存在、是否為空之類的東西。
  • 由于需要對(duì)文件進(jìn)行哈希避免沖突,因此需要將文件的類型從名稱中截取出來(lái)、然后另外使用哈希給文件生成一個(gè)隨機(jī)名稱并且拼接文件類型!
@Service
@EnableAsync
public class FileUploadService {

    @Async
    public void upload(MultipartFile[] file) throws IOException {
        System.out.println(" =========================== "+Thread.currentThread().getName()+" ===========");
        int length = file.length;
        if(length > 0){
            for(int i = 0;i < length;i++){
                // 獲取文件的類型
                String type = file[i].getOriginalFilename().substring(file[i].getOriginalFilename().lastIndexOf("."));
                System.out.println(type);

                // UUID、雪花算法、MD5等一些哈希算法對(duì)文件名進(jìn)行特殊處理,避免文件重名
                String name = UUID.randomUUID().toString();
                file[i].transferTo(new File("C:\\Users\\Splay\\Desktop\\上傳的文件\\" + name + type));
            }
        }
        System.out.println("上傳完畢!");
    }
}

2.4、參數(shù)配置

springboot可以支持自定義的參數(shù)配置,用于限制上傳文件的大小。

spring:    
  servlet:
    multipart:
      enabled: true
      max-file-size: 10MB				# 單個(gè)文件大小
      max-request-size: 100MB			# 多文件總大小

請(qǐng)?zhí)砑訄D片描述

3、文件上傳原理

首先文件上傳是通過(guò)請(qǐng)求發(fā)送出去的,那么肯定在中央調(diào)度DispatcherServlet中。

任何數(shù)據(jù)在網(wǎng)絡(luò)傳輸?shù)臅r(shí)候都是01比特串,因此只需要將文件上傳與普通參數(shù)一同看待即可!

```java
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
	
	// 1. 保存一個(gè)額外請(qǐng)求processedRequest 
	HttpServletRequest processedRequest = request;
	HandlerExecutionChain mappedHandler = null;
	boolean multipartRequestParsed = false;

	// 這里檢查是否異步請(qǐng)求    暫時(shí)忽略
	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

	try {
		ModelAndView mv = null;
		Exception dispatchException = null;

		try {
			// 2. 檢查是不是文件上傳的請(qǐng)求
			processedRequest = checkMultipart(request);

			// 3. 判斷檢查前后請(qǐng)求是否一致
			multipartRequestParsed = (processedRequest != request);

			// 4. 拿到HandlerExecution執(zhí)行鏈
			mappedHandler = getHandler(processedRequest);
			if (mappedHandler == null) {
				noHandlerFound(processedRequest, response);
				return;
			}

			// 查找適配器HandlerAdapter
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

			// 請(qǐng)求方式解析
			String method = request.getMethod();
			boolean isGet = HttpMethod.GET.matches(method);
			if (isGet || HttpMethod.HEAD.matches(method)) {
				long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
				if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
					return;
				}
			}
			// 前置攔截器調(diào)用
			if (!mappedHandler.applyPreHandle(processedRequest, response)) {
				return;
			}

			// 5. 所有參數(shù)解析并且執(zhí)行
			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

			applyDefaultViewName(processedRequest, mv);
			mappedHandler.applyPostHandle(processedRequest, response, mv);
			...
			...
			// 善后處理
		}
	}
}

3.1、整體調(diào)度

  • 先將請(qǐng)求當(dāng)做一個(gè)普通請(qǐng)求processedRequest,然后checkMultipart(request)檢查本次請(qǐng)求是否是文件上傳。
  • 檢查的方式很簡(jiǎn)單通過(guò)StandardServletMultipartResolver類判斷form表單中的contentType是否為enctype=“multipart/form-data”。
public class StandardServletMultipartResolver implements MultipartResolver {
	@Override
	public boolean isMultipart(HttpServletRequest request) {
		return StringUtils.startsWithIgnoreCase(request.getContentType(),
				(this.strictServletCompliance ? MediaType.MULTIPART_FORM_DATA_VALUE : "multipart/"));
	}
}
@Override
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
	// 返回一個(gè)文件上傳請(qǐng)求的對(duì)象
	return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}
  • 如果是文件上傳那么會(huì)將本次請(qǐng)求調(diào)用resolveMultipart進(jìn)行解析一下并且封裝成一個(gè)新的請(qǐng)求。此時(shí)processRequest 一定不等于 request。
  • 之后就是拿到HandlerExecution執(zhí)行鏈、查找HandlerAdapter適配器、請(qǐng)求方式method解析、調(diào)用preHandler前置攔截器做攔截。

3.2、設(shè)置與校驗(yàn)

  1. 即上面執(zhí)行完畢后,來(lái)到ha.handle()方法;所有上面在執(zhí)行controller時(shí)沒(méi)做的東西都會(huì)在這里執(zhí)行(請(qǐng)求方式驗(yàn)證、參數(shù)解析、反射調(diào)用controller…)
  2. 并且在這里會(huì)設(shè)置一堆的東西,例如:參數(shù)解析器(不同注解、類型的參數(shù)由不同的解析器)、數(shù)據(jù)綁定器(DataBinder),之后數(shù)據(jù)解析與綁定就是交由DataBinder做。
  3. 再一堆雜七雜八的設(shè)置之后來(lái)到invokeForRequest方法,拿到參數(shù)之后調(diào)用doInvoke()反射執(zhí)行controller。
public class InvocableHandlerMethod extends HandlerMethod {
	@Nullable
	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		
		// 參數(shù)解析
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		if (logger.isTraceEnabled()) {
			logger.trace("Arguments: " + Arrays.toString(args));
		}
		return doInvoke(args);				//執(zhí)行Controller
	}
}

3.3、參數(shù)解析大致流程

  • 首先要避開(kāi)一個(gè)彎,參數(shù)是在調(diào)用controller之前解析完畢的
  • 不同參數(shù)使用不同的參數(shù)解析器,這里采用了策略模式,supportsParameter方法中是一個(gè)增強(qiáng)for循環(huán);匹配合適的直接丟入map中,在第4步的解析中直接從map中獲取!
  • 整個(gè)方法核心就是不同參數(shù)是如何適配到解析器的、參數(shù)又是如何解析的。
public class InvocableHandlerMethod extends HandlerMethod {
	protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
		
		// 1. 拿到前端上傳的所有參數(shù)名稱
		MethodParameter[] parameters = getMethodParameters();
		if (ObjectUtils.isEmpty(parameters)) {
			return EMPTY_ARGS;
		}
		
		// 2. 參數(shù)分配空間
		Object[] args = new Object[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
			MethodParameter parameter = parameters[i];
			parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
			args[i] = findProvidedArgument(parameter, providedArgs);
			if (args[i] != null) {
				continue;
			}
			
			// 3. 參數(shù)解析器的適配,不同參數(shù)會(huì)使用不同解析器
			if (!this.resolvers.supportsParameter(parameter)) {
				throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
			}
			try {
				// 4. 參數(shù)解析
				args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
			}
			....
		}
		return args;
	}
}

3.4、參數(shù)解析器的適配

這里只是適配每一個(gè)參數(shù)的解析器、并不會(huì)解析參數(shù);因此緩存池是非常有必要的,下次解析參數(shù)就可以直接從緩存池中拿!

@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
	// 緩存池便于
	HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
	if (result == null) {
		for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
			if (resolver.supportsParameter(parameter)) {
				result = resolver;
				this.argumentResolverCache.put(parameter, result);
				break;
			}
		}
	}
	return result;
}
  • 文件上傳參數(shù)的解析器的適配是通過(guò)RequestPartMethodArgumentResolver類判斷的。
  • 這里直接判斷參數(shù)上的注解類型是否為@RequestPart,而參數(shù)的信息在之前執(zhí)行過(guò)程中就已經(jīng)全部拿到了。
  • 判斷為true之后這個(gè)RequestPartMethodArgumentResolver解析器就會(huì)被扔到上面的緩存池中便于下次直接獲取
public boolean supportsParameter(MethodParameter parameter) {
	// 直接判斷參數(shù)上的注解類型是否為@RequestPart
	if (parameter.hasParameterAnnotation(RequestPart.class)) {
		return true;
	}
	else {
		if (parameter.hasParameterAnnotation(RequestParam.class)) {
			return false;
		}
		return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
	}
}

在這里插入圖片描述

3.5、參數(shù)解析

由于前面鋪墊太多東西,參數(shù)解析就變得非常簡(jiǎn)單了。緩存拿到對(duì)應(yīng)的解析器、然后解析

@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) {
	
	// map緩存池拿解析器
	HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
	if (resolver == null) {
		throw new IllegalArgumentException("Unsupported parameter type [" +
				parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
	}
	// 解析文件
	return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
  • 這里整體的流程就是先拿到參數(shù)注解判斷注解中的屬性情況,是否required、是否為空…
  • 然后resolveMultipartArgument()方法判斷是單文件還是多文件上傳
  • 找到對(duì)應(yīng)的HttpMessageConvert轉(zhuǎn)換器進(jìn)行對(duì)應(yīng)參數(shù)數(shù)據(jù)到目標(biāo)參數(shù)類型的解析
  • 最后將轉(zhuǎn)換器交由DataBinder進(jìn)行解析與數(shù)據(jù)綁定。
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) {

	HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
	Assert.state(servletRequest != null, "No HttpServletRequest");
	
	// 拿到參數(shù)注解
	RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
	
	// 注解是否必須,且是否為空
	boolean isRequired = ((requestPart == null || requestPart.required()) &&!parameter.isOptional());

	// 參數(shù)名
	String name = getPartName(parameter, requestPart);
	parameter = parameter.nestedIfOptional();
	Object arg = null;
	
	// 這里判斷是否文件上傳、并且是單文件還是多文件上傳
	Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
	if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
		arg = mpArg;
	...
	HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, name);
	// 拿到convert轉(zhuǎn)換器
	arg = readWithMessageConverters(inputMessage, parameter, parameter.getNestedGenericParameterType());
	if (binderFactory != null) {
	...
	// dataBinder參數(shù)解析,這里結(jié)束文件就成型了!
		WebDataBinder binder = binderFactory.createBinder(request, arg, name);
	....
	return adaptArgumentIfNecessary(arg, parameter);
}

到此這篇關(guān)于Spring Boot文件上傳原理與實(shí)現(xiàn)詳解的文章就介紹到這了,更多相關(guān)Spring Boot 文件上傳內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java繼承與多態(tài)的正確打開(kāi)方式

    Java繼承與多態(tài)的正確打開(kāi)方式

    這篇文章主要為大家介紹了Java的繼承與多態(tài),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-01-01
  • 深度優(yōu)先與廣度優(yōu)先Java實(shí)現(xiàn)代碼示例

    深度優(yōu)先與廣度優(yōu)先Java實(shí)現(xiàn)代碼示例

    這篇文章主要介紹了深度優(yōu)先與廣度優(yōu)先Java實(shí)現(xiàn)代碼示例,具有一定借鑒價(jià)值,需要的朋友可以參考下。
    2017-12-12
  • springmvc中下載中文文件名稱為下劃線的解決方案

    springmvc中下載中文文件名稱為下劃線的解決方案

    這篇文章主要介紹了springmvc中下載中文文件名稱為下劃線的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • 簡(jiǎn)單了解java自定義和自然排序

    簡(jiǎn)單了解java自定義和自然排序

    這篇文章主要介紹了簡(jiǎn)單了解java自定義和自然排序,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-07-07
  • 詳解SpringBoot如何統(tǒng)一后端返回格式

    詳解SpringBoot如何統(tǒng)一后端返回格式

    今天我們來(lái)聊一聊在基于SpringBoot前后端分離開(kāi)發(fā)模式下,如何友好的返回統(tǒng)一的標(biāo)準(zhǔn)格式以及如何優(yōu)雅的處理全局異常,感興趣的可以了解一下
    2021-07-07
  • Java數(shù)組初始化的五種方式

    Java數(shù)組初始化的五種方式

    數(shù)組是Java中最基礎(chǔ)且常用的數(shù)據(jù)結(jié)構(gòu)之一,其初始化方式多樣且各具特點(diǎn),本文詳細(xì)講解Java數(shù)組初始化的五種方式,分析其適用場(chǎng)景、優(yōu)劣勢(shì)對(duì)比及注意事項(xiàng),幫助避免常見(jiàn)陷阱并提升代碼質(zhì)量,需要的朋友可以參考下
    2025-04-04
  • 全面分析Java文件上傳

    全面分析Java文件上傳

    本片文章給大家詳細(xì)分析了Java文件上傳的相關(guān)知識(shí)點(diǎn),以及相關(guān)代碼做了詳細(xì)分析,有興趣的朋友學(xué)習(xí)下。
    2018-02-02
  • spring?data?jpa查詢一個(gè)實(shí)體類的部分屬性方式

    spring?data?jpa查詢一個(gè)實(shí)體類的部分屬性方式

    這篇文章主要介紹了spring?data?jpa查詢一個(gè)實(shí)體類的部分屬性方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • Java利用Spire.XLS for Java自動(dòng)化設(shè)置Excel的文檔屬性

    Java利用Spire.XLS for Java自動(dòng)化設(shè)置Excel的文檔屬性

    一個(gè)專業(yè)的 Excel 文件,其文檔屬性往往能大大提升文件的可管理性和可檢索性,下面我們就來(lái)看看Java如何使用Spire.XLS for Java實(shí)現(xiàn)自動(dòng)化設(shè)置Excel的文檔屬性吧
    2025-12-12
  • java線程中synchronized和Lock區(qū)別及介紹

    java線程中synchronized和Lock區(qū)別及介紹

    這篇文章主要為大家介紹了java線程中synchronized和Lock區(qū)別及介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06

最新評(píng)論

比如县| 子洲县| 阿荣旗| 瑞昌市| 秦皇岛市| 承德县| 若羌县| 灵寿县| 安化县| 万全县| 四子王旗| 玉环县| 锦屏县| 门头沟区| 尚志市| 东海县| 休宁县| 晋中市| 同德县| 苏尼特右旗| 麦盖提县| 阿拉善左旗| 上高县| 石林| 青河县| 鲁山县| 灯塔市| 沈丘县| 黎城县| 金山区| 高淳县| 锡林浩特市| 陇南市| 天镇县| 普兰店市| 温州市| 兴安县| 台北县| 阜新| 青川县| 象州县|