精华内容
下载资源
问答
  • 这章源码分析一下,主要从filter的过滤计数、双返回值的执行过程、一次性filter的用法分析。 1. filter执行过程 先简单说一下,每次执行请求,Tomcat等容器会创建责任链,filter的执行时,框架会计数,每个filter...

    前言

           笔者的项目出现双返回值的问题,然而返回String的返回值时却十分正常,引起了笔者的好奇心。这章源码分析一下,主要从filter的过滤计数、双返回值的执行过程、一次性filter的用法分析。

    1. filter执行过程

            先简单说一下,每次执行请求,Tomcat等容器会创建责任链,filter的执行时,框架会计数,每个filter做一次dofilter操作,然后执行servlet的操作,是责任链模式的体现。

    还是上一章的demo,每次请求执行需要创建

    org.apache.catalina.core.StandardWrapperValve// Create the filter chain for this request
    ApplicationFilterChain filterChain =
            ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

    跟踪创建过程,非常简单

    /**
         * Construct a FilterChain implementation that will wrap the execution of
         * the specified servlet instance.
         *
         * @param request The servlet request we are processing
         * @param wrapper The wrapper managing the servlet instance
         * @param servlet The servlet instance to be wrapped
         *
         * @return The configured FilterChain instance or null if none is to be
         *         executed.
         */
        public static ApplicationFilterChain createFilterChain(ServletRequest request,
                Wrapper wrapper, Servlet servlet) {
    
            // If there is no servlet to execute, return null
            if (servlet == null)
                return null;
    
            // Create and initialize a filter chain object
            //创建责任链,ApplicationFilterChain
            ApplicationFilterChain filterChain = null;
            if (request instanceof Request) {
                Request req = (Request) request;
                if (Globals.IS_SECURITY_ENABLED) {
                    // Security: Do not recycle
                    filterChain = new ApplicationFilterChain();
                } else {
                    filterChain = (ApplicationFilterChain) req.getFilterChain();
                    if (filterChain == null) {
                        filterChain = new ApplicationFilterChain();
                        req.setFilterChain(filterChain);
                    }
                }
            } else {
                // Request dispatcher in use
                filterChain = new ApplicationFilterChain();
            }
    
            filterChain.setServlet(servlet);
            filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());
    
            // Acquire the filter mappings for this Context
            StandardContext context = (StandardContext) wrapper.getParent();
            //content容器拿到filter map
            FilterMap filterMaps[] = context.findFilterMaps();
    
            // If there are no filter mappings, we are done
            if ((filterMaps == null) || (filterMaps.length == 0))
                return filterChain;
    
            // Acquire the information we will need to match filter mappings
            DispatcherType dispatcher =
                    (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);
    
            String requestPath = null;
            Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);
            if (attribute != null){
                requestPath = attribute.toString();
            }
    
            String servletName = wrapper.getName();
    
            // Add the relevant path-mapped filters to this filter chain
            for (int i = 0; i < filterMaps.length; i++) {
                if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
                    continue;
                }
                if (!matchFiltersURL(filterMaps[i], requestPath))
                    continue;
                ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                    context.findFilterConfig(filterMaps[i].getFilterName());
                if (filterConfig == null) {
                    // FIXME - log configuration problem
                    continue;
                }
                filterChain.addFilter(filterConfig);
            }
    
            // Add filters that match on servlet name second
            for (int i = 0; i < filterMaps.length; i++) {
                if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
                    continue;
                }
                if (!matchFiltersServlet(filterMaps[i], servletName))
                    continue;
                ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                    context.findFilterConfig(filterMaps[i].getFilterName());
                if (filterConfig == null) {
                    // FIXME - log configuration problem
                    continue;
                }
                //filter塞进ApplicationFilterChain中
                filterChain.addFilter(filterConfig);
            }
    
            // Return the completed filter chain
            return filterChain;
        }
    
    
    

    然后去dofilter

    if (request.isAsyncDispatching()) {
        request.getAsyncContextInternal().doInternalDispatch();
    } else {
        filterChain.doFilter
            (request.getRequest(), response.getResponse());
    }

    跟踪org.apache.catalina.core.ApplicationFilterChain

    private void internalDoFilter(ServletRequest request,
                                      ServletResponse response)
            throws IOException, ServletException {
    
            // Call the next filter if there is one
            //就是这里,计数,表示设计之初每次filter只执行一次dofilter
            if (pos < n) {
                ApplicationFilterConfig filterConfig = filters[pos++];
                try {
                    Filter filter = filterConfig.getFilter();
    
                    if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                            filterConfig.getFilterDef().getAsyncSupported())) {
                        request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
                    }
                    if( Globals.IS_SECURITY_ENABLED ) {
                        final ServletRequest req = request;
                        final ServletResponse res = response;
                        Principal principal =
                            ((HttpServletRequest) req).getUserPrincipal();
    
                        Object[] args = new Object[]{req, res, this};
                        SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
                    } else {
                        filter.doFilter(request, response, this);
                    }
                } catch (IOException | ServletException | RuntimeException e) {
                    throw e;
                } catch (Throwable e) {
                    e = ExceptionUtils.unwrapInvocationTargetException(e);
                    ExceptionUtils.handleThrowable(e);
                    throw new ServletException(sm.getString("filterChain.filter"), e);
                }
                return;
            }
    
            //执行完filter就做servlet.service(request, response);
            // We fell off the end of the chain -- call the servlet instance
            try {
                if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                    lastServicedRequest.set(request);
                    lastServicedResponse.set(response);
                }
    
                if (request.isAsyncSupported() && !servletSupportsAsync) {
                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                            Boolean.FALSE);
                }
                // Use potentially wrapped request from this point
                if ((request instanceof HttpServletRequest) &&
                        (response instanceof HttpServletResponse) &&
                        Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal =
                        ((HttpServletRequest) req).getUserPrincipal();
                    Object[] args = new Object[]{req, res};
                    SecurityUtil.doAsPrivilege("service",
                                               servlet,
                                               classTypeUsedInService,
                                               args,
                                               principal);
                } else {
                    servlet.service(request, response);
                }
            } catch (IOException | ServletException | RuntimeException e) {
                throw e;
            } catch (Throwable e) {
                e = ExceptionUtils.unwrapInvocationTargetException(e);
                ExceptionUtils.handleThrowable(e);
                throw new ServletException(sm.getString("filterChain.servlet"), e);
            } finally {
                if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                    lastServicedRequest.set(null);
                    lastServicedResponse.set(null);
                }
            }
        }

    到此,filter的核心原理就明白了,创建责任链,塞filter,计数dofilter,执行servlet.service

     

    2. 双返回值原理分析

            虽然是我自己业务代码的问题,但是通过上面的源码可以看出,笔者示例demo

    public void doFilter(ServletRequest servletRequest,
                             ServletResponse servletResponse,
                             FilterChain filterChain) throws IOException, ServletException {
            try {
                LOGGER.info("=============secondFilter============");
                //模拟异常
                throw new RuntimeException("xxx");
            } catch (Exception e) {
                filterChain.doFilter(servletRequest, servletResponse);
                //e.printStackTrace();
            }
            filterChain.doFilter(servletRequest, servletResponse);
        }

    这个filter执行2次dofilter,导致计数超标,然后执行2次servlet.service方法,执行本身也没什么事,关键在于servlet的response返回的时候,写操作的方式是不一样的,跟踪org.springframework.web.servlet.DispatcherServlet

    /**
    	 * Process the actual dispatching to the handler.
    	 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
    	 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
    	 * to find the first that supports the handler class.
    	 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
    	 * themselves to decide which methods are acceptable.
    	 * @param request current HTTP request
    	 * @param response current HTTP response
    	 * @throws Exception in case of any kind of processing failure
    	 */
    	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 {
    				processedRequest = checkMultipart(request);
    				multipartRequestParsed = (processedRequest != request);
    
    				// Determine handler for the current request.
    				mappedHandler = getHandler(processedRequest);
    				if (mappedHandler == 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 (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
    						return;
    					}
    				}
    
    				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    					return;
    				}
    
                                     //本质在这里
    				// Actually invoke the handler.
    				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
    				if (asyncManager.isConcurrentHandlingStarted()) {
    					return;
    				}
    
    				applyDefaultViewName(processedRequest, mv);
    				mappedHandler.applyPostHandle(processedRequest, response, mv);
    			}
    			catch (Exception ex) {
    				dispatchException = ex;
    			}
    			catch (Throwable err) {
    				// As of 4.3, we're processing Errors thrown from handler methods as well,
    				// making them available for @ExceptionHandler methods and other scenarios.
    				dispatchException = new NestedServletException("Handler dispatch failed", err);
    			}
    			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    		}
    		catch (Exception ex) {
    			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    		}
    		catch (Throwable err) {
    			triggerAfterCompletion(processedRequest, response, mappedHandler,
    					new NestedServletException("Handler processing failed", err));
    		}
    		finally {
    			if (asyncManager.isConcurrentHandlingStarted()) {
    				// Instead of postHandle and afterCompletion
    				if (mappedHandler != null) {
    					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
    				}
    			}
    			else {
    				// Clean up any resources used by a multipart request.
    				if (multipartRequestParsed) {
    					cleanupMultipart(processedRequest);
    				}
    			}
    		}
    	}

    继续跟踪,发现org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor在处理返回值的时候,HttpMessageConverter原因造成的。

    @Override
    	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
    			ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
    			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    
    		mavContainer.setRequestHandled(true);
    		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
    		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
    
    		// Try even with null return value. ResponseBodyAdvice could get involved.
    		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
    	}

    writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); 

    if (selectedMediaType != null) {
    			selectedMediaType = selectedMediaType.removeQualityValue();
    			for (HttpMessageConverter<?> converter : this.messageConverters) {
    				GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
    						(GenericHttpMessageConverter<?>) converter : null);
    				if (genericConverter != null ?
    						((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
    						converter.canWrite(valueType, selectedMediaType)) {
    					body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
    							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
    							inputMessage, outputMessage);
    					if (body != null) {
    						Object theBody = body;
    						LogFormatUtils.traceDebug(logger, traceOn ->
    								"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
    						addContentDispositionHeader(inputMessage, outputMessage);
    						if (genericConverter != null) {
    							genericConverter.write(body, targetType, selectedMediaType, outputMessage);
    						}
    						else {
    							((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
    						}
    					}
    					else {
    						if (logger.isDebugEnabled()) {
    							logger.debug("Nothing to write: null body");
    						}
    					}
    					return;
    				}
    			}
    		}

    Spring会在converter中选择一个converter处理返回值操作。 

    处理bean以及map时,springboot默认使用org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter

    /**
         * Method that can be used to serialize any Java value as
         * JSON output, using provided {@link JsonGenerator}.
         */
        public void writeValue(JsonGenerator g, Object value) throws IOException
        {
            _assertNotNull("g", g);
            _configureGenerator(g);
            if (_config.isEnabled(SerializationFeature.CLOSE_CLOSEABLE)
                    && (value instanceof Closeable)) {
    
                Closeable toClose = (Closeable) value;
                try {
                    _prefetch.serialize(g, value, _serializerProvider());
                    if (_config.isEnabled(SerializationFeature.FLUSH_AFTER_WRITE_VALUE)) {
                        g.flush();
                    }
                } catch (Exception e) {
                    ClassUtil.closeOnFailAndThrowAsIOE(null, toClose, e);
                    return;
                }
                toClose.close();
            } else {
                _prefetch.serialize(g, value, _serializerProvider());
                if (_config.isEnabled(SerializationFeature.FLUSH_AFTER_WRITE_VALUE)) {
                    g.flush();
                }
            }
        }

    看到了,使用序列化方式输出写操作

    _prefetch.serialize(g, value, _serializerProvider());

    每执行一次servlet.service方法序列化输出一次

    在看看String返回值

    使用org.springframework.http.converter.StringHttpMessageConverter

    @Override
    	protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
    		HttpHeaders headers = outputMessage.getHeaders();
    		if (this.writeAcceptCharset && headers.get(HttpHeaders.ACCEPT_CHARSET) == null) {
    			headers.setAcceptCharset(getAcceptedCharsets());
    		}
    		Charset charset = getContentTypeCharset(headers.getContentType());
    		StreamUtils.copy(str, charset, outputMessage.getBody());
    	}

    继续跟踪

    /**
    	 * Copy the contents of the given String to the given output OutputStream.
    	 * Leaves the stream open when done.
    	 * @param in the String to copy from
    	 * @param charset the Charset
    	 * @param out the OutputStream to copy to
    	 * @throws IOException in case of I/O errors
    	 */
    	public static void copy(String in, Charset charset, OutputStream out) throws IOException {
    		Assert.notNull(in, "No input String specified");
    		Assert.notNull(charset, "No charset specified");
    		Assert.notNull(out, "No OutputStream specified");
    
    		Writer writer = new OutputStreamWriter(out, charset);
    		writer.write(in);
    		writer.flush();
    	}

    难怪正常

    Writer writer = new OutputStreamWriter(out, charset);

    每次new,下一次输出会覆盖上一次输出,😓

    从容错角度,String返回处理正常;但从正确的角度,Jackson的返回时正确的

     

    3. org.springframework.web.filter.OncePerRequestFilter

    很多人说org.springframework.web.filter.OncePerRequestFilter可以规避多次dofilter的问题,然而这个只能让多个继承这个抽象类的filter,只执行一个filter,并不是规避多次dofilter的问题,原理

    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
    			throws ServletException, IOException {
    
    		if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
    			throw new ServletException("OncePerRequestFilter just supports HTTP requests");
    		}
    		HttpServletRequest httpRequest = (HttpServletRequest) request;
    		HttpServletResponse httpResponse = (HttpServletResponse) response;
    
    		String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
    		boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
    
    		if (skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {
    
    			// Proceed without invoking this filter...
    			filterChain.doFilter(request, response);
    		}
    		else if (hasAlreadyFilteredAttribute) {
    
    			if (DispatcherType.ERROR.equals(request.getDispatcherType())) {
    				doFilterNestedErrorDispatch(httpRequest, httpResponse, filterChain);
    				return;
    			}
    
    			// Proceed without invoking this filter...
    			filterChain.doFilter(request, response);
    		}
    		else {
                            //设置属性标记
    			// Do invoke this filter...
    			request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
    			try {
    				doFilterInternal(httpRequest, httpResponse, filterChain);
    			}
    			finally {
                                    //这里要注意,责任链是链路,执行完成要移除设置的attribute,对业务无感知
                                    //此处对try代码的入栈代码生效,此处出栈移除
    				// Remove the "already filtered" request attribute for this request.
    				request.removeAttribute(alreadyFilteredAttributeName);
    			}
    		}
    	}

    就是set setAttribute,然后下次判断。attributeName就是filter的名称+后缀

    protected String getAlreadyFilteredAttributeName() {
    		String name = getFilterName();
    		if (name == null) {
    			name = getClass().getName();
    		}
    		return name + ALREADY_FILTERED_SUFFIX;
    	}

     

    总结

           仅仅是简单的filter,不注意写的有问题,结合springboot,就会有各种原因,非常的诡异,需要分析源码才能根源上解决问题。最怕的是在特定情况出现,比如笔者模拟的异常时多返回值,但String返回正常,这时就需要更多的日志,或者Java请求栈分析了。

     

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • zuulGateway 通过filter统一修改返回值

    千次阅读 2018-04-08 11:55:46
    比如加密,多添加一个返回值等等。当然可以在方法里面处理,但如果方法很多,需要统一处理的,就很不方便了,这时候可以通过zuulGateway的filter来统一处理。直接看代码,很简单: import java.io.InputStream; ...

    使用spring cloud有时候我们给客户端返回内容的时候,往往需要添加一些额外的东西。比如加密,多添加一个返回值等等。当然可以在方法里面处理,但如果方法很多,需要统一处理的,就很不方便了,这时候可以通过zuulGateway的filter来统一处理。

    直接看代码,很简单:

    
    import java.io.InputStream;
    import java.nio.charset.Charset;
    
    import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
    import org.springframework.util.StreamUtils;
    import org.springframework.util.StringUtils;
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import com.poly.zuul.common.Constants;
    import com.poly.zuul.enums.ServiceId;
    import com.poly.zuul.utils.AESJSEncryptUtils;
    import com.poly.zuul.utils.RsaEncryptUtil;
    
    
    /**
     * 返回值输出过滤器,这里用来处理返回值
     * 
     * @Title: ResponseFilter
     * @Description:
     * @author kokJuis
     * @date 上午9:52:42
     */
    public class ResponseFilter extends ZuulFilter
    {
    
        @Override
        public Object run()
        {
    
            RequestContext context = RequestContext.getCurrentContext();
    
            try
            {
                // 获取返回值内容,加以处理
                InputStream stream = context.getResponseDataStream();
                String body = StreamUtils.copyToString(stream, Charset.forName("UTF-8"));
                String returnStr = "";
    
    			
    			//你的处理逻辑,加密,添加新的返回值等等.....
    			
    
                // 内容重新写入
                context.setResponseBody(returnStr);
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
    
            return null;
        }
    
        @Override
        public boolean shouldFilter()
        {
            RequestContext ctx = RequestContext.getCurrentContext();
    
            String requestURI = String.valueOf(ctx.get("requestURI"));
    
            if (requestURI.contains(Constants.alipay))
            {
    			//不需要处理的URL请求,返回false
                return false;
            }
    
    
            return true;
        }
    
        @Override
        public int filterOrder()
        {
            return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 3;
        }
    
        @Override
        public String filterType()
        {
            return FilterConstants.POST_TYPE;// 在请求被处理之后,会进入该过滤器
        }
    
    }


    然后在@Configuration里面注册一下这个filter就可以了

     @Bean
        public ResponseFilter responseFilter()
        {
            return new ResponseFilter();
        }
    


    展开全文
  • 有时候在开发过程中会有这样一个场景,有时需要对返回的数据进行处理。比如加密啊,添加某些标记啊等等。这种情况我们可以通过过滤器去处理。 过滤器是什么就不介绍了,百度一下,...package com.channel.filter; ...

    有时候在开发过程中会有这样一个场景,有时需要对返回的数据进行处理。比如加密啊,添加某些标记啊等等。这种情况我们可以通过过滤器去处理。

    过滤器是什么就不介绍了,百度一下,你就知道。

    返回值输出代理类:这个类主要是为了吧Response里面的返回值获取到,因为直接Response没有提供直接拿到返回值的方法。所以要通过代理来取得返回值

    package com.channel.filter;
     
     
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
     
    import javax.servlet.ServletOutputStream;
    import javax.servlet.WriteListener;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpServletResponseWrapper;
     
     
    /**
     * 返回值输出代理类
     * 
     * @Title: ResponseWrapper
     * @Description:
     * @author kokJuis
     * @date 上午9:52:11
     */
    public class ResponseWrapper extends HttpServletResponseWrapper
    {
     
        private ByteArrayOutputStream buffer;
     
        private ServletOutputStream out;
     
        public ResponseWrapper(HttpServletResponse httpServletResponse)
        {
            super(httpServletResponse);
            buffer = new ByteArrayOutputStream();
            out = new WrapperOutputStream(buffer);
        }
     
        @Override
        public ServletOutputStream getOutputStream()
            throws IOException
        {
            return out;
        }
     
        @Override
        public void flushBuffer()
            throws IOException
        {
            if (out != null)
            {
                out.flush();
            }
        }
     
        public byte[] getContent()
            throws IOException
        {
            flushBuffer();
            return buffer.toByteArray();
        }
     
        class WrapperOutputStream extends ServletOutputStream
        {
            private ByteArrayOutputStream bos;
     
            public WrapperOutputStream(ByteArrayOutputStream bos)
            {
                this.bos = bos;
            }
     
            @Override
            public void write(int b)
                throws IOException
            {
                bos.write(b);
            }
     
            @Override
            public boolean isReady()
            {
     
                // TODO Auto-generated method stub
                return false;
     
            }
     
            @Override
            public void setWriteListener(WriteListener arg0)
            {
     
                // TODO Auto-generated method stub
     
            }
        }
     
    }
    

    过滤器类:

    package com.channel.filter;
     
     
    import java.io.IOException;
     
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletOutputStream;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletResponse;
     
    import com.channel.util.RsaEncryptUtil;
     
     
    /**
     * 返回值输出过滤器,这里用来加密返回值
     * 
     * @Title: ResponseFilter
     * @Description:
     * @author kokJuis
     * @date 上午9:52:42
     */
    public class ResponseFilter implements Filter
    {
     
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException
        {
            ResponseWrapper wrapperResponse = new ResponseWrapper((HttpServletResponse)response);//转换成代理类
            // 这里只拦截返回,直接让请求过去,如果在请求前有处理,可以在这里处理
            filterChain.doFilter(request, wrapperResponse);
            byte[] content = wrapperResponse.getContent();//获取返回值
    	//判断是否有值
            if (content.length > 0)
            {
     
                String str = new String(content, "UTF-8");
                System.out.println("返回值:" + str);
                String ciphertext = null;
     
                try
                {
                    //......根据需要处理返回值
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
    		    //把返回值输出到客户端
                ServletOutputStream out = response.getOutputStream();
                //我测试了需要加上这行代码,才能对 response的内容 修改生效,
                //好像是需要保持长度一致,不然请求就一直会处于等待,希望对后来的小伙伴有点帮助
                out.setContentLength(ciphertext.getBytes().length);
                out.write(ciphertext.getBytes());
                out.flush();
            }
     
        }
     
        @Override
        public void init(FilterConfig arg0)
            throws ServletException
        {
     
        }
     
        @Override
        public void destroy()
        {
     
        }
     
    }
    

    spring mvc 下直接在web.xml下添加配置即可

    <!-- 返回值过滤器 -->
    	<filter>
    		<filter-name>responseFilter</filter-name>
    		<filter-class>com.im.filter.ResponseFilter</filter-class>
    	</filter>
     
    	<filter-mapping>
    		<filter-name>responseFilter</filter-name>
    		<url-pattern>/*</url-pattern>
    	</filter-mapping>
    

    一个朋友新做的公众号,帮忙宣传一下,会不定时推送一些开发中碰到的问题的解决方法,以及会分享一些开发视频。资料等。请大家关注一下谢谢:

     

    展开全文
  • spring mvc统一处理接口返回值filter过滤器实现HttpServletResponse的包装类过滤器具体实现特殊说明:Aop切面实现添加pom.xml修改配置文件spring-servlet.xmlaop类实现最后简单说一下拦截器以上代码如有侵权,请与我...


    对于处理接口返回值统一加密,过滤,特定值统一处理,统一返回等多种需求,您需要看一下此文章,java的拦截器前置拦截比较常用,例如:登录校验,参数格式校验等等。后置拦截没有找到好的实现方式。接下来介绍filter和aop。

    filter过滤器实现

    filter的执行是链式的,对于ServletRequest和ServletResponse都可以做一定的处理,通过对chain.doFilter(req, resp);的执行控制,来实现很多场景。

    HttpServletResponse的包装类

    ServletResponse的子接口为HttpServletResponse,我们通过创建HttpServletResponse的包装类,来实现spring往response写返回值时进行控制。

    import javax.servlet.ServletOutputStream;
    import javax.servlet.WriteListener;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpServletResponseWrapper;
    import java.io.*;
    
    
    public class ResponseWrapper extends HttpServletResponseWrapper {
    
       private ByteArrayOutputStream bytes = new ByteArrayOutputStream();
       private HttpServletResponse response;
       private PrintWriter pwrite;
    
       public ResponseWrapper(HttpServletResponse response) {
           super(response);
           this.response = response;
       }
    
       /**
        * 重写父类的 getWriter() 方法,将响应数据缓存在 bytes 中
        */
       @Override
       public ServletOutputStream getOutputStream() throws IOException {
           //将数据写入内部的字节流
           return new MyServletOutputStream(bytes);
       }
    
       /**
        * 重写父类的 getWriter() 方法,将响应数据由bytes放入pwrite中
        */
       @Override
       public PrintWriter getWriter() {
           try {
               pwrite = new PrintWriter(new OutputStreamWriter(bytes, "utf-8"));
           } catch (UnsupportedEncodingException e) {
               e.printStackTrace();
           }
           return pwrite;
       }
    
       /**
        * 获取缓存在 pwrite 中的响应数据
        *
        * @return
        */
       public byte[] getBytes() {
           if (null != pwrite) {
               pwrite.close();
               return bytes.toByteArray();
           }
    
           if (null != bytes) {
               try {
                   bytes.flush();
               } catch (IOException e) {
                   e.printStackTrace();
               }
           }
           return bytes.toByteArray();
       }
    
       /**
        * 内部类实现流写入
        */
       class MyServletOutputStream extends ServletOutputStream {
           private ByteArrayOutputStream ostream;
    
           public MyServletOutputStream(ByteArrayOutputStream ostream) {
               this.ostream = ostream;
           }
    
           @Override
           public void write(int b) throws IOException {
               // 将数据写到 stream 中
               ostream.write(b);
           }
    
           @Override
           public boolean isReady() {
               return false;
           }
    
           @Override
           public void setWriteListener(WriteListener writeListener) {
           }
       }
    
    }
    

    过滤器具体实现

    通过将包装类暂时替换接口的ServletResponse,拦截到接口返回值后,处理后再写回接口返回值即可。urlPatterns 指定拦截的url路径。

    import javax.servlet.*;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    @WebFilter(filterName = "Filter", urlPatterns = {"*.do"})
    public class StringFilter implements Filter {
        @Override
        public void destroy() {
        }
    
        @Override
        public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException,
                IOException {
            if (resp instanceof HttpServletResponse) {
                HttpServletResponse response = (HttpServletResponse) resp;
                ResponseWrapper mResp = new ResponseWrapper(response);
                //将包装类写入方法执行
                chain.doFilter(req, mResp);
                //获取方法返回值
                byte[] bytes = mResp.getBytes();
                String s = new String(bytes);
                //写回接口返回值
                PrintWriter writer = resp.getWriter();
                writer.write(s);
                writer.close();
                System.out.println("过滤器拦截:" + s);
            } else {
                chain.doFilter(req, resp);
            }
    
        }
    
        @Override
        public void init(FilterConfig config) throws ServletException {
    
        }
    
    }
    
    

    特殊说明:

    不要再在web.xml内添加的配置,这样的话,过滤器会执行两次

    Aop切面实现

    添加pom.xml

    添加spring中的aop和aspects依赖

            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
                <version>3.2.0.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aop</artifactId>
            </dependency>
            <dependency>
                 <groupId>org.aspectj</groupId>
                <artifactId>aspectjrt</artifactId>
                <version>1.6.11</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.6.11</version>
            </dependency>
    

    修改配置文件spring-servlet.xml

    开启aop的自动注解,切记!
    不是applicationContext.xml,是spring-servlet.xml

    <aop:aspectj-autoproxy expose-proxy="true"></aop:aspectj-autoproxy>
    

    aop类实现

    Aspect的多个方法注解中,只有Around注解的方法是有返回值的,可以对方法的入参和返回值均进行操作。
    @Before 在切点方法之前执行
    @After 在切点方法之后执行
    @AfterReturning 切点方法返回后执行
    @AfterThrowing 切点方法抛异常执行
    @Around 属于环绕增强,能控制切点执行前,执行后,,用这个注解后,程序抛异常,会影响@AfterThrowing这个注解

    import com.alibaba.fastjson.JSONObject;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;
    
    /**
     * @author john
     * @date 2019/12/19
     */
    @Component
    @Aspect
    public class StringAop {
    
        @Around(value = "@annotation(org.springframework.web.bind.annotation.RequestMapping)")
        public Object afterDeal(ProceedingJoinPoint joinPoint) throws Throwable {
            Object proceed = joinPoint.proceed();
            System.out.println("方法返回值:" + JSONObject.toJSONString(proceed));
    
            return "修改后的返回值";
        }
    }
    
    

    最后简单说一下拦截器

    有一个思路使用拦截器实现,只简单说一下思路,是否可以后期有时间检验,欢迎大家评论反馈

    import com.edujia.coll.utils.JsonUtils;
    import com.edujia.xcx.portal.redis.RedisXcxTokenManager;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.OutputStream;
    
    public class ByteTokenInterceptor implements HandlerInterceptor {
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object, Exception arg3)
                throws Exception {
    		//拿出前置方法的ResponseWrapper 然后更新response的指向
    		ResponseWrapper mResp= (ResponseWrapper) response;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object arg2, ModelAndView arg3) throws Exception {
    
        }
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    		//这里将response修改为包装类
    		response = new ResponseWrapper(response);
            return true;
        }
    }
    
    

    拦截器的代码仅限参考思路,具体是否可以实现有待检验。

    以上代码如有侵权,请与我联系。

    如需转载,请注明出处和连接。

    展开全文
  • Stream流中的常用方法filter的用法

    万次阅读 2020-08-15 12:51:49
    filter:主要是流的过滤 可以通过filter 方法将一个流转换成另一个子集流。查看方法签名: Stream<T> filter(Predicate<? super T> predicate); 该接口接收一个Predicate 函数式接口参数(可以是一个...
  • java8 .stream().filter() 代替for循环 lambda表达式

    万次阅读 多人点赞 2019-01-17 16:44:05
    先说一下lambda表达式: lambda 表达式的语法格式如下: (parameters) -&gt; expression ... 不需要参数,返回值为 5 () -&gt; 5 // 2. 接收一个参数(数字类型),返回其2倍的值 x -&...
  • Java8-Stream流操作filter

    2021-04-11 00:23:24
    1.遍历数据并检查其中的元素时,可尝试使用Stream中提供的新方法filter,称为filter模式。该模式的核心思想是 保留Stream中的一些元素,而过滤掉其他的。 /** * @Author yqq * @Date 2021/4/11 0:04 * @Version ...
  • Optional firstA= AList.stream() .filter(a -> "小明".equals(a.getUserName())) .findFirst(); 关于Optional,java API中给了解释。 A container object which may or may not contain a non-null value. If a ...
  • 在jdk1.8以后,我们可以使用stream.filter过滤list中的数据,主要作用就是模拟sql查询,从集合中查询想要的数据,不用像之前一样遍历list再筛选。 实例: public class Test { public static void main(String...
  • stream().filter()用法

    2021-07-08 17:38:44
    stream.filter一般适用于list集合,主要作用就是模拟sql查询,从集合中查询想要的数据。filter里面的参数user是指集合里面的每一项 使用时要注意:输出的也是list,且filter中参数类型是list中的参数类型 ...
  • Java学习笔记-stream().filter()

    千次阅读 2020-12-21 21:06:38
    stream().filter(timerTaskDTO -> ("N") .equals(timerTaskDTO.getIsActive())).collect(Collectors.toList());、 作用:查找符合条件的对象集合,所以返回值一定是个List 用法: //在集合中查询用户名...
  • streamfilter、map和flatMap方法

    千次阅读 2018-11-15 21:41:44
    Java核心技术 卷二 filter、map和flatMap方法 流的转换会产生一个新的流,它的元素派生自另一个流中的... longWords = wordList.stream().filter(w-&gt;w.length() &gt; 12); 将所有的单词转化成小写 Stre...
  • Stream流 说到Stream便容易想到I/O Stream,而实际上,谁规定“流”就一定是“IO流”呢?在Java 8中,得益于Lambda所带 来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。 引言 ...
  • Stream

    2019-04-26 14:39:50
    Stream 概念:可以支持顺序和并行对元素操作的元素的集合. ...中间方法: 返回值Stream 终点方法:返回值不是Stream 如何使用Stream? 通过Stream接口的静态工厂方法 通过collection接口的默认方法(常用) 常...
  • 1、lambda表达式特征 ... * 4、可选的返回关键字:如果主体只有一个表达式返回值,那么编译器会自动返回值,大括号需要指明表达式返回了一个数值 * * 1、不需要参数,返回值为5 * () -> 5 * * 2、
  • Stream

    2019-07-26 16:50:53
    文章目录Stream流map方法(映射)filter方法(过滤)forEach方法(遍历)并发流 StreamStream(java.util.stream.Stream)流是集合的一种增强(比如遍历,过滤),Stream方法中必须传入引用类型。   &...
  • 示例2:由于使用了惰性求值,没有输出艺术家的名字 allArtists.stream() .filter(artist -> { System.out.println(artist.getName()); return artist.isFrom("London"); }); 2.及早求值方法 示例:...
  • java8引入了很多新的方法,这篇文章简单介绍一下其中一部分的使用.stream()stream()把一个源数据,可以是集合,数组,I/O channel, 产生器generator 等,转化成流.filter()filter()方法用于通过设置的条件过滤出元素...
  • 1、Stream流 java.util.stream.Stream是Java 8新加入的最常用的流接口(并不是一个函数式接口) 获取一个流非常简单,有以下几种常用的方式: -所有的Collection集合都可以通过stream默认方法获取流; default ...
  • jdk1.8 stream特性

    2019-09-21 15:08:25
    1 filter方法 返回值stream 参数为一个方法,返回true或者false 过滤数据为false的。 2 foreach 方法 返回值void参数方法 单个item ,没有返回值,和for循环类似,效率没有for高。 3 map方法 返回值stream 参数...
  • java 8中stream.filter()的用法

    万次阅读 2019-07-22 16:46:10
    List<VehicleInfo> vehicleInfos = list.stream().filter(a ->100==a.getSysOrgId()).collect(Collectors.toList()); //查询出sysOrgId是100的集合的第一个,返回值是Optional类型 Optional<VehicleInfo> ...
  • http请求返回值修改

    2021-03-09 13:53:11
    正常的请求返回值 { "code":"userCode", "name":"userName" } 添加特异参数的返回值 { "code":"userCode", "name":"userName", "sex":"男", "age":"18", } 如上json返回值,在返回报文中添加特异的...
  • 过滤:filter 映射:map 统计个数:count 取用前几个:limit 跳过前几个:skip 组合:concat 逐一处理:forEach package JDK8.Xin.Demo01Stream; import java.util.stream.Stream; /* Stream流中的常用...
  • 在Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。 Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。 其中关于filter方法:...
  • stream方法获取指向当前Collection对象的流对象,filter将对流中元素进行过滤,结合lambda表达式,需要在filter参数中实现一个类似于比较器的Predicate对象,返回一个boolean类型返回值,只有返回为true的Collection...
  • 1、谓词筛选filter 2、筛选不同的元素distinct 3、截断流limit 4、跳过元素 二、映射 1、map对每个元素应用函数 2、流的扁平化 三、查找和匹配 1、至少匹配一个 2、匹配所有 ...
  • Collection接口改进2.1 forEach2.2 Stream流2.2.1 统计操作 count2.2.2 数据过滤 filter 以及 匹配函数allMatch、anyMatch2.2.3 skip 和 limit 1.MapReduce基础模型 Map:处理数据 Reduce:分析数据 假设我们...
  • Java 8 stream的详细用法

    万次阅读 多人点赞 2019-01-08 23:12:43
    Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 19,930
精华内容 7,972
关键字:

filterstream返回值