Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

# Filter, Interceptor, Aop, 啥, 啥, 啥? 这些都是啥?

发表于 2020-08-22 更新于 2022-06-11 分类于 Java ， Filter ， Interceptor - AOP ， Spring ， Servlet ， Interceptor ， AOP 阅读次数： 阅读次数： Disqus：

本来是想取个像现在那些公众号转了又转的文章标题，”面试官再问你xxxxx，就把这篇文章甩给他看”这种标题，但是觉得实在太 low 了，还是用一部我比较喜欢的电影里的一句台词，《人在囧途》里王宝强对着那张老板给他的欠条，看不懂字时候说的那句，这些都是些啥（第四声）
当我刚开始面 Java 的时候，其实我真的没注意这方面的东西，实话说就是不知道这些是啥，开发中用过 Interceptor和 Aop，了解 aop 的实现原理，但是不知道 Java web 中的 Filter 是怎么回事，知道 dubbo 的 filter，就这样，所以被问到了的确是回答不出来，可能就觉得这个渣渣，这么简单的都不会，所以还是花点时间来看看这个是个啥，为了避免我口吐芬芳，还是耐下性子来简单说下这几个东西
首先是 servlet，怎么去解释这个呢，因为之前是 PHPer，所以比较喜欢用它来举例子，在普通的 PHP 的 web 应用中一般有几部分组成，接受 HTTP 请求的是前置的 nginx 或者 apache，但是这俩玩意都是只能处理静态的请求，远古时代 PHP 和 HTML 混编是通过 apache 的 php module，跟后来 nginx 使用 php-fpm 其实道理类似，就是把请求中需要 PHP 处理的转发给 PHP，在 Java 中呢，是有个比较牛叉的叫 Tomcat 的，它可以把请求转成 servlet，而 servlet 其实就是一种实现了特定接口的 Java 代码，

    package javax.servlet;

    import java.io.IOException;

    /**
     * Defines methods that all servlets must implement.
     *
     * <p>
     * A servlet is a small Java program that runs within a Web server. Servlets
     * receive and respond to requests from Web clients, usually across HTTP, the
     * HyperText Transfer Protocol.
     *
     * <p>
     * To implement this interface, you can write a generic servlet that extends
     * <code>javax.servlet.GenericServlet</code> or an HTTP servlet that extends
     * <code>javax.servlet.http.HttpServlet</code>.
     *
     * <p>
     * This interface defines methods to initialize a servlet, to service requests,
     * and to remove a servlet from the server. These are known as life-cycle
     * methods and are called in the following sequence:
     * <ol>
     * <li>The servlet is constructed, then initialized with the <code>init</code>
     * method.
     * <li>Any calls from clients to the <code>service</code> method are handled.
     * <li>The servlet is taken out of service, then destroyed with the
     * <code>destroy</code> method, then garbage collected and finalized.
     * </ol>
     *
     * <p>
     * In addition to the life-cycle methods, this interface provides the
     * <code>getServletConfig</code> method, which the servlet can use to get any
     * startup information, and the <code>getServletInfo</code> method, which allows
     * the servlet to return basic information about itself, such as author,
     * version, and copyright.
     *
     * @see GenericServlet
     * @see javax.servlet.http.HttpServlet
     */
    public interface Servlet {

        /**
         * Called by the servlet container to indicate to a servlet that the servlet
         * is being placed into service.
         *
         * <p>
         * The servlet container calls the <code>init</code> method exactly once
         * after instantiating the servlet. The <code>init</code> method must
         * complete successfully before the servlet can receive any requests.
         *
         * <p>
         * The servlet container cannot place the servlet into service if the
         * <code>init</code> method
         * <ol>
         * <li>Throws a <code>ServletException</code>
         * <li>Does not return within a time period defined by the Web server
         * </ol>
         *
         *
         * @param config
         *            a <code>ServletConfig</code> object containing the servlet's
         *            configuration and initialization parameters
         *
         * @exception ServletException
         *                if an exception has occurred that interferes with the
         *                servlet's normal operation
         *
         * @see UnavailableException
         * @see #getServletConfig
         */
        public void init(ServletConfig config) throws ServletException;

        /**
         *
         * Returns a {@link ServletConfig} object, which contains initialization and
         * startup parameters for this servlet. The <code>ServletConfig</code>
         * object returned is the one passed to the <code>init</code> method.
         *
         * <p>
         * Implementations of this interface are responsible for storing the
         * <code>ServletConfig</code> object so that this method can return it. The
         * {@link GenericServlet} class, which implements this interface, already
         * does this.
         *
         * @return the <code>ServletConfig</code> object that initializes this
         *         servlet
         *
         * @see #init
         */
        public ServletConfig getServletConfig();

        /**
         * Called by the servlet container to allow the servlet to respond to a
         * request.
         *
         * <p>
         * This method is only called after the servlet's <code>init()</code> method
         * has completed successfully.
         *
         * <p>
         * The status code of the response always should be set for a servlet that
         * throws or sends an error.
         *
         *
         * <p>
         * Servlets typically run inside multithreaded servlet containers that can
         * handle multiple requests concurrently. Developers must be aware to
         * synchronize access to any shared resources such as files, network
         * connections, and as well as the servlet's class and instance variables.
         * More information on multithreaded programming in Java is available in <a
         * href
         * ="http://java.sun.com/Series/Tutorial/java/threads/multithreaded.html">
         * the Java tutorial on multi-threaded programming</a>.
         *
         *
         * @param req
         *            the <code>ServletRequest</code> object that contains the
         *            client's request
         *
         * @param res
         *            the <code>ServletResponse</code> object that contains the
         *            servlet's response
         *
         * @exception ServletException
         *                if an exception occurs that interferes with the servlet's
         *                normal operation
         *
         * @exception IOException
         *                if an input or output exception occurs
         */
        public void service(ServletRequest req, ServletResponse res)
                throws ServletException, IOException;

        /**
         * Returns information about the servlet, such as author, version, and
         * copyright.
         *
         * <p>
         * The string that this method returns should be plain text and not markup
         * of any kind (such as HTML, XML, etc.).
         *
         * @return a <code>String</code> containing servlet information
         */
        public String getServletInfo();

        /**
         * Called by the servlet container to indicate to a servlet that the servlet
         * is being taken out of service. This method is only called once all
         * threads within the servlet's <code>service</code> method have exited or
         * after a timeout period has passed. After the servlet container calls this
         * method, it will not call the <code>service</code> method again on this
         * servlet.
         *
         * <p>
         * This method gives the servlet an opportunity to clean up any resources
         * that are being held (for example, memory, file handles, threads) and make
         * sure that any persistent state is synchronized with the servlet's current
         * state in memory.
         */
        public void destroy();
    }

重点看 servlet 的 service方法，就是接受请求，处理完了给响应，不说细节，不然光 Tomcat 的能说半年，所以呢再进一步去理解，其实就能知道，就是一个先后的问题，盗个图
filter 跟后两者最大的不一样其实是一个基于 servlet，在非常外层做的处理，然后是 interceptor 的 prehandle 跟 posthandle，接着才是我们常规的 aop，就这么点事情，做个小试验吧(还是先补段代码吧)

## Filter

    // ---------------------------------------------------- FilterChain Methods

        /**
         * Invoke the next filter in this chain, passing the specified request
         * and response.  If there are no more filters in this chain, invoke
         * the <code>service()</code> method of the servlet itself.
         *
         * @param request The servlet request we are processing
         * @param response The servlet response we are creating
         *
         * @exception IOException if an input/output error occurs
         * @exception ServletException if a servlet exception occurs
         */
        @Override
        public void doFilter(ServletRequest request, ServletResponse response)
            throws IOException, ServletException {

            if( Globals.IS_SECURITY_ENABLED ) {
                final ServletRequest req = request;
                final ServletResponse res = response;
                try {
                    java.security.AccessController.doPrivileged(
                        new java.security.PrivilegedExceptionAction<Void>() {
                            @Override
                            public Void run()
                                throws ServletException, IOException {
                                internalDoFilter(req,res);
                                return null;
                            }
                        }
                    );
                } catch( PrivilegedActionException pe) {
                    Exception e = pe.getException();
                    if (e instanceof ServletException)
                        throw (ServletException) e;
                    else if (e instanceof IOException)
                        throw (IOException) e;
                    else if (e instanceof RuntimeException)
                        throw (RuntimeException) e;
                    else
                        throw new ServletException(e.getMessage(), e);
                }
            } else {
                internalDoFilter(request,response);
            }
        }
        private void internalDoFilter(ServletRequest request,
                                      ServletResponse response)
            throws IOException, ServletException {

            // Call the next filter if there is one
            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;
            }

            // 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.doFilter(request, response, this);`
是不是看懂了，就是个 filter 链，但是这个代码在哪呢，`org.apache.catalina.core.ApplicationFilterChain#doFilter`
然后是interceptor，

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
            HttpServletRequest processedRequest = request;
            HandlerExecutionChain mappedHandler = null;
            boolean multipartRequestParsed = false;
            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

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

                    try {
                        processedRequest = this.checkMultipart(request);
                        multipartRequestParsed = processedRequest != request;
                        mappedHandler = this.getHandler(processedRequest);
                        if (mappedHandler == null) {
                            this.noHandlerFound(processedRequest, response);
                            return;
                        }

                        HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                        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;
                        }

                        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                        if (asyncManager.isConcurrentHandlingStarted()) {
                            return;
                        }

                        this.applyDefaultViewName(processedRequest, mv);
                        /**
                         * 再看这里看这里‼️
                         */
                        mappedHandler.applyPostHandle(processedRequest, response, mv);
                    } catch (Exception var20) {
                        dispatchException = var20;
                    } catch (Throwable var21) {
                        dispatchException = new NestedServletException("Handler dispatch failed", var21);
                    }

                    this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
                } catch (Exception var22) {
                    this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
                } catch (Throwable var23) {
                    this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
                }

            } finally {
                if (asyncManager.isConcurrentHandlingStarted()) {
                    if (mappedHandler != null) {
                        mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                    }
                } else if (multipartRequestParsed) {
                    this.cleanupMultipart(processedRequest);
                }

            }
        }

代码在哪呢，`org.springframework.web.servlet.DispatcherServlet#doDispatch`，然后才是我们自己写的 aop，是不是差不多明白了，嗯，接下来是例子
写个 filter

    public class DemoFilter extends HttpServlet implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("==>DemoFilter启动");
        }

        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            // 将请求转换成HttpServletRequest 请求
            HttpServletRequest req = (HttpServletRequest) servletRequest;
            HttpServletResponse resp = (HttpServletResponse) servletResponse;
            System.out.println("before filter");
            filterChain.doFilter(req, resp);
            System.out.println("after filter");
        }

        @Override
        public void destroy() {

        }
    }

因为用的springboot，所以就不写 web.xml 了，写个配置类

    @Configuration
    public class FilterConfiguration {
        @Bean
        public FilterRegistrationBean filterDemo4Registration() {
            FilterRegistrationBean registration = new FilterRegistrationBean();
            //注入过滤器
            registration.setFilter(new DemoFilter());
            //拦截规则
            registration.addUrlPatterns("/*");
            //过滤器名称
            registration.setName("DemoFilter");
            //是否自动注册 false 取消Filter的自动注册
            registration.setEnabled(true);
            //过滤器顺序
            registration.setOrder(1);
            return registration;
        }

    }

然后再来个 interceptor 和 aop,以及一个简单的请求处理

    public class DemoInterceptor extends HandlerInterceptorAdapter {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("preHandle test");
            return true;
        }

        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("postHandle test");
        }
    }
    @Aspect
    @Component
    public class DemoAspect {

        @Pointcut("execution( public * com.nicksxs.springbootdemo.demo.DemoController.*())")
        public void point() {

        }

        @Before("point()")
        public void doBefore(){
            System.out.println("==doBefore==");
        }

        @After("point()")
        public void doAfter(){
            System.out.println("==doAfter==");
        }
    }
    @RestController
    public class DemoController {

        @RequestMapping("/hello")
        @ResponseBody
        public String hello() {
            return "hello world";
        }
    }

好了，请求一下，看看 stdout，
搞定完事儿~