• spring中自定义过滤器


    OncePerRequestFilter

     而Spring的OncePerRequestFilter类实际上是一个实现了Filter接口的抽象类。spring对Filter进行了一些封装处理

    OncePerRequestFilter,顾名思义,它能够确保在一次请求中只通过一次filter,而需要重复的执行。大家常识上都认为,一次请求本来就只filter一次,为什么还要由此特别限定呢,往往我们的常识和实际的实现并不真的一样,经过一番资料的查阅,此方法是为了兼容不同的web container,也就是说并不是所有的container都入我们期望的只过滤一次,servlet版本不同,执行过程也不同,因此,为了兼容各种不同运行环境和版本,默认filter继承OncePerRequestFilter是一个比较稳妥的选择

    源码说明:

    1. public abstract class OncePerRequestFilter extends GenericFilterBean {
    2. //一个标记,后面会用到
    3. public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";
    4. @Override
    5. public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
    6. throws ServletException, IOException {
    7. if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
    8. throw new ServletException("OncePerRequestFilter just supports HTTP requests");
    9. }
    10. HttpServletRequest httpRequest = (HttpServletRequest) request;
    11. HttpServletResponse httpResponse = (HttpServletResponse) response;
    12. //这里获取一个名称,该名称后面会被用于放到request当作key
    13. String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
    14. //检测当前请求是否已经拥有了该标记,如果拥有该标记则代表该过滤器执行过了(后面注释有说明)
    15. boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
    16. if (skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {
    17. // Proceed without invoking this filter...
    18. filterChain.doFilter(request, response);
    19. }
    20. //如果此过滤器已经被执行过则执行如下的逻辑
    21. else if (hasAlreadyFilteredAttribute) {
    22. if (DispatcherType.ERROR.equals(request.getDispatcherType())) {
    23. doFilterNestedErrorDispatch(httpRequest, httpResponse, filterChain);
    24. return;
    25. }
    26. // Proceed without invoking this filter...
    27. filterChain.doFilter(request, response);
    28. }
    29. //走到这里说明该过滤器没有被执行过
    30. else {
    31. // Do invoke this filter...
    32. // 在当前请求里面设置一个标记,key就是前面拼接的那个变量,value是true,这个标记如果在request存在则在前面会被检测到并改变hasAlreadyFilteredAttribute的值
    33. request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
    34. try {
    35. // 这个方法是一个抽象方法需要子类去实现具体的过滤逻辑
    36. doFilterInternal(httpRequest, httpResponse, filterChain);
    37. }
    38. finally {
    39. // Remove the "already filtered" request attribute for this request.
    40. // 执行完毕之后移除该标记
    41. request.removeAttribute(alreadyFilteredAttributeName);
    42. }
    43. }
    44. }
    45. //其余代码略
    46. }


    自定义代码案例

    1. @Component
    2. @Order(-1)
    3. @Slf4j
    4. public class SessionContextFilter extends OncePerRequestFilter implements Ordered {
    5. /**
    6. * 请求拦截器
    7. */
    8. @Override
    9. protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    10. Thread thread = Thread.currentThread();
    11. Long userId = request.getHeader("userId") == null ? 0L : Long.valueOf(request.getHeader("userId"));
    12. String userName = request.getHeader("userName") == null ? "admin" : request.getHeader("userName");
    13. ContextHolder.setContext(new OperationVO(userId,userName));
    14. try {
    15. OperationVO operationVO = ContextHolder.getContext();
    16. // 除这行代码外,其他均为自定义业务代码
    17. filterChain.doFilter(request,response);
    18. } finally {
    19. ContextHolder.clearContext();
    20. OperationVO operationVO = ContextHolder.getContext();
    21. }
    22. }
    23. @Override
    24. public int getOrder() {
    25. return 0;
    26. }
    27. }

    filterChain.doFilter

    实现filterchain的dofilter方法采用 责任链设计模式,把自身接收到的请求request对象和response对象和自身对象即filterchain

    作为下一个过滤器的dofilter的三个形参传递过去,这样才能使得过滤器传递下去,当然这个方法中还存在一些判断if等机制

    用来判断现在的这个过滤器是不是最后一个,是的话就可以把请求和响应对象传递给浏览器请求的页面

    第一个疑问是该过滤器链里面的过滤器源于哪里?

    答案是该类里面包含了一个ApplicationFilterConfig对象,而该对象则是个filter容器

    当web容器启动是ApplicationFilterConfig自动实例化,它会从该web工程的web.xml文件中读取配置的filter信息,然后装进该容器
     下个疑问是它如何执行该过滤器容器里面的filter呢?

    答案是通过pos它来标识当前ApplicationFilterChain(当前过滤器链)执行到哪个过滤器

     ApplicationFilterChain采用责任链设计模式达到对不同过滤器的执行
    首先ApplicationFilterChain 会调用它重写FilterChain的doFilter方法,然后doFilter里面会调用
     internalDoFilter(request,response)方法;该方法使过滤器容器拿到每个过滤器,然后调用它们重写Filter接口里面的dofilter方法

    以下是ApplicationFilterChain 里面重写FilterChain里面的doFilter方法的描述

    1. /**
    2. * Invoke the next filter in this chain, passing the specified request
    3. * and response. If there are no more filters in this chain, invoke
    4. * the service() method of the servlet itself.
    5. *
    6. * @param request The servlet request we are processing
    7. * @param response The servlet response we are creating
    8. *
    9. * @exception IOException if an input/output error occurs
    10. * @exception ServletException if a servlet exception occurs
    11. */
    12. @Override
    13. public void doFilter(ServletRequest request, ServletResponse response)

    以下是internalDoFilter的部分代码

    1. // Call the next filter if there is one
    2. if (pos < n) {
    3. //先拿到下个过滤器,将指针向下移动一位
    4. ApplicationFilterConfig filterConfig = filters[pos++];
    5. Filter filter = null;
    6. try {
    7. //获取当前指向的filter实例
    8. filter = filterConfig.getFilter();
    9. support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
    10. filter, request, response);
    11. if (request.isAsyncSupported() && "false".equalsIgnoreCase(
    12. filterConfig.getFilterDef().getAsyncSupported())) {
    13. request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
    14. Boolean.FALSE);
    15. }
    16. if( Globals.IS_SECURITY_ENABLED ) {
    17. final ServletRequest req = request;
    18. final ServletResponse res = response;
    19. Principal principal =
    20. ((HttpServletRequest) req).getUserPrincipal();
    21. Object[] args = new Object[]{req, res, this};
    22. SecurityUtil.doAsPrivilege
    23. ("doFilter", filter, classType, args, principal);
    24. } else {
    25. //filter调用doFilter(request, response, this)方法
    26. //ApplicationFilterChain里面的filter都实现了filter
    27. filter.doFilter(request, response, this);
    28. }
    29. }
    30. }

    以下是Filter接口doFilter定义如下
     public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)

    过滤器链里面的filter在调用dofilter完成后,会继续调用chain.doFilter(request,response)方法,而这个chain其实就是applicationfilterchain,所以调用过程又回到了上面调用dofilter和调用internalDoFilter方法,这样执行直到里面的过滤器全部执行

    当filte都调用完成后,它就会初始化相应的servlet,(例如jsp资源,默认它会开启一个 JspServlet对象)

    1. // We fell off the end of the chain -- call the servlet instance
    2. try {
    3. if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
    4. lastServicedRequest.set(request);
    5. lastServicedResponse.set(response);
    6. }
    7. support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT,
    8. servlet, request, response);

  • 相关阅读:
    【微服务35】分布式事务Seata源码解析三:从Spring Boot特性来看Seata Client 启动时都做了什么【云原生】
    解决Vue多次传递重复参数会报错
    LeetCode - #89 格雷编码
    夏洛克和他的女朋友—线性筛—逻辑
    微服务(二) php laravel 用户客户端
    Ubuntu环境下基于libxl库文件使用C++实现对表格的操作
    通信缓冲数据ModBusRTU
    低温下安装振弦采集仪注意事项
    如何优雅的杀掉一个进程
    centOs云服务器安装Docker
  • 原文地址:https://blog.csdn.net/qq_36042938/article/details/127984606