目錄
Maven
- 搭建一個項目需要考慮哪些內容?
- Maven是一個Java項目管理和構建工具
- 標準化項目結構
- 標準化構建流程
- 依賴管理等
- 標準的Maven項目結構
- 普通java項目的描述文件
groupId與artifactId被稱爲項目座標,作用就是定位!
groupId一般被設置爲包名,而包名一般使用“域名+其他名稱”,如org.apache
,apache代表公司名
artifactId是唯一的,一般設爲模塊名;即使項目分爲多模塊,也能唯一定位子模塊
創建Maven項目
- 藉助IDEA工具可以創建Maven項目,一般選擇quickstart
- 第一次創建會下載一些插件
- 聲明一個依賴項,Maven可以自動下載並導入相關jar包
依賴管理
- Maven爲我們管理了複雜的依賴包
- Maven設置的依賴關係有以下幾種:
- Maven從何處下載這些依賴?
- Maven的中央倉庫
- 最重要的還是配置文件
pom.xml
,包含項目屬性、依賴、插件等信息;下面是一個Maven項目的配置文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.feiyangedu.sample</groupId>
<artifactId>hellodep</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<version>2.8.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
- 可以給Maven配置鏡像,加速下載,需要在用戶目錄的.m2文件夾下新建
settings
Maven執行流程
- Maven的生命週期由一系列階段(phase)構成
- 使用Maven構建項目就是執行指定的
Phase
- 執行一個Phase其實執行了一系列階段,只不過到此爲止,例如:
- 執行一個Phase又會觸發綁定的一個或多個
Goal
,Goal是最小執行單元
結合命令行使用Maven
- 下載Maven管理工具
- 將bin/目錄添加至環境變量,也可以創建
M2_HOME
環境變量 - 使用
mvn
命令查看是否安裝成功 - 接着便可以使用命令行編譯和測試項目了
mvn clean compile // 可能需要將JDK的目錄設爲%JAVA_HOME%並添加至path,上移
mvn test // 執行項目測試
mvn clean package // 最常用,打成jar包 不會打包依賴的jar
- Maven實際上是通過插件執行各phase
- 常用的插件
- 如果提供的插件不滿足要求,可以自己引入插件,常見的有:
- maven-shade-plugin:打包所有依賴包生成可執行jar
- cobertura-maven-plugin:生成單元測試覆蓋率報告
- findbugs-maven-plugin:對java源碼進行靜態分析
- 我們只需要將插件的配置加入到項目配置文件即可,如下所示:
- 打包依賴文件,生成可執行jar包
// 使用maven-shade-plugin,生成可執行jar包
// 網頁搜索maven shade plugin executable jar
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.sonatype.haven.HavenCli</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
// 將上面的配置放在<dependencies><build> </build></dependencies>中
// 項目文件夾下使用命令:mvn clean package即可將相關依賴也打包
// 注意區分依賴jar包和插件
2. 使用cobertura生成單元測試覆蓋率
// 網頁搜索 cobertura maven plugin usage
// 加入到pom.xml
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<version>2.7</version>
</plugin>
// 貌似這些東西都放在<build>中才行
// 編寫個除main之外的方法
public class App {
public static void main(String[] args) {
Log log = LogFactory.getLog(App.class);
log.info("Hello, world!");
}
public int sum(int ... ns) {
int x = 0;
for (int n:ns){
x += n;
}
return x;
}
}
// 編寫測試
import org.junit.Test;
import static org.junit.Assert.*;
public class AppTest {
@Test
public void testApp() {
// assertTrue(true);
// App.main(null); // 未測試到
assertEquals(6,new App().sum(1,2,3));
}
}
// 命令行:mvn cobertura:cobertura
// 這是一個Goal操作,可以直接執行
- 在target/site中可以查看index.html頁面報告
- 插件的配置和用法需要參考官方文檔
模塊管理
- 將一個大項目拆分成模塊是降低複雜度的有效方法
- Maven可以有效管理多個模塊,IDEA創建多模塊:
// 新建Maven項目(基模塊)mavenFirst,項目中:New——Module——Maven,填入子模塊的標識信息即可
// 也可以通過 File——Project Structure——Modules——New Module創建
// 子模塊配置繼承父模塊:
<parent>
<artifactId>mavenFirst</artifactId>
<groupId>xyz.roykun</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
// 子模塊一般不需再設置groupId,和父模塊一致即可!
// 繼承父模塊後,可以使用其下載好的依賴;也可將公共配置均放置在父模塊
// 在父模塊中設置<modules>,可以在編譯項目的時候自動解析出各模塊,生成文件也存放在各自的target
<modules>
<module>greeting</module>
</modules>
- 在父模塊下使用
mvn clean compile
查看效果
網絡編程
- 這部分需要計算機網絡的基礎知識,包括TCP/IP協議,OSI模型等,這裏不介紹
TCP編程
- 網絡中進程間如何通信?操作系統抽象出socket(套接字),用來對接通信的地址和應用的進程
- 可以將socket理解爲IP地址+端口號
- 進行socket編程分爲客戶端和服務器端
- 客戶端
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
/**
* Java網絡編程
*/
public class TCPClient {
public static void main(String[] args) throws IOException {
InetAddress addr = InetAddress.getLoopbackAddress(); // "127.0.0.1"
try (Socket sock = new Socket(addr, 9090)) { // 客戶端套接字
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(sock.getInputStream(), StandardCharsets.UTF_8))) {
try (BufferedWriter writer = new BufferedWriter(// Filter包裝還記得嗎?
new OutputStreamWriter(sock.getOutputStream(), StandardCharsets.UTF_8))) {
writer.write("time\n");
writer.flush(); // 強制輸出緩衝區
String resp = reader.readLine(); // 讀取服務器返回信息
System.out.println("Response: " + resp);
}
}
}
}
}
- 服務器端
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
public class TCPServer {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(9090); // 服務器端套接字,監聽端口信息
System.out.println("TCP server ready.");
Socket sock = ss.accept(); // 接收連接(套上了)
try (BufferedReader reader = new BufferedReader(// 這個sock是服務器的
new InputStreamReader(sock.getInputStream(), StandardCharsets.UTF_8))) {
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(sock.getOutputStream(), StandardCharsets.UTF_8))) {
String cmd = reader.readLine();
if ("time".equals(cmd)) {
writer.write(LocalDateTime.now().toString() + "\n");
writer.flush();
} else {
writer.write("Sorry?\n");
writer.flush();
}
}
}
sock.close();
ss.close();
}
}
- 先啓動服務器端程序開啓監聽,再啓動客戶端程序即可得到Response
TCP多線程編程
- 一個ServerSocket可以與多個客戶端建立聯繫
- 服務器端使用無限循環建立連接:
- 客戶端
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class TCPClient {
public static void main(String[] args) throws Exception {
InetAddress addr = InetAddress.getByName("localhost");
try (Socket sock = new Socket(addr, 9090)) {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(sock.getInputStream(), StandardCharsets.UTF_8))) {
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(sock.getOutputStream(), StandardCharsets.UTF_8))) {
writer.write("time\n");
writer.flush();
String resp = reader.readLine();
System.out.println("Response: " + resp);
Thread.sleep(1000);
writer.write("q\n");
writer.flush();
resp = reader.readLine();
System.out.println("Response: " + resp);
}
}
}
}
}
- 服務器端
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
public class TCPServer {
public static void main(String[] args) throws Exception {
@SuppressWarnings("resource")
ServerSocket ss = new ServerSocket(9090);
System.out.println("TCP server ready.");
for (;;) {
Socket sock = ss.accept();
System.out.println("Accept from " + sock.getRemoteSocketAddress());
TimeHandler handler = new TimeHandler(sock);// 開啓一個新的線程
handler.start();
}
}
}
class TimeHandler extends Thread {
Socket sock;
TimeHandler(Socket sock) {
this.sock = sock;
}
@Override
public void run() {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(sock.getInputStream(), StandardCharsets.UTF_8))) {
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(sock.getOutputStream(), StandardCharsets.UTF_8))) {
for (;;) {
String cmd = reader.readLine();
if ("q".equals(cmd)) {
writer.write("bye!\n");
writer.flush();
break;
} else if ("time".equals(cmd)) {
writer.write(LocalDateTime.now().toString() + "\n");
writer.flush();
} else {
writer.write("Sorry?\n");
writer.flush();
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
this.sock.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 使用線程池可以提高效率
UDP編程
- 不需要建立連接,可以直接收發數據
- 客戶端:
- 服務器端:
- 使用
DatagramSocket
建立套接字 - 使用
receive/send
方法發送接收數據 - 沒有IO流接口
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
public class UDPClient {
public static void main(String[] args) throws Exception {
InetAddress addr = InetAddress.getLoopbackAddress();
try (DatagramSocket sock = new DatagramSocket()) {
sock.connect(addr, 9090); // 並不建立連接
byte[] data = "time".getBytes(StandardCharsets.UTF_8);
DatagramPacket packet = new DatagramPacket(data, data.length);
sock.send(packet);
System.out.println("Data was sent.");
Thread.sleep(1000);
byte[] buffer = new byte[1024];
DatagramPacket resp = new DatagramPacket(buffer, buffer.length);
sock.receive(resp);
byte[] respData = resp.getData();
String respText = new String(respData, 0, resp.getLength(), StandardCharsets.UTF_8);
System.out.println("Response: " + respText);
}
}
}
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
public class UDPServer {
public static void main(String[] args) throws Exception {
@SuppressWarnings("resource")
DatagramSocket ds = new DatagramSocket(9090);// 服務器端監聽
System.out.println("UDP server ready.");
for (;;) {
// receive:
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
ds.receive(packet); // 接收客戶端信息
byte[] data = packet.getData();
String s = new String(data, StandardCharsets.UTF_8);
System.out.println("Packet received from: " + packet.getSocketAddress() + " " + s);
// send:
String resp = LocalDateTime.now().toString();
packet.setData(resp.getBytes(StandardCharsets.UTF_8));
ds.send(packet); // 發送
}
}
}
郵件收發
發送郵件
- 發送郵件就是從MUA到MDA的過程
- 從MUA到MTA發送email使用的協議是SMTP
- 端口號25,加密端口465/587
- 使用JavaMail API發送郵件:
- 使用Maven引入Javamail依賴
- 確定SMTP服務器信息:域名/端口/使用明文/SSL
- 調用API發送(無序關心底層socket連接)
- 設置debug模式可以排查錯誤
import java.util.Properties;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
public class SendMail {
final String smtpHost;
final String username;
final String password;
final boolean debug;
public SendMail(String smtpHost, String username, String password) {
// 這裏填入你自己的某個郵箱信息
//使用哪個郵箱就用哪個的SMTP服務器,有的服務器可能需要開啓一下SMTP服務
this.smtpHost = smtpHost;
this.username = username;
this.password = password;
this.debug = true;
}
public static void main(String[] args) throws Exception {
// 發送者信息
SendMail sender = new SendMail("smtp.qq.com", "[email protected]", "xxxxxx");
Session session = sender.createTLSSession();
Message message = createTextMessage(session, "[email protected]", "[email protected]", "Java郵件測試",
"Hello, 這是一封javamail測試郵件!");
Transport.send(message);
}
Session createSSLSession() {
Properties props = new Properties();
props.put("mail.smtp.host", this.smtpHost); // SMTP主機名
props.put("mail.smtp.port", "465"); // 主機端口號
props.put("mail.smtp.auth", "true"); // 是否需要用戶認證
// 啓動SSL:
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.put("mail.smtp.socketFactory.port", "465");
Session session = Session.getInstance(props, new Authenticator() {
// 用戶名+口令認證:
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(SendMail.this.username, SendMail.this.password);
}
});
session.setDebug(this.debug); // 顯示調試信息
return session;
}
Session createTLSSession() {
Properties props = new Properties();
props.put("mail.smtp.host", this.smtpHost); // SMTP主機名
props.put("mail.smtp.port", "587"); // 主機端口號
props.put("mail.smtp.auth", "true"); // 是否需要用戶認證
props.put("mail.smtp.starttls.enable", "true"); // 啓用TLS加密
Session session = Session.getInstance(props, new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(SendMail.this.username, SendMail.this.password);
}
});
session.setDebug(this.debug); // 顯示調試信息
return session;
}
Session createInsecureSession(String host, String username, String password) {
Properties props = new Properties();
props.put("mail.smtp.host", this.smtpHost); // SMTP主機名
props.put("mail.smtp.port", "25"); // 主機端口號
props.put("mail.smtp.auth", "true"); // 是否需要用戶認證
Session session = Session.getInstance(props, new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(SendMail.this.username, SendMail.this.password);
}
});
session.setDebug(this.debug); // 顯示調試信息
return session;
}
// 發送文本郵件使用的方法
static Message createTextMessage(Session session, String from, String to, String subject, String body)
throws MessagingException {
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
message.setSubject(subject, "UTF-8");
message.setText(body, "UTF-8"); // 發送文本郵件
return message;
}
// 發送HTML格式文本郵件使用的方法
static Message createHtmlMessage(Session session, String from, String to, String subject, String body)
throws MessagingException {
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
message.setSubject(subject, "UTF-8");
message.setText(body, "UTF-8", "html");
return message;
}
// 發送帶附件郵件使用的方法
static Message createMessageWithAttachment(Session session, String from, String to, String subject, String body,
String fileName, InputStream input) throws MessagingException, IOException {
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
message.setSubject(subject, "UTF-8");
Multipart multipart = new MimeMultipart(); // 帶附件
// 添加text:
BodyPart textpart = new MimeBodyPart();
textpart.setContent(body, "text/html;charset=utf-8");
multipart.addBodyPart(textpart);
// 添加image:
BodyPart imagepart = new MimeBodyPart();
imagepart.setFileName(fileName);
imagepart.setDataHandler(new DataHandler(new ByteArrayDataSource(input, "application/octet-stream")));
multipart.addBodyPart(imagepart);
message.setContent(multipart);
return message;
}
// 將附件內嵌在文本中使用的方法
static Message createMessageWithInlineImage(Session session, String from, String to, String subject, String body,
String fileName, InputStream input) throws MessagingException, IOException {
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
message.setSubject(subject, "UTF-8");
Multipart multipart = new MimeMultipart();
// 添加text:
BodyPart textpart = new MimeBodyPart();
textpart.setContent(body, "text/html;charset=utf-8");
multipart.addBodyPart(textpart);
// 添加image:
BodyPart imagepart = new MimeBodyPart();
imagepart.setFileName(fileName);
imagepart.setDataHandler(new DataHandler(new ByteArrayDataSource(input, "image/jpeg")));
// 與HTML的<img src="cid:img01">關聯:
imagepart.setHeader("Content-ID", "<img01>");
multipart.addBodyPart(imagepart);
message.setContent(multipart);
return message;
}
}
接收郵件
- 常見的協議有
POP3/IMAP
協議,這裏介紹POP3
// 可以打印出郵件信息
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import javax.mail.Address;
import javax.mail.Authenticator;
import javax.mail.BodyPart;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.URLName;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMessage.RecipientType;
import javax.mail.internet.MimeUtility;
import com.sun.mail.pop3.POP3SSLStore;
public class Pop3 {
final String popHost;
final String username;
final String password;
final boolean debug;
public Pop3(String popHost, String username, String password) {
this.popHost = popHost;
this.username = username;
this.password = password;
this.debug = true;
}
public static void main(String[] args) throws Exception {
Pop3 pop = new Pop3("pop.qq.com", "[email protected]", "yr143364");
Folder folder = null;
Store store = null;
try {
store = pop.createSSLStore();
folder = store.getFolder("INBOX");
folder.open(Folder.READ_WRITE);
System.out.println("Total messages: " + folder.getMessageCount());
System.out.println("New messages: " + folder.getNewMessageCount());
System.out.println("Unread messages: " + folder.getUnreadMessageCount());
System.out.println("Deleted messages: " + folder.getDeletedMessageCount());
Message[] messages = folder.getMessages();
for (Message message : messages) {
printMessage((MimeMessage) message);
}
} finally {
if (folder != null) {
try {
folder.close(true);
} catch (MessagingException e) {
e.printStackTrace();
}
}
if (store != null) {
try {
store.close();
} catch (MessagingException e) {
e.printStackTrace();
}
}
}
}
public Store createSSLStore() throws MessagingException {
Properties props = new Properties();
props.setProperty("mail.store.protocol", "pop3");
props.setProperty("mail.pop3.port", "995"); // 主機端口號
props.setProperty("mail.pop3.host", this.popHost);// POP3主機名
// 啓動SSL:
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.put("mail.smtp.socketFactory.port", "995");
URLName url = new URLName("pop3", this.popHost, 995, "", this.username, this.password);
Session session = Session.getInstance(props, null);
session.setDebug(this.debug); // 顯示調試信息
Store store = new POP3SSLStore(session, url);
store.connect();
return store;
}
Session createTLSStore() {
Properties props = new Properties();
props.put("mail.smtp.host", this.popHost); // POP3主機名
props.put("mail.smtp.port", "587"); // 主機端口號
props.put("mail.smtp.auth", "true"); // 是否需要用戶認證
props.put("mail.smtp.starttls.enable", "true"); // 啓用TLS加密
Session session = Session.getInstance(props, new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(Pop3.this.username, Pop3.this.password);
}
});
session.setDebug(this.debug); // 顯示調試信息
return session;
}
Session createInsecureStore() {
Properties props = new Properties();
props.put("mail.smtp.host", this.popHost); // POP3主機名
props.put("mail.smtp.port", "25"); // 主機端口號
props.put("mail.smtp.auth", "true"); // 是否需要用戶認證
Session session = Session.getInstance(props, new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(Pop3.this.username, Pop3.this.password);
}
});
session.setDebug(this.debug); // 顯示調試信息
return session;
}
static void printMessage(MimeMessage msg) throws IOException, MessagingException {
System.out.println("--------------------");
System.out.println("Subject: " + MimeUtility.decodeText(msg.getSubject()));
System.out.println("From: " + getFrom(msg));
System.out.println("To: " + getTo(msg));
System.out.println("Sent: " + msg.getSentDate().toString());
System.out.println("Seen: " + msg.getFlags().contains(Flags.Flag.SEEN));
System.out.println("Priority: " + getPriority(msg));
System.out.println("Size: " + msg.getSize() / 1024 + "kb");
System.out.println("Body: " + getBody(msg));
System.out.println("--------------------");
System.out.println();
}
static String getFrom(MimeMessage msg) throws IOException, MessagingException {
Address[] froms = msg.getFrom();
return addressToString(froms[0]);
}
static String getTo(MimeMessage msg) throws MessagingException, IOException {
// 使用 msg.getAllRecipients() 獲取所有收件人
Address[] tos = msg.getRecipients(RecipientType.TO);
List<String> list = new ArrayList<>();
for (Address to : tos) {
list.add(addressToString(to));
}
return String.join(", ", list);
}
static String addressToString(Address addr) throws IOException {
InternetAddress address = (InternetAddress) addr;
String personal = address.getPersonal();
return personal == null ? address.getAddress()
: (MimeUtility.decodeText(personal) + " <" + address.getAddress() + ">");
}
static String getPriority(MimeMessage msg) throws MessagingException {
String priority = "Normal";
String[] headers = msg.getHeader("X-Priority");
if (headers != null) {
String header = headers[0];
if ("1".equals(header) || "high".equalsIgnoreCase(header)) {
priority = "High";
} else if ("5".equals(header) || "low".equalsIgnoreCase(header)) {
priority = "Low";
}
}
return priority;
}
static String getBody(Part part) throws MessagingException, IOException {
if (part.isMimeType("text/*")) {
return part.getContent().toString();
}
if (part.isMimeType("multipart/*")) {
Multipart multipart = (Multipart) part.getContent();
for (int i = 0; i < multipart.getCount(); i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
String body = getBody(bodyPart);
if (!body.isEmpty()) {
return body;
}
}
}
return "";
}
}
HTTP請求響應協議
- 基於TCP的高級網絡傳輸協議
- POST請求既有
header
又有body
- HTTP響應格式:
- 這裏介紹HTTP客戶端請求:
- Java提供了HttpURLConnection實現客戶端
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class Response {
final int code;
final byte[] data;
public Response(int code, byte[] data) {
this.code = code;
this.data = data;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(1024);
sb.append(code).append("\n");
String s = new String(data, StandardCharsets.UTF_8);
if (s.length() > 1024) {
sb.append(s.substring(0, 1024)).append("\n...");
} else {
sb.append(s);
}
return sb.toString();
}
}
public class HttpClient {
public static void main(String[] args) throws Exception {
Response resp = get("https://www.douban.com");
System.out.println(resp);// get請求
Map<String, String> postMap = new HashMap<>();// 模擬post表單
postMap.put("form_email", "test");
postMap.put("form_password", "password");
Response postResp = post("https://www.douban.com/accounts/login", "application/x-www-form-urlencoded",
toFormData(postMap));// post請求
System.out.println(postResp);
}
static Response get(String theUrl) {
System.err.println("GET: " + theUrl);
HttpURLConnection conn = null;
try {
URL url = new URL(theUrl);
conn = (HttpURLConnection) url.openConnection();
ByteArrayOutputStream responseBuffer = new ByteArrayOutputStream();
try (InputStream input = conn.getInputStream()) {
byte[] buffer = new byte[1024];
for (;;) {
int n = input.read(buffer);
if (n == (-1)) {
break;
}
responseBuffer.write(buffer, 0, n);
}
}
return new Response(conn.getResponseCode(), responseBuffer.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
static Response post(String theUrl, String contentType, String contentData) {
System.err.println("POST: " + theUrl);
HttpURLConnection conn = null;
try {
URL url = new URL(theUrl);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true); // 表示需要發送數據
byte[] postData = contentData.getBytes(StandardCharsets.UTF_8);
conn.setRequestProperty("Content-Type", contentType);
conn.setRequestProperty("Content-Length", String.valueOf(postData.length));
try (OutputStream output = conn.getOutputStream()) {
output.write(postData);
}
ByteArrayOutputStream responseBuffer = new ByteArrayOutputStream();
try (InputStream input = conn.getInputStream()) {
byte[] buffer = new byte[1024];
for (;;) {
int n = input.read(buffer);
if (n == (-1)) {
break;
}
responseBuffer.write(buffer, 0, n);
}
}
return new Response(conn.getResponseCode(), responseBuffer.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
static String toFormData(Map<String, String> map) throws IOException {
List<String> list = new ArrayList<>(map.size());
for (String key : map.keySet()) {
list.add(key + "=" + URLEncoder.encode(map.get(key), "UTF-8"));
}
return String.join("&", list);
}
}
RMI遠程調用
- Remote Method Invocation:將一個接口暴露給遠程
- 底層原理:
- 定義一個interface
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.time.LocalDateTime;
public interface Clock extends Remote { // 必須繼承自Remote
LocalDateTime currentTime() throws RemoteException; // 必須拋出
}
- 實現類
import java.rmi.RemoteException;
import java.time.LocalDateTime;
public class ClockImpl implements Clock { // 繼承接口
@Override
public LocalDateTime currentTime() throws RemoteException {
return LocalDateTime.now();
}
}
- server
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class ClockServer {
public static void main(String[] args) throws Exception {
Clock impl = new ClockImpl();
// 得到一個stub對象
Clock stub = (Clock) UnicastRemoteObject.exportObject(impl, 1099);// 1099端口
LocateRegistry.createRegistry(1099);
Registry registry = LocateRegistry.getRegistry();
registry.bind("Clock", stub); // 綁定stub對象而非實例,遠程調用需要使用這個名稱
System.out.println("Clock server ready.");
}
}
- client
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.time.LocalDateTime;
public class ClockClient {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.getRegistry(null); // null = "localhost",建立遠程連接
Clock clock = (Clock) registry.lookup("Clock"); // 獲得遠程對象的引用
LocalDateTime dt = clock.currentTime(); // 調用RMI方法
System.out.println("RMI result: " + dt);
}
}
- 服務器端通過自動生成的stub類接收遠程調用請求
XML&JSON
XML
- eXtensible Markup Language,是一種數據格式
- 可以描述非常複雜的數據結構,用於傳輸和存儲數據
- 特點:純文本(默認utf8編碼),可嵌套(結構化)
.dtd
表示數據結構可以被DTD或XSD驗證
isbn
元素必須包含屬性lang
- 瞭解即可:
- 特殊字符表示
字符 | 表示 |
---|---|
< | < |
> | > |
& | & |
" | " |
’ | ' |
解析XML
- 兩種標準的解析XML的API:
- DOM:一次性讀取XML,在內存中表示爲樹形結構
- SAX:以流的形式讀取,使用事件回調
- DOM(文檔對象模型 )
注:W3C DOM 標準被分爲 3 個不同的部分:
- 核心 DOM - 針對任何結構化文檔的標準模型
- XML DOM - 針對 XML 文檔的標準模型
- HTML DOM - 針對 HTML 文檔的標準模型
- 核心API
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();// 操作工場
DocumentBuilder db = dbf.newDocumentBuilder();// 操作類
Document doc = db.parse('./hello.xml');// 傳入文檔
Element elt = doc.getDocumentElement();
- Java Dom Node
- 所有的節點包括Document都可以看做Node
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
public class DOMSample {
static final String XML_URL = "http://rss.sina.com.cn/tech/internet/home28.xml";
public static void main(String[] args) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(XML_URL);
printNode(doc, 0);
}
static void printNode(Node n, int indent) {
for (int i = 0; i < indent; i++) {
System.out.print(' ');
}
switch (n.getNodeType()) {
case Node.DOCUMENT_NODE: // 文檔節點也視爲Node
System.out.println("Document: " + n.getNodeName());
break;
case Node.ELEMENT_NODE:
System.out.println("Element: " + n.getNodeName());
break;
case Node.TEXT_NODE:
System.out.println("Text: " + n.getNodeName() + " = " + n.getNodeValue());
break;
case Node.ATTRIBUTE_NODE:
System.out.println("Attr: " + n.getNodeName() + " = " + n.getNodeValue());
break;
case Node.CDATA_SECTION_NODE:
System.out.println("CDATA: " + n.getNodeName() + " = " + n.getNodeValue());
break;
case Node.COMMENT_NODE:
System.out.println("Comment: " + n.getNodeName() + " = " + n.getNodeValue());
break;
default:
System.out.println("NodeType: " + n.getNodeType() + ", NodeName: " + n.getNodeName());
}
for (Node child = n.getFirstChild(); child != null; child = child.getNextSibling()) {
printNode(child, indent + 1);
}
}
}
JSON
- json是一種類似JavaScript對象的數據表示格式(JavaScript Object Notation)
- 去除了JavaScript的執行語句,僅保留數據
- 特點:只允許使用utf-8編碼,必須使用雙引號,特殊字符使用
\
轉義 - 適合表示層次結構,結構簡單速度快
- 瀏覽器直接支持json的讀寫,因此非常適合REST API做數據通訊
REST API是前後端分離的一套標準
// json數據,key-value形式
{
"data": [{
"name": "RoyKun"
},
{
"age": 22
},
{
"hobby": [{
"first": "study"
}, {
"second": "play"
}, {
"third": "sport"
}]
},
{
"language": ["C", "java", "Python"]
},
"just a test"
]
}
解析JSON
- JSR 353 API
- 第三方庫(Jackson、Gson等)
- 使用Jackson:
// 使用Jackson
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
public class JsonToBean {
// 豆瓣API搜索
static final String JSON_URL = "https://api.douban.com/v2/book/search?q=";
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());// 日期時間格式
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);// 忽略在javabean中找不到的屬性
String json = search("java");// 返回下載的json字符串
SearchResult result = mapper.readValue(json, SearchResult.class);// 可以直接把一個json字符串反序列化爲指定的javabean對象,這裏指定爲SearchResult了
// SearchResult的定義會事先參考下載的json數據
// 再將javabean對象轉化爲json輸出
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result));
// 這樣倒騰一下可以理解成規範數據
}
static String search(String q) {
HttpURLConnection conn = null;
StringBuilder sb = new StringBuilder(4096);
try {
URL url = new URL(JSON_URL + URLEncoder.encode(q, "UTF-8"));
conn = (HttpURLConnection) url.openConnection();
if (200 == conn.getResponseCode()) {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream(), "UTF-8"))) {
char[] buffer = new char[1024];
for (;;) {
int n = reader.read(buffer);
if (n == (-1)) {
break;
}
sb.append(buffer, 0, n);
}
}
return sb.toString();
}
throw new RuntimeException("Bad response code: " + conn.getResponseCode());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
}
// javabean
import java.util.List;
public class SearchResult { // 數據成員
public long count;
public long total;
public List<Book> books;
}
import java.time.LocalDate;
import java.util.List;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
public class Book {
public String id;
public String title;
public String subtitle;
public List<String> author;
// 使用Annotion定製序列化和反序列化
@JsonSerialize(using = CustomLocalDateSerializer.class)
@JsonDeserialize(using = CustomLocalDateDeserializer.class)
public LocalDate pubdate;// 會用到上面的序列化和反序列化,都是自定義的對數據成員的小操作
public String url;
public String price;
}
在解釋一下什麼是JavaBean:
JavaBean 是一種JAVA語言寫成的可重用組件。爲寫成JavaBean,類必須是具體的和公共的,並且具有無參數的構造器。JavaBean 通過提供符合一致性設計模式的公共方法暴露成員屬性,即set和get方法。衆所周知,屬性名稱符合這種模式,其他Java 類可以通過反射機制發現和操作這些JavaBean 的屬性
// 自定義註解,格式化數據
import java.io.IOException;
import java.time.LocalDate;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
public class CustomLocalDateSerializer extends JsonSerializer<LocalDate> {// 數據序列化
@Override
public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(value.toString()); // 使用自定義的方法封裝對象
}
}
import java.io.IOException;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
public class CustomLocalDateDeserializer extends JsonDeserializer<LocalDate> {// 反序列化數據
static DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-M-d", Locale.US);
@Override
public LocalDate deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
String s = p.getValueAsString();
if (s != null) {
try {
return LocalDate.parse(s, FORMATTER); // 使用自定義的格式解析對象
} catch (DateTimeException e) {
// ignore
}
}
return null;
}
}
- Jackson提供了讀寫json的API,實現了json和javabean的相互轉換,可使用Annotion定製序列化和反序列化