Java基礎——Maven

Maven

  • 搭建一個項目需要考慮哪些內容?
    在這裏插入圖片描述
  • Maven是一個Java項目管理和構建工具
  1. 標準化項目結構
  2. 標準化構建流程
  3. 依賴管理等
  • 標準的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
    在這裏插入圖片描述
  • 常用的插件
    在這裏插入圖片描述
  • 如果提供的插件不滿足要求,可以自己引入插件,常見的有:
  1. maven-shade-plugin:打包所有依賴包生成可執行jar
  2. cobertura-maven-plugin:生成單元測試覆蓋率報告
  3. findbugs-maven-plugin:對java源碼進行靜態分析
  • 我們只需要將插件的配置加入到項目配置文件即可,如下所示:
  1. 打包依賴文件,生成可執行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發送郵件:
    在這裏插入圖片描述
  1. 使用Maven引入Javamail依賴
  2. 確定SMTP服務器信息:域名/端口/使用明文/SSL
  3. 調用API發送(無序關心底層socket連接)
  4. 設置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:將一個接口暴露給遠程
  • 底層原理:
    在這裏插入圖片描述
  1. 定義一個interface
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.time.LocalDateTime;

public interface Clock extends Remote {	// 必須繼承自Remote
	LocalDateTime currentTime() throws RemoteException;	// 必須拋出
}
  1. 實現類
import java.rmi.RemoteException;
import java.time.LocalDateTime;

public class ClockImpl implements Clock {	// 繼承接口
	@Override
	public LocalDateTime currentTime() throws RemoteException {
		return LocalDateTime.now();
	}
}
  1. 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.");
	}
}
  1. 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

  • 瞭解即可:
    在這裏插入圖片描述
  • 特殊字符表示
字符 表示
< &lt;
> &gt;
& &amp;
" &quot;
&apos;

解析XML

  • 兩種標準的解析XML的API:
  1. DOM:一次性讀取XML,在內存中表示爲樹形結構
  2. 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定製序列化和反序列化
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章