# Nicksxs's Blog

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

0%

## mybatis 的 $ 和 # 是有啥区别

Posted on 2020-09-06 In Java , Mybatis , Mysql , Sql注入 , Spring , Mybatis Views: Disqus:

这个问题也是面试中常被问到的，就抽空来了解下这个，跳过一大段前面初始化的逻辑，
对于一条`select * from t1 where id = #{id}`这样的 sql，在初始化扫描 mapper 的xml文件的时候会根据是否是 dynamic 来判断生成 DynamicSqlSource 还是 RawSqlSource，这里它是一条 RawSqlSource，
在这里做了替换，将`#{}`替换成了`?`
前面说的是否 dynamic 就是在这里进行判断

    // org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
    public SqlSource parseScriptNode() {
        MixedSqlNode rootSqlNode = parseDynamicTags(context);
        SqlSource sqlSource;
        if (isDynamic) {
          sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
        } else {
          sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
        }
        return sqlSource;
      }
    // org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseDynamicTags
    protected MixedSqlNode parseDynamicTags(XNode node) {
        List<SqlNode> contents = new ArrayList<>();
        NodeList children = node.getNode().getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
          XNode child = node.newXNode(children.item(i));
          if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
            String data = child.getStringBody("");
            TextSqlNode textSqlNode = new TextSqlNode(data);
            if (textSqlNode.isDynamic()) {
              contents.add(textSqlNode);
              isDynamic = true;
            } else {
              contents.add(new StaticTextSqlNode(data));
            }
          } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
            String nodeName = child.getNode().getNodeName();
            NodeHandler handler = nodeHandlerMap.get(nodeName);
            if (handler == null) {
              throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
            }
            handler.handleNode(child, contents);
            isDynamic = true;
          }
        }
        return new MixedSqlNode(contents);
      }
    // org.apache.ibatis.scripting.xmltags.TextSqlNode#isDynamic
      public boolean isDynamic() {
        DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
        GenericTokenParser parser = createParser(checker);
        parser.parse(text);
        return checker.isDynamic();
      }
        private GenericTokenParser createParser(TokenHandler handler) {
        return new GenericTokenParser("${", "}", handler);
      }

可以看到其中一个条件就是是否有`${}`这种占位符，假如说上面的 sql 换成 `${}`，那么可以看到它会在这里创建一个 dynamicSqlSource,

    // org.apache.ibatis.scripting.xmltags.DynamicSqlSource
    public class DynamicSqlSource implements SqlSource {

      private final Configuration configuration;
      private final SqlNode rootSqlNode;

      public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
        this.configuration = configuration;
        this.rootSqlNode = rootSqlNode;
      }

      @Override
      public BoundSql getBoundSql(Object parameterObject) {
        DynamicContext context = new DynamicContext(configuration, parameterObject);
        rootSqlNode.apply(context);
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        context.getBindings().forEach(boundSql::setAdditionalParameter);
        return boundSql;
      }

    }

这里眼尖的同学可能就可以看出来了，RawSqlSource 在初始化的时候已经经过了 parse，把`#{}`替换成了`?`占位符，但是 DynamicSqlSource 并没有
再看这个图，我们发现在这的时候还没有进行替换
然后往里跟
好像是这里了
这里 rootSqlNode.apply 其实是一个对原来 sql 的解析结果的一个循环调用，不同类型的标签会构成不同的 node，像这里就是一个 textSqlNode
可以发现到这我们的 sql 已经被替换了，而且是直接作为 string 类型替换的，所以可以明白了这个问题所在，就是注入，不过细心的同学发现其实这里是有个
理论上还是可以做过滤的，不过好像现在没用起来。
我们前面可以发现对于`#{}`是在启动扫描 mapper的 xml 文件就替换成了 `?`,然后是在什么时候变成实际的值的呢
发现到这的时候还是没有替换，其实说白了也就是 prepareStatement 那一套，
在这里进行替换，会拿到 org.apache.ibatis.mapping.ParameterMapping，然后进行替换，因为会带着类型信息，所以不用担心注入咯

## 这周末我又在老丈人家打了天小工

Posted on 2020-08-30 In 生活 , 运动 , 跑步 , 干活 Views: Disqus:

因为活实在比较多，也不太好叫大工(活比较杂散)，相比上一次我跟 LD 俩人晚起了一点，我真的是只要有事，早上就醒的很早，准备八点出发的，六点就醒了，然后想继续睡就一直做梦🤦‍♂️，差不多八点半多到的丈人家，他们应该已经干了有一会了，我们到了以后就分配给我撬地板的活，上次说的那个敲掉柜子的房间里，还铺着质地还不错的木地板，但是也不想要了，得撬掉重新铺。
拿着撬棍和榔头就上楼去干了，浙江这几天的天气，最高温度一般 38、9，楼上那个房间也没风扇，有了也不能用，都是灰尘，撬了两下，我感觉我体内的水就像真气爆发一样变成汗炸了出来，眼睛全被汗糊住了，可能大部分人不太了解地板是怎么铺的，一般是在地面先铺一层混凝土，混凝土中间嵌进去规则的长条木条，然后真正的地板一块块的都是钉在那个木条上，用那种气枪钉和普通的钉子，并且块跟块之前还有一个木头的槽结构相互耦合，然后边缘的一圈在用较薄的木板将整个木地板封边(这些词都是我现造的)，边缘的用的钉子会更多，所以那几下真的很用力，而且撬地板，得蹲下起来，如此反复，对于我这个体重快超过身高的中年人来说的确是非常大的挑战，接下来继续撬了几个，已经有种要虚脱晕倒的感觉了，及时去喝水擦了汗，又歇了一会，为啥一上来就这么拼呢，主要是因为那个房间丈人在干活的时候是直接看得到的🤦‍♂️，后来被 LD 一顿教育，本来就是去帮忙的，又不是专业做这个的，急啥。
喝了水之后，又稍稍歇了一会，就开始继续撬了，本来觉得这个地板撬着好像还行，房间不大，没多久就撬完了，撬完之后喝了点饮料(补充点糖分，早餐吃得少，有点低血糖)，然后看到 LD 在撬下面的木条了，这个动作开始了那天最大的经验值收集行动，前面说了这个木条一般是跟混凝土一块铺上去的，但是谁也没想到，这个混凝土铺上去的时候竟然处理的这么随意，根本没考虑跟下面的贴合，所以撬木条的时候直接把木条跟木条中间大块大块的混凝土一块撬起来了，想想那重量，于是我这靠蛮力干活的，就用力把木条带着混凝土一块撬了起来，还沾沾自喜，但是发现结果是撬起来一块之后，体力值瞬间归零，上一篇我也提到了，其实干这类活也是很有技巧性的，但是上次的是我没学会，可能需要花时间学的，但是这次是LD 用她的纤细胳膊教会我的，我在撬的时候，屏住一口气，双手用力，起，大概是吃好几口奶的力气都用出来了，但是 LD 在我休息的时候，慢慢悠悠的，先把撬棍挤到木条或者混凝土跟下层的缝里，然后往下垫一小块混凝土碎石，然后轻轻松松的扳两下，就撬开了，亏我高中的时候引以为傲的物理成绩，作为物理课代表，这么浅显易懂的杠杆原理都完全不会用到生活里，后面在用这个技巧撬的过程中，真的觉得自己蠢到家了，当然在明白了用点杠杆原理之后，撬地板的活就变得慢慢悠悠，悠哉悠哉的了(其实还是很热的，披着毛巾擦眼睛)。
上午的活差不多完了，后面就是把撬出来的混凝土和地板条丢下去，地上铺着不用了的被子，然后就是午饭和午休环节了，午饭换了一家快餐，味道非常可以，下午的活就比较单调了，帮忙清理了上去扔下来的混凝土碎块跟木条，然后稍微打扫了下，老丈人就让我们回家了，接着上次说的，还是觉得比跑步啥的消耗大太多了，那汗流的，一口就能喝完一瓶 500 毫升左右的矿泉水。

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

Posted on 2020-08-22 In Java , Spring , Filter , Interceptor - AOP , Servlet , Interceptor , AOP Views: 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，
搞定完事儿~

## 周末我在老丈人家打了天小工

Posted on 2020-08-16 In 生活 , 运动 , 跑步 , 干活 Views: Disqus:

这周回家提前约好了要去老丈人家帮下忙，因为在翻修下老房子，活不是特别整的那种，所以大部分都是自己干，或者找个大工临时干几天（我们这那种比较专业的泥工匠叫做大工），像我这样去帮忙的，就是干点小工（把给大工帮忙的，干些偏体力活的叫做小工）的活。从大学毕业以后真的蛮少帮家里干活了，以前上学的时候放假还是帮家里淘个米，简单的扫地拖地啥的，当然刚高考完的时候，还去我爸厂里帮忙干了几天的活，实在是比较累，不过现在想着是觉得自己那时候比较牛，而不是特别排斥这个活，相对于现在的工作来说，导致了一系列的职业病，颈椎腰背都很僵硬，眼镜也不好，还有反流，像我爸那种活反而是脑力加体力的比较好的结合。
这一天的活前半部分主要是在清理厨房，瓷砖上的油污和墙上天花板上即将脱落的石灰或者白色涂料层，这种活特别是瓷砖上的油污，之前在自己家里也干活，还是比较熟悉的，不过前面主要是LD 在干，我主要是先搞墙上和天花板上的，干活还是很需要技巧的，如果直接去铲，那基本我会变成一个灰人，而且吸一鼻子灰，老丈人比较专业，先接上软管用水冲，一冲效果特别好，有些石灰涂料层直接就冲掉了，冲完之后先用带加长杆的刀片铲铲了一圈墙面，说实话因为老房子之前租出去了，所以墙面什么的被糟蹋的比较难看，一层一层的，不过这还算还好，后面主要是天花板上的，这可难倒我了，从小我爸妈是比较把我当小孩管着，爬上爬下的基本都是我爸搞定，但是到了老丈人家也只得硬着头皮上了，爬到跳（一种建筑工地用的架子）上，还有点晃，小心脏扑通扑通跳，而且带加长杆的铲子还是比较重的，铲一会手也有点累，不过坚持着铲完了，上面还是比较平整的，不过下来的时候又把我难住了🤦‍♂️，往下爬的时候有根杆子要跨过去，由于裤子比较紧，强行一把跨过去怕抽筋，所以以一个非常尴尬的姿势停留休息了一会，再跨了过去，幸好事后问 LD，他们都没看到，哈哈哈，然后就是帮忙一起搞瓷砖上的油污，这个太有经验了，不过老丈人更有意思，一会试试啤酒，一会用用沙子，后面在午饭前基本就弄的比较干净了，就坐着等吃饭了，下午午休了会，就继续干活了。
下午是我这次体验的重点了，因为要清理以前贴的墙纸，真的是个很麻烦的活，只能说贴墙纸的师傅活干得太好了，基本不可能整个撕下来，想用铲子一点点铲下来也不行，太轻了就只铲掉表面一层，太重了就把墙纸跟墙面的石灰啥的整个铲下来了，而且手又累又酸，后来想着是不是继续用水冲一下，对着一小面墙试验了下，效果还不错，但是又发现了个问题，那一面墙又有一块是后面糊上去的，铲掉外层的石灰后不平，然后就是最最重头的，也是让我后遗症持续到第二天的，要把那一块糊上去的水泥敲下来，毛估下大概是敲了80%左右，剩下的我的手已经不会用力了，因为那一块应该是要糊上去的始作俑者，就一块里面凹进去的，我拿着榔头敲到我手已经没法使劲了，而且大下午，感觉没五分钟，我的汗已经糊满脸，眼睛也睁不开，不然就流到眼睛里了，此处获得成就一：用榔头敲墙壁，也是个技术加体力的活，而且需要非常好的技巧，否则手马上就废了，敲下去的反作用力，没一会就不行了，然后是看着老丈人兄弟帮忙拆一个柜子，在我看来是个几天都搞不定的活，他轻轻松松在我敲墙的那会就搞定了，以前总觉得我干的活非常有技术含量，可是这个事情真的也是很有技巧啊，它是个把一间房间分隔开的柜子，从底到顶上，还带着门，我还在旁边帮忙撬一下脚踢，一根木条撬半天，唉，成就二：专业的人就是不一样。
最后就是成就三了：我之前沾沾自喜的跑了多少步，做了什么锻炼，其实都是渣渣，像这样干一天活，没经历过的，基本大半天就废了，反过来说，如果能经常去这么干一天活，跑步啥的都是渣渣，消耗的能量远远超过跑个十公里啥的。

## Linux 下 grep 命令的一点小技巧

Posted on 2020-08-06 In Linux , 命令 , grep , 小技巧 , grep , 查日志 Views: Disqus:

用了比较久的 grep 命令，其实都只是用了最最基本的功能来查日志，

譬如

    grep 'xxx' xxxx.log

然后有挺多情况比如想要找日志里带一些符号什么的，就需要用到一些特殊的

比如这样`\"userId\":\"123456\"`，因为比如用户 ID 有时候会跟其他的 id 一样，只用具体的值 123456 来查的话干扰信息太多了，如果直接这样

    grep '\"userId\":\"123456\"' xxxx.log

好像不行，盲猜就是符号的问题，特别是`\`和`"`这两个，

之前一直是想试一下，但是没成功，昨天在排查一个问题的时候发现了，只要把这些都转义了就行了

`grep '\\\"userId\\\":\\\"123456\\\"' xxxx.log`