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 &mdash; 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的文件大小拦截?相关推荐

  1. mysql标准化存储结构_Atitit.自定义存储引擎的接口设计 api 标准化 attilax 总结  mysql...

    Atitit.自定义存储引擎的接口设计api标准化attilax总结mysql 1.图16.1:MySQL体系结构 存储引擎负责管理数据存储,以及MySQL的索引管理.通过定义的API,MySQL服务 ...

  2. kubebuilder自定义资源

    github地址 一直在网上看k8s自定义资源这一块的内容,但是只停留于看,并没有真正的自己去实践一波,写这篇文章主要参考的是这篇博客,只是我对他做了一些简化,我只希望外部能够通过nodeip+por ...

  3. 整理可视化大屏设计教程与相关资源,大屏设计,可视化

    GIT地址: https://gitee.com/AiShiYuShiJiePingXing/bigscreen 点击前往GIT查看 一.基础概念 1.1 什么是数据可视化 把相对复杂.抽象的数据通过 ...

  4. operator-sdk实战开发K8S CRD自定义资源对象

    环境说明 系统:CentOS Linux release 7.6.1810 (Core) golang:v1.15 operator-sdk:v1.7.0 docker:v1.19 # 因为 oper ...

  5. 通过自定义资源扩展Kubernetes

    原文链接:通过自定义资源扩展Kubernetes 转载于:https://www.cnblogs.com/wangjq19920210/p/11555996.html

  6. VB将自定义资源中的文件释放出来

    程序代码: Option Explicit '************************************************************************* '** ...

  7. Unity使用自定义资源(.asset)配置数据

    本文原创版权归 强哥的私房菜 所有,此处为转载,如有再转,请于篇首位置标明原创作者及出处,以示尊重! 作者:强哥的私房菜 原文:http://blog.csdn.net/liqiangeastsun/ ...

  8. Crd(自定义资源类型)2021.12.05

    目录 文章目录 目录 实验环境 实验软件 1.什么是CRD 2.CRD的定义 3.Controller 4.Operator 5.参考文档 关于我 最后 实验环境 实验环境: 1.win10,vmwr ...

  9. k8s自定义资源CRD

    一.概述 在K8S系统扩展点中,开发者可以通过CRD(CustomResourceDefinition)来扩展K8SAPI,其功能主要由APIExtensionServer负责.使用kubernete ...

最新文章

  1. 关于深度学习的小知识点
  2. 从 Zero 到 Hero ,一文掌握 Python--转
  3. 微信小程序入门资源整理(热更新)
  4. jzoj4228-C【dp】
  5. python系统下载-python
  6. angularJs解决跨域问题-最简单的完美实例
  7. mysql锁表查询_如何通过自动增加索引,实现数据库查询耗时降低50%?
  8. 计算机网络基础(一)
  9. 学以致用深入浅出数字信号处理 pdf_数字阵列雷达--相控阵专题讲座之三
  10. 为什么要学习Go语言?
  11. 误删除文件恢复工具免费下载
  12. Windows timeout命令
  13. 纯js制作图片轮播效果
  14. 如何修改cef源码支持windows xp
  15. 过来人:软件测试自学还是报班好?需要掌握哪些技能?
  16. 【TeXstudio】【2】一般的图片和表格的表现形式
  17. mysql加密函数password
  18. 数通 | 某些基本知识梳理
  19. Ubuntu下搭建第一台hadoop输入start-dfs.sh出现Permission denied (publickey,password)的问题
  20. 07 verilog基础语法-条件语句

热门文章

  1. 创业谨记15点--来自36kr
  2. Windows查看当前目录下所有文件夹大小方法
  3. 3D游戏建模零基础学习路线
  4. “X Partners计划” 青云和合作伙伴的相乘关系
  5. 开源项目-蘑菇博客搭建
  6. Excel清理数据的十大方法
  7. 设置iframe标签的大小
  8. 元素浮动布局,微信聊天框
  9. java swing 字体设置_如何在Java SWING中设置自定义字体的大小和其他属性(粗体,斜体等)...
  10. android权限申请完成后app闪退,Android动态申请Camera权限应用闪退问题