Java工程如何打印程序日志过程解析

很久之前,有个朋友问我,如果一个老项目让你接手去进行后续维护,你会先从哪里入手、让自己更快地上手项目?当时我没有特别正面去回答这个朋友的问题,我说:一个老项目是否容易上手,一个非常关键的地方就是这个项目的日志是否打得足够好。因为通常来说,一个老项目相对比较稳定了,后续大概率不会有比较大的变更和改动,那么对于这样的项目,核心就是“维稳”。但是任何人都无法保证项目在线上运行时不会出线上故障,在出现线上问题或者故障时,如何快速止损就是第一要义,而日志在止损过程中就扮演着非常重要的角色。日志打的足够明了清晰,可以帮助开发和运维人员快速定位问题,继而决定采取何种方案进行止损。

今天就让我们一起来聊一聊如何把项目程序日志打“好”。

一.为何需要规范地打印程序日志?

我们平时在写程序代码过程中,一般会把主要精力集中在功能实现上,往往会忽视日志的重要性,然而日志在系统上线后是极其重要的,因为系统上线后,只有通过日志才能了解当前系统的运行状态,在出现线上故障时,日志是否足够清晰明了决定了是否能够快速找到止损方案。我们可以看一下下面这段代码:

public class HttpClient {
  private static final Logger LOG = LoggerFactory.getLogger(HttpClient.class);

  private static int CONNECT_TIMEOUT = 5000;  // unit ms
  private static int READ_TIMEOUT = 10000;   // unit ms

  public static String sendPost(String url, String param) {
    OutputStream out = null;
    BufferedReader in = null;
    String result = "";
    try {
      URL realUrl = new URL(url);
      URLConnection conn = realUrl.openConnection();
      conn.setDoInput(true);
      conn.setDoOutput(true);
      conn.setConnectTimeout(CONNECT_TIMEOUT);
      conn.setReadTimeout(READ_TIMEOUT);
      conn.setRequestProperty("charset", "UTF-8");
      out = new PrintWriter(conn.getOutputStream());
      out.print(parm);
      out.flush();
      in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
      String line;
      while ((line = in.readLine()) != null) {
        result += line;
      }
    } catch (Exception ex) {
      LOG.error("post request error!!!");
    } finally {
      try {
        if (out != null) {
          out.close();
        }
        if (in != null) {
          in.close();
        }
      } catch (IOException ex) {
        LOG.error("close stream error!!!");
      }
      return result;
    }
  }
}

某一天线上突然大量http请求失败,然后查看日志,发现了大量的“post request error!!!”错误,此时假如看到这样的日志你可能完全不知道究竟是什么原因导致的,还得继续通过一些其他的手段来定位具体原因。

假如打印的错误日志是这样的:

post request error!!!, url:[http://www.123.test.com], param:[name=jack]
java.net.ConnectException: Connection refused
at java.net.PlainSocketImpl.socketConnect(Native Method)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.net.Socket.connect(Socket.java:579)

那么便能很快地断定是下游http服务问题导致的,且下游http服务域名为www.123.test.com(Connection refused通常是由于下游服务端口未启动引起的),可以迅速找相应的人员进行止损,避免在故障定位阶段耗费大量的时间。

上面举的例子只是一个非常小的例子,实际日常开发中可能碰到的线上问题比这个更加复杂和棘手,总结来看,日志的主要作用有以下几点:

1)日志是系统运行的“照妖镜”,通过它能够实时反映系统的运行状态;

如上图所示,系统A中producer不断产生数据放入到data queue中,sender不断从data queue中取数据发送给下游系统B的receiver,那么对于系统A来说,data queue中的待发送数据量便是一个非常关键的指标,它能够从侧面真实反应当前系统的运行状况,如果data queue中的element个数超过容量的90%了,那么标志着此时系统可能运行不正常了,会有队列堵塞的风险;如果data queue中的element个数不到容量的10%,那么标志着此时系统运行比较正常,出现队列堵塞的风险较低。

如果这个指标没有输出到日志中,开发和运维人员是无法确切知道当前系统A的运行状态的(当然也有其他的方式来获取这个指标,比如通过http接口暴露出来也是一种方式之一)。

2)良好的日志便于后期运维和开发人员迅速定位线上问题,加快止损速度,减少系统故障带来的损失;

3)日志还有另外一个作用便是能够无缝与监控系统结合,通过监控系统进行日志采集,拿到系统运行的相关性能指标,有利于分析系统的性能瓶颈、提前规避风险;

举例说明:

假如有一个商城系统,在初期,数据库通过2台服务器提供服务(1台master,1台slave),此时大部分接口能在秒级内响应用户请求。随着时间的推移,商城系统的用户量逐渐增多,并发查询和写入量都出现了一定的增长,数据库中的数据量也慢慢增多,导致部分sql语句查询越来越慢,突然有一天,数据库的slave机器由于过多的慢查询导致被拖垮,彻底宕机了,导致商城服务不可用。

如果商城系统在日志中记录了每个http请求的耗时情况,通过监控系统配置日志采集,同时配置相应的报警,那么便能提前发现由于业务增长带来的系统性能瓶颈,提前进行系统优化(如机器扩容、sql语句优化、分库分表等),规避风险。

4)便于统计与业务相关的指标数据,进行相关业务分析和功能优化。

举例说明:

比如一个搜索系统,想统计过去一周不同地域(如南北地域)的搜索使用占比,如果日志中本身打印了每个搜索query请求的ip,则很容易统计,否则需要重新上线加日志才能统计。

因此,大家在日常编写代码过程中要注重日志书写的规范性,让它发挥出它应有的价值,在辅助保障我们服务稳定运行的同时,能够有效提升后期系统维护效率。

二.如何规范地打印程序日志?

接下来,我们从以下几个方面来谈谈如何规范地打印日志。

2.1 日志文件命名

通常来说日志文件的命名可包括以下几个关键信息:

  • 类型标识(logTypeName)
  • 日志级别(logLevel)
  • 日志生成时间(logCreateTime)
  • 日志备份编号(logBackupNum)

类型标识:指此日志文件的功能或者用途,比如一个web服务,记录http请求的日志通常命名为request.log或者access.log,request、access就是类型标识,而java的gc日志通常命名为gc.log,这样看一目了然;而通常用来记录服务的整体运行的日志一般用服务名称(serviceName、appKey)或者机器名(hostName)来命名,如 nginx.log;

日志级别:打印日志的时候直接通过文件来区分级别是一种比较推荐的方式,如果把所有级别的日志打到同一个日志文件中,在定位问题时,还需要去文件中进行查找操作,相对繁琐。日志级别一般包括DEBUG、INFO、WARN、ERROR、FATAL这五个级别,在实际编写代码中,可以采取严格匹配模式或者非严格匹配模式,严格匹配模式即INFO日志文件中只打印INFO日志,ERROR日志文件只打印ERROR日志;非严格匹配模式即INFO日志文件可以打印INFO日志、WARN日志、ERROR日志、FATAL日志,WARN日志文件可以打印WARN日志、ERROR日志、FATAL日志,以此类推。

日志生成时间:即在日志文件名称中附带上日志文件创建的时间,方便在查找日志文件时进行排序;

日志备份编号:当进行日志切割时,如果是以文件大小进行滚动,此时可以在日志文件名称末尾加上编号;

2.2 日志滚动

  虽然日志中能够保存系统运行时的关键信息,但是由于磁盘空间有限,所以我们不能无限制地保留日志,因此必须有日志滚动策略。日志滚动通常有以下几种模式:

  第一种:按照时间滚动

  第二种:按照单个日志文件大小滚动

  第三种:同时按照时间和单个日志文件大小滚动。

按照时间滚动,即每隔一定的时间建立一个新的日志文件,通常可以按照小时级别滚动或者天级别滚动,具体采取哪种方式取决于系统日志的打印量。如果系统日志比较少,可以采取天级别滚动;而如果系统日常量比较大,则建议采取小时级别滚动。
按照单个日志文件大小滚动,即每当日志文件达到一定大小则建立一个新的日志文件,通常建议单个日志文件大小不要超过500M,日志文件过大的话,对于日志监控或者问题定位排查都可能会造成一定影响。
按照时间和单个日志文件大小滚动,这种模式通常适用于希望保留一定时间的日志,但是又不希望单个日志文件过大的场景。比如logback就提供了这种配置模式,可参考:https://logback.qos.ch/manual/appenders.html#SizeAndTimeBasedFNATP
  对于日志滚动策略来说,有2个比较关键的参数:最大保留日志数量和最大磁盘占用空间。这2个参数切记一定要设置,如果没有设置,则很有可能会出现把线上机器磁盘打满的情况。

2.3 日志级别

  日志的级别通常有以下几种:

  debug/trace、info、warning、error、fatal

  这几种日志级别的严重程序依次递增:

  debug/trace:debug和trace级别的日志由于打印内容较多,所以通常情况下不适用于线上生产环境使用,一般使用于前期线下环境调试。即使线上环境要使用,也需要通过开关来控制,只在定位追踪线上问题时才开启;

  info:info日志一般用来记录系统运行的关键状态、关键业务逻辑或者关键执行节点。但切记一点,info日志绝不可滥用,如果info日志滥用,则和debug/trace日志没有太大区别了。

  warning:warning日志一般用来记录系统运行时的一些非预期情况,顾名思义,是作为一种警示,提醒开发和运维人员需要关注,但是不用人为介入立刻去处理的。

  error:error日志一般用来记录系统运行时的一些普通错误,这些错误一旦出现,则表示已经影响了用户的正常访问或者使用,通常意味着需要人为介入处理。但很多时候在生产环境中,也不一定是出现error日志就需要人工立即介入处理的,通常会结合error日志的数量以及持续时间来进行综合判断。

  fatal:属于系统致命错误,一般出现意味着系统基本等于挂掉了,需要人工立即介入处理。

  下面举个简单的例子来说明,假如我们有这样一个场景,我们有一个工资计算系统,每隔月1号需要从员工考勤系统获取公司所有员工的考勤数据,然后根据考勤数据来计算上月应发工资,那么需要有一个函数从考勤系统获取员工考勤数据:

public Map<Long, Double> getEmployeeWorkDaysFromAttendance(int year, int month, Set<Long> employeeList) throws BusiessException {
    // 入口关键日志,需要打印关键的参数,因为employeeList可能数量较大,所以次数没有直接打印employeeList列表内容,只打印了size
    logger.info("get employee work days, year:{}, month:{}, employeeList.size:{}", year, month, employeeList.size());

    // 如果需要临时检验员工列表,可以把debug日志开关打开
    if (debugOpen()) {
      logger.debug("employ list content:{}", JSON.toJsonString(employeeList));
    }

    int retry = 1;
    while (retry <= MAX_RETRY_TIMES) {
      try {
        Map<Long, Double> employeeWorkDays = employeeAttendanceRPC.getEmployeeWorkDays(year, month, employeeList);
        logger.info("get employee work days success, year:{}, month:{}, employeeList.size:{}, employeeWorkDays.size:{}", year, month, employeeList.size(), employeeWorkDays.size());
        return employeeWorkDays;
      } catch (Exception ex) {
        logger.warning("rpc invoke failed(employeeAttendanceRPC.getEmployeeWorkDays), retry times:{}, year:{}, month:{}, employeeList.size:{}", retry, year, month, employeeList.size(), ex);

        // 连续重试失败之后,向上跑出异常
        // 对于没有异常机制的语言,此处应该打印error日志
        if (retry == MAX_RETRY_TIMES) {
          throw new BusiessException(ex, "rpc invoke failed(employeeAttendanceRPC.getEmployeeWorkDays)");
        }
      }
      retry++;
    }
  }

2.4 日志打印时机的选择
  由于日志是为了方便我们了解系统当前的运行状况以及定位线上问题,所以日志打印的时机非常重要,如果滥用日志,则会导致日志内容过多,影响问题定位的效率;如果日志打印过少,则容易导致缺少关键日志,导致在线上定位问题时找不到问题根音。因此把握日志打印的时机至关重要,以下是常见的适合打印日志的时机:

1)http调用或者rpc接口调用

  在程序调用其他服务或者系统的时候,需要打印接口调用参数和调用结果(成功/失败)。

2)程序异常

  在程序出现exception的时候,要么选择向上抛出异常,要么必须在catch块中打印异常堆栈信息。不过需要注意的是,最好不要重复打印异常日志,比如在catch块里既向上抛出了异常,又去打印错误日志(对外rpc接口函数入口处除外)。

3)特殊的条件分支

  程序进入到一些特殊的条件分支时,比如特殊的else或者switch分支。比如我们根据工龄计算薪资:

public double calSalaryByWorkingAge(int age) {
    if (age < 0) {
      logger.error("wrong age value, age:{}", age);
      return 0;
    }
    // ..
  }

  理论上工龄不可能小于0,所以需要打印出这种非预期情况,当然通过抛出异常的方式也是可行的。

4)关键执行路径及中间状态

  在一些关键的执行路径以及中间状态也需要记录下关键日志信息,比如一个算法可能分为很多步骤,每隔步骤的中间输出结果是什么,需要记录下来,以方便后续定位跟踪算法执行状态。

5)请求入口和出口

  在函数或者对外接口的入口/出口处需要打印入口/出口日志,一来方便后续进行日志统计,同时也更加方便进行系统运行状态的监控。

2.5 日志内容与格式

  日志打印时机决定了能够根据日志去进行问题定位,而日志的内容决定了是否能够根据日志快速找出问题原因,因此日志内容也是至关重要的。通常来说,一行日志应该至少包括以下几个组成部分:

  logTag、param、exceptionStacktrace

  logTag为日志标识,用来标识此日志输出的场景或者原因,param为函数调用参数,exceptionStacktrace为异常堆栈。举例说明:

good case

public class HttpClient {
    private static final Logger LOG = LoggerFactory.getLogger(HttpClient.class);

    private static int CONNECT_TIMEOUT = 5000;  // unit ms
    private static int READ_TIMEOUT = 10000;   // unit ms

    public static String sendPost(String url, String param) {
      OutputStream out = null;
      BufferedReader in = null;
      String result = "";
      try {
        URL realUrl = new URL(url);
        URLConnection conn = realUrl.openConnection();
        conn.setDoInput(true);
        conn.setDoOutput(true);
        conn.setConnectTimeout(CONNECT_TIMEOUT);
        conn.setReadTimeout(READ_TIMEOUT);
        conn.setRequestProperty("charset", "UTF-8");
        out = new PrintWriter(conn.getOutputStream());
        out.print(parm);
        out.flush();
        in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        String line;
        while ((line = in.readLine()) != null) {
          result += line;
        }
      } catch (Exception ex) {
        // 有关键logTag,有参数信息,有错误堆栈
        LOG.error("post request error!!!, url:[[}], param:[{}]", url, param, ex);
      } finally {
        try {
          if (out != null) {
            out.close();
          }
          if (in != null) {
            in.close();
          }
        } catch (IOException ex) {
          LOG.error("close stream error!!!, url:[[}], param:[{}]", url, param, ex);
        }
        return result;
      }
    }
  }

bad case

public class HttpClient {
  private static final Logger LOG = LoggerFactory.getLogger(HttpClient.class);

  private static int CONNECT_TIMEOUT = 5000;  // unit ms
  private static int READ_TIMEOUT = 10000;   // unit ms

  public static String sendPost(String url, String param) {
    OutputStream out = null;
    BufferedReader in = null;
    String result = "";
    try {
      URL realUrl = new URL(url);
      URLConnection conn = realUrl.openConnection();
      conn.setDoInput(true);
      conn.setDoOutput(true);
      conn.setConnectTimeout(CONNECT_TIMEOUT);
      conn.setReadTimeout(READ_TIMEOUT);
      conn.setRequestProperty("charset", "UTF-8");
      out = new PrintWriter(conn.getOutputStream());
      out.print(parm);
      out.flush();
      in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
      String line;
      while ((line = in.readLine()) != null) {
        result += line;
      }
    } catch (Exception ex) {
      // 没有任何错误信息
      LOG.error("post request error!!!");
    } finally {
      try {
        if (out != null) {
          out.close();
        }
        if (in != null) {
          in.close();
        }
      } catch (IOException ex) {
        LOG.error("close stream error!!!");
      }
      return result;
    }
  }
}

  另外,对于对外http接口或者rpc接口,最好对于每个请求都有requestId,以便跟踪每个请求后续所有的执行路径。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Java SSM框架如何添加写日志功能

    前提:要导入log4j的jar包 在web.xml中输入: <!--日志加载--> <context-param> <param-name>log4jConfigLocation</param-name> <param-value>/WEB-INF/log4j.properties</param-value> </context-param> <listener> <listener-class>o

  • 别在Java代码里乱打日志了,这才是正确的打日志姿势

    使用slf4j 使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一. 实现方式统一使用: Logback框架 打日志的正确方式 什么时候应该打日志 当你遇到问题的时候,只能通过debug功能来确定问题,你应该考虑打日志,良好的系统,是可以通过日志进行问题定为的. 当你碰到if-else 或者 switch这样的分支时,要在分支的首行打印日志,用来确定进入了哪个分支 经常以功能为核心进行开发,你应该在提交代码前,可以确定通过日志可以看到整个流程 基本格式 必须使用参数化信息的方式: lo

  • 浅谈Java slf4j日志简单理解

    一.理解 slf4j(Simple Logging Facade for Java),表示为java提供的简单日志门面,更底层一点说就是接口.通过将程序中的信息导入到日志系统并记录,实现程序和日志系统的解耦 日志门面接口本身通常并没有实际的日志输出能力,它底层还是需要去调用具体的日志框架API的,也就是实际上它需要跟具体的日志框架结合使用.由于具体日志框架比较多,而且互相也大都不兼容,日志门面接口要想实现与任意日志框架结合可能需要对应的桥接器,就好像JDBC与各种不同的数据库之间的结合需要对应的

  • 简单了解Java日志脱敏框架sensitive

    这篇文章主要介绍了简单了解Java日志脱敏框架sensitive,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 问题 为了保证用户的信息安全,敏感信息需要脱敏. 项目开发过程中,每次处理敏感信息的日志问题感觉很麻烦,大部分都是用工具类单独处理,不利于以后统一管理,很不优雅. 于是,就写了一个基于 java 注解的日志脱敏工具. github sensitive 项目介绍 日志脱敏是常见的安全需求.普通的基于工具类方法的方式,对代码的入侵性太强.

  • Java日志框架之logback使用详解

    为什么使用logback 记得前几年工作的时候,公司使用的日志框架还是log4j,大约从16年中到现在,不管是我参与的别人已经搭建好的项目还是我自己主导的项目,日志框架基本都换成了logback,总结一下,logback大约有以下的一些优点: 内核重写.测试充分.初始化内存加载更小,这一切让logback性能和log4j相比有诸多倍的提升 logback非常自然地直接实现了slf4j,这个严格来说算不上优点,只是这样,再理解slf4j的前提下会很容易理解logback,也同时很容易用其他日志框架

  • java启动jar包将日志打印到文本的简单操作

    启动命令: java -jar weichi-1.0.0.jar 将命令打印到1.log上 java -jar weichi-1.0.0.jar > 1.log 补充知识:Java中日志的使用(包含指定日志信息输出到指定地方) 一.前言 对于我们开发者而言,日志存在的意义十分重大:本文主要是自己整理了关于日志的一些知识点,希望能帮助到需要的人,也希望各位能指出我的错误. 二.日志的作用 ① 记录运行信息,方便调试 ② 记录错误信息,方便排查错误 ③ 存储运行记录,方便后期的数据分析 三.日志的主

  • java项目打包成可执行jar用log4j将日志写在jar所在目录操作

    开发一个demo时想将日志输出到最终打包的jar所在目录,从网上学习实验整理之后的配置如下, log4j.properties log4j.rootLogger = INFO,console,logFile log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.Threshold=INFO log4j.appender.console.ImmediateFlush=true log4j.app

  • IntelliJ IDEA远程Debug Linux的Java程序,找问题不要只会看日志了(推荐)

    1 前言 我们习惯于在本地开发的时候debug,能快速定位与解决问题,那部署在服务器上是不是就没有办法了呢?只能通过查看日志来定位? 不是的,在远端的服务器上,我们一样可以debug. 2 IDEA的debug 我们先来看一下在IntelliJ IDEA直接debug是怎样的. 先准备一个简单的Java程序: package com.pkslow.basic; import java.util.Map; public class RemoteDebug { public static void

  • Java日志API管理最佳实践详解

    概述 对于现在的应用程序来说,日志的重要性是不言而喻的.很难想象没有任何日志记录功能的应用程序运行在生产环境中.日志所能提供的功能是多种多样的,包括记录程序运行时产生的错误信息.状态信息.调试信息和执行时间信息等.在生产环境中,日志是查找问题来源的重要依据.应用程序运行时的产生的各种信息,都应该通过日志 API 来进行记录. 很多开发人员习惯于使用 System.out.println.System.err.println 以及异常对象的 printStrackTrace 方法来输出相关信息.这

  • Java工程如何打印程序日志过程解析

    很久之前,有个朋友问我,如果一个老项目让你接手去进行后续维护,你会先从哪里入手.让自己更快地上手项目?当时我没有特别正面去回答这个朋友的问题,我说:一个老项目是否容易上手,一个非常关键的地方就是这个项目的日志是否打得足够好.因为通常来说,一个老项目相对比较稳定了,后续大概率不会有比较大的变更和改动,那么对于这样的项目,核心就是"维稳".但是任何人都无法保证项目在线上运行时不会出线上故障,在出现线上问题或者故障时,如何快速止损就是第一要义,而日志在止损过程中就扮演着非常重要的角色.日志打

  • Java对象转json的方法过程解析

    这篇文章主要介绍了Java对象转json的方法过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1. jsonlib:个人感觉最麻烦的一个需要导入的包也多,代码也相对多一些. 2.Gson:google的 3.FastJson:阿里巴巴的,个人觉得这个比较好,而且据说这个也是性能最好一个. 下面就贴出三种写法的代码,读者可以任选其一去使用.关于demo里面所使用的jar包,可以自行去下载. Jsonlib: package json; i

  • Spring Boot Logback配置日志过程解析

    这篇文章主要介绍了Spring Boot Logback配置日志过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 出于性能等原因,Logback 目前是springboot应用日志的标配: 当然有时候在生产环境中也会考虑和三方中间件采用统一处理方式. 配置时考虑点 支持日志路径,日志level等配置 日志控制配置通过application.yml下发 按天生成日志,当天的日志>50MB回滚 最多保存10天日志 生成的日志中Pattern自

  • Spring Boot2配置服务器访问日志过程解析

    这篇文章主要介绍了Spring Boot2配置服务器访问日志过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Tomcat控制台中看到的日志是服务器的日志,而服务器访问日志则是记录服务处理的请求信息. 开发环境:IntelliJ IDEA 2019.2.2 Spring Boot版本:2.1.8 1.新建一个名称为demo的Spring Boot项目. 2.application.yml 添加配置 server: tomcat: base

  • JavaWeb工程web.xml基本配置过程解析

    这篇文章主要介绍了JavaWeb工程web.xml基本配置过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.理论准备 先说下我记得xml规则,必须有且只有一个根节点,大小写敏感,标签不嵌套,必须配对. web.xml是不是必须的呢?不是的,只要你不用到里面的配置信息就好了,不过在大型web工程下使用该文件是很方便的,若是没有也会很复杂. 那么web.xml能做的所有事情都有那些?其实,web.xml的模式(Schema)文件中定义了多

  • Java简单数据加密方法DES实现过程解析

    这篇文章主要介绍了Java简单数据加密方法DES实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.数据在网络中传输时,需要进行加密处理 双方约定一个相同的key(key不在网络中进行传输,只传输加密数据),然后根据将key根据一定的DES规则转换,得到真正的key,在进行加密和解密,为了增加安全性,加密过程中再加上编码base64转换,解密时先解码base64 加密和解密的完整的代码: package com.cmit.hall.

  • Java获取客户端真实IP地址过程解析

    这篇文章主要介绍了Java获取客户端真实IP地址过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 业务背景 服务器端接收客户端请求的时候,一般需要进行签名验证,客户端IP限定等拦截,在进行IP限定的时候就需要获取客户端真实的IP. 基础知识 访问服务端的方式一般分为两种: 未经过代理,直接访问服务器端: 通过多级代理,最终到达服务器端(nginx,squid,haproxy). 客户端请求信息都包含在HttpServletRequest中

  • Java lambda表达式实现Flink WordCount过程解析

    这篇文章主要介绍了Java lambda表达式实现Flink WordCount过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 本篇我们将使用Java语言来实现Flink的单词统计. 代码开发 环境准备 导入Flink 1.9 pom依赖 <dependencies> <dependency> <groupId>org.apache.flink</groupId> <artifactId>

  • Java加载property文件配置过程解析

    这篇文章主要介绍了java加载property文件配置过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1 properties简介: properties是一种文本文件,内容格式为: key = value #单行注释 适合作为简单配置文件使用,通常作为参数配置.国际化资源文件使用. 对于复杂的配置,就需要使用XML.YML.JSON等了 2 java加载Properties: java加载properties主要通过2个util包下的

  • Java实现Android拼图游戏设计过程解析

    目录 1.项目介绍 2.项目原理 3.项目设计 4.项目实现 5.获取布局 6.准备图片 7.初始化item 8.游戏图片的切换 9.游戏胜利的判断 10.游戏封面 1.项目介绍 这是一款基于 Java 开发的移动端安卓小游戏——大家来拼图 2.项目原理 把选定的一张图片切分很多份,先是 33 格式,在一定的时间内点击格子交换使图形拼成一张完整的图片就算闯关成功,这样关卡也很容易设计,33:44:55:6*6: 3.项目设计 我们需要一个容器,可以放这些图片的块块,为了方便,我们准备使用Rela

随机推荐