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

Posted 叶卡捷琳堡

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了软件工程应用与实践(14)——工具类分析相关的知识,希望对你有一定的参考价值。

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<

以上是关于软件工程应用与实践(14)——工具类分析的主要内容,如果未能解决你的问题,请参考以下文章

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

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

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

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

软件工程应用与实践(16)——总结

软件工程应用与实践(16)——总结