Multipart自定义资源限制文件大小限制设计——aop切面怎么才能切入Multipart的文件大小拦截?
Multipart自定义资源限制文件大小限制设计——aop切面怎么才能切入Multipart的文件大小拦截?
author:陈镇坤27
创建时间:2022年1月23日
创作不易,转载请注明来源
摘要:利用AOP切面、ThreadLocal、自定义注解、Spring统一异常处理等知识,自定义上传文件大小限制。
——————————————————————————————
前言
产品需求为做一个资源上传拓展。其中涉及到限制上传视频的视频时长20分钟。我在看旧的代码时,发现旧代码是全局适配Multipart文件的一次性上传文件总和大小,并且没有抛出的合理的业务异常。所以就做了一次较大的修改。
主要思路:
希望自定义注解,在想要适配限制大小的接口上贴注解,输入限制文件大小值,然后系统自动会捕捉对应的文件大小,并设置判断是否拦截。
技术涉略:
AOP切面、ThreadLocal、自定义异常处理器
操作
(先看操作,再看解释)
首先,设计自定义注解和aop切面
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
//@Inherited //允许被子类继承(实现类本身不加此注解,也可以继承)
public @interface MultipartConfigAnnotation {// 0:根据配置文件拦截 -1:无限制 其他 单位:bytelong sizeMax() default 0;
}
@Aspect/*aspect class*/
@Component
@Slf4j
public class MultipartAspect {/*** 记录调用耗时的本地Map变量*/private static final ThreadLocal<Long> MAX_SIZE_CONFIG_LOCAL = new ThreadLocal<>();public static Long getMaxSizeConfigThreadLocalL(){return MAX_SIZE_CONFIG_LOCAL.get();}/*切面只会切外面第一层*/// @Pointcut("execution(* com.aopuse.jkun.aopuse.controller..*.*(..))")@Pointcut("@annotation(com.aopuse.jkun.annotation.MultipartConfigAnnotation)")public void LogAspect(){}@Around("@annotation(log)") public Object deAround(ProceedingJoinPoint joinPoint, MultipartConfigAnnotation log) throws Throwable{MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();MultipartConfigAnnotation annotation = method.getAnnotation(MultipartConfigAnnotation.class);MAX_SIZE_CONFIG_LOCAL.set(annotation.sizeMax());Object proceed = joinPoint.proceed();MAX_SIZE_CONFIG_LOCAL.remove();return proceed ;}
}
再自定义一个文件上传处理器,并覆写父类的方法,自定义文件大小的拦截限制。
/*** @describe:* 注解拦截,注解自定义拦截时间* 1、覆盖父类方法,并重写prepareFileUpload方法,加参数request* 2、在新的prepareFileUpload方法中,获取请求路径,或特殊参数* 3、根据特殊参数对上传文件最大值进行重复赋值* @author: jkun(练习时长两年半)* @return:*/
public class EmodorMultipartResolver extends CommonsMultipartResolver {protected CommonsFileUploadSupport.MultipartParsingResult parseRequest(HttpServletRequest request)throws MultipartException {String encoding = determineEncoding(request);FileUpload fileUpload = this.prepareFileUpload(encoding,request);try {List fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);return parseFileItems(fileItems, encoding);} catch (FileUploadBase.SizeLimitExceededException ex) {throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(),ex);} catch (FileUploadException ex) {throw new MultipartException("Could not parse multipart servlet request", ex);}}protected FileUpload prepareFileUpload(String encoding,HttpServletRequest request) {FileUpload fileUpload = getFileUpload();FileUpload actualFileUpload = fileUpload;// Use new temporary FileUpload instance if the request specifies// its own encoding that does not match the default encoding.if (encoding != null && !encoding.equals(fileUpload.getHeaderEncoding())) {actualFileUpload = newFileUpload(getFileItemFactory());actualFileUpload.setHeaderEncoding(encoding);// 获取ThreadLocal中的配置Long maxSizeConfigThreadLocalL = MultipartAspect.getMaxSizeConfigThreadLocalL();if(maxSizeConfigThreadLocalL != null) {actualFileUpload.setSizeMax(maxSizeConfigThreadLocalL);} else {actualFileUpload.setSizeMax(fileUpload.getSizeMax());}
// // 下面是根据请求路径配置
// MultipartHttpServletRequest multipartRequest =
// WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
// boolean isAddProduct = request.getRequestURI().contains("/api/file/uploadEducationResource");
// if(isAddProduct){// actualFileUpload.setSizeMax(300 * 1024 * 1024);//重新设置文件无大小限制
// }else{// actualFileUpload.setSizeMax(fileUpload.getSizeMax());
// }}return actualFileUpload;}
}
设置大小拦截方式有两种:
1、通过加载路径的形式;
2、通过注解的方式;
我这里选择第二种。在特定的接口上添加注解,然后切面拦截到该注解的请求,获取注解中的值。如下:
创建自定义的文件上传配置后,需要设置其为懒加载。
之后,再接口中,需要使用到下面这行代码
最后,在统一异常处理器中,增加一个异常处理方法
@ResponseStatus(value=HttpStatus.ACCEPTED)@ExceptionHandler(MaxUploadSizeExceededException.class)@ResponseBodypublic ResponseDTO<String> handleException(MaxUploadSizeExceededException orginalException,HttpServletRequest request,HttpServletResponse response) {String msg = "该文件大小不得大于" +String.valueOf(orginalException.getMaxUploadSize() / (1024 * 1024)) + "M";logger.info("[抛出文件大小异常 :" + msg + "]");return new ResponseDTO<String>(10002, msg, null);}
解释
切面的意义
切面环切,主要作用是在进入目标方法之前,执行自定义函数。
ThreadLocal的意义和注意事项
ThreadLocal的主要作用是“跨函数传递”数据,目的是将解析到的注解值在Thread内传递。
为了方便一个线程内hreadLocalMap的调动,不反复实例该变量所在的类,我们使用static final修饰ThreadLocal变量,让Class对象持有该变量引用的实例即可。(一则方便调用,二则节省实例空间。)
因为修饰了local变量为static,该变量为类对象所持有,而类对象存在于方法区中,几乎不会被回收,所以该threadLocal实例便被一个static变量所强引用,该ThreadLocal实例便不会被回收,则同样意味ThreadLocalMap的key(该key即为ThreadLocal实例)不会被回收,则其他线程在调用ThreadLocal的get、set、remove方法时,这些key不为空的ThreadLocalMap键值对(即使不需要被使用了)无法被清除。
此外,由于线程池的作用,一个线程执行过一次ThreadLocal,该线程不消亡,加上实例为强引用,所以ThreadMap中存储的k,v无法被清除,一方面发生内存泄露,一方面会影响到线程池中该线程的复用(数据紊乱)。
所以在每次环形切面之后,都要调用remove方法将其释放。
在方法中使用request.getFileNames()、配置中增加懒加载标识的意义
Multipart对文件的拦截,如果不加request.getFileNames()这行代码在拦截的目标方法内,则该拦截将会在aop切面之上。
通俗地来讲,如果不加request.getFileNames(),该拦截会在请求进入controller层之前进行。
加上之后,还需要在配置中定义为懒加载,拦截会在进入请求之后执行,详细过程,可自行调试源码。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {// 检查Multi配置,其中根据父类的CommonsMultipartResolver是否懒加载决定是否优先解析MultipartResolver的文件拦截设置,细节方法如下注释// return this.multipartResolver.resolveMultipart(request);processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// Determine handler for the current request.mappedHandler = getHandler(processedRequest);if (mappedHandler == null || mappedHandler.getHandler() == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (logger.isDebugEnabled()) {logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);}if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler.// 代理调用目标方法,在这里,就是我们贴注解的方法,如果上面检查是为懒处理,则将会执行到这里,成功进入controllermv = ha.handle(processedRequest, response, mappedHandler.getHandler());......}
执行到controller,解析request的getFileNames()方法
@Overridepublic Iterator<String> getFileNames() {return getMultipartFiles().keySet().iterator();}
会执行getMultipartFiles方法,该方法又会去执行initializeMultipart方法。这个方法也是懒加载出的方法,见下图:
(如果懒处理为false,则立刻解析,为true,则稍后再解析)
到这里,终于会去加载我们自定义的拦截大小配置。
拓展
@ExceptionHandler注解是spring容器统一异常控制注解
一般,一个异常只会被一个处理器捕捉一次。如果再抛出异常,则不会再被捕捉。
PS:要控制由哪个处理器优先执行,是无法通过注解@order进行的。
<p><b>NOTE</b>: Annotation-based ordering is supported for specific kindsof components only — for example, for annotation-based AspectJaspects. Ordering strategies within the Spring container, on the otherhand, are typically based on the {@link Ordered} interface in order toallow for programmatically configurable ordering of each <i>instance</i>.
总而言之,order更多地用于控制下面几类(参考博客:https://blog.csdn.net/qianshangding0708/article/details/107373538)
- 控制AOP的类的加载顺序,也就是被
@Aspect
标注的类 - 控制
ApplicationListener
实现类的加载顺序 - 控制
CommandLineRunner
实现类的加载顺序
真正决定由哪个异常处理器处理对应的异常,根据匹配深度来进行的(参考博客:https://blog.csdn.net/weixin_34210740/article/details/93182306)
Multipart自定义资源限制文件大小限制设计——aop切面怎么才能切入Multipart的文件大小拦截?相关推荐
- mysql标准化存储结构_Atitit.自定义存储引擎的接口设计 api 标准化 attilax 总结 mysql...
Atitit.自定义存储引擎的接口设计api标准化attilax总结mysql 1.图16.1:MySQL体系结构 存储引擎负责管理数据存储,以及MySQL的索引管理.通过定义的API,MySQL服务 ...
- kubebuilder自定义资源
github地址 一直在网上看k8s自定义资源这一块的内容,但是只停留于看,并没有真正的自己去实践一波,写这篇文章主要参考的是这篇博客,只是我对他做了一些简化,我只希望外部能够通过nodeip+por ...
- 整理可视化大屏设计教程与相关资源,大屏设计,可视化
GIT地址: https://gitee.com/AiShiYuShiJiePingXing/bigscreen 点击前往GIT查看 一.基础概念 1.1 什么是数据可视化 把相对复杂.抽象的数据通过 ...
- operator-sdk实战开发K8S CRD自定义资源对象
环境说明 系统:CentOS Linux release 7.6.1810 (Core) golang:v1.15 operator-sdk:v1.7.0 docker:v1.19 # 因为 oper ...
- 通过自定义资源扩展Kubernetes
原文链接:通过自定义资源扩展Kubernetes 转载于:https://www.cnblogs.com/wangjq19920210/p/11555996.html
- VB将自定义资源中的文件释放出来
程序代码: Option Explicit '************************************************************************* '** ...
- Unity使用自定义资源(.asset)配置数据
本文原创版权归 强哥的私房菜 所有,此处为转载,如有再转,请于篇首位置标明原创作者及出处,以示尊重! 作者:强哥的私房菜 原文:http://blog.csdn.net/liqiangeastsun/ ...
- Crd(自定义资源类型)2021.12.05
目录 文章目录 目录 实验环境 实验软件 1.什么是CRD 2.CRD的定义 3.Controller 4.Operator 5.参考文档 关于我 最后 实验环境 实验环境: 1.win10,vmwr ...
- k8s自定义资源CRD
一.概述 在K8S系统扩展点中,开发者可以通过CRD(CustomResourceDefinition)来扩展K8SAPI,其功能主要由APIExtensionServer负责.使用kubernete ...
最新文章
- 关于深度学习的小知识点
- 从 Zero 到 Hero ,一文掌握 Python--转
- 微信小程序入门资源整理(热更新)
- jzoj4228-C【dp】
- python系统下载-python
- angularJs解决跨域问题-最简单的完美实例
- mysql锁表查询_如何通过自动增加索引,实现数据库查询耗时降低50%?
- 计算机网络基础(一)
- 学以致用深入浅出数字信号处理 pdf_数字阵列雷达--相控阵专题讲座之三
- 为什么要学习Go语言?
- 误删除文件恢复工具免费下载
- Windows timeout命令
- 纯js制作图片轮播效果
- 如何修改cef源码支持windows xp
- 过来人:软件测试自学还是报班好?需要掌握哪些技能?
- 【TeXstudio】【2】一般的图片和表格的表现形式
- mysql加密函数password
- 数通 | 某些基本知识梳理
- Ubuntu下搭建第一台hadoop输入start-dfs.sh出现Permission denied (publickey,password)的问题
- 07 verilog基础语法-条件语句
热门文章
- 创业谨记15点--来自36kr
- Windows查看当前目录下所有文件夹大小方法
- 3D游戏建模零基础学习路线
- “X Partners计划” 青云和合作伙伴的相乘关系
- 开源项目-蘑菇博客搭建
- Excel清理数据的十大方法
- 设置iframe标签的大小
- 元素浮动布局,微信聊天框
- java swing 字体设置_如何在Java SWING中设置自定义字体的大小和其他属性(粗体,斜体等)...
- android权限申请完成后app闪退,Android动态申请Camera权限应用闪退问题