呵呵虫
体育竞猜区
级别: 竞猜管理组

UID: 109
精华: 0
发帖: 3856
威望: 64246
霏币: 87421.4
活跃度: 3902
技术分: 81
非凡币: 3581
交易值: 0
在线时间: 1876(小时)
注册时间: 2012-01-01
最后登录: 2018-10-18
楼主  发表于: 2018-02-07 09:25:03

【其他交流】 hehec的胡言乱语

管理提醒: 本帖被 thinker 执行提前操作(2018-02-08)
告别码农十余年了,由于工作需要必须将这些东西重新捡起来

以此帖记录一下重新回炉学习的点点滴滴
本帖最近评分记录:
  • 技术分:+22(virus1999) 您的帖子很精彩,期待您的下一贴!
  • 技术分:+8(virus1999) 您的帖子很精彩,期待您的下一贴!
  • 霏币:+100(thinker) 您的帖子很精彩,期待您的下一贴!
  • 为了而努力
    呵呵虫
    体育竞猜区
    级别: 竞猜管理组

    UID: 109
    精华: 0
    发帖: 3856
    威望: 64246
    霏币: 87421.4
    活跃度: 3902
    技术分: 81
    非凡币: 3581
    交易值: 0
    在线时间: 1876(小时)
    注册时间: 2012-01-01
    最后登录: 2018-10-18
    1楼  发表于: 2018-02-07 09:49:32
    log4j这个开源的记录日志的工具相信是个做java编程的人都用过,通过配置properties(或者xml文件)就可以轻松实现log参数的配置
    而且网上关于配置文件的例子也是数不胜数,基本快要被写烂貌似没有什么好写的。

    最近做的一个工具有个特殊的需求,需要程序根据程序的执行将log动态的写入某个指定的目录。例如程序执行多个实例,需要将log文件放入以该实例+日期命名的文件夹内
    这种scenario使用传统的配置文件的方法肯定是touch不到了,因为配置文件中的文件夹是固定的或者没有这么灵活。

    so。。。。有了一个大胆的想法,在程序里动态为每个实例生成对应的logger就可以解决
    但是网上关于log4j的教程基本都是与配置文件或者都是log4j的框架有关,有关配置文件动态化的教程简直是凤毛麟角
    没办法通过stackflow查找以及自己阅读log4j的源代码,总算码出来一个还算能用的东西
    代码如下:
    复制代码
    1. public static LoggerContext createLoggerCtx(String loggername, String filePath) {
    2.         LoggerContext ctx = (LoggerContext)LogManager.getContext(false);
    3.         Configuration config = ctx.getConfiguration();
    4.         
    5.         //creates a new pattern layout (what determines how the log is formated, i.e. date, thread etc.)
    6.         Layout layout = PatternLayout.createLayout("%d %-5p %-12C{1} - %m%n", null, config,  null, null, true,
    7.                 false, null, null);
    8.         //creates a file appender for the logger (so that it knows what file to log to)
    9.         String filename_error = filePath + File.separator + "error.log";
    10.         FileAppender fileAppender_error = FileAppender.createAppender(filename_error, "true",
    11.                 "false", "file_error", "true", "false", "true", "4000", layout, null, "false", null, config);
    12.         ThresholdFilter filter = ThresholdFilter.createFilter(Level.ERROR, Result.ACCEPT, Result.DENY);
    13.         fileAppender_error.addFilter(filter);
    14.         fileAppender_error.start();
    15.         config.addAppender(fileAppender_error);
    16.         
    17.         String filename_info = filePath + File.separator + "info.log";
    18.         FileAppender fileAppender_info = FileAppender.createAppender(filename_info, "true",
    19.                 "false", "file_info", "true", "false", "true", "4000", layout, null, "false", null, config);
    20.         ThresholdFilter filter1 = ThresholdFilter.createFilter(Level.INFO, Result.ACCEPT, Result.DENY);
    21.         fileAppender_info.addFilter(filter1);
    22.         fileAppender_info.start();
    23.         config.addAppender(fileAppender_info);
    24.         
    25.         String filename_debug = filePath + File.separator + "debug.log";
    26.         FileAppender fileAppender_debug = FileAppender.createAppender(filename_debug, "true",
    27.                 "false", "file_debug", "true", "false", "true", "4000", layout, null, "false", null, config);
    28.         ThresholdFilter filter2 = ThresholdFilter.createFilter(Level.DEBUG, Result.ACCEPT, Result.DENY);
    29.         fileAppender_debug.addFilter(filter2);
    30.         fileAppender_debug.start();
    31.         config.addAppender(fileAppender_debug);
    32.         //creates also a console appender for the logger (so that the logger also outputs the log in the console)
    33.         Appender consoleAppender = ConsoleAppender.createAppender(layout, null, "SYSTEM_OUT", "console", null, null);
    34.         consoleAppender.start();
    35.         config.addAppender(consoleAppender);
    36.         //adds appenders to an array called refs. It will later serve as references to the logger as to what
    37.         AppenderRef fileRef_error = AppenderRef.createAppenderRef("file_error", Level.DEBUG, null);
    38.         AppenderRef fileRef_info = AppenderRef.createAppenderRef("file_info", Level.DEBUG, null);
    39.         AppenderRef fileRef_debug = AppenderRef.createAppenderRef("file_debug", Level.DEBUG, null);
    40.         AppenderRef consoleRef = AppenderRef.createAppenderRef("console", Level.DEBUG, null);
    41.         AppenderRef[] refs = new AppenderRef[]{fileRef_error, fileRef_info, fileRef_debug, consoleRef};
    42.         //creates the logger configurations for the logger, where the appender-references are also added
    43.         LoggerConfig loggerConfig = LoggerConfig.createLogger("false", Level.DEBUG, loggername,
    44.                 "true", refs, null, config, null);
    45.         loggerConfig.addAppender(fileAppender_error, Level.DEBUG, null);
    46.         loggerConfig.addAppender(fileAppender_info, Level.DEBUG, null);
    47.         loggerConfig.addAppender(fileAppender_debug, Level.DEBUG, null);
    48.         loggerConfig.addAppender(consoleAppender, Level.DEBUG, null);
    49.         
    50.         //filter some logs from 3rd package from apache
    51.         LoggerConfig filter_loggerConfig = LoggerConfig.createLogger("false", Level.ERROR, "org.apache",
    52.                 "true", new AppenderRef[] {consoleRef}, null, config, null);        
    53.         //finally creates the ctx and returns it
    54.         config.addLogger(loggername, loggerConfig);
    55.         config.addLogger("org.apache", filter_loggerConfig);
    56.         ctx.updateLoggers();
    57.         return ctx;
    58.     }


    思路就是初始化一个loggercontext,修改它的configuration还是老三板斧
    1. 生成layout  2. 生成appender   3. 对应的reference list
    最后将其更新到context即可,当然最后还需要过滤一些不需要的log,例如引用的第三方的jar包打印的log
    如果需要一些回滚的策略可以自己定制

    使用方法如下:
    loggerCtx = logUtil.createLoggerCtx("hehec", testPath);
    Logger logger = loggerCtx.getLogger("hehec");

    是不是很简单呢,以为事情就这样结束了,但是的但是还有个趟过的坑坑需要说明一下:
    程序执行过程中会发现虽然日志的目录和日志文件已经生成,但是里面的log是空的,再仔细观察发现log都写到上个实例的log文件中了
    这个是咩回事呢?
    原来logger的IO流如果不关闭的话会仍然指向之前的文件不会指向新生成的log文件,知道原因解决起来就容易多了,写个函数把它关闭就是了。
    复制代码
    1.     public static void consumeCtx(LoggerContext loggerCtx) {
    2.         Map<String, Appender> reflist = loggerCtx.getConfiguration().getAppenders();
    3.         for (String key: reflist.keySet()) {
    4.             Appender appender = reflist.get(key);
    5.             appender.stop();
    6.         }
    7.         loggerCtx.close();
    8.     }


    好了到这里就可以高枕无忧,愉快的使用log4j来玩耍了
    呵呵虫
    体育竞猜区
    级别: 竞猜管理组

    UID: 109
    精华: 0
    发帖: 3856
    威望: 64246
    霏币: 87421.4
    活跃度: 3902
    技术分: 81
    非凡币: 3581
    交易值: 0
    在线时间: 1876(小时)
    注册时间: 2012-01-01
    最后登录: 2018-10-18
    2楼  发表于: 2018-02-08 08:58:11
    十年的时间没想到技术发展如此之快,出现了各种各样的新名词,该如何下口学习啊呢
    前端:jquery,angularjs
    持久层:mybatis,hibernate
    Spring框架、nginx web服务、maven构建,git版本控制
    regdis和MongoDB数据库
    中间件技术
    网络开发netty
    比较火的方向:人工智能、机器学习
    清单先列出来,学不学根据项目需要在说吧
    呵呵虫
    体育竞猜区
    级别: 竞猜管理组

    UID: 109
    精华: 0
    发帖: 3856
    威望: 64246
    霏币: 87421.4
    活跃度: 3902
    技术分: 81
    非凡币: 3581
    交易值: 0
    在线时间: 1876(小时)
    注册时间: 2012-01-01
    最后登录: 2018-10-18
    3楼  发表于: 2018-02-08 09:28:28
    近期的一个项目需要实现向一个组件发送http消息(restAPI消息),于是乎想起了apache的一个开源包httpclient

    使用起来也不要太简单,也是三板斧:
    初始化一个httpclient:private CloseableHttpClient httpclient = HttpClients.createDefault();
    初始化一个httprequest(get、put、post、delete等),并设置header,cookie和消息体
    最后执行:CloseableHttpResponse response = httpclient.execute(post);

    那随着项目的深入,显然这三板斧行不通了:
    1. 要支持https怎么办
    2. http执行过程中出现一些异常需要重传怎么办

    那么这里就需要对httpclient本身下功夫了,让初始化后的httpclient实例具备https和重传功能
    复制代码
    1.     private static SSLConnectionSocketFactory sslsf = null;
    2.     private static PoolingHttpClientConnectionManager cm = null;
    3.     private static SSLContextBuilder builder = null;
    4.     private static final String HTTP = "http";
    5.     private static final String HTTPS = "https";
    6.     static {
    7.         try {
    8.             builder = new SSLContextBuilder();
    9.             // 全部信任 不做身份鉴定
    10.             builder.loadTrustMaterial(null, new TrustStrategy() {
    11.                 @Override
    12.                 public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
    13.                     return true;
    14.                 }
    15.             });
    16.             sslsf = new SSLConnectionSocketFactory(builder.build(), new String[]{"SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.2"}, null, NoopHostnameVerifier.INSTANCE);
    17.             Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
    18.                     .register(HTTP, new PlainConnectionSocketFactory())
    19.                     .register(HTTPS, sslsf)
    20.                     .build();
    21.             cm = new PoolingHttpClientConnectionManager(registry);
    22.             cm.setMaxTotal(200);//max connection
    23.         } catch (Exception e) {
    24.             e.printStackTrace();
    25.         }
    26.     }
    27.     public static CloseableHttpClient createHttpclient() {
    28.         HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
    29.             @Override
    30.             public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
    31.                 if (executionCount > 3) {
    32.                     // Do not retry if over max retry count
    33.                     return false;
    34.                 }
    35.                 if (exception instanceof InterruptedIOException) {
    36.                     // An input or output transfer has been terminated
    37.                     return false;
    38.                 }
    39.                 if (exception instanceof UnknownHostException) {
    40.                     // Unknown host 修改代码让不识别主机时重试,实际业务当不识别的时候不应该重试,再次为了演示重试过程,执行会显示retryCount次下面的输出
    41.                     System.out.println("不识别主机重试");
    42.                     return true;
    43.                 }
    44.                 if (exception instanceof ConnectException) {
    45.                     // Connection refused
    46.                     return true;
    47.                 }
    48.                 if (exception instanceof ConnectionClosedException) {
    49.                     return true;
    50.                 }
    51.                 if (exception instanceof SSLException) {
    52.                     // SSL handshake exception
    53.                     return false;
    54.                 }
    55.                 
    56.                 if (exception instanceof NoHttpResponseException) {
    57.                     // SSL handshake exception
    58.                     return true;
    59.                 }
    60.                 
    61.                 HttpClientContext clientContext = HttpClientContext.adapt(context);
    62.                 HttpRequest request = clientContext.getRequest();
    63.                 boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
    64.                 if (idempotent) {
    65.                     // Retry if the request is considered idempotent
    66.                     return true;
    67.                 }
    68.                 return false;
    69.             }
    70.         };
    71.         CloseableHttpClient client = HttpClients.custom().setRetryHandler(httpRequestRetryHandler)
    72.                 .setSSLSocketFactory(sslsf)
    73.                 .setConnectionManager(cm)
    74.                 .setConnectionManagerShared(true).build();
    75.         return client;
    76.     }


    思路就是先将https的支持以及重传策略的支持初始化,然后初始化httpclient时对其赋值配置即可。
    重传策略需要对retryRequest方法进行重写,可以结合自己的项目特点来编写重传策略
    例如目前的项目中,经常出现连接异常中断的exception,因此对其父类ConnectException以及ConnectionClosedException进行处理

    Httpclient开源工具现在已经到了4.5了,jar也根据模块独立出来了
    项目经常用到的jar包为httpclient和httpcore,同时项目中还用到了httpmime包
    呵呵虫
    体育竞猜区
    级别: 竞猜管理组

    UID: 109
    精华: 0
    发帖: 3856
    威望: 64246
    霏币: 87421.4
    活跃度: 3902
    技术分: 81
    非凡币: 3581
    交易值: 0
    在线时间: 1876(小时)
    注册时间: 2012-01-01
    最后登录: 2018-10-18
    4楼  发表于: 2018-02-09 11:07:50
    java IO流小结:
    以前涉及到java IO操作的时候,大多数都是文件IO操作时基本都使用第三方jar包了,例如Apache的common包,简单方便易用
    近期的一个项目由于项目需要需要封装一些自己的IO操作,于是翻了一下网上的教程以及java的代码,讲IO这块的来龙去脉搞清楚了一些,顺便做个小结:
    引用网上一张图:


    如下知识点:
    1. java的IO流分为字节流和字符流,继承关系如上图,上图同时列出了一些比较常用的Java IO类
    2. 顾名思义以InputStream和OutPutStream为父类字节流,其操作对象为byte或者byte[],以Reader或者Writer为父类的字节流类操作的对象为char、char[]甚至String对象。
    3. java流的来源包括文件、数据、线程间的管道以及网络IO流
    4. 虽然我们使用使用字符流类对文件或者其它IO流来源进行字符或者String级别的操作,其实是java自动帮你将字符流转为字节流了
    5. java的IO编程其实就是根据程序的需要去定制或者引用所需要的IO类,针对文件、数据、还是byteArray还是String等等。万变不离其宗的其实就是read和write两个方法,就看怎么去读或者写了。
    6. 根据第5条编程的时候到底使用哪个IO类呢,关键还是看自己的需要,例如在做网络编程的时候需要读取并解析网络字节流时,DataInputStream类已经实现了一些方法可以让我们轻松的把里面的数据读取出来,例如封装了readShort(),readChar(),readInt(),readLong(),readDouble,readFloat,,甚至readLine()方法,是不是很惊喜。
    这些方法的实现原理其实还是针对字节做操作,真不过帮我们封装了移位的操作,例如readInt()方法的源码如下:
    复制代码
    1.     public final int readInt() throws IOException {
    2.         int ch1 = in.read();
    3.         int ch2 = in.read();
    4.         int ch3 = in.read();
    5.         int ch4 = in.read();
    6.         if ((ch1 | ch2 | ch3 | ch4) < 0)
    7.             throw new EOFException();
    8.         return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
    9.     }

    7. 接着第4条,InputStream和InputStreamReader的关系其实就是讲InputStream的字节流按照指定的字符集进行编码进行字符的操作的一个过程(OutputStream和Writer类似)
    如果不明白可以看下图:(画的比较丑看看就好)


    在初始化一个InputStreamReader时,其实就是生成了一个按照指定字符集进行编码的StreamDecoder,InputStreamReader进行操作时其实就是调用StreamDecoder的方法。
    贴上这个类的部门代码加深印象:
    复制代码
    1.     public InputStreamReader(InputStream in, String charsetName)
    2.         throws UnsupportedEncodingException
    3.     {
    4.         super(in);
    5.         if (charsetName == null)
    6.             throw new NullPointerException("charsetName");
    7.         sd = StreamDecoder.forInputStreamReader(in, this, charsetName);
    8.     }
    9.     public int read(char cbuf[], int offset, int length) throws IOException {
    10.         return sd.read(cbuf, offset, length);
    11.     }

    8. 那么什么时候使用字节流、什么时候使用字节流呢,答案还是根据自己的需要来,IO流的来龙去脉搞清楚了这些都不是事。
    9. Reader的IO类里有一个BufferedReader,可以进行readLine()操作哟,将IO流转直接转成String进行行处理的,这个类可以优先考虑。
    10. 做网络IO流的时候需要注意一下,有时候使用字符流时可能会阻塞,所以还是老老实实的使用字节流把。
    举个例子:最想项目需要使用ssh登录到远端服务器执行shell命令,我使用了第三方的jsch工具来实现,其中有个ChannelShell类负责发送shell命令并接受回显。
    如果回显的内容使用BufferedReader进行接受那么就会阻塞,但是直接进行字节流的读取就是ok的,至于原因晚上一大堆,可以自行百度。
    11. 最后贴上个小福利,用来实现InputStream和OutputStream的互转(其实也是网上找的)
    复制代码
    1.     //inputStream转outputStream
    2.     public static ByteArrayOutputStream parse(InputStream in) throws IOException
    3.     {
    4.         ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
    5.         int ch;
    6.         while ((ch = in.read()) != -1) {  
    7.             swapStream.write(ch);  
    8.         }
    9.         return swapStream;
    10.     }
    11.     //outputStream转inputStream
    12.     public static ByteArrayInputStream parse(OutputStream out)
    13.     {
    14.         ByteArrayOutputStream   baos=new   ByteArrayOutputStream();
    15.         baos=(ByteArrayOutputStream) out;
    16.         ByteArrayInputStream swapStream = new ByteArrayInputStream(baos.toByteArray());
    17.         return swapStream;
    18.     }
    级别: 新手上路
    UID: 234599
    精华: 0
    发帖: 116
    威望: 89
    霏币: 459
    活跃度: 86
    技术分: 0
    非凡币: 0
    交易值: 0
    在线时间: 5(小时)
    注册时间: 2018-02-09
    最后登录: 2018-04-17
    5楼  发表于: 2018-02-10 19:24:57
    对于大神,我只能膜拜,代码直接看不懂
    呵呵虫
    体育竞猜区
    级别: 竞猜管理组

    UID: 109
    精华: 0
    发帖: 3856
    威望: 64246
    霏币: 87421.4
    活跃度: 3902
    技术分: 81
    非凡币: 3581
    交易值: 0
    在线时间: 1876(小时)
    注册时间: 2012-01-01
    最后登录: 2018-10-18
    6楼  发表于: 2018-02-11 14:18:36
    关于实现java上传下载文件时进度监测的总结:
    在实际项目中涉及到文件的上传和下载,尤其是大文件的上传和下载时,进度的监测也是必须的(不然心里总是慌慌的,程度到底是死了还是阻塞了还是跑偏了)
    进度监测时有的开源工具已经有了进度监测的接口只需要去实现即可,有的则需要自己去实现了。
    例子1:
    使用jsch做sftp的上传和下载时,它的ChannelSftp类提供了不少灰常简单易用的文件上传和下载的方法


    在其get和put方法中明确告诉开发人员,你可以使用我的SftpProgressMonitor来实现进度监测,打开看看里面都是些神马
    复制代码
    1. public interface SftpProgressMonitor{
    2.   public static final int PUT=0;
    3.   public static final int GET=1;
    4.   public static final long UNKNOWN_SIZE = -1L;
    5.   void init(int op, String src, String dest, long max);
    6.   boolean count(long count);
    7.   void end();
    8. }

    原来里面定义了get和put两个方法,同时定义了初始化、计数和结束三个方法
    初始化的几个参数应该分别为get、put方法,原路径、目标路径和源文件的大小(为后续计数方便)
    下面来分析get(String src, String dst, SftpProgressMonitor monitor)这个方法的源码看看SftpProgressMonitor是怎么调用的。
    在各种参数校验后,get方法中对调用初始化函数
    复制代码
    1.     if(monitor!=null){
    2.       monitor.init(SftpProgressMonitor.GET, _src, _dst, attr.getSize());
    3.       if(mode==RESUME){
    4.         monitor.count(_dstFile.length());
    5.       }
    6.     }

    在文件传输过程中,count方法的调用如下:


    大概意思就是调用方法记录本次传输的字节大小,程序会根据监听器的返回值一个false那么应该忽略剩余文件的传输了
    这里就给了我们很大的操作空间,例如在count方法中如果出现某些错误或者人工取消的话就可以取消本次传输了。

    下面的代码实现一个非常简单的监控,统计已传输的文件,并计算传输百分比,思路就是创建一个SftpProgressMonitor ,并实现该接口的三个方法即可。
    复制代码
    1.     private SftpProgressMonitor createSftpProgressMonitor() {
    2.         return new SftpProgressMonitor() {
    3.             private long total = 0;     //文件总大小
    4.             private long transfered = 0;   //已传输的大小
    5.             private long start = 0;
    6.             
    7.             @Override
    8.             public void init(int op, String src, String dst, long max) {
    9.                 this.total = max;
    10.                 logger.info(String.format("SftpProgressMonitor init called. : (op, src, dst, max)=(%d, %s, %s, %d)",  
    11.                         op, src, dst, max));
    12.             }
    13.             @Override
    14.             public boolean count(long count) {
    15.                 transfered = transfered + count;
    16.                 long percent = 100*transfered/total;
    17.                 //大文件传输过程中会频繁调用count方法,所以这里每隔5s打印一次即可
    18.                 if (System.currentTimeMillis() - start >= 5000) {
    19.                     logger.info(String.format("SftpProgressMonitor count called, count:%d,transfered:%d,percent:%d", count, transfered, percent));
    20.                     //重新计时
    21.                     start = System.currentTimeMillis();
    22.                 }
    23.                 return true;
    24.             }
    25.             @Override
    26.             public void end() {
    27.                 logger.info(String.format("SftpProgressMonitor end called, total transfered:%d, total size:%d", transfered, total));
    28.             }
    29.         };
    30.     }

    下图是上述代码在传输一个1.5G的文件时的调试结果:


    最后附上这个开源工具ChannelSftp类的源代码
    [upload=4]

    休息一下,稍后会介绍没有自定义listener去监控下载
    呵呵虫
    体育竞猜区
    级别: 竞猜管理组

    UID: 109
    精华: 0
    发帖: 3856
    威望: 64246
    霏币: 87421.4
    活跃度: 3902
    技术分: 81
    非凡币: 3581
    交易值: 0
    在线时间: 1876(小时)
    注册时间: 2012-01-01
    最后登录: 2018-10-18
    7楼  发表于: 2018-02-12 11:39:57
    上面的例子中,第三方工具jsch包本身提供了一个monitor接口供我们去使用,那么如果第三方工具包没有提供这样的接口该怎么办呢?
    先请上我们的主角apache的http component(即常用的apache httpclient),这个包的普及度就不用多提了
    平时我们使用这个包上传一个文件到指定的web server基本都是这么写的:
    复制代码
    1.        String url = "http://XXXXXX";
    2.     HttpPost post = new HttpPost(url);
    3.     post.setHeader("XXXXX", "xxxxxx");
    4.     FileBody fileBody = new FileBody(new File(filename), "xxxxxx");  
    5.     MultipartEntity entity = new MultipartEntity();  
    6.         entity.addPart("file", fileBody);
    7.         post.setEntity(entity);
    8.         try {
    9.             CloseableHttpResponse response = httpclient.execute(post);
    10.             String rspbody = EntityUtils.toString(response.getEntity());
    11.             int status = response.getStatusLine().getStatusCode();
    12.             if (status == HttpStatus.SC_OK) {
    13.                 xxxxx;
    14.             } else {
    15.                 xxxxxx;
    16.             }
    17.         } catch (Exception ex) {
    18.             logger.error(ex.toString());
    19.         }

    那么下面就从httpclient.execute(post)入手,研究一下httpclient的源代码是怎么实现的。
    通过研读它的源代码,并通过debug终于找到了一些线索,如下图:


    这张图的价值在于:1)httpclient向web server发送消息时,如果有消息体那么调用的是消息体(即HttpEntity)的writeTo方法
    2)图中我们也可以很清晰的看到了httpclient发送一个post或者put消息时的调用栈,便于以后研究

    这里有一个大胆的猜测,我们平时上传文件就是调用entity.writeTo(outstream)将文件写到outputstream这个网络IO流中来实现的。
    下面的重点就放到这个writeTo方法,看看有没有什么突破口。
    下图是官方上传文件的调用关系的源代码:


    注:由于找不到httpmime的源码包,所以只能从官网的xref去浏览代码了,地址:http://hc.apache.org/httpcomponents-client-ga/httpmime/xref/index.html
    谁只能httpmime的源码包的下载地址可以告诉我

    现在调用关系基本理清楚了,下面就是实现的问题了
    从上面的截图来看不管如何调用,最终文件流的出口都是调用OutputStream的write(final byte[] b, final int off, final int len)方法
    那么我们是不是我可以重写这个OutputStream类就可以实现上传进度监测的功能呢,因为传出去多少包这里最清楚
    有人问重写FileBody不可以吗,不是不行但是同时AbstractMultipartForm也需要重写,因为这里有两个出口一个是FileBody的writeTo,一个是AbstractMultipartForm的writeBytes方法。
    同时还需要重写MultipartEntity类,在调用writeTo方法是把里面的OutputStream替换为我们自定义的OutputStream类。

    下面先来重写OutputStream类
    复制代码
    1. public class CountOutputStream extends FilterOutputStream  {
    2.     
    3.     private final ProgressListener listener;
    4.     private long transferred;
    5.     public CountOutputStream(final OutputStream out,
    6.             final ProgressListener listener) {
    7.         super(out);
    8.         this.listener = listener;
    9.         this.transferred = 0;
    10.     }
    11.     public void write(byte[] b, int off, int len) throws IOException {
    12.         super.write(b, off, len);
    13.         this.transferred += len;
    14.         this.listener.transferred(this.transferred);
    15.     }
    16.     public void write(int b) throws IOException {
    17.         super.write(b);
    18.         this.transferred++;
    19.         this.listener.transferred(this.transferred);
    20.     }
    21. }


    然后重写MultipartEntity类
    复制代码
    1. public class MyMultipartEntity extends MultipartEntity {
    2.     
    3.     private final ProgressListener listener;
    4.     public MyMultipartEntity(final ProgressListener listener) {
    5.         super();
    6.         this.listener = listener;
    7.     }
    8.     public MyMultipartEntity(final HttpMultipartMode mode,
    9.             final ProgressListener listener) {
    10.         super(mode);
    11.         this.listener = listener;
    12.     }
    13.     public MyMultipartEntity(HttpMultipartMode mode, final String boundary,
    14.             final Charset charset, final ProgressListener listener) {
    15.         super(mode, boundary, charset);
    16.         this.listener = listener;
    17.     }
    18.     @Override
    19.     public void writeTo(OutputStream outstream) throws IOException {
    20.         super.writeTo(new CountOutputStream(outstream, this.listener));
    21.     }
    22. }


    监控类就是一个简单的接口
    复制代码
    1. public interface ProgressListener {
    2.     void transferred(long num);
    3. }


    这样之前的代码:MultipartEntity entity = new MultipartEntity();  修改为下面的样子就可以大功告成了
    复制代码
    1.         MultipartEntity entity = new MyMultipartEntity(new ProgressListener() {            
    2.             @Override
    3.             public void transferred(long num) {
    4.                 // TODO Auto-generated method stub
    5.                 xxxxxxx;
    6.             }
    7.         });


    好了研究了好几天写了一上午,这篇文章终于可以告一段落了
    真是好记性不如烂笔头,在写的过程中自己也是受益匪浅学习了大量的第三方的优秀源代码,对自己也很有启发的
    呵呵虫
    体育竞猜区
    级别: 竞猜管理组

    UID: 109
    精华: 0
    发帖: 3856
    威望: 64246
    霏币: 87421.4
    活跃度: 3902
    技术分: 81
    非凡币: 3581
    交易值: 0
    在线时间: 1876(小时)
    注册时间: 2012-01-01
    最后登录: 2018-10-18
    8楼  发表于: 2018-02-13 09:10:33
    最近一直在学习《深入分析JavaWeb技术内幕》这本书,轻松愉快的看到第9章Servlet工作原理的时候突然卡壳了,整个就好像看天书一样。
    让我一度怀疑难道自己真的老了吗,看东西怎么这么费劲。
    后来仔细想想,我初次接触Servlet连它是干什么的怎么用都不知道,直接去看它的实现原理不是自讨苦吃事倍功半嘛。
    于是网上随便找了一个本电子书《Servlet+JSP+Spring MVC初学指南》,下了一份tomcat7的源代码,对照着一口气看了100多页。
    期间还在简书上拜读了个大神写的一篇文章《WEB请求处理三:Servlet容器请求处理》,翻了翻tomcat中相关类的源代码,总算知道server是干什么的、怎么用了。这时回头再看Servlet工作原理不算吃力能看得懂。

    今天的最大收获是:学习要学会变通,有时候换个角度可能说不准就海阔天空

    最后说一下《深入分析JavaWeb技术内幕》确实是本好书,书中涉及的知识面很广虽然讲的不深但是会引导你入门让你很清楚接下来该学什么。
    呵呵虫
    体育竞猜区
    级别: 竞猜管理组

    UID: 109
    精华: 0
    发帖: 3856
    威望: 64246
    霏币: 87421.4
    活跃度: 3902
    技术分: 81
    非凡币: 3581
    交易值: 0
    在线时间: 1876(小时)
    注册时间: 2012-01-01
    最后登录: 2018-10-18
    9楼  发表于: 2018-02-13 09:18:19
    引用
    引用第8楼明天减肥于2018-02-13 09:00发表的  :
    大神归来啊,不愧是有10年从业经验的大神。

    见笑了,毕业后做了一年左右的编程就改行做通信行业了,编程这方面完全是个新手
    级别: 高级会员
    UID: 9732
    精华: 0
    发帖: 1509
    威望: 2442
    霏币: 3391
    活跃度: 1533
    技术分: 0
    非凡币: 0
    交易值: 0
    在线时间: 780(小时)
    注册时间: 2004-07-06
    最后登录: 2018-10-21
    10楼  发表于: 2018-02-13 14:41:17
    高手,膜拜。
    呵呵虫
    体育竞猜区
    级别: 竞猜管理组

    UID: 109
    精华: 0
    发帖: 3856
    威望: 64246
    霏币: 87421.4
    活跃度: 3902
    技术分: 81
    非凡币: 3581
    交易值: 0
    在线时间: 1876(小时)
    注册时间: 2012-01-01
    最后登录: 2018-10-18
    11楼  发表于: 2018-02-14 11:12:40
    巩固一下今天学的小知识:类图,这块知识以前一直比较迷糊,这回一定更要搞清楚
    类图之间有六种关系,如下图所示


    泛化:一般用于类的继承,两者一般均为具体的对象
    实现:一般常用语抽象类或者接口,父类一般为抽象的对象
    聚合:用来表示两个对象之间的组成关系,与组合关系不同的是两个对象之间不是强相关的,即整体类不存在了,部分类仍然可以存在
    组合:同样用来表示两个对象之间的组成关系,组合关系的整体不存在那么部分也就消失了。
    关联:关联关系是一种长期性、常识性的而且两个对象之间关系对等的联系
    依赖:依赖是一种弱化的关联关系,更侧重于偶然性、临时性的关系。
    关联关系可以是单向也可以是双向关系,但是依赖关系必然是单向依赖。
    贴一个大神画的例子:

    霏凡活动区
    级别: 霏凡版主
    UID: 219092
    精华: 0
    发帖: 12917
    威望: 30236
    霏币: 4806.4
    活跃度: 12910
    技术分: 23
    非凡币: 2869
    交易值: 0
    在线时间: 988(小时)
    注册时间: 2014-06-04
    最后登录: 2018-10-21
    12楼  发表于: 2018-02-14 13:30:40
    有那个是学以致用的学生?
    呵呵虫
    体育竞猜区
    级别: 竞猜管理组

    UID: 109
    精华: 0
    发帖: 3856
    威望: 64246
    霏币: 87421.4
    活跃度: 3902
    技术分: 81
    非凡币: 3581
    交易值: 0
    在线时间: 1876(小时)
    注册时间: 2012-01-01
    最后登录: 2018-10-18
    13楼  发表于: 2018-02-14 17:53:44
    今天下午看了看单例模式,本来觉得单例模式太简单了没什么好说的,但是看完大神们对单例模式的讲解后,真是学习到了。
    首先来一个单例模式比较完美的终极模式的代码:

    复制代码
    1. public class Single4 {
    2.     private static volatile Single instance;
    3.     private Single() {}
    4.     public static Single getInstance() {
    5.         if (instance == null) {
    6.             synchronized (Single.class) {
    7.                 if (instance == null) {
    8.                     instance = new Single();
    9.                 }
    10.             }
    11.         }
    12.         return instance;
    13.     }
    14. }

    下面一层一层的往下扒:
    一般情况下单例模式是像下面这样实现的:
    复制代码
    1. public class Single {
    2.     private static  Single instance;
    3.     private Single() {}
    4.     public static Single getInstance() {
    5.         if (instance == null) {
    6.             instance = new Single();
    7.         }
    8.         return instance;
    9.     }
    10. }

    这段代码单线程顺序执行没有任何问题,但是遇到多线程就有问题了,每个程序都会进行初始化那么这样就不是单例了,下面把同步加上
    复制代码
    1. public class Single {
    2.     private static  Single instance;
    3.     private Single() {}
    4.     public static synchronized Single getInstance() {
    5.         if (instance == null) {
    6.             instance = new Single();
    7.         }
    8.         return instance;
    9.     }
    10. }

    这样差不多可以应付多线程了吧,但是感觉还是有点怪怪的,问题就出在同步这块
    如果当前有多个线程调用getInstance方法,其中1个线程获得同步锁以后,其它线程此时处于等待状态,这显示不是我们在实现多线程的时候愿意看到的。
    好了下面继续改进这个程序:
    复制代码
    1. public class Single {
    2.     private static  Single instance;
    3.     private Single() {}
    4.     public static Single getInstance() {
    5.         if (instance == null) {
    6.             synchronized (Single.class) {
    7.                 if (instance == null) {
    8.                     instance = new Single();
    9.                 }
    10.             }
    11.         }
    12.         return instance;
    13.     }
    14. }

    这里同步锁加到了class文件上,第一层判断是降低进步同步锁的概率,第二层判断是为了避免多线程情况下出现多实例的情况。

    做到这一步这个程序基本差不多可以收工了,但是对比最终版本还是有差别多了一个volatile声明,这里引入原子操作和指令重排的概念,先看看大神们的解释

    引用

    3.3.1知识点:什么是原子操作?

    简单来说,原子操作(atomic)就是不可分割的操作,在计算机中,就是指不会因为线程调度被打断的操作。比如,简单的赋值是一个原子操作:m = 6; // 这是个原子操作
      假如m原先的值为0,那么对于这个操作,要么执行成功m变成了6,要么是没执行m还是0,而不会出现诸如m=3这种中间态——即使是在并发的线程中。而,声明并赋值就不是一个原子操作:int n = 6; // 这不是一个原子操作对于这个语句,至少有两个操作:①声明一个变量n②给n赋值为6——这样就会有一个中间状态:变量n已经被声明了但是还没有被赋值的状态。——这样,在多线程中,由于线程执行顺序的不确定性,如果两个线程都使用m,就可能会导致不稳定的结果出现。

    3.3.2知识点:什么是指令重排?

    简单来说,就是计算机为了提高执行效率,会做的一些优化,在不影响最终结果的情况下,可能会对一些语句的执行顺序进行调整。比如,这一段代码:

    int a ;   // 语句1
    a = 8 ;   // 语句2
    int b = 9 ;     // 语句3
    int c = a + b ; // 语句4
    正常来说,对于顺序结构,执行的顺序是自上到下,也即1234。但是,由于指令重排
    的原因,因为不影响最终的结果,所以,实际执行的顺序可能会变成3124或者1324。由于语句3和4没有原子性的问题,语句3和语句4也可能会拆分成原子操作,再重排。——也就是说,对于非原子性的操作,在不影响最终结果的情况下,其拆分成的原子操作可能会被重新排列执行顺序。
      OK,了解了原子操作和指令重排的概念之后,我们再继续看Version3代码的问题。下面这段话直接从陈皓的文章(深入浅出单实例SINGLETON设计模式)中复制而来:主要在于instance = new Single()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
      1. 给 Single分配内存
      2. 调用 Single的构造函数来初始化成员变量,形成实例
      3. 将Single对象指向分配的内存空间(执行完这步 singleton才是非 null 了)但是在 JVM 的即时编译器中存在指令重排序的优化。



    根据上面的知识,那么下面分析程序中运行的可能性

    1. 既然指令的实际执行顺序可能会重排,那么上面执行时的指令顺序可能是1,3,2
    2. 如果线程1按照指令1-3-2的顺序执行,执行到指令3单未执行指令2时,线程2进入了判断环节,那么此时的instance不为null就直接返回。
    3. 线程2后续执行时会出现的问题是:它拿到了一个没有经过指令2实例化的instance,后续出现什么样的后果谁知道呢。。。。


    为了解决上述可能出现的低概率事件,所以使用volatile来声明变量,其作用就是禁止指令重排

    原来一段简简单单的代码居然有这么多学问
    级别: 新手上路
    UID: 234563
    精华: 0
    发帖: 105
    威望: 1
    霏币: 96
    活跃度: 107
    技术分: 0
    非凡币: 0
    交易值: 0
    在线时间: 5(小时)
    注册时间: 2018-02-09
    最后登录: 2018-07-24
    14楼  发表于: 2018-02-17 00:37:12
    厉害了,只是一时间看不太懂!
    杀杀人,跳跳舞
    级别: 资深会员
    UID: 54969
    精华: 0
    发帖: 1693
    威望: 3108
    霏币: 340
    活跃度: 5248
    技术分: 0
    非凡币: 0
    交易值: 0
    在线时间: 763(小时)
    注册时间: 2004-10-01
    最后登录: 2018-03-17
    15楼  发表于: 2018-02-20 02:57:45

    【其他交流】 回 楼主(hehec) 的帖子

    请问,这种带数字的TXT,用的是什么软件???
    呵呵虫
    体育竞猜区
    级别: 竞猜管理组

    UID: 109
    精华: 0
    发帖: 3856
    威望: 64246
    霏币: 87421.4
    活跃度: 3902
    技术分: 81
    非凡币: 3581
    交易值: 0
    在线时间: 1876(小时)
    注册时间: 2012-01-01
    最后登录: 2018-10-18
    16楼  发表于: 2018-02-21 10:24:51

    【其他交流】 Re:回 楼主(hehec) 的帖子

    引用
    引用第16楼wgw555于2018-02-20 02:57发表的 回 楼主(hehec) 的帖子 :
    请问,这种带数字的TXT,用的是什么软件???

    用的eclipse,专业的java编程工具
    杀杀人,跳跳舞
    级别: 资深会员
    UID: 54969
    精华: 0
    发帖: 1693
    威望: 3108
    霏币: 340
    活跃度: 5248
    技术分: 0
    非凡币: 0
    交易值: 0
    在线时间: 763(小时)
    注册时间: 2004-10-01
    最后登录: 2018-03-17
    17楼  发表于: 2018-02-21 21:35:16

    【其他交流】 回 17楼(hehec) 的帖子

    怎样设置才可以保存数字列号????????
    杀杀人,跳跳舞
    级别: 资深会员
    UID: 54969
    精华: 0
    发帖: 1693
    威望: 3108
    霏币: 340
    活跃度: 5248
    技术分: 0
    非凡币: 0
    交易值: 0
    在线时间: 763(小时)
    注册时间: 2004-10-01
    最后登录: 2018-03-17
    18楼  发表于: 2018-02-21 21:36:08

    【其他交流】 回 17楼(hehec) 的帖子

    怎样设置才可以保存数字列号????????       
    呵呵虫
    体育竞猜区
    级别: 竞猜管理组

    UID: 109
    精华: 0
    发帖: 3856
    威望: 64246
    霏币: 87421.4
    活跃度: 3902
    技术分: 81
    非凡币: 3581
    交易值: 0
    在线时间: 1876(小时)
    注册时间: 2012-01-01
    最后登录: 2018-10-18
    19楼  发表于: 2018-02-22 08:33:32

    【其他交流】 回 19楼(wgw555) 的帖子

    你说的保存数字列号是什么意思?
    呵呵虫
    体育竞猜区
    级别: 竞猜管理组

    UID: 109
    精华: 0
    发帖: 3856
    威望: 64246
    霏币: 87421.4
    活跃度: 3902
    技术分: 81
    非凡币: 3581
    交易值: 0
    在线时间: 1876(小时)
    注册时间: 2012-01-01
    最后登录: 2018-10-18
    20楼  发表于: 2018-02-22 11:25:22
    假期期间的一些学习进度:
    1. 《深入分析JavaWeb技术内幕》学习完第11章
    2. 看了一些spring boot的例子和教学视频

    总结一下:
    1. java的注解(annotation)在Spring框架编程中是无处不在,这个在以前Java编程中是很罕见的,很难想象注解也成为编程主要的组成部分。
    2. Spring boot这个框架简直是逆天了,实在是太强大(直接开启懒人模式),现在的框架越来越强大也难怪码农不值钱呢
    3. 那程序员的价值在哪里呢,答案是一些架构需要有自己的想法,如何利用现有的技术将一个项目搭建起来
        《深入分析JavaWeb技术内幕》这本书的第十章真是让人脑洞大开,原来cookie与session在服务器端还有这么多学问。
    4. 《深入分析JavaWeb技术内幕》这本书花了大量的篇章去介绍类加载机制,起初没有明白其用意,学习了Spring boot后才明白这里涉及到了Spring的热部署,后续高级编程可能会涉及到自定义类加载器的事宜了
    呵呵虫
    体育竞猜区
    级别: 竞猜管理组

    UID: 109
    精华: 0
    发帖: 3856
    威望: 64246
    霏币: 87421.4
    活跃度: 3902
    技术分: 81
    非凡币: 3581
    交易值: 0
    在线时间: 1876(小时)
    注册时间: 2012-01-01
    最后登录: 2018-10-18
    21楼  发表于: 2018-02-22 17:49:56
    近期使用Jmeter进行测试期间,注意到jmeter的sampler中有一个JSR223 Sampler,其作用就是运行脚本语言
    能够支持的脚本语言也比较多,具体如下图



    下面以选择一种比较常用的groovy语言为例,做一个非常简单的测试脚本
    1. 创建测试计划
    2. 添加用户自定义变量


    3. 加入JSR223 Sampler,编写一个非常简单的groovy脚本,同时引用到jmeter的内部变量
    4. 运行测试脚本并查看结果



    上图中运行了一个groovy的helloword级别的测试脚本,同时引用了Jmeter提供的一些内部变量,最后打印出来的测试结果也是我们期望的测试结果。

    接下来就要刨根问底儿,看看这个Jmeter是如何去执行这段测试脚本的
    问:研究这个有什么意义和价值吗?
    答:这个有很大的研究价值,例如你的一个app处于长期运行状态,期间需要动态编码运行一些场景的话,就非常有用了。
            当然最常见的场景是例如类似于Jmeter这样,我们自己搭建的测试平台上运行编写的测试脚本是一个非常典型的应用场景。

    在正式开始介绍前,如果对Jmeter二次开发有兴趣,可以下载去Jmeter官网下载一份Jmeter源码,并在Eclipse等IDE工具上能够跑起来,Jmeter工程如下图所示:

    呵呵虫
    体育竞猜区
    级别: 竞猜管理组

    UID: 109
    精华: 0
    发帖: 3856
    威望: 64246
    霏币: 87421.4
    活跃度: 3902
    技术分: 81
    非凡币: 3581
    交易值: 0
    在线时间: 1876(小时)
    注册时间: 2012-01-01
    最后登录: 2018-10-18
    22楼  发表于: 2018-02-22 18:27:49
    接上贴
    在Jmeter的运行框架,上图中“groovy脚本测试”其实对应的一个TestElement(测试元素)同时也是一个Sampler,所以运行时对应的实例为JSR223Sampler
    运行的方法为:public SampleResult sample(Entry entry),在接口中定义的方法。
    具体运行的代码如下:


    以这个为入口,运行的序列图如下:



    下面开始逐步分析序列图中的各个步骤
    1. 要执行groovy脚本,第一步就是要获取一个ScriptEngine实例(它相当于一个脚本解释器)
    2. 获取ScriptEngine的源代码如下:


    代码比较简单,需要注意三点:1)String lang就是所使用的脚本语言的类型
                                                            2)getInstance()一看就是一个单例模式,这里其实就是声明了一个ScriptEngineManager,具体代码如下图:


                                                            3)后续调用getEngineByName()方法会根据脚本语言类型来获取相应的ScriptEngine
    3. 继续分析ScriptEngineManager的初始化过程,因为后续获取Engine时会依赖初始化的结果,代码如下图:
        

        1)这里看到Classloader就知道肯定涉及到了类的动态加载了,代码如下:
    复制代码
    1.     private ServiceLoader<ScriptEngineFactory> getServiceLoader(final ClassLoader loader) {
    2.         if (loader != null) {
    3.             return ServiceLoader.load(ScriptEngineFactory.class, loader);
    4.         } else {
    5.             return ServiceLoader.loadInstalled(ScriptEngineFactory.class);
    6.         }
    7.     }

        2)这里动态加载的对象为ScriptEngineFactory工厂
        3)将这些ScriptEngineFactory放入到一个HashSet类engineSpis中


    4. 初始化完成后,就可以调用ScriptEngineManager的getEngineByName方法,代码如下:


    代码分析如下:
    1)代码对所有的ScriptEngineFactory进行遍历。
    2)如果当前工厂的所支持的脚本类型(即spi.getNames())与入参中需要的脚本类型一致,那么就调用ScriptEngineFactory的getScriptEngine方法,返回ScriptEngine实例

    到这里第一个阶段,或许脚本语言解释器的工作基本完成,后面会继续分析如何去运行脚本
    呵呵虫
    体育竞猜区
    级别: 竞猜管理组

    UID: 109
    精华: 0
    发帖: 3856
    威望: 64246
    霏币: 87421.4
    活跃度: 3902
    技术分: 81
    非凡币: 3581
    交易值: 0
    在线时间: 1876(小时)
    注册时间: 2012-01-01
    最后登录: 2018-10-18
    23楼  发表于: 2018-02-23 17:54:18
    接着上文继续分析脚本语言是如何运行起来的
    1. 先看processFileOrScript这个方法的源码如下图


    具体分析如下:
    1)这段代码的主框架是两个分支,分别对脚本来源于脚本文件还是文本框编辑的脚本分别进行处理。
    2.)脚本语言涉及到是否需要编译的问题,所以判断对应的scriptEngine是否实现了Compilable接口
    3)Bindings对应的是一张Map,用于存放脚本运行期间的一些参数,这里的参数在populateBindings方法中进行设置。
    4)如果当前的scriptEngine支持编译,并且有对应的cache的key,那么需要先将脚本编译以后再运行,同时将脚本保存到MAP中(即compiledScriptsCache)
    5)是否需要进行cache是由创建JSR223 Sampler时的"Cache Complied Script if Available"这个开关决定的,之所以这么设计我猜测的原因是:
         在脚本运行过程中,如果每次执行脚本都要编译后运行那么势必会影响执行效率,所以如果将这个选项勾选的话后续运行相同的脚本时不需要重复编译直接从MAP中取出来直接运行即可。(Jmeter是做性能测试的,反复运行一个脚本也是家常便饭了),具体见下面这段代码
    复制代码
    1.                     CompiledScript compiledScript = compiledScriptsCache.get(this.scrip***5);
    2.                     if (compiledScript == null) {
    3.                         synchronized (compiledScriptsCache) {
    4.                             compiledScript = compiledScriptsCache.get(this.scrip***5);
    5.                             if (compiledScript == null) {
    6.                                 compiledScript = ((Compilable) scriptEngine).compile(getScript());
    7.                                 compiledScriptsCache.put(this.scrip***5, compiledScript);
    8.                             }
    9.                         }
    10.                     }

    6)脚本最终运行是通过调用scriptEngine或者CompiledScript的eval()方法去运行。

    2. populateBindings方法分析,其代码如下:


    之间细心的人可能会有疑问,我编写的那段简单的groovy代码,vars和log这两个对象在groovy的语法中并没有定义,那groovy是怎么识别的呢,答案全在这个函数里面了
    在这个函数中可以看到许多Jmeter自定义的内部变量,并且将这些Jmeter定义的自定义变量的真实对象放到Binds这个Map中,那么后续脚本引擎在执行时遇到了这段代码也就知道该如何去调用了。

    3. 虽然没有源代码,但是大胆猜测CompiledScript中的eval方法最终应该还是调用的scriptEngine中的eval()方法,因为scriptEngine已经注入到了CompiledScript实例中。

    4.scriptEngine的eval()方法到底是怎么将脚本运行起来的,由于scriptEngine是java提供的框架,但是具体的scriptEngine的实现是由第三方根据脚本语言的特点开发的,会涉及到更深层次的话题,由于水平有限先就此打住不再探讨了。
    呵呵虫
    体育竞猜区
    级别: 竞猜管理组

    UID: 109
    精华: 0
    发帖: 3856
    威望: 64246
    霏币: 87421.4
    活跃度: 3902
    技术分: 81
    非凡币: 3581
    交易值: 0
    在线时间: 1876(小时)
    注册时间: 2012-01-01
    最后登录: 2018-10-18
    24楼  发表于: 2018-02-23 18:42:56
    最后用Eclipse将Jmeter跑起来,并进行debug看一下整个执行过程中的几个关键点
    1. 程序运行到getEngineByName函数时,找到对应的ScriptEngine时进行返回的状态如下图:


    几个注意点如下:
    1) 由于运行脚本需要找groovy对应的编译器,从截图中可以看到调用的方法为:getEngineByName("groovy")方法
    2)ScriptEngineManager初始化完成后,共加载了5个ScriptEngineFactory工厂,我们要找的是GroovyScriptEngineFactory
    3)这5个ScriptEngineFactory工厂分别位于如下几个个jar中,分别是
          a. Jmeter工程下对应的bsh-2.0b5.jar、groovy-all-2.4.12.jar以及commons-jexl3.3.1.jar, commons-jexl2.1.1.jar
          b. JDK 中jre的lib\exe目录下的nashorn.jar

    2. 程序运行到processFileOrScript函数时,执行eval()方法进行返回时的状态如下图:


    几个注意点如下:
    1) 运行groovy对应的最终的脚本引擎是GroovyScriptEngineImpl这个类。
    2)运行groovy最终对应的compliedScript类为GroovyCompiledScript,而且GroovyScriptEngineImpl实例是它的一个对象,处于依赖关系
          所以compliedScript的eval()方法最终调用的是GroovyScriptEngineImpl中的eval()方法的应该是大概率事件了。
    3)compiledScriptsCache缓存中已经将编译好的脚本类GroovyCompiledScript放入其中,其键值为:cacheKey
    4)Jmeter运行脚本的机制是开启一个线程(Thread),并处理里面的Sampler,通过调用栈也能很清楚的看到Jmeter的调用关系,可供后续研究学习

    到此JSR223 Sampler运行groovy脚本的原理和套路就基本摸清楚了
    呵呵虫
    体育竞猜区
    级别: 竞猜管理组

    UID: 109
    精华: 0
    发帖: 3856
    威望: 64246
    霏币: 87421.4
    活跃度: 3902
    技术分: 81
    非凡币: 3581
    交易值: 0
    在线时间: 1876(小时)
    注册时间: 2012-01-01
    最后登录: 2018-10-18
    25楼  发表于: 2018-02-24 09:03:43
    最后再补充一下:
    昨天翻到大神写的一篇博客【JMeter】Groovy和BeanShell脚本的性能比较,可以分析一下:
    1. 博客中提到第一次运行时,groovy的运行效率与beanshell相比差了非常多,按照博主的测试结果差了10倍多
        结合上面介绍的内容其实原因很简单第一次运行时groovy脚本需要先编译再运行,beanshell是解释语言,所以beanshell比groovy运行速度快也是正常的
    2. 后面进行多次运行时groovy明显比beanshell快了不少,其实原因就是因为将已经编译好的脚本做了cache避免了再次编译,所以后面groovy运行明显快了不少。

    下面自己也对之前的测试脚本做一下改造来验证一下执行效率的问题
    Jmeter脚本做如下改造:
    1)新增一个循环控制器,循环次数为20次
    2)新增一个Beanshell Sampler
    3)修改两个sampler的测试代码如下:
    复制代码
    1. String testString = "hello " + vars.get("name");
    2. for(int i = 0; i < 1000; i++) {
    3.     vars.put("name", "hehec");
    4.     testString = "hello " + vars.get("name");
    5. }
    6. log.info(testString);

    先让其做1000次map的存取操作后再进行打印
    4)加入一个表格查看监听器,用于查看最后的运行结果。
    为了尽量公平两个sampler交叉运行

    准备工作做好以后,就运行一下看看测试结果:


    基本如博主所说,第一次运行时groovy运行时间较长,大部分时间都耗在了编译上,后续运行groovy比beanshell确实快了很多
    级别: 中级会员
    UID: 234596
    精华: 0
    发帖: 1303
    威望: 1381
    霏币: 627
    活跃度: 1300
    技术分: 0
    非凡币: 0
    交易值: 0
    在线时间: 183(小时)
    注册时间: 2018-02-09
    最后登录: 2018-10-21
    26楼  发表于: 2018-02-28 13:41:08
    想准备开始学html5和java
    呵呵虫
    体育竞猜区
    级别: 竞猜管理组

    UID: 109
    精华: 0
    发帖: 3856
    威望: 64246
    霏币: 87421.4
    活跃度: 3902
    技术分: 81
    非凡币: 3581
    交易值: 0
    在线时间: 1876(小时)
    注册时间: 2012-01-01
    最后登录: 2018-10-18
    27楼  发表于: 2018-02-28 16:56:27
    近期在做项目和学习的过程中经常有两个问题:
    1. 在学习Springboot的一些视频中发现,控制层返回一个实例,系统返回时会直接将这个类转回为Json格式
    2. 将一个实例中转换为Json格式时,某些字段不想被用户看到,该如何隐藏

    好了上面的问题都牵扯到了Java的两个功能和反射
    问题1的解决思路:遍历该实例的所有变量,并调用其getter方法加入到Json中
    问题2的解决思路:为该实例的每个字段添加注解,根据注解来决定是否返回到Json中

    首先自定义一个注解,具体代码如下:
    复制代码
    1. package test;
    2. import java.lang.annotation.Documented;
    3. import java.lang.annotation.ElementType;
    4. import java.lang.annotation.Retention;
    5. import java.lang.annotation.RetentionPolicy;
    6. import java.lang.annotation.Target;
    7. @Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到  
    8. @Target({ElementType.FIELD,ElementType.METHOD})//定义注解的作用目标**作用范围字段、枚举的常量/方法  
    9. @Documented//说明该注解将被包含在javadoc中  
    10. public @interface FieldMeta {  
    11.     
    12.     /**
    13.      * 字段名称
    14.      * @return
    15.      */  
    16.     String name() default "";  
    17.     /**
    18.      * 是否转换Json
    19.      * @return
    20.      */  
    21.     boolean isJson() default true;  
    22. }  

    这个注解有两个字段,第一个字段用来注释当前field的名称,第二个字段用来注释是否加入到Json中,默认是true

    定义一个具体的用户类:
    复制代码
    1. public class User {
    2.     @FieldMeta(name="学生id")
    3.     private int id;
    4.     @FieldMeta(name="学生姓名")
    5.     private String name;
    6.     @FieldMeta(name="学生密码")
    7.     private String password;
    8.     @FieldMeta(name="学生的秘密")
    9.     private String secret;
    10.     
    11.     public User(int id, String name, String password) {
    12.         this.id = id;
    13.         this.name = name;
    14.         this.password = password;
    15.     }
    16.     
    17.     public String getSecret() {
    18.         return secret;
    19.     }
    20.     public void setSecret(String secret) {
    21.         this.secret = secret;
    22.     }
    23.     public int getId() {
    24.         return id;
    25.     }
    26.     public void setId(int id) {
    27.         this.id = id;
    28.     }
    29.     public String getName() {
    30.         return name;
    31.     }
    32.     public void setName(String name) {
    33.         this.name = name;
    34.     }
    35.     public String getPassword() {
    36.         return password;
    37.     }
    38.     public void setPassword(String password) {
    39.         this.password = password;
    40.     }
    41. }


    最后做一个用于处理类
    复制代码
    1. import java.lang.reflect.Field;
    2. import java.lang.reflect.InvocationTargetException;
    3. import java.lang.reflect.Method;
    4. import org.json.JSONObject;
    5. public class ProcessHandler {
    6.     
    7.     public JSONObject converToJson(User user) {
    8.         JSONObject result = new JSONObject();
    9.         Class<?> tmpclass = user.getClass();
    10.         Field[] fields = tmpclass.getDeclaredFields();
    11.         //遍历User这个类的所有字段
    12.         for (Field f : fields) {
    13.             FieldMeta meta = f.getAnnotation(FieldMeta.class);
    14.             //如果当前字段存在FieldMeta注解,并且字段的isJson为true
    15.             if (meta != null && meta.isJson()) {
    16.                 try {
    17.                     //生成当前字段对应的getter方法
    18.                     String methodname = getGetterMethodName(f.getName(), f.getType());
    19.                     //查找getter方法
    20.                     Method m = tmpclass.getDeclaredMethod(methodname, null);
    21.                     //反射getter方法并加入到Json对象中
    22.                     result.put(f.getName(), m.invoke(user, null));
    23.                 } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
    24.                         | InvocationTargetException e) {
    25.                     // TODO Auto-generated catch block
    26.                     e.printStackTrace();
    27.                 }
    28.             }
    29.         }
    30.         return result;
    31.     }
    32.     
    33.     /**
    34.      * 根据属性名称和java类型,获取对应的getter方法名称
    35.      * @param property
    36.      * @param javaType
    37.      * @return
    38.      */  
    39.     public String getGetterMethodName(String property, Class<?> classType) {  
    40.         StringBuilder sb = new StringBuilder();  
    41.         sb.append(property);  
    42.         if (Character.isLowerCase(sb.charAt(0))) {  
    43.             if (sb.length() == 1 || !Character.isUpperCase(sb.charAt(1))) {  
    44.                 sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));  
    45.             }  
    46.         }  
    47.         if (classType == Boolean.class) {  
    48.             sb.insert(0, "is");  
    49.         } else {  
    50.             sb.insert(0, "get");  
    51.         }  
    52.         return sb.toString();  
    53.     }
    54.     
    55.     public static void main(String[] args) {
    56.         ProcessHandler handler = new ProcessHandler();
    57.         User user = new User(133, "hehec", "123");
    58.         user.setSecret("I'm a boy");
    59.         JSONObject userObj = handler.converToJson(user);
    60.         System.out.println(userObj.toString());
    61.     }
    62. }


    先看看最后的执行结果:


    这里实现了将一个实例转换为Json对象,下面如果要隐藏secret字段,那么将该注释修改为:
    复制代码
    1.     @FieldMeta(name="学生的秘密", isJson=false)
    2.     private String secret;

    再次运行结果如下:


    好了这次返回的结果中没有了secret字段,说明改动有效果。

    在上面的例子中,是通过FieldMeta meta = f.getAnnotation(FieldMeta.class);这句话来获取注解的,那么Java提供了哪些方式获取注解呢?
    目前主要通过反射来获取运行时注解,可以从 Package、Class、Field、Method...上面获取,基本方法都一样,常用的方法如下图所示:



    通过网上搜索和学习,写完这篇文章个人的收获还是蛮大的,对注解和反射有了一定的认识,尤其是注解对以后学习Spring会有很大的帮助
    级别: 新手上路
    UID: 234563
    精华: 0
    发帖: 105
    威望: 1
    霏币: 96
    活跃度: 107
    技术分: 0
    非凡币: 0
    交易值: 0
    在线时间: 5(小时)
    注册时间: 2018-02-09
    最后登录: 2018-07-24
    28楼  发表于: 2018-02-28 21:27:04
    大佬有空应该开个科普教学帖,好像和你学习!
    呵呵虫
    体育竞猜区
    级别: 竞猜管理组

    UID: 109
    精华: 0
    发帖: 3856
    威望: 64246
    霏币: 87421.4
    活跃度: 3902
    技术分: 81
    非凡币: 3581
    交易值: 0
    在线时间: 1876(小时)
    注册时间: 2012-01-01
    最后登录: 2018-10-18
    29楼  发表于: 2018-03-01 08:37:05
    引用
    引用第29楼g00gle于2018-02-28 21:27发表的  :
    大佬有空应该开个科普教学帖,好像和你学习!

    现在网络发达的年代学习一些基础性、科普的编程东西其实很简单,各种博客、技术论坛等随便搜到处都是
    我常去的是CSDN、博客园、简书、知乎、Stack Overflow等等

    其实我也是个新手很多东西都在摸索中,所以记录一些项目实战过程中碰到的问题和解决应该更有价值些
    你有什么疑问可以一起交流