软件工程应用与实践(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)——工具类分析的主要内容,如果未能解决你的问题,请参考以下文章