软件工程应用与实践(14)——工具类分析(3)

2021SC@SDUSC

一、概述

在之前的博客中,我们分析了老年健康管理系统中的部分工具类,以及jwt相关的类,本篇博客继续分析之前未分析的工具类,为项目收尾做准备。

本篇博客计划分析一下工具类
在这里插入图片描述
关于Http请求的HttpHelper和HttpUtils,关于知识图谱树的TreeUtil和TreeNode(这个在之前的博客中有所涉及,本次是更详细的分析),另外还有UUIDUtils,以及其他工具类。

在本次阅读源码的过程中,关于请求和UUID的部分遇到了一些问题,这个过程中通过小组讨论,成功解决了相关的问题。

二、源码分析

2.1 HttpHelper类

本类是对HTTP工具的封装类,便于在其他类中调用

首先我们看到该类的引入,其中最关键的引入是ServletRequest,这个类用于获取InputStream,在后续的分析中我们可以看到

import javax.servlet.ServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;

在接下来的方法中,我们可以看到该类的一个方法(本类只有一个方法,是一个静态方法)

  • 首先创建StringBuilder对象,这个对象用于拼接字符串,在java中,如果涉及字符串的频繁拼接,一般不建议直接使用String对象,而应使用StringBuilder对象
  • 之后通过request.getInputStream()获取请求输入流
  • 通过while循环读取对应的数据,将对应的字符串保存在StringBuilder对象中
  • 在finally语句中,
public static String getBodyString(ServletRequest request) {
    StringBuilder sb = new StringBuilder();
    BufferedReader reader = null;
    try (InputStream inputStream = request.getInputStream()) {
        reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
        String line = "";
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
    } catch (IOException e) {
        log.warn(e.getMessage());
    } finally {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
    }
    return sb.toString();
}

2.2 HttpUtils类

在上面的分析中,我们分析了HttpHelper类,该类主要利用ServletRequest类获取InputStream,并获取请求中对应的数据,本工具类是对上面类的提升和拓展

首先我们看到该类的引入

  • 在该类的引入中,我们可以看到,引入了java.net包下的大量内容,并且,引入了URL类,URLConnection类,这几个类是后续方法中进行网路连接的重点,
  • 本类还引入了日志类,如Logger和LoggerFactory(工厂)
import javax.net.ssl.*;
import java.io.*;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.security.cert.X509Certificate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

接下来我们看到该类的属性

private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);

这里使用设计模式中的工厂模式,使用LoggerFactory的getLogger方法获取对应的日志对象,后续方法中,会使用这个日志对象进行打印输出。

接下来我们看到该类的第一个方法

/**
 * 向指定 URL 发送GET方法的请求
 *
 * @param url 发送请求的 URL
 * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
 * @return 所代表远程资源的响应结果
 */
public static String sendGet(String url, String param)
{
    return sendGet(url, param, CommonConstants.UTF8);
}

可以看到,在该类的这个静态的sendGet方法中,传入了url和param两个参数,并且调用了该类的另一个方法(该方法的重载),并且传入了字符编码为UTF-8

通过阅读源码我们知道,在CommonConstants类中,保存了很多的常量(项目中需要用到的和常量),其中就有UTF-8这个常量,另外,还有GBK这个常量

public class CommonConstants {
    /**
     * UTF-8 字符集
     */
    public static final String UTF8 = "UTF-8";

    /**
     * GBK 字符集
     */
    public static final String GBK = "GBK";
}

这里仅摘取了部分变量

接下来我们看到另一个send方法

  • 首先声明了一个StringBuilder对象和一个BufferedReader对象,第一个对象用于拼接字符串,另一个用于读取请求传来的数据(拼接字符串的部分与上一个方法类似,这里不再赘述)
  • 创建一个URL对象,并传入对应的URLString
  • 通过这个URL对象的openConnection方法,得到一个URLConnection对象,这个连接对象中保存了连接的相关信息
  • 向URLConnection对象中设置相关的属性值,并调用connect对象连接
  • 通过URLConnection对象的getInputStream方法获取流对象,在通过while循环读取通过请求获取到的数据
  • 接下来是对各种异常的处理,在finally语句中释放资源
  • 调用StringBuilder的toString方法,返回读取到的数据
/**
  * 向指定 URL 发送GET方法的请求
  *
  * @param url 发送请求的 URL
  * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
  * @param contentType 编码类型
  * @return 所代表远程资源的响应结果
  */
 public static String sendGet(String url, String param, String contentType)
 {
     StringBuilder result = new StringBuilder();
     BufferedReader in = null;
     try
     {
         String urlNameString = url + "?" + param;
         log.info("send : {}", urlNameString);
         URL realUrl = new URL(urlNameString);
         URLConnection connection = realUrl.openConnection();
         connection.setRequestProperty("accept", "*/*");
         connection.setRequestProperty("connection", "Keep-Alive");
         connection.connect();
         in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));
         String line;
         while ((line = in.readLine()) != null)
         {
             result.append(line);
         }
         log.info("receive : {}", result);
     }
     catch (ConnectException e)
     {
         log.error("ConnectException, url=" + url + ",param=" + param, e);
     }
     catch (SocketTimeoutException e)
     {
         log.error("SocketTimeoutException, url=" + url + ",param=" + param, e);
     }
     catch (IOException e)
     {
         log.error("IOException, url=" + url + ",param=" + param, e);
     }
     catch (Exception e)
     {
         log.error("Exception, url=" + url + ",param=" + param, e);
     }
     finally
     {
         try
         {
             if (in != null)
             {
                 in.close();
             }
         }
         catch (Exception ex)
         {
             log.error("Exception, url=" + url + ",param=" + param, ex);
         }
     }
     return result.toString();
 }

接下来是关于post请求的方法

  • 由于该方法发送的是post请求,而post请求传递参数不能使用queryString的形式
  • 该方法使用PrintWriter对象发送对应的参数
  • PrintWriter对象的创建时,传入conn.getOutputStream()作为对应的参数
  • 通过out.print和out.flush方法发送对应的数据
  • 其他内容与get请求类似
/**
 * 向指定 URL 发送POST方法的请求
 *
 * @param url 发送请求的 URL
 * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
 * @return 所代表远程资源的响应结果
 */
public static String sendPost(String url, String param)
{
    PrintWriter out = null;
    BufferedReader in = null;
    StringBuilder result = new StringBuilder();
    try
    {
        String urlNameString = url;
        log.info("sendPost : {}", urlNameString);
        URL realUrl = new URL(urlNameString);
        URLConnection conn = realUrl.openConnection();
        conn.setRequestProperty("accept", "*/*");
        conn.setRequestProperty("connection", "Keep-Alive");
        conn.setRequestProperty("Accept-Charset", "utf-8");
        conn.setRequestProperty("contentType", "utf-8");
        conn.setDoOutput(true);
        conn.setDoInput(true);
        out = new PrintWriter(conn.getOutputStream());
        out.print(param);
        out.flush();
        in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
        String line;
        while ((line = in.readLine()) != null)
        {
            result.append(line);
        }
        log.info("receive : {}", result);
    }
    catch (ConnectException e)
    {
        log.error("ConnectException, url=" + url + ",param=" + param, e);
    }
    catch (SocketTimeoutException e)
    {
        log.error("SocketTimeoutException, url=" + url + ",param=" + param, e);
    }
    catch (IOException e)
    {
        log.error("IOException, url=" + url + ",param=" + param, e);
    }
    catch (Exception e)
    {
        log.error("Exception, url=" + url + ",param=" + param, e);
    }
    finally
    {
        try
        {
            if (out != null)
            {
                out.close();
            }
            if (in != null)
            {
                in.close();
            }
        }
        catch (IOException ex)
        {
            log.error("Exception, url=" + url + ",param=" + param, ex);
        }
    }
    return result.toString();
}

接下来看到下一个方法,通过方法名可以知道,该方法的主要作用是发送SSL连接的POST请求

  • 可以看到该方法首先创建了一个StringBuilder对象,之后又通过拼接字符串的方法得到对应的url。
  • 通过SSLContext的getInstance方法获取SSLContext对象,这个getInstance方法用于放回对象
  • 之后对该对象进行初始化操作,调用init方法
  • 之后获取连接对象HttpsURLConnection,通过HttpsURLConnection对象的setSSLSocketFactory方法,将SSLContext的Factory对象传入
  • 之后通过输入流读取对应的数据,利用while循环
  • 在循环中,原本的编码是ISO-8859-1,需要转换为UTF-8
  • 在catch语句块中处理可能抛出的各种异常,并在finally语句块中释放资源
public static String sendSSLPost(String url, String param)
{
    StringBuilder result = new StringBuilder();
    String urlNameString = url + "?" + param;
    try
    {
        log.info("sendSSLPost : {}", urlNameString);
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
        URL console = new URL(urlNameString);
        HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
        conn.setRequestProperty("accept", "*/*");
        conn.setRequestProperty("connection", "Keep-Alive");
        conn.setRequestProperty("Accept-Charset", "utf-8");
        conn.setRequestProperty("contentType", "utf-8");
        conn.setDoOutput(true);
        conn.setDoInput(true);

        conn.setSSLSocketFactory(sc.getSocketFactory());
        conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
        conn.connect();
        InputStream is = conn.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String ret = "";
        while ((ret = br.readLine()) != null)
        {
            if (ret != null && !ret.trim().equals(""))
            {
                result.append(new String(ret.getBytes("ISO-8859-1"), "utf-8"));
            }
        }
        log.info("receive : {}", result);
        conn.disconnect();
        br.close();
    }
    catch (ConnectException e)
    {
        log.error("ConnectException, url=" + url + ",param=" + param, e);
    }
    catch (SocketTimeoutException e)
    {
        log.error("SocketTimeoutException, url=" + url + ",param=" + param, e);
    }
    catch (IOException e)
    {
        log.error("IOException, url=" + url + ",param=" + param, e);
    }
    catch (Exception e)
    {
        log.error("Exception, url=" + url + ",param=" + param, e);
    }
    return result.toString();
}

在这里需要简单介绍一下SSL,SSL的全称是Secure Sockets Layer(安全套接字协议),是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层与应用层之间对网络连接进行加密。

SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。

在这里插入图片描述

上面是SSL客户端与服务器的握手过程

2.3 树节点及建树过程

在之前的博客中,我曾介绍过老年健康知识图谱中关于知识树的节点和建树过程,这次我们需要更详细地分析一下建树的过程,以及树节点(树节点不仅保存了子节点,还保存了父节点),上次的博客仅分析了子节点的情况

可以看到,树节点类保存了id,父节点id,以及子节点,子节点用链表保存,同样是TreeNode对象,里面提供了get和set方法,其中关于子节点的set方法,使用了List的add方法

public class TreeNode {
    protected int id;
    protected int parentId;
    protected List<TreeNode> children = null;

    public List<TreeNode> getChildren() {
        return children;
    }
    public void setChildren(List<TreeNode> children) {
        this.children = children;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public int getParentId() {
        return parentId;
    }
    public void setParentId(int parentId) {
        this.parentId = parentId;
    }
    public void add(TreeNode node){
        children.add(node);
    }
}

在构建树的TreeUtil类中,我们可以看到,本类中使用了范型T,在bulid方法中,T要求继承TreeNode类,而TreeNode类中的各个属性都是protected,子类可以继承。在该类中,可以看到,建树的方法一共有两个,一个是两层循环建树,另一个是递归建树

  • 在build方法中,通过foreach循环创建树
  • 首先判断是否为根节点,如果是,就加入树中,树的结构用一个链表存储
  • 如果子节点为空,则创建一个空的ArrayList
  • 在递归建树方法中,调用findChildren方法,而findChildren方法中再次调用自身,实现递归建树。
  • 当子节点为空时,递归逐层返回
public class TreeUtil{
  /**
   * 两层循环实现建树
   * 
   * @param treeNodes 传入的树节点列表
   * @return
   */
  public static <T extends TreeNode> List<T> bulid(List<T> treeNodes,Object root) {

    List<T> trees = new ArrayList<T>();

    for (T treeNode : treeNodes) {

      if (root.equals(treeNode.getParentId())) {
        trees.add(treeNode);
      }

      for (T it : treeNodes) {
        if (it.getParentId() == treeNode.getId()) {
          if (treeNode.getChildren() == null) {
            treeNode.setChildren(new ArrayList<TreeNode>());
          }
          treeNode.add(it);
        }
      }
    }
    return trees;
  }

  /**
   * 使用递归方法建树
   * 
   * @param treeNodes
   * @return
   */
  public static <T extends TreeNode> List<T> buildByRecursive(List<T> treeNodes,Object root) {
    List<T> trees = new ArrayList<T>();
    for (T treeNode : treeNodes) {
      if (root.equals(treeNode.getParentId())) {
        trees.add(findChildren(treeNode, treeNodes));
      }
    }
    return trees;
  }

  /**
   * 递归查找子节点
   * 
   * @param treeNodes
   * @return
   */
  public static <T extends TreeNode> T findChildren(T treeNode, List<T> treeNodes) {
    for (T it : treeNodes) {
      if (treeNode.getId() == it.getParentId()) {
        if (treeNode.getChildren() == null) {
          treeNode.setChildren(new ArrayList<TreeNode>());
        }
        treeNode.add(findChildren(it, treeNodes));
      }
    }
    return treeNode;
  }
}

2.4 关于UUID

在老年健康知识图谱系统中,由于需要产生唯一标识,因此封装了UUID相关的工具类。该UUID工具类的代码如下

可以看到,这个类中有一个String类型的数组,里面保存了26个字母,在之后生成uuid的方法中会使用。

public static String[] chars = new String[] { "a", "b", "c", "d", "e", "f",
        "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
        "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
        "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
        "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
        "W", "X", "Y", "Z" };

该方法主要用于生成简单的uuid

  • 在该方法中,首先创建了一个StringBuilder对象
  • 接着,通过java.util.UUID类的randomUUID方法生成对应的uuid,并将-替换成空字符串
  • 之后利用for循环,对我们得到的uuid进行修改
  • 使用Integer.parseInt(str, 16)将字符串转为16进制数
  • 使用StringBuilder的append方法连接生成的字符串
public class UUIDUtils {
    public static String generateShortUuid() {
        StringBuffer shortBuffer = new StringBuffer();
        String uuid = UUID.randomUUID().toString().replace("-", "");
        for (int i = 0; i < 8; i++) {
            String str = uuid.substring(i * 4, i * 4 + 4);
            int x = Integer.parseInt(str, 16);
            shortBuffer.append(chars[x % 0x3E]);
        }
        return shortBuffer.toString();
    }
}

因此可见,该方法的主要目的是,对java.util.UUID类中生成的randomUUID进行修改,修改成短uuid,由于for循环只循环8次,因此生成的是8位的短uuid。

三、总结

在本次的源码分析中,通过与小组成员和老师的交流,我成功地分析了这部分代码。总的来说,通过这一部分代码的阅读,对于项目中请求发送,建树过程,生成8位短uuid等有了更深入的了解,希望在之后的项目实训中,能用上本项目的一些设计思想。同时也感谢小组成员和老师对我的帮助。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值