以太網驅動的流程淺析(一)-Ifconfig主要流程【原創】

以太網驅動的流程淺析(一)-Ifconfig主要流程

Author:張昺華
Email:920052390@qq.com
Time:2019年3月23日星期六

此文也在我的個人公眾號以及《Linux內核之旅》上有發表:

很喜歡一群人在研究技術,一起做有意思的東西,一起分享技術帶給我們的快樂,也希望中國有更多的人熱愛技術,喜歡一起研究、分享技術,然後可以一起用我們的技術來做一些好玩的東西,可以為這個社會創造一些東西來改善人們的生活。
如下是本人調試過程中的一點經驗分享,以太網驅動架構畢竟涉及的東西太多,如下僅僅是針對加載流程和圍繞這個問題產生的分析過程和驅動加載流程部分,並不涉及以太網協議層的數據流程分析。

【硬件環境】 Imx6ul

【Linux kernel版本】 Linux4.1.15

【以太網phy】 Realtek8201f

一個以太網的案例來講述Ifconfig

1. 問題描述

【問題】

機器通過usb方式下載了mac地址后,發現以太網無法正常使用,敲命令 ifconfig eth0 up出現:ifconfig: SIOCSIFFLAGS: No such device,而對於沒有下載以太網mac address的機器表現均正常。調試過程中發現在以太網控制器代碼中加入一些printk,不正常的機器又正常了,打印的位置不同,機器的以太網有時會正常,有時會異常,十分詭異。

2. 原因分析

【根本原因】

reset時序問題導致,phy reset的時間不滿足時序要求。如下圖,如果硬件接了reset引腳,應滿足時序要求在reset保持10ms有效電平后,還必須維持至少150ms才可以訪問phy register,也就是reset要在B點之後才可以正常通過MDC/MDIO來訪問phy register。如果是不使用硬件reset,使用軟件reset方式,那也要至少在A點,也就是在reset維持10ms有效電平后,再維持3.5個clk才能正常訪問phy register。

那為什麼下載了mac地址后才異常呢?不下載的又正常呢?

【原因分析】

freescale控制器獲取mac address流程如下:
1)模塊化參數設置,如果沒有跳到步驟2;
2)device tree中設置,如果沒有跳到步驟3;
3)from flash / fuse / via platform data,如果沒有跳到步驟4;
4)FEC mac registers set by bootloader===》即靠usb方式下載mac address ,如果沒有跳到步驟5;
5)靠kernel算一個隨機數mac address出來,然後寫入mac

那為什麼下載了mac地址后才異常呢?
下了mac后,會執行步驟4,不會執行步驟5,此時目前的代碼不滿足150ms的時序要求,無法訪問phy register,
導致phy_id獲取不到,因此phy_device也不會創建

那為什麼不下載的又正常呢?
不下載mac address,會執行步驟5 ,步驟5中調用了函數eth_hw_addr_random
剛好滿足了150ms的時序要求,所以才可以正常

跟入代碼eth_hw_addr_random看下

繼續看:

最終調用了kernel提供的獲取隨機數的一個函數,這塊代碼比較多就不繼續追下去了。

所以這塊步驟五的代碼剛剛好好在這個硬件條件下,恰巧滿足了150ms的reset時序要求,所以以太網才可以正常。

3. 以太網流程分析跟蹤

3.1 Ifconfig主要流程

回歸主題,根據這個ifconfig失敗的現象,我們追蹤一下code:
ifconfig: SIOCSIFFLAGS: No such device,既然出現了這個問題log,我們就從應用層的log入手,首先我們使用strace命令來追蹤下系統調用,以便於我們追蹤內核代碼實現。
strace ifconfig eth0 up跟蹤一下

可以發現主要是ioctl的操作,SIOCSIFFLAGS,然後我們需要了解下這個宏的意思,說白了就是設置各種flag,靠ioctl第三個參數把所需要的動作flag傳入,比如說此時要對eth0進行up動作,那麼就傳入IFF_UP,例如:
struct ifreq ifr;

我們看這些主要是想知道為什麼會打印這個log:
ifconfig: SIOCSIFFLAGS: No such device
那麼內核中又是對ioctl做了什麼動作呢?因為strace命令讓我們知道了系統調用調用函數,我們可以在kernel中直接搜索SIOCSIFFLAGS,或者去以太網驅動net目錄下直接搜索更快。最終我搜到了,路徑是:net/ipv4/devinet.c
我們可以看到內核的宏定義:

查看devinet.c的代碼,我們找到了那個宏,也就是做devinet_ioctl函數中,這也就是應用層的ioctl最終的實現函數,然後我們在裏面加一些打印,

通過打印結果我們可以確認是這個函數devinet_ioctl為應用層的ioctl的實現函數,因為你在kernel中搜SIOCSIFFLAGS宏的話會有很多地方出現的,所以我們需要確認我們找的函數
沒問題:

看到這裏返回值ret是-19,那麼我們繼續順着追蹤下去,上代碼:
net/core/dev.c

繼續追蹤:net/core/dev.c

因此我們可以看到返回值-19就是如下代碼產生的

因此我們需要追蹤__dev_open函數,繼續看代碼:

通過調試,比如說加打印,或者是經驗我們可以推斷出是這裏返回的-19,那麼這個ndo_open又是在哪裡回調的呢?

我們可以看到ops這個結構的結構體
struct net_device dev
const struct net_device_ops
ops = dev->netdev_ops;

這裏熟悉驅動的朋友應該可以猜到這在在freescale的以太網控制器驅動中一定有它的實現
net_device_ops就是kernel提供給drvier操作net_device的一些操作方法,具體實現自然由相應廠商的driver自己去實現。
路徑:drivers/net/Ethernet/freescale/fec_main.c

我們可以在這個fec_enet_open函數中加入dump_stack來看下整個調用情況
我們打出kernel的dump_stack信息來看:

這個調用過程就是應用層ioctl一直到kernel最底層fec_enet_open的過程。
應用代碼這樣:

總體流程:kill() -> kill.S -> swi陷入內核態 -> 從sys_call_table查看到sys_kill -> ret_fast_syscall -> 回到用戶態執行kill()下一行代碼
Ioctl《==ret_fast_syscall 《==SyS_ioctl《==do_vfs_ioctl《==vfs_ioctl《==sock_ioctl《==
devinet_ioctl《==dev_change_flags《==__dev_change_flags《==__dev_open《==fec_enet_open
我附上每個函數的代碼:
如果大家想看系統調用流程的話,參考這篇,我就不做這塊的說明了:
Linux系統調用(syscall)原理
http://gityuan.com/2016/05/21/syscall/
Arm Linux系統調用流程詳細解析
https://www.cnblogs.com/cslunatic/p/3655970.html

4. 網址分享

http://stackoverflow.com/questions/5308090/set-ip-address-using-siocsifaddr-ioctl
http://www.ibm.com/support/knowledgecenter/ssw_aix_72/com.ibm.aix.commtrf2/ioctl_socket_control_operations.htm
https://lkml.org/lkml/2017/2/3/396

http://www.latelee.org/programming-under-linux/linux-phy-driver.html
Linux PHY幾個狀態的跟蹤
http://www.latelee.org/programming-under-linux/linux-phy-state.html
第十六章PHY -基於Linux3.10
https://blog.csdn.net/shichaog/article/details/44682931

“`

End

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品在網路上成為最夯、最多人討論的話題?

※高價收購3C產品,價格不怕你比較

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

JavaScript 關於setTimeout與setInterval的小研究

說明

在開發功能“軌跡播放”時,遇到了一個情況。
原先同事已經開發了一版,這次有個新功能:點擊線上任意一點后可以從點擊處重新播放。
看了一下原來的版本,發現同時使用了setTimeout和setInterval,兩者配合實現點線播放。
簡單結構如下

        function test() {
            setInterval(function () {
                console.log("interval");
                                //省略插值方法得到arr
                                (...)
                play(arr);
            }, 2000);
        }
        function play(arr) {
            setTimeout(function () {
                play(arr);
                console.log("setTimeout");
            }, 40);
        }

我覺得這個結構欠妥,兩個定時器配合必定會出現失誤!因此重構了一版,將兩個定時器改為一個,用setInterval解決。
但是此時我並不知道欠妥欠在什麼地方,缺乏理論支持,現在閑下來仔細研究了一下

找問題

在仔細研究了舊版本后,我先把舊版本結構扒了出來,排除其他因素,自己模擬了一個簡單版(就是上面的代碼)
setTimeout:在執行時,是在載入后延遲指定時間后,去執行一次表達式,僅執行一次
setInterval:在執行時,它從載入后,每隔指定的時間就執行一次表達式

  • 實驗一:在使用setInterval和setTimeout方法上,並沒有什麼問題,決定跑一下,結果如下

從結果得出兩點結論

  1. setTimeout與setInterval並不是50倍速度配合運行着
  2. 兩次interval間,timeout運行的次數越來越多,表明setInterval運行間隔越來越長,延遲越來越大
  • 實驗二:加一點人工干預再執行
        function test() {
            setInterval(function () {
                console.log("interval");
                play();
            }, 2000);
        }
        function play() {
                    //延遲執行
            for (var i = 0; i < 100000000; i++) {
                
             }
            setTimeout(function () {
                play();
                console.log("setTimeout");
            }, 40);
        }

從結果得出兩點結論

  1. setInterval可能會隨函數處理時間,減少間隔
  2. 推測,因為Javascript是單線程的,setInterval和setTimeout是放隊列里執行的,很容易受到回調事件影響
  • 實驗三:拖動縮放瀏覽器

從結果得出結論

  1. 當瀏覽器標籤切換到其他頁面,或者瀏覽器最小化,會影響計時器,兩者會出現間隔減小

涉及知識點

綜上實驗結果,網上搜集了一些資料能說明問題:

  1. JavaScript是單線程,但是瀏覽器是多線程,Javascript是瀏覽器多線程中的一個線程。(圖參考自:)
  1. Javascript會把執行的回調函數、瀏覽器的觸發事件、UI渲染事件,先放到隊列中,隊列根據先進先出的規則,依次執行他們,當執行到隊列中的setInterval時很難保證其與setTimeout同步關係還保持。
  2. setInterval無視代碼錯誤:代碼報錯,但是setInterval依舊會按時執行,不會中斷。
  3. setInterval無視網絡延遲:如果調用ajax或其他服務,他不會管是否返回回調,會繼續按時執行。
  4. setInterval不保證執行:因為setInterval會定時執行,如果函數邏輯很長,間隔時間內執行不完,後續方法會被拋棄。
  5. 會受瀏覽器狀態影響,tab切換、最小化等

解決方案

在做軌跡播放時,setInterval的延遲還在可接受範圍之內,但是網上給出的最佳解決方案是用setTimeout做。
setTimeout只會執行一次,在執行完成后,重新啟動新的Timeout,時間runtime計算設置為差時,減少出現間隔越來越大的情況

        function test() {
                    //runTime,計算差時
                        runTime = 1000 - 執行耗時;
            setTimeout(callback, runTime);
        }
        setTimeout(test, 1000);

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※高價收購3C產品,價格不怕你比較

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

3c收購,鏡頭 收購有可能以全新價回收嗎?

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

Matplotlib入門簡介

Matplotlib是一個用Python實現的繪圖庫。現在很多機器學習,深度學習教學資料中都用它來繪製函數圖形。在學習算法過程中,Matplotlib是一個非常趁手的工具。

一般概念

圖形(figure)
類似於畫布,它包含一個或多個子坐標系(axes)。至少有一個坐標系才能有用。

下面是一段簡單的示例代碼,只是創建了一個子坐標系

import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure() #空figure,沒有坐標系.
fig.suptitle("No Axes on this figure") #設置頂部標題

fig, ax_lst = plt.subplots(2, 2) #一個2 x 2 網格的的坐標系

坐標系(Axes): figure的繪圖區域。一個figure只能有可以有多個Axes,但一個Axes只能位於一個figure中。一個Axes包含兩個(在3D情況下有3個)坐標軸(Axis),Axis的主要作用是限制數據的範圍(可使用Axes的set_xlim()和set_ylim()方法設限制)。每個坐標系有一個標題(title),使用set_title()設置,一個x軸標籤(x-label,使用set_xlabel()設置),一個y軸標籤(y-label,使用set_ylabel()設置)。

坐標軸(Axis): 類似於数字線( number-line-like)的對象,可設置圖表的限制並生成刻度和刻度標籤。Locator對象用來決定刻度的位置。刻度標籤字符串使用Formattor格式化。恰當的Locator和Formattor組合可以有效地控制刻度位置可刻度標籤。

畫家(Artist): 一般來說,所有你能在figure中看到的都使用一個畫家(Artist)(包括Figure, Axes和Axis對象),這其中包含:文本對象(Text), 2D線條(line2D), 集合對象,點(Path)對象等等。當一個figure被渲染時,所有的Artist都會在畫布上回繪圖。大多數Artist被綁定在一個Axes上,不能被多個Axes共享,或從一個Axes移動到另一個。

繪圖函數的輸入類型

所有的繪圖函數期待的輸入類型是np.array或np.ma.masked_array。看起來像數組的類比如np.martrix可能能正常使用。

Matplotlib,pyplot和pylab之間的關係

Matplotlib是整個包,matplotlib.pyplot是Matplotlib中的一個模塊。
對pyplot模塊中的函數來說,總是有一個”當前的”figure和axes。例如在下面的例子中,第一次調用pyplot.plot會創建一個axes,接下來的一系列pyplot.plot調用迴向同一個axes中添加多條線,plt.xlabel, plt.ylabel, plt.title and plt.legend調用回在這個axes中添加標籤,標題和圖例。

x = np.linspace(0, 2, 100)

plt.plot(x, x, label='linear')
plt.plot(x, x**2, label='quadratic')
plt.plot(x, x**3, label='cubic')

plt.xlabel('x label')
plt.ylabel('y label')

plt.title("Simple Plot")

plt.legend()

plt.show()
這段代碼輸出的圖形如下。可以把最後一行的plt.show(),改成plt.savefig("simplePlot.png"),把圖形輸出成png格式的文件。

pylab是一個可方便地把matplotlib.pyplot和numpy批量導入到一個獨立命名空間的模塊,現已被棄用,建議使用pyplot代替。

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

USB CONNECTOR 掌控什麼技術要點? 帶您認識其相關發展及效能

※高價3c回收,收購空拍機,收購鏡頭,收購 MACBOOK-更多收購平台討論專區

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

收購3c瘋!各款手機、筆電、相機、平板,歡迎來詢價!

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

ASP.NET Core gRPC 使用 Consul 服務註冊發現

一. 前言

gRPC 在當前最常見的應用就是在微服務場景中,所以不可避免的會有服務註冊與發現問題,我們使用gRPC實現的服務可以使用 Consul 或者 etcd 作為服務註冊與發現中心,本文主要介紹Consul。

二. Consul 介紹

Consul是一種服務網絡解決方案,可跨任何運行平台以及公共或私有雲來連接和保護服務。它可以讓你發現服務並保護網絡流量。它可以在Kubernetes中使用,實現服務發現和服務網格功能(k8s默認etcd)。 提供安全服務通訊,保護和觀察服務之間的通信,而無需修改其代碼。 提供動態負載平衡, 使用Consul和HAProxy,Nginx或F5自動執行負載均衡器配置。 Consul 可以用於服務發現和服務網格。

翻譯自官網

三. Consul 安裝配置

安裝

Consul 下載地址:

根據自己的系統來選擇,我這裏選擇的是 Windows 版本的,直接解壓即可運行。

啟動

consul agent -dev -ui

本文不詳細介紹Consul使用,如需請自行查看相關資料

四. .NET Core Consul 客戶端的選擇

Consul 提供了 HTTP API 的方式來進行通訊,我們可以直接調用API或者是使用第三方封裝好的客戶端組件,通過Nuget搜索可以發現許多。

這裏面我沒有一一測試,但是目前使用量最多的 Consul 組件是不支持設置 GRPC 健康檢查的,而且 github 也停止了更新。

所以我 Fork 了這個倉庫,然後添加了 GRPC 的健康檢查支持,本文也將使用這個庫,歡迎大家使用:

因為原倉庫已經 Archived 了,所以我才 Fork 了自己改一下,改動很小,不影響原來的穩定性。

Nuget:

Github:

求個star

關於支持 GPRC 健康檢查的好處:

偷個懶,不翻譯了,摘自GRPC官方文檔

五. 註冊GRPC服務與健康檢查

基於前文()的Demo

1.為服務端項目安裝 NConsul.AspNetCore ( )

這裏面對 AspNetCore 做了適配,使用簡單。

2.在 Startup 的 ConfigureServices方法內進行配置

public void ConfigureServices(IServiceCollection services)
{
    services.AddGrpc();

    services.AddConsul("http://localhost:8500")
        .AddGRPCHealthCheck("localhost:5000")
        .RegisterService("grpctest","localhost",5000,new []{"xc/grpc/test"});
}

AddConsul 添加 Consul Server 地址。

AddGRPCHealthCheck 添加 GRPC 健康檢查,即健康檢查走的是 GRPC 協議,該值為 GRPC 服務的地址,不需要path不需要提供 http/https

RegisterService 註冊服務

到這步,還不能啟動運行,如果運行健康檢查是會失敗的。

3.編寫 Health Check 服務 **

對於 GRPC 的健康檢查,官方有標準的定義,新建一個 proto 文件,命名為 HealthCheck.proto

syntax = "proto3";

package grpc.health.v1;

message HealthCheckRequest {
    string service = 1;
}

message HealthCheckResponse {
    enum ServingStatus {
        UNKNOWN = 0;
        SERVING = 1;
        NOT_SERVING = 2;
    }
    ServingStatus status = 1;
}

service Health {
    rpc Check(HealthCheckRequest) returns (HealthCheckResponse);

    rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}

這裏面的內容不得更改,是官方標準,資料見後文

這裏編譯一下項目,以便自動生成代碼。

然後,添加一個服務的實現類 HealthCheckService

public class HealthCheckService:Health.HealthBase
{
    public override Task<HealthCheckResponse> Check(HealthCheckRequest request, ServerCallContext context)
    {
        //TODO:檢查邏輯
        return Task.FromResult(new HealthCheckResponse(){Status = HealthCheckResponse.Types.ServingStatus.Serving});
    }

    public override async Task Watch(HealthCheckRequest request, IServerStreamWriter<HealthCheckResponse> responseStream, ServerCallContext context)
    {
        //TODO:檢查邏輯
        await responseStream.WriteAsync(new HealthCheckResponse()
            {Status = HealthCheckResponse.Types.ServingStatus.Serving});
    }
}

示例代碼直接返回了檢查結果,實際使用中應該在這裏編寫檢查邏輯,然後根據情況返回相應的檢查結果。檢查結果有3種情況:

結果類型 說明
Unknown 未知狀態
Serving 正常
NotServing 異常,不能提供服務

最後別忘了註冊服務:

4.測試運行

啟動 GRPC 服務

然後訪問 http://localhost:8500/ui 訪問 Consul 控制台

可以看到服務成功註冊,並且健康檢查也是通過了的。通過控制台日誌,還可以看到健康檢查的請求:

六. 客戶端使用服務發現

客戶端項目安裝 Consul 組件,然後改造下代碼:

static async Task Main(string[] args)
{
    var serviceName = "grpctest";
    var consulClient = new ConsulClient(c => c.Address = new Uri("http://localhost:8500"));
    var services = await consulClient.Catalog.Service(serviceName);
    if (services.Response.Length == 0)
    {
        throw new Exception($"未發現服務 {serviceName}");
    }

    var service = services.Response[0];
    var address = $"http://{service.ServiceAddress}:{service.ServicePort}";

    Console.WriteLine($"獲取服務地址成功:{address}");

    //啟用通過http使用http2.0
    AppContext.SetSwitch(
        "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
    var channel = GrpcChannel.ForAddress(address);
    var catClient = new LuCat.LuCatClient(channel);
    var catReply = await catClient.SuckingCatAsync(new Empty());
    Console.WriteLine("調用擼貓服務:"+ catReply.Message);
    Console.ReadKey();
}

通過服務名稱獲取服務地址,然後來進行訪問。

運行測試:

可以看到,成功的從Consul獲取了我們的服務地址,然後調用。

六. 參考資料

  • gRPC in Asp.Net Core :

  • GPRC Health Check Doc:

  • 本文 Demo:

  • 本系列文章目錄:

  • NConsul:

  • by Edison Zhou

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

3c收購,鏡頭 收購有可能以全新價回收嗎?

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

賣IPHONE,iPhone回收,舊換新!教你怎麼賣才划算?

MySQL權限管理

目錄

設置用戶與權限

一個MySQL系統可能有許多用戶。為了安全起見,root用戶通常只用管理目的。對於每個需要使用該系統的用戶,應該為他們創建一個賬號和密碼。這些用戶名和密碼不必與MySQL之外的用戶名稱和密碼(例如,Linux或NT用戶名和密碼)相同。同樣的原則也適合於root用戶。對於系統用戶和MySQL用戶最好使用不同的密碼,這一點對root用戶尤其應該這樣。
為用戶設置密碼不是必須的,但是強烈建議為所有創建的用戶設定密碼。要建立一個Web數據庫,最好為每個網站應用程序建立一個用戶。你可能會問,“為什麼要這麼做呢?”—-答案在於權限。

用戶管理

我們的MySQL的用戶,都被記錄在了mysql數據庫的user表中,如下圖:

在MySQL數據庫中,一個完整的用戶賬號應該包含用戶名登錄主機,也就是說 用戶名@主機名才是一個完整的用戶賬號。

創建用戶基本語法:

CREATE USER 用戶名@主機名 IDENTIFIED BY '密碼';

刪除用戶基本語法:

DROP USER 用戶名@主機名;

修改用戶密碼:

- 修改自己的密碼
set password = password('新密碼');
- 修改別人的密碼(需要有該權限)
set password for 用戶名@主機名 = password('新密碼');

MySQL權限系統介紹

MySQL的最好特性之一是支持複雜的權限系統權限是對特定對象執行特定操作的權力,它與特定用戶相關。其概念非常類似於文件的權限。擋在MySQL中創建一個用戶時,就賦予了該用戶一定的權限,這些權限指定了用戶在本系統中可以做什麼和不可以做什麼。

最少權限原則

最少權限原則可以用來提高任何計算機系統的安全性。它是一個基本的、但又是非常重要的而且容易為我們忽略的原則。該原則包含如下內容:

一個用戶(或一個進程)應該擁有能夠執行分配給他的任務的最低級別的權限。

該原則同樣適用於MySQL,就像它應用於其他地方一樣。例如,要在網站上運行查詢,用戶並不需要root用戶所擁有的所有權限。因此,我們應該創建另一個用戶,這個用戶只有訪問我們剛剛建立的數據庫的必要權限。

創建用戶:GRANT命令

GRANT和REVOKE命令分別用來授予取消MySQL用戶的權限,這些權限分4個級別。它們分別是:

  • 全局
  • 數據庫

GRANT命令用來創建用戶並賦予他們權限。GRANT命令的常見形式是:

GRANT privileges [columns]
ON item
TO user_name [IDENTIFIED BY 'password']
[REQUIRE ssl_options]
[WITH [GRANT OPTION | limit_options]]

普通寫法可以簡略如下:

GRANT 權限列表 ON 數據庫.表 TO 用戶名@主機名 [IDENTIFIED BY '密碼'];

identified by可以省略,也可以寫出
(1)如果寫了,用戶存在,就是修改用戶的密碼
(2)如果寫了,該用戶不存在,就是創建用戶,同時指定密碼

方括號內的子句是可選的。在本語法中,出現了許多佔位符。第一個佔位符是 privileges,應該是由逗號分開的一組權限。MySQL已經有一組已定義的權限。它們在下一節詳細介紹。
佔位符 columns是可選的。可以用它對每一個列指定權限。也可以使用單列的名稱或者逗號分開的一組列的名稱。

佔位符 item是新權限的所應用於的數據庫或表。可以將項目指定為“.”,而將權限應用於所有數據庫。這叫做賦予全局權限。如果沒有使用在特定的數據庫,也可以通過只指定*完成賦予全局權限。更常見的是,以dbname.*的形式指定數據庫中所有的表,以dbname.tablename的形式指定單個表,或者通過指定tablename來指定特定的列。這些分別表示其他3個可以利用的權限:數據庫、表、列。如果在輸入命令的時候正在使用一個數據庫,tablename本身將被解釋成當前數據庫中的一個表。

user_name應該是用戶登錄MySQL所使用的用戶名。請注意,它不必與登錄系統時所使用的用戶名相同。MySQL中的user_name也可以包含一個主機名。可以用它來區分如itbsl(解釋成itbsl@localhost)和itbsl@somewhere.com。這是非常有用的一項能力,因為來自不同域的用戶經常可能使用同一個名字。這也提高了安全性能,因為可以指定用戶從什麼地方連接到本機,甚至可以指定它們在特定的地方可以訪問那些表和數據庫。

password應該是用戶登錄時使用的密碼。常見的密碼選擇規則在這裏都適用。我們後面將更詳細地講述安全問題,但是密碼應該不容易被猜出來。這意味着,密碼不應該是一個字段單詞或與用戶名相同。理想的密碼應該是大、小寫字母和非字母的組合。

REQUIRE子句允許指定用戶是否必須通過加密套接字連接,或者指定其它的SSL選項,關於SSL到MySQL連接的更多信息,請參閱MySQL手冊。

WITH GRANT OPTION選項,如果指定,表示允許指定的用戶向別人授予自己所擁有的權限。
我們也可以指定如下所示的WITH子句:
MAX_QUERIES_PER_HOUR n
或者
MAX_UPDATES_PER_HOUR n
或者
MAX_CONNECTIONS_PER_HOUR n
這些子句可以指定每個用戶每小時執行的查詢、更新和鏈接的數量。在共享的系統上限制單個用戶的負載時,這些子句是非常有用的。
權限存儲在名為mysql的數據庫中的5個系統中。這些表分別是mysql.user、mysql.db、mysql.host、mysql.tables_priv和mysql.columns_priv。作為GRANT命令的替代,可以直接修改這些表。

權限的類型和級別

MySQL中存在3個基本類型的權限:適用於賦予一般用戶的權限、適用於賦予管理員的權限和幾個特定的權限。任何用戶都可以被賦予這3類權限,但是根據最少權限原則,最好嚴格限定只將管理員類型的權限賦予管理員。

我們應該只賦予用戶訪問他們必須使用的數據庫和表的權限。而不應該將訪問mysql的權限賦予不是管理員的人。mysql數據庫是所有用戶名、密碼等信息存儲的地方。

常規用戶的權限直接與特定的SQL命令類型以及用戶是否被允許運行它們相關。下錶所示的是基本用戶權限。“應用於”列下面的對象給出了該類型權限可以授予的對象。

用戶的權限

權限 應用於 描述
SELECT 表、列 允許用戶從表中查詢行(記錄)
INSERT 表、列 允許用戶在表中插入新行
UPDATE 表、列 允許用戶修改現存表裡行中的值
DELETE 允許用戶刪除現存表的行
INDEX 允許用戶創建和拖動特定表索引
ALTER 允許用戶改變現存表的結構,例如,可添加列、重命名列或表、修改列的數據類型
CREATE 數據庫、表 允許用戶創建新數據庫或表。如果在GRANT中指定了一個特定的數據庫或表,它們只能夠創建該數據庫或表,即它們必須首先刪除(drop)它
DROP 數據庫、表 允許用戶拖動(刪除)數據庫或表

從系統的安全性方面考慮,適於常規用戶的權限大多數是相對無害的。ALTER權限通過重命名表可能會影響權限系統,但是大多數用戶需要它。安全性常常是可用性與保險性的折中。遇到ALTER的時候,應當做出自己的選擇,但是通常還是會將這個權限授予用戶。

下面的表給出了適用於管理員用戶使用的權限。
可以將這些權限授予非管理員用戶,這樣做的時候要非常小心。
FILE權限有些不同,它對普通用戶非常有用,因為它可以將數據從文件載入數據庫,從而可以節省許多時間,否則,每次將數據輸入數據庫都需要重新輸入,這很浪費時間。
然而,文件載入可以用來載入MySQL可識別的任何文件,包括屬於其他用戶的數據庫和潛在的密碼文件。授予該權限的時候需要小心,或者自己為用戶載入數據。

管理員權限

權限 描述
CREATE TEMPORARY TABLES 允許管理員在CREATE TABLE語句中使用TEMPORARY關鍵字
FILE 允許將數據從文件讀入表,或從表讀入文件
LOCK TABLES 允許使用LOCK TABLES語句
PROCESS 允許管理員查看屬於所有用戶的服務器進程
RELOAD 允許管理員重新載入授權表、清空授權、主機、日誌和表
REPLICATION CLIENT 允許管理員重新載入授權表、和從機(Slave)上使用SHOW STATUS
REPLICATION SLAVE 允許複製從服務器連接到主服務器
SHOW DATABASES 允許使用SHOW DATABASES語句查看所有數據庫列表。沒有這個權限,用戶只能看到他們能夠看到的數據庫
SHUTDOWN 允許管理員關閉MySQL服務器
SUPER 允許管理員關閉屬於任何用戶的的線程

特別的權限

權限 描述
ALL 授予上面兩個表列表的所有權限。也可以將ALL寫成ALL PRIVILEGES
USAGE 不授予權限。這創建一個用戶並允許他登錄,但是不允許進行任何操作。通常會在以後授予該用戶更多的權限

REVOKE命令

與GRANT相反的命令是REVOKE。它用來從一個用戶收回權限。在語法上與GRANT非常相似:

REVOKE privileges [(columns)]
ON item
FROM user_name

中文翻譯:

REVOKE 權限列表 ON 數據庫.對象 FROM 用戶名@主機名;

如果已經給出了WITH GRANT OPTION子句,可以按如下方式撤銷它(以及所有其他權限):

REVOKE ALL PRIVILEGES, GRANT
FROM user_name

權限立即生效

當我們修改用戶權限之後,如果想不重啟立即生效,需要執行以下flush privileges,這樣能快速刷新權限

FULSH PRIVILEGES;

使用GRANT和REVOKE的例子

要創建一個管理員,可以輸入如下所示的命令:

grant all on * to fred identified by 'mnb123' with grant option;

以上命令授予了用戶名為fred、密碼為mnb123的用戶使用所有數據庫的所有權限,並允許他向其他人授予這些權限。

如果不希望用戶在系統中存在,可以按如下方式撤銷:

revoke all privileges, grant from red;

現在,我們可以按如下方式創建一個沒有任何權限的常規用戶:

grant usage on books.* to sally identified by 'magic123';

在與sally交談后,我們對她需要進行的操作有了一定的了解,因此按如下方式可以授予她適當的權限:

grant select, insert, update, delete, index, alter, create, drop on books.* to sally;

請注意,要完成這些,並不需要指定sally密碼。

如果我們認為sally權限過高,可能會決定按如下方式減少一些權限:

revoke alter, create, drop on books.* from sally;

後來,當她不再需要使用數據庫時,可以按如下方式撤銷所有的權限:

revoke all on books.* from sally;

參考資料:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

平板收購,iphone手機收購,二手筆電回收,二手iphone收購-全台皆可收購

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

Spring Boot (一) 校驗表單重複提交

一、前言

在某些情況下,由於網速慢,用戶操作有誤(連續點擊兩下提交按鈕),頁面卡頓等原因,可能會出現表單數據重複提交造成數據庫保存多條重複數據。

存在如上問題可以交給前端解決,判斷多長時間內不能再次點擊保存按鈕,當然,如果存在聰明的用戶能夠繞過前端驗證,後端更應該去進行攔截處理,下面小編將基於SpringBoot 2.1.8.RELEASE環境通過AOP切面+ 自定義校驗註解+ Redis緩存來解決這一問題。

二、Spring Boot 校驗表單重複提交操作

1、pom.xml中引入所需依賴

<!-- ==================  校驗表單重複提交所需依賴 ===================== -->
<!-- AOP依賴 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、application.yml中引入Redis配置

spring:
  redis:
    # Redis數據庫索引(默認為0)
    database: 0
    # Redis服務器地址
    host: 127.0.0.1
    # Redis服務器連接端口
    port: 6379
    timeout: 6000
    # Redis服務器連接密碼(默認為空)
    #      password:
    jedis:
      pool:
        max-active: 1000  # 連接池最大連接數(使用負值表示沒有限制)
        max-wait: -1      # 連接池最大阻塞等待時間(使用負值表示沒有限制)
        max-idle: 10      # 連接池中的最大空閑連接
        min-idle: 5       # 連接池中的最小空閑連接

3、自定義註解 @NoRepeatSubmit

// 作用到方法上
@Target(ElementType.METHOD)
// 運行時有效
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {
    /**
     * 默認時間3秒
     */
    int time() default 3 * 1000;
}

4、AOP 攔截處理

注:這裏redis存儲的key值可由個人具體業務靈活發揮,這裏只是示例
ex:單用戶登錄情況下可以組合token + url請求路徑,多個用戶可以同時登錄的話,可以再加上ip地址

@Slf4j
@Aspect
@Component
public class NoRepeatSubmitAop {

    @Autowired
    RedisUtil redisUtil;

    /**
     * <p> 【環繞通知】 用於攔截指定方法,判斷用戶表單保存操作是否屬於重複提交 <p>
     *
     *      定義切入點表達式: execution(public * (…))
     *      表達式解釋: execution:主體    public:可省略   *:標識方法的任意返回值  任意包+類+方法(…) 任意參數
     *
     *      com.zhengqing.demo.modules.*.api : 標識AOP所切服務的包名,即需要進行橫切的業務類
     *      .*Controller : 標識類名,*即所有類
     *      .*(..) : 標識任何方法名,括號表示參數,兩個點表示任何參數類型
     *
     * @param pjp:切入點對象
     * @param noRepeatSubmit:自定義的註解對象
     * @return: java.lang.Object
     */
    @Around("execution(* com.zhengqing.demo.modules.*.api.*Controller.*(..)) && @annotation(noRepeatSubmit)")
    public Object doAround(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) {
        try {
            HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();

            // 拿到ip地址、請求路徑、token
            String ip = IpUtils.getIpAdrress(request);
            String url = request.getRequestURL().toString();
            String token = request.getHeader(Constants.REQUEST_HEADERS_TOKEN);

            // 現在時間
            long now = System.currentTimeMillis();

            // 自定義key值方式
            String key = "REQUEST_FORM_" + ip;
            if (redisUtil.hasKey(key)) {
                // 上次表單提交時間
                long lastTime = Long.parseLong(redisUtil.get(key));
                // 如果現在距離上次提交時間小於設置的默認時間 則 判斷為重複提交  否則 正常提交 -> 進入業務處理
                if ((now - lastTime) > noRepeatSubmit.time()) {
                    // 非重複提交操作 - 重新記錄操作時間
                    redisUtil.set(key, String.valueOf(now));
                    // 進入處理業務
                    ApiResult result = (ApiResult) pjp.proceed();
                    return result;
                } else {
                    return ApiResult.fail("請勿重複提交!");
                }
            } else {
                // 這裡是第一次操作
                redisUtil.set(key, String.valueOf(now));
                ApiResult result = (ApiResult) pjp.proceed();
                return result;
            }
        } catch (Throwable e) {
            log.error("校驗表單重複提交時異常: {}", e.getMessage());
            return ApiResult.fail("校驗表單重複提交時異常!");
        }

    }

}

5、其中用到的Redis工具類

由於太多,這裏就不直接貼出來了,可參考文末給出的案例demo源碼

三、測試

在需要校驗的方法上加上自定義的校驗註解@NoRepeatSubmit即可

@RestController
public class IndexController extends BaseController {

    @NoRepeatSubmit
    @GetMapping(value = "/index", produces = "application/json;charset=utf-8")
    public ApiResult index() {
        return ApiResult.ok("Hello World ~ ");
    }

}

這裏重複訪問此indexapi請求以模擬提交表單測試

第一次訪問

多次刷新此請求,則提示請勿重複提交!

四、總結

實現思路
  1. 首先利用AOP切面在進入方法前攔截進行表單重複提交校驗邏輯處理
  2. 通過Rediskey-value鍵值對存儲需要的邏輯判斷數據【ex:key存儲用戶提交表單的api請求路徑,value存儲提交時間】
  3. 邏輯處理
    第一次提交時存入相應數據到redis中
    當再次提交保存時從redis緩存中取出上次提交的時間與當前操作時間做判斷,
    如果當前操作時間距離上次操作時間在我們設置的’判斷為重複提交的時間(3秒內)’則為重複提交直接返回重複提交提示語句或其它處理,
    否則為正常提交,進入業務方法處理…
補充

如果api遵從的是嚴格的Restful風格@PostMapping用於表單提交操作,則可不用自定義註解方式去判斷需要校驗重複提交的路徑,直接在aop切面攔截該請求路徑後,通過反射拿到該方法上的註解是否存在@PostMapping如果存在則是提交表單的api,即進行校驗處理,如果不存在即是其它的@GetMapping@PutMapping@DeleteMapping操作…

本文案例demo源碼

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品在網路上成為最夯、最多人討論的話題?

※高價收購3C產品,價格不怕你比較

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

二、從零開始搭建自己的靜態博客 — 主題篇

我們已經成功地在本地搭建了一個博客網站,它使用的是pelican默認的notmyidea主題;

如果你不太記得了,可以再看看這篇文章:;

其實,pelican擁有眾多的開源主題庫,我們可以在上選擇一個自己喜歡的主題應用到項目中;

網站提供在線預覽主題的功能;

我選擇的是主題,它的在線Demo是:;

下面,我們來一步一步的將其應用到我們的項目中;

1. 下載主題

我粗略的瀏覽了一下pelican-alchemy的文檔和issue列表,考慮到後續有可能會做一些修改,所以我決定先將其fork到自己的倉庫;

然後,我在項目根目錄新建一個目錄themes/用於存放所有下載的主題,然後將fork後的pelican-alchemy作為一個獨立的子倉庫克隆到目錄下:

λ mkdir themes
λ git submodule add git@github.com:luizyao/pelican-alchemy.git themes/pelican-alchemy

注意:

git submodule add <url> <path>命令是將一個倉庫添加到指定的目錄下作為獨立的子倉庫;

如果你仔細觀察,會發現我們的根目錄下多了一個文件:.gitmodules,它記錄了子倉庫的信息;

例如:我們項目中這個文件的內容是:

[submodule "themes/pelican-alchemy"]
    path = themes/pelican-alchemy
    url = git@github.com:luizyao/pelican-alchemy.git

常用的和子倉庫的相關的操作有下面幾個:

  • 克隆父倉庫時,連同子倉庫一起克隆:

    git clone --recurse-submodules <URL> <directory>
  • 查看父倉庫中所有子倉庫的狀態:

    λ git submodule status
    3381c5031bf30d3b1212619b662898f178d695f1 themes/pelican-alchemy (v2.1-43-g3381c50)

    3381c5031bf30d3b1212619b662898f178d695f1是對當前Commit IdSHA-1加密字串;

  • 刪除子倉庫:

    git rm <submodule path> && git commit

    再手動刪除.git/modules/<name>/目錄

如果你想了解更多關於git submodule的內容,可以通過git submodule --help閱讀它的官方文檔;

2. 使用主題

2.1. 基本配置

# pelicanconf.py

# 主題所在的相對目錄
THEME = 'themes/pelican-alchemy/alchemy'

# 副標題
SITESUBTITLE = '戒驕戒躁 砥礪前行'

# 頭像
SITEIMAGE = '/images/profile.png width=200 height=200'

# 友鏈
LINKS = (
    ('pytest-chinese-doc', 'https://luizyao.github.io/pytest-chinese-doc/'),
)

# 代碼高亮的樣式
PYGMENTS_STYLE = 'friendly'

# 使用 Bootswatch 樣式:https://bootswatch.com/
BOOTSTRAP_CSS = 'https://cdn.bootcss.com/bootswatch/4.3.1/lux/bootstrap.min.css'

# 生成 sitemap.xml 文件,它是一個對爬蟲友好的文件,方便搜索引擎抓取網站頁面
DIRECT_TEMPLATES = ['index', 'tags', 'categories', 'authors', 'archives', 'sitemap']
SITEMAP_SAVE_AS = 'sitemap.xml'

# 構建后的 html 文件路徑和 URL 標識
ARTICLE_URL = 'posts/{date:%Y}/{date:%m}/{slug}.html'
ARTICLE_SAVE_AS = ARTICLE_URL
DRAFTS_URL = 'drafts/{date:%Y}/{date:%m}/{slug}.html'
DRAFTS_SAVE_AS = ARTICLE_URL
PAGE_URL = 'pages/{slug}.html'
PAGE_SAVE_AS = PAGE_URL

# RSS 訂閱
FEED_ALL_RSS = 'feeds/all.rss.xml'

具體細節可以參考:

2.2. 高級配置

2.2.1. 配置網站圖標

通過在線工具可以生成適配各種平台和瀏覽器的favicon文件:

下載上面生成的favicon包,並解壓到項目content/extras目錄下:

λ ls content/extras/
android-chrome-192x192.png  favicon.ico         safari-pinned-tab.svg
android-chrome-384x384.png  favicon-16x16.png   site.webmanifest
apple-touch-icon.png        favicon-32x32.png
browserconfig.xml           mstile-150x150.png

修改模版中的base.html文件:

<!-- themes/pelican-alchemy/alchemy/templates/base.html --> 

{% if RFG_FAVICONS %}
  <link rel="apple-touch-icon" href="{{ SITEURL }}/apple-touch-icon.png" sizes="180x180">
  <link rel="icon" type="image/png" href="{{ SITEURL }}/favicon-32x32.png" sizes="32x32">
  <link rel="icon" type="image/png" href="{{ SITEURL }}/favicon-16x16.png" sizes="16x16">
  <link rel="manifest" href="{{ SITEURL }}/manifest.json">
  <meta name="theme-color" content="#333333">
{% endif %}

<!-- 改成 --> 

{% if RFG_FAVICONS %}
  <link rel="apple-touch-icon" href="{{ SITEURL }}/apple-touch-icon.png" sizes="180x180">
  <link rel="icon" type="image/png" href="{{ SITEURL }}/favicon-32x32.png" sizes="32x32">
  <link rel="icon" type="image/png" href="{{ SITEURL }}/favicon-16x16.png" sizes="16x16">
  <link rel="manifest" href="{{ SITEURL }}/site.webmanifest">
  <link rel="mask-icon" href="{{ SITEURL }}/safari-pinned-tab.svg" color="#5bbad5">
  <meta name="msapplication-TileColor" content="#da532c">
  <meta name="theme-color" content="#ffffff">
{% endif %}

修改pelicanconf.py配置文件:

# pelicanconf.py

# 在構建中,它們會無損的拷貝到 output 的同名目錄下
STATIC_PATHS = ['extras', 'images', 'css']

# 構建時,extras/android-chrome-192x192.png文件,拷貝到output/android-chrome-192x192.png,不再是output/extras/android-chrome-192x192.png
EXTRA_PATH_METADATA = {
    'extras/android-chrome-192x192.png': {'path': 'android-chrome-192x192.png'},
    'extras/android-chrome-512x512.png': {'path': 'android-chrome-512x512.png'},
    'extras/apple-touch-icon.png': {'path': 'apple-touch-icon.png'},
    'extras/browserconfig.xml': {'path': 'browserconfig.xml'},
    'extras/favicon-16x16.png': {'path': 'favicon-16x16.png'},
    'extras/favicon-32x32.png': {'path': 'favicon-32x32.png'},
    'extras/favicon.ico': {'path': 'favicon.ico'},
    'extras/manifest.json': {'path': 'manifest.json'},
    'extras/mstile-150x150.png': {'path': 'mstile-150x150.png'},
    'extras/safari-pinned-tab.svg': {'path': 'safari-pinned-tab.svg'},
    # 自定義樣式
    'css/custom.css': {'path': 'theme/css/custom.css'},
}

# 自定義樣式的URL目錄
THEME_CSS_OVERRIDES = ('theme/css/custom.css',)

RFG_FAVICONS = True

2.2.2.更新Font Awesome的版本

pelican-alchemy使用Font Awesome 4.7.0版本,並且使用的是靜態資源的相對引用;

我們將其修改為最新的5.11.2版本的CDN引入,修改主題模版中的base.html文件:

<!-- themes/pelican-alchemy/alchemy/templates/base.html --> 

<link rel="stylesheet" href="{{ SITEURL }}/theme/css/font-awesome.min.css">

<!-- 改成 --> 

<link href="https://cdn.bootcss.com/font-awesome/5.11.2/css/fontawesome.min.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/font-awesome/5.11.2/css/solid.css" rel="stylesheet">
<link href="https://cdn.bootcss.com/font-awesome/5.11.2/css/brands.css" rel="stylesheet">

除了上面的步驟,我們還有一個額外的工作要做:因為5.x的版本已經不使用fa前綴,取而代之的是fas()和fab();

所以,對於主題中那些類似class="fa fa-github"的樣式,應該修改為class="fab fa-github",主要涉及article.htmlindex.htmlheader.html這些文件;

最後,修改pelicanconf.py文件中關於ICONS配置的格式,需要額外指定樣式類別:

# pelicanconf.py

# 社交屬性,請到<https://fontawesome.com/icons>網站確定圖標樣式的類別
ICONS = [
    ('fab', 'github', 'https://github.com/luizyao'),
    ('fas', 'blog', 'https://www.cnblogs.com/luizyao/'),
    ('fas', 'rss', 'feeds/all.rss.xml')
]

pelican-alchemy有一個openissue:是關於Font Awesome版本的,後續可能會更新到5.x版本,目前issue處於接收反饋的狀態;

至於為什麼不使用CDN,貌似還和偉大的防火牆有關呢。

I’m sure you’ve heard of the Great Firewall of China; India, Russia, some African countries are doing similar things. You never know which URL or IP might become inaccessible

2.2.3.使用Bootstrap的樣式

我們可以為特定類型的元素添加Bootstrap的官方樣式;例如:為每個img元素添加class = "img-fluid"的樣式;

首先,安裝依賴包:

# beautifulsoup4為插件所依賴的第三方包
λ pipenv install beautifulsoup4

然後,下載插件:

λ mkdir plugins
λ git submodule add git@github.com:ingwinlu/pelican-bootstrapify.git plugins/pelican-bootstrapify

最後,修改pelicanconf.py配置文件:

# 到哪裡尋找插件
PLUGIN_PATHS = ['plugins']

# 想要使用的插件名
PLUGINS = ['pelican-bootstrapify']

# 想要添加的 Bootstrap 樣式
BOOTSTRAPIFY = {
    'table': ['table', 'table-striped', 'table-hover'],
    'img': ['img-fluid'],
}

2.3. 定製主題

下面我們為pelican-alchemy做一些定製化的操作,添加一些新的功能;

2.3.1. 添加返回頂部鏈接

修改base.html文件,在<head>中添加如下部分:

<!-- themes/pelican-alchemy/alchemy/templates/base.html --> 

<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/scrollup/2.4.1/jquery.scrollUp.min.js"></script>

<script>
  $(function () {
    $.scrollUp({
      scrollText: '<i class="fas fa-2x fa-chevron-circle-up"></i>'
    });
  });
</script>

2.3.2. 支持目錄

我自己寫了一個的插件,用於替代pelican默認的MarkdownReader,它有以下功能:

  • 使用增強的markdown解析

    • 代替markdown.extensions.extra
    • 代替markdown.extensions.codehilite
  • 支持以下方式生成文章目錄:

    1. markdown文本內的[TOC]標記處生成目錄;

    2. 通過元數據toc自定義目錄樣式;例如:

      {% if article.toc %}
        <aside class="col-md-4">
          <div class="widget widget-content">
            <h3 class="widget-title">文章目錄</h3>
            <div class="toc">
              <ul>
                {{ article.toc | safe }}
              </ul>
            </div>
          </div>
        </aside>
      {% endif %}
  • 如果沒配summary或者summary為空,支持自動截取開頭部分字符作為摘要;

使用方法:

  1. 作為一個子倉庫下載

    # 項目根目錄創建目錄
    λ mkdir plugins
    # 下載
    λ git submodule add git@github.com:luizyao/pelican-md-reader.git plugins/pelican-md-reader
  2. 修改pelicanconf.py配置文件

    # pelicanconf.py
    
    # 到哪裡尋找插件
    PLUGIN_PATHS = ['plugins']
    
    # 想要使用的插件名
    PLUGINS = ['pelican-md-reader']

更多細節可以參考:

2.3.3. 漢化

主要關鍵字漢化;

3.完整的pelicanconf.py文件

#!/usr/bin/env python
# -*- coding: utf-8 -*- #
from __future__ import unicode_literals

AUTHOR = 'luizyao'
SITENAME = "luizyao's blog"
SITEURL = ''

PATH = 'content'

DEFAULT_LANG = 'en'

# Feed generation is usually not desired when developing
FEED_ALL_ATOM = None
CATEGORY_FEED_ATOM = None
TRANSLATION_FEED_ATOM = None
AUTHOR_FEED_ATOM = None
AUTHOR_FEED_RSS = None

DEFAULT_PAGINATION = 10

# Uncomment following line if you want document-relative URLs when developing
# RELATIVE_URLS = True

# 修改時區
TIMEZONE = 'Asia/Shanghai'

# 修改默認的時間格式('%a %d %B %Y')
DEFAULT_DATE_FORMAT = "%Y-%m-%d %H:%M"

# 為元數據定義默認值
DEFAULT_METADATA = {
    # 默認發布的文章都是草稿,除非在文章元數據中明確指定:Status: published
    'status': 'draft',
}

# pelican-alchemy 原有的配置

# 主題所在的相對目錄
THEME = 'themes/pelican-alchemy/alchemy'

# 副標題
SITESUBTITLE = '戒驕戒躁 砥礪前行'

# 頭像
SITEIMAGE = '/images/profile.png width=200 height=200'

# 友鏈
LINKS = (
    ('pytest-chinese-doc', 'https://luizyao.github.io/pytest-chinese-doc/'),
)

# 代碼高亮的樣式
PYGMENTS_STYLE = 'friendly'

# 使用 Bootswatch 樣式:https://bootswatch.com/
BOOTSTRAP_CSS = 'https://cdn.bootcss.com/bootswatch/4.3.1/lux/bootstrap.min.css'

# 生成 sitemap.xml 文件
DIRECT_TEMPLATES = ['index', 'tags', 'categories', 'authors', 'archives', 'sitemap']
SITEMAP_SAVE_AS = 'sitemap.xml'

# 構建后的 html 文件路徑和 URL 標識
ARTICLE_URL = 'posts/{date:%Y}/{date:%m}/{slug}.html'
ARTICLE_SAVE_AS = ARTICLE_URL
DRAFTS_URL = 'drafts/{date:%Y}/{date:%m}/{slug}.html'
DRAFTS_SAVE_AS = ARTICLE_URL
PAGE_URL = 'pages/{slug}.html'
PAGE_SAVE_AS = PAGE_URL

# RSS 訂閱
FEED_ALL_RSS = 'feeds/all.rss.xml'

# 在構建中,它們會無損的拷貝到 output 的同名目錄下
STATIC_PATHS = ['extras', 'images', 'css']

# 構建時,extras/android-chrome-192x192.png文件,拷貝到output/android-chrome-192x192.png,不再是output/extras/android-chrome-192x192.png
EXTRA_PATH_METADATA = {
    'extras/android-chrome-192x192.png': {'path': 'android-chrome-192x192.png'},
    'extras/android-chrome-512x512.png': {'path': 'android-chrome-512x512.png'},
    'extras/apple-touch-icon.png': {'path': 'apple-touch-icon.png'},
    'extras/browserconfig.xml': {'path': 'browserconfig.xml'},
    'extras/favicon-16x16.png': {'path': 'favicon-16x16.png'},
    'extras/favicon-32x32.png': {'path': 'favicon-32x32.png'},
    'extras/favicon.ico': {'path': 'favicon.ico'},
    'extras/manifest.json': {'path': 'manifest.json'},
    'extras/mstile-150x150.png': {'path': 'mstile-150x150.png'},
    'extras/safari-pinned-tab.svg': {'path': 'safari-pinned-tab.svg'},
    # 自定義樣式
    'css/custom.css': {'path': 'theme/css/custom.css'},
}

# 自定義樣式的URL目錄
THEME_CSS_OVERRIDES = ('theme/css/custom.css',)

RFG_FAVICONS = True

# 到哪裡尋找插件
PLUGIN_PATHS = ['plugins']

# 想要使用的插件名
PLUGINS = ['pelican-bootstrapify', 'pelican-md-reader']

# 想要添加的 Bootstrap 樣式
BOOTSTRAPIFY = {
    'table': ['table', 'table-striped', 'table-hover'],
    'img': ['img-fluid'],
}

# 社交屬性,請到<https://fontawesome.com/icons>網站確定圖標樣式的類別
ICONS = [
    ('fab', 'github', 'https://github.com/luizyao'),
    ('fas', 'blog', 'https://www.cnblogs.com/luizyao/'),
    ('fas', 'rss', 'feeds/all.rss.xml')
]

4. 預覽

Github:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※高價收購3C產品,價格不怕你比較

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

3c收購,鏡頭 收購有可能以全新價回收嗎?

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

dom4j的測試例子和源碼詳解(重點對比和DOM、SAX的區別)

目錄

簡介

dom4j用於創建和解析XML文件,不是純粹的DOMSAX,而是兩者的結合和改進,另外,dom4j支持Xpath來獲取節點。目前,由於其出色的性能和易用性,目前dom4j已經得到廣泛使用,例如SpringHibernate就是使用dom4j來解析xml配置。

注意,dom4j使用Xpath需要額外引入jaxen的包。

DOM、SAX、JAXP和DOM4J

其實,JDK已經帶有可以解析xml的api,如DOMSAXJAXP,但為什麼dom4j會更受歡迎呢?它們有什麼區別呢?在學習dom4j之前,需要先理解下DOMSAX等概念,因為dom4j就是在此基礎上改進而來。

xerces解釋器

先介紹下xerces解釋器,下面介紹的SAXDOMJAXP都只是接口,而xerces解釋器就是它們的具體實現,在com.sun.org.apache.xerces.internal包。xerces被稱為性能最好的解釋器,除了xerces外,還有其他的第三方解釋器,如crimson

SAX

JDK針對解析xml提供的接口,不是具體實現,在org.xml.sax包。SAX基於事件處理,解析過程中根據當前的XML元素類型,調用用戶自己實現的回調方法,如:startDocument();,startElement()。下面以例子說明,通過SAX解析xml並打印節點名:

    /*這裏解釋下四個的接口:
    EntityResolver:需要實現resolveEntity方法。當解析xml需要引入外部數據源時觸發,通過這個方法可以重定向到本地數據源或進行其他操作。
    DTDHandler:需要實現notationDecl和unparsedEntityDecl方法。當解析到"NOTATION", "ENTITY"或 "ENTITIES"時觸發。
    ContentHandler:最常用的一個接口,需要實現startDocument、endDocument、startElement、endElement等方法。當解析到指定元素類型時觸發。
    ErrorHandler:需要實現warning、error或fatalError方法。當解析出現異常時會觸發。
    */
    @Test
    public void test04() throws Exception {
        //DefaultHandler實現了EntityResolver, DTDHandler, ContentHandler, ErrorHandler四個接口     
        DefaultHandler handler = new DefaultHandler() {
            @Override
            //當解析到Element時,觸發打印該節點名
            public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
                System.out.println(qName);
            }
        };
        //獲取解析器實例
        XMLReader xr = XMLReaderFactory.createXMLReader();
        //設置處理類
        xr.setContentHandler(handler);
        /*
         * xr.setErrorHandler(handler); 
         * xr.setDTDHandler(handler); 
         * xr.setEntityResolver(handler);
         */
        xr.parse(new InputSource("members.xml"));
    }

因為SAX是基於事件處理的,不需要等到整個xml文件都解析完才執行我們的操作,所以效率較高。但SAX存在一個較大缺點,就是不能隨機訪問節點,因為SAX不會主動地去保存處理過的元素(優點就是內存佔用小、效率高),如果想要保存讀取的元素,開發人員先構建出一個xml樹形結構,再手動往裡面放入元素,非常麻煩(其實dom4j就是通過SAX來構建xml樹)。

DOM

JDK針對解析xml提供的接口,不是具體實現,在org.w3c.dom包。DOM採用了解析方式是一次性加載整個XML文檔,在內存中形成一個樹形的數據結構,開發人員可以隨機地操作元素。見以下例子:

    @SuppressWarnings("restriction")
    @Test
    public void test05() throws Exception {
        //獲得DOMParser對象
        com.sun.org.apache.xerces.internal.parsers.DOMParser domParser = new com.sun.org.apache.xerces.internal.parsers.DOMParser();
        //解析文件
        domParser.parse(new InputSource("members.xml"));
        //獲得Document對象
        Document document=domParser.getDocument();
        // 遍歷節點
        printNodeList(document.getChildNodes());        
    }

通過DOM解析,我們可以獲取任意節點進行操作。但是,DOM有兩個缺點:

  1. 由於一次性加載整個XML文件到內存,當處理較大文件時,容易出現內存溢出。
  2. 節點的操作還是比較繁瑣。

以上兩點,dom4j都進行了相應優化。

JAXP

封裝了SAXDOM兩種接口,它並沒有為JAVA解析XML提供任何新功能,只是對外提供更解耦、簡便操作的API。如下:

DOM解析器

    @Test
    public void test02() throws Exception {
        // 獲得DocumentBuilder對象
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        // 解析xml文件,獲得Document對象
        Document document = builder.parse("members.xml");
        // 遍歷節點
        printNodeList(document.getChildNodes());
    }

獲取SAX解析器

    @Test
    public void test03() throws Exception {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXParser saxParser = factory.newSAXParser();
        saxParser.parse("members.xml", new DefaultHandler() {
            @Override
            public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
                System.out.println(qName);
            }
        });
    }

其實,JAXP並沒有很大程度提高DOM和SAX的易用性,更多地體現在獲取解析器時實現解耦。完全沒有解決SAXDOM的缺點。

DOM4j

對比過dom4jJAXP就會發現,JAXP本質上還是將SAXDOM當成兩套API來看待,而dom4j就不是,它將SAXDOM結合在一起使用,取長補短,並對原有的api進行了改造,在使用簡便性、性能、面向接口編程等方面都要優於JDK自帶的SAXDOM

以下通過使用例子和源碼分析將作出說明。

項目環境

工程環境

JDK:1.8

maven:3.6.1

IDE:sts4

dom4j:2.1.1

創建項目

項目類型Maven Project,打包方式jar。

引入依賴

注意:dom4j使用XPath,必須引入jaxen的jar包。

<!-- junit -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
<!-- dom4j的jar包 -->
<dependency>
    <groupId>org.dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>2.1.1</version>
</dependency>
<!-- dom4j使用XPath需要的jar包 -->
<dependency>
    <groupId>jaxen</groupId>
    <artifactId>jaxen</artifactId>
    <version>1.1.6</version>
</dependency>
<!-- 配置BeanUtils的包,這個我自定義工具類用的,如果只是簡單使用dom4j可以不引入 -->
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.3</version>
</dependency>

使用例子–生成xml文件

本例子將分別使用dom4j和JDK的DOM接口生成xml文件(使用JDK的DOM接口時會使用JAXP的API)。

需求

構建xml樹,添加節點,並生成xml文件。格式如下:

<?xml version="1.0" encoding="UTF-8"?>
<members>
  <students>
    <student name="張三" location="河南" age="18"/>
    <student name="李四" location="新疆" age="26"/>
    <student name="王五" location="北京" age="20"/>
  </students>
  <teachers>
    <teacher name="zzs" location="河南" age="18"/>
    <teacher name="zzf" location="新疆" age="26"/>
    <teacher name="lt" location="北京" age="20"/>
  </teachers>
</members>

生成xml文件–使用w3c的DOM接口

主要步驟

  1. 通過JAXP的API獲得Document對象,這個對象可以看成xml的樹;

  2. 將對象轉化為節點,並添加在Document這棵樹上;

  3. 通過Transformer對象將樹輸出到文件中。

編寫測試類

路徑:test目錄下的cn.zzs.dom4j

注意:因為使用的是w3cDOM接口,所以節點對象導的是org.w3c.dom包,而不是org.dom4j包。

    @Test
    public void test02() throws Exception {
        // 創建工廠對象
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        // 創建DocumentBuilder對象
        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
        // 創建Document對象
        Document document = documentBuilder.newDocument();

        // 創建根節點
        Element root = document.createElement("members");
        document.appendChild(root);

        // 添加一級節點
        Element studentsElement = (Element)root.appendChild(document.createElement("students"));
        Element teachersElement = (Element)root.appendChild(document.createElement("teachers"));

        // 添加二級節點並設置屬性
        Element studentElement1 = (Element)studentsElement.appendChild(document.createElement("student"));
        studentElement1.setAttribute("name", "張三");
        studentElement1.setAttribute("age", "18");
        studentElement1.setAttribute("location", "河南");
        Element studentElement2 = (Element)studentsElement.appendChild(document.createElement("student"));
        studentElement2.setAttribute("name", "李四");
        studentElement2.setAttribute("age", "26");
        studentElement2.setAttribute("location", "新疆"); 
        Element studentElement3 = (Element)studentsElement.appendChild(document.createElement("student"));
        studentElement3.setAttribute("name", "王五");
        studentElement3.setAttribute("age", "20");
        studentElement3.setAttribute("location", "北京");     
        Element teacherElement1 = (Element)teachersElement.appendChild(document.createElement("teacher"));
        teacherElement1.setAttribute("name", "zzs");
        teacherElement1.setAttribute("age", "18");
        teacherElement1.setAttribute("location", "河南"); 
        Element teacherElement2 = (Element)teachersElement.appendChild(document.createElement("teacher"));
        teacherElement2.setAttribute("name", "zzf");
        teacherElement2.setAttribute("age", "26");
        teacherElement2.setAttribute("location", "新疆");     
        Element teacherElement3 = (Element)teachersElement.appendChild(document.createElement("teacher"));
        teacherElement3.setAttribute("name", "lt");
        teacherElement3.setAttribute("age", "20");
        teacherElement3.setAttribute("location", "北京"); 
            
        // 獲取文件對象
        File file = new File("members.xml");
        if(!file.exists()) {
            file.createNewFile();
        }
        // 獲取Transformer對象
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        // 設置編碼、美化格式
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        // 創建DOMSource對象
        DOMSource domSource = new DOMSource(document);
        // 將document寫出
        transformer.transform(domSource, new StreamResult(new PrintWriter(new FileOutputStream(file))));    
    }   

測試結果

此時,在項目路徑下會生成members.xml,文件內容如下,可以看到,使用w3cDOM接口輸出的內容沒有縮進格式。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<members>
<students>
<student age="18" location="河南" name="張三"/>
<student age="26" location="新疆" name="李四"/>
<student age="20" location="北京" name="王五"/>
</students>
<teachers>
<teacher age="18" location="河南" name="zzs"/>
<teacher age="26" location="新疆" name="zzf"/>
<teacher age="20" location="北京" name="lt"/>
</teachers>
</members>

生成xml文件–使用dom4j的DOM接口

主要步驟

  1. 通過DocumentHelper獲得Document對象,這個對象可以看成xml的樹;

  2. 將對象轉化為節點,並添加在Document這棵樹上;

  3. 通過XMLWriter對象將樹輸出到文件中。

編寫測試類

路徑:test目錄下的cn.zzs.dom4j。通過對比,可以看出,dom4j的API相比JDK的還是要方便很多。

注意:因為使用的是dom4jDOM接口,所以節點對象導的是org.dom4j包,而不是org.w3c.dom包(dom4j一個很大的特點就是改造了w3cDOM接口,極大地簡化了我們對節點的操作)。

    @Test
    public void test02() throws Exception {
        // 創建Document對象
        Document document = DocumentHelper.createDocument();

        // 添加根節點
        Element root = document.addElement("members");

        // 添加一級節點
        Element studentsElement = root.addElement("students");
        Element teachersElement = root.addElement("teachers");

        // 添加二級節點並設置屬性,dom4j改造了w3c的DOM接口,極大地簡化了我們對節點的操作
        studentsElement.addElement("student").addAttribute("name", "張三").addAttribute("age", "18").addAttribute("location", "河南");
        studentsElement.addElement("student").addAttribute("name", "李四").addAttribute("age", "26").addAttribute("location", "新疆");
        studentsElement.addElement("student").addAttribute("name", "王五").addAttribute("age", "20").addAttribute("location", "北京");
        teachersElement.addElement("teacher").addAttribute("name", "zzs").addAttribute("age", "18").addAttribute("location", "河南");
        teachersElement.addElement("teacher").addAttribute("name", "zzf").addAttribute("age", "26").addAttribute("location", "新疆");
        teachersElement.addElement("teacher").addAttribute("name", "lt").addAttribute("age", "20").addAttribute("location", "北京");

        // 獲取文件對象
        File file = new File("members.xml");
        if(!file.exists()) {
            file.createNewFile();
        }
        // 創建輸出格式,不設置的話不會有縮進效果
        OutputFormat format = OutputFormat.createPrettyPrint();
        format.setEncoding("UTF-8");
        // 獲得XMLWriter
        XMLWriter writer = new XMLWriter(new FileWriter(file), format);
        // 打印Document
        writer.write(document);
        // 釋放資源
        writer.close();
    }

測試結果

此時,在項目路徑下會生成members.xml,文件內容如下,可以看出dom4j輸出文件會進行縮進處理,而JDK的不會:

<?xml version="1.0" encoding="UTF-8"?>

<members>
  <students>
    <student name="張三" age="18" location="河南"/>
    <student name="李四" age="26" location="新疆"/>
    <student name="王五" age="20" location="北京"/>
  </students>
  <teachers>
    <teacher name="zzs" age="18" location="河南"/>
    <teacher name="zzf" age="26" location="新疆"/>
    <teacher name="lt" age="20" location="北京"/>
  </teachers>
</members>

使用例子–解析xml文件

需求

  1. 解析xml:解析上面生成的xml文件,將學生和老師節點按以下格式遍歷打印出來(當然也可以再封裝成對象返回給調用者,這裏就不擴展了)。
student:name=張三,location=河南,age=18
student:name=李四,location=新疆,age=26
student:name=王五,location=北京,age=20
teacher:name=zzs,location=河南,age=18
teacher:name=zzf,location=新疆,age=26
teacher:name=lt,location=北京,age=20
  1. dom4j結合XPath查找指定節點

主要步驟

  1. 通過SAXReader對象讀取和解析xml文件,獲得Document對象,即xml樹;

  2. 調用Node的方法遍歷打印xml樹的節點;

  3. 使用XPath查詢指定節點。

測試遍歷節點

考慮篇幅,這裏僅給出一種節點遍歷方式,項目源碼中還給出了其他的幾種。

    /**
     *  測試解析xml
     */
    @Test
    public void test03() throws Exception {
        // 創建指定文件的File對象
        File file = new File("members.xml");
        // 創建SAXReader
        SAXReader saxReader = new SAXReader();
        // 將xml文件讀入成document
        Document document = saxReader.read(file);
        // 獲得根元素
        Element root = document.getRootElement();
        // 遞歸遍歷節點
        list1(root);
    }

    /**
     * 遞歸遍歷節點
     */
    private void list1(Element parent) {
        if(parent == null) {
            return;
        }
        // 遍歷當前節點屬性並輸出
        printAttr(parent);
        // 遞歸打印子節點
        Iterator<Element> iterator2 = parent.elementIterator();
        while(iterator2.hasNext()) {
            Element son = (Element)iterator2.next();
            list1(son);
        }
    }

測試結果如下:

-------第一種遍歷方式:Iterator+遞歸--------
student:name=張三,location=河南,age=18
student:name=李四,location=新疆,age=26
student:name=王五,location=北京,age=20
teacher:name=zzs,location=河南,age=18
teacher:name=zzf,location=新疆,age=26
teacher:name=lt,location=北京,age=20

測試XPath獲取指定節點

    @Test
    public void test04() throws Exception {
        // 創建指定文件的File對象
        File file = new File("members.xml");
        // 創建SAXReader
        SAXReader saxReader = new SAXReader();
        // 將xml文件讀入成document
        Document document = saxReader.read(file);
        // 使用xpath隨機獲取節點
        List<Node> list = document.selectNodes("//members//students/student");
        // List<Node> list = xmlParser.getDocument().selectSingleNode("students");
        // 遍歷節點
        Iterator<Node> iterator = list.iterator();
        while(iterator.hasNext()) {
            Element element = (Element)iterator.next();
            printAttr(element);
        }
    }

測試結果如下:

student:age=18,location=河南,name=張三
student:age=26,location=新疆,name=李四
student:age=20,location=北京,name=王五

XPath語法

利用XPath獲取指定節點,平時用的比較多,這裏列舉下基本語法。

表達式 結果
/members 選取根節點下的所有members子節點
//members 選取根節點下的所有members節點
//students/student[1] 選取students下第一個student子節點
//students/student[last()] 選取students下的最後一個student子節點
//students/student[position()<3] 選取students下前兩個student子節點
//student[@age] 選取所有具有age屬性的student節點
//student[@age=’18’] 選取所有age屬性為18的student節點
//students/* 選取students下的所有節點
//* 選取文檔中所有節點
//student[@*] 選取所有具有屬性的節點
//members/students\ //members/teachers

源碼分析

本文會先介紹dom4j如何將xml元素抽象成具體的對象,再去分析dom4j解析xml文件的過程(注意,閱讀以下內容前需要了解和使用過JDK自帶的DOMSAX)。

dom4j節點的類結構

先來看下一個完整xml的元素組成,可以看出,一個xml文件包含了DocumentElementCommentAttributeDocumentTypeText等等。

DOM的思想就是將xml元素解析為具體對象,並構建樹形數據結構。基於此,w3c提供了xml元素的接口規範,dom4j基本借用了這套規範(如下圖),只是改造了接口的方法,使得我們操作時更加簡便。

SAXReader.read(File file)

通過使用例子可知,我們解析xml文件的入口是SAXReader對象的read方法,入參可以是文件路徑、url、字節流、字符流等,這裏以傳入文件路徑為例。

注意:考慮篇幅和可讀性,以下代碼經過刪減,僅保留所需部分。

    public Document read(File file) throws DocumentException {
        //不管是URI,path,character stream還是byte stream,都會包裝成InputSource對象
        InputSource source = new InputSource(new FileInputStream(file));
        if (this.encoding != null) {
            source.setEncoding(this.encoding);
        }
        
        //下面這段代碼是為了設置systemId,當傳入URI且沒有指定字符流和字節流時,可以通過systemId去連接URL並解析
        //如果一開始傳入了字符流或字節流,這個systemId就是可選的
        String path = file.getAbsolutePath();
        if (path != null) {
            StringBuffer sb = new StringBuffer("file://");
            if (!path.startsWith(File.separator)) {
                sb.append("/");
            }
            path = path.replace('\\', '/');
            sb.append(path);
            source.setSystemId(sb.toString());
        }

        //這裏調用重載方法解析InputSource對象
        return read(source);
    }

SAXReader.read(InputSource in)

看到這個方法的代碼時,使用過JDK的SAX的朋友應該很熟悉,沒錯,dom4j也是採用事件處理的機制來解析xml。其實,只是這裏設置的SAXContentHandler已經實現好了相關的方法,這些方法共同完成一件事情:構建xml樹。明白這一點,應該就能理解dom4j是如何解決SAXDOM的缺點了。

注意:考慮篇幅和可讀性,以下代碼經過刪減,僅保留所需部分。

    public Document read(InputSource in) throws DocumentException {
        // 這裡會調用JAXP接口獲取XMLReader實現類對象
        XMLReader reader = getXMLReader();
        reader = installXMLFilter(reader);
        
        // 下面這些操作,是不是和使用JDK的SAX差不多,dom4j也是使用了事件處理機制。

        // EntityResolver:通過實現resolveEntity方法,當解析xml需要引入外部數據源時觸發,可以重定向到本地數據源或進行其他操作。
        EntityResolver thatEntityResolver = this.entityResolver;
        if (thatEntityResolver == null) {
            thatEntityResolver = createDefaultEntityResolver(in
                    .getSystemId());
            this.entityResolver = thatEntityResolver;
        }
        reader.setEntityResolver(thatEntityResolver);
        
        // 下面的SAXContentHandler繼承了DefaultHandler,即實現了EntityResolver, DTDHandler, ContentHandler, ErrorHandler等接口
        // 其中最重要的是ContentHandler接口,通過實現startDocument、endDocument、startElement、endElement等方法,當dom4j解析xml文件到指定元素類型時,可以觸發我們自定義的方法。
        // 當然,dom4j已經實現了ContentHandler的方法。具體實現的方法內容為:在解析xml時構建xml樹
        SAXContentHandler contentHandler = createContentHandler(reader);
        contentHandler.setEntityResolver(thatEntityResolver);
        contentHandler.setInputSource(in);
        boolean internal = isIncludeInternalDTDDeclarations();
        boolean external = isIncludeExternalDTDDeclarations();
        contentHandler.setIncludeInternalDTDDeclarations(internal);
        contentHandler.setIncludeExternalDTDDeclarations(external);
        contentHandler.setMergeAdjacentText(isMergeAdjacentText());
        contentHandler.setStripWhitespaceText(isStripWhitespaceText());
        contentHandler.setIgnoreComments(isIgnoreComments());
        reader.setContentHandler(contentHandler);

        configureReader(reader, contentHandler);
        
        // 使用事件處理機制解析xml,處理過程會構建xml樹
        reader.parse(in);
        // 返回構建好的xml樹
        return contentHandler.getDocument();
    }

SAXContentHandler

通過上面的分析,可知SAXContentHandlerdom4j構建xml樹的關鍵。這裏看下它的幾個重要方法和屬性。

startDocument()

    // xml樹
    private Document document;

    // 節點棧,棧頂存放當前解析節點(節點解析結束)、或當前解析節點的父節點(節點解析開始)
    private ElementStack elementStack;

    // 節點處理器,可以看成節點開始解析或結束解析的標誌
    private ElementHandler elementHandler;
    
    // 當前解析節點(節點解析結束)、或當前解析節點的父節點(節點解析開始)
    private Element currentElement;
    public void startDocument() throws SAXException {
        document = null;
        currentElement = null;
        
        // 清空節點棧
        elementStack.clear();
        // 初始化節點處理器
        if ((elementHandler != null)
                && (elementHandler instanceof DispatchHandler)) {
            elementStack.setDispatchHandler((DispatchHandler) elementHandler);
        }

        namespaceStack.clear();
        declaredNamespaceIndex = 0;

        if (mergeAdjacentText && (textBuffer == null)) {
            textBuffer = new StringBuffer();
        }

        textInTextBuffer = false;
    }

startElement(String,String,String,Attributes)

    public void startElement(String namespaceURI, String localName,
            String qualifiedName, Attributes attributes) throws SAXException {
        if (mergeAdjacentText && textInTextBuffer) {
            completeCurrentTextNode();
        }

        QName qName = namespaceStack.getQName(namespaceURI, localName,
                qualifiedName);
        // 獲取當前解析節點的父節點
        Branch branch = currentElement;

        if (branch == null) {
            branch = getDocument();
        }
        // 創建當前解析節點
        Element element = branch.addElement(qName);
        addDeclaredNamespaces(element);

        // 添加節點屬性
        addAttributes(element, attributes);
        
        //將當前節點壓入節點棧
        elementStack.pushElement(element);
        currentElement = element;
        entity = null; // fixes bug527062

        //標記節點解析開始
        if (elementHandler != null) {
            elementHandler.onStart(elementStack);
        }
    }

endElement(String, String, String)

    public void endElement(String namespaceURI, String localName, String qName)
            throws SAXException {
        if (mergeAdjacentText && textInTextBuffer) {
            completeCurrentTextNode();
        }
        // 標記節點解析結束
        if ((elementHandler != null) && (currentElement != null)) {
            elementHandler.onEnd(elementStack);
        }
        // 當前解析節點從節點棧中彈出
        elementStack.popElement();
        // 指定為棧頂節點
        currentElement = elementStack.peekElement();
    }

endDocument()

    public void endDocument() throws SAXException {
        namespaceStack.clear();
        // 清空節點棧
        elementStack.clear();
        currentElement = null;
        textBuffer = null;
    }

以上,dom4j的源碼分析基本已經分析完,其他具體細節後續再做補充。

參考以下資料:

本文為原創文章,轉載請附上原文出處鏈接:https://github.com/ZhangZiSheng001/dom4j-demo

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

平板收購,iphone手機收購,二手筆電回收,二手iphone收購-全台皆可收購

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

SpringMvc demo示例及源碼詳細分析

三層架構介紹

  我們的開發架構一般都是基於兩種形式,一種C/S架構,也就是客戶端/服務器,另一種是B/S架構,也就是瀏覽器/服務器。在JavaEE開發中,幾乎全部都是基於B/S架構的開發。那麼在B/S架構中,系統標準的三層架構包括:表現層、業務層、持久層。三層架構在我們的實際開發中使用的非常多。

三層職責

表現層

  也就是我們長說的web層。它負責接收客戶端請求,向客戶端響應結果,通常客戶端使用http協議請求web層,web需要接收http請求,完成http響應。

  表現層包括展示層和控制層:控制層負責接收請求,展示層負責結果的展示。

  表現層依賴業務層,接收到客戶端請求一般會調用業務層進行業務處理,並將處理結果響應給客戶端。

  表現層的設計一般都是使用mvc模型。(mvc是表現層的設計模型,和其他層沒有關係)

業務層

  也就是我們常說的 service層。它負責業務邏輯處理,和我們開發項目的需求息息相關。web層依賴業務層,但是業務層不依賴web層。

  業務層在業務處理時可能會依賴持久層,如果要對數據持久化需要保證事務一致性。(也就是我們說的,事務應該放到業務層來控制)

持久層

  也就是我們常說的dao層。負責數據持久化,包括數據層即數據庫和數據訪問層,數據庫是對數據進行持久化的載體,數據訪問層是業務層和持久層交互的接口,業務層需要通過數據訪問層將數據持久化到數據庫中。

  通俗的講,持久層就是和數據交互,對數據庫表進行增刪改查的。

mvc設計模式介紹

  mvc全名是Model View Controller,模型(Model)-視圖(View)-控制器(Controller)的縮寫,是一種用於設計創建web應用程序表現層的模式。mvc中每個部分各司其職:

Model(模型)

  模型包含業務模型和數據模型,數據模型用於封裝數據,業務模型用於處理業務。

View(視圖)

  通常指的就是我們的jsp或者html。作用一般就是展示數據的。

  通過視圖是依據模型數據創建的。

Controller(控制器)

  是應用程序中處理用戶交互的部分。作用一般就是處理程序邏輯的。

SpringMVC介紹

Spring MVC是什麼?

  SpringMVC是一種基於Java的實現MVC設計模型的請求驅動類型的輕量級Web框架,屬於SpringFrameWork的後續產品,已經融合在Spring Web Flow裏面。Spring框架提供了構建Web應用程序的全功能MVC模塊。使用Spring可插入的MVC架構,從而在使用Spring進行Web開發時,可以選擇使用Spring的Spring MVC框架或集成其他MVC開發框架,如Struts1(現在一般不用),Struts2等。

  SpringMVC已經成為目前最主流的MVC框架之一,並隨着Spring3.0的發布,全面超越Struts2,成為最優秀的MVC框架。

  它通過一套註解,讓一個簡單的Java類稱為處理請求的控制器,而無需實現任何接口。同時它還支持RESTful編程風格的請求。

總結

  Spring MVC和Struts2一樣,都是為了解決表現層問題的web框架,他們都是基於MCC設計模式的。而這些表現層框架的主要職責就是處理前端HTTP請求

 Spring MVC由來?

 Spring MVC全名叫Spring Web MVC,它是Spring家族Web模塊的一個重要成員。這一點,我們可以從Spring的整體結構中看的出來:

 

 

 為什麼學習SpringMVC?

   也許你會問,為什麼要學習Spring MVC呢?struts2不才是主流嘛?看SSH的概念有多火?

  其實很多初學者混淆了一個概念,SSH實際上指的是Struts1.x+Spring+Hibernate。這個概念已經有十幾年的歷史了。在Struts1.x時代,它是當之無愧的霸主,但是在新的MVC框架湧現的時代,形式已經不是這樣了,Struts2.x藉助了Struts1.x的好名聲,讓國內開發人員認為Struts2.x是霸主繼任者(其實兩者在技術上無任何關係),導致國內程序員大多數學習基於Struts2.x的框架,又一個貌似很多的概念出來了S2SH(Struts2+Spring+Hibernate)整合開發。

 SpringMVC如何處理請求?

   SpringMVC是基於MVC設計模型的,MVC模式指的就是Model(業務模型)、View(視圖)、Controller(控制器)。SpringMVC處理請求就是通過MVC這三個角色來實現的。

注:不要把MVC設計模式工程的三層架構混淆,三層結構指的是表現層、業務層、數據持久層。而MVC只針對表現層進行設計

  下面讓我們看看處理流程吧

 

 

 第一個MVC程序

達到效果

  1. 學會如果配置前端控制器
  2. 如何開發處理器

任務需求

  訪問/queryItem,返回商品列表頁面,商品數據暫時使用靜態數據(不從數據庫查詢並返回)。

 實現

pom.xml

<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.cyb</groupId>
    <artifactId>springmvc-demo01</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <dependencies>
        <!-- spring ioc組件需要的依賴包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>

        <!-- 基於AspectJ的aop依賴 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>

        <!-- spring MVC依賴包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>

        <!-- jstl -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        
        <!-- servlet -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!-- 配置Maven的JDK編譯級別 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.2</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <port>8080</port>
                </configuration>
            </plugin>
            <!-- tomcat依賴包 -->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
            </plugin>
        </plugins>
    </build>
</project>

注:

1、依賴添加完之後,項目上右鍵->maven->Update Maven Project

2、項目上右鍵->Java EE Tools->Generate Deployment Descriptor Stub

 web.xml

路徑:src/main/webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    version="2.5">
    <!-- 學習前置條件 -->
    <!-- 問題1:web.xml中servelet、filter、listener、context-param加載順序 -->
    <!-- 問題2:load-on-startup標籤的作用,影響了Servlet對象創建的時機 -->
    <!-- 問題3:url-pattern:標籤的配置方式有四種:/dispatcherServlet、/servlet/*、*.do、/ 以上四種配置-->
    <!-- 問題4:url-pattern標籤的配置為什麼配置/就不攔截jsp請求,而配置/*,就會攔截jsp請求 -->
    <!-- 問題4原因:標籤配置為/*報錯,因為它攔截了jsp請求,但是又不能處理jsp請求。 -->
    <!-- 問題5:配置了springmvc去讀取spring配置文件之後,就產生了spring父子容器的問題 -->
    
    <!-- 配置前端控制器 -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 設置spring配置文件路徑 -->
        <!-- 如果不設置初始化參數,那麼DispatcherServlet會讀取默認路徑下的配置文件 -->
        <!-- 默認配置文件路徑:/WEB-INF/springmvc-servlet.xml -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <!-- 指定初始化時機,設置為2,表示Tomcat啟動時,它會跟隨着啟動,DispatcherServlet會跟隨着初始化 -->
        <!-- 如果沒有指定初始化時機,DispatcherServlet就會在第一次被請求的時候,才會初始化,而且只會被初始化一次(單例模式) -->
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!-- url-pattern的設置 -->
        <!-- 不要配置為/*,否則報錯 -->
        <!-- 通俗解釋:會攔截整個項目中的資源訪問,包含JSP和靜態資源的訪問,對於JS的訪問,springmvc提供了默認Handler處理器 -->
        <!-- 但是對於JSP來講,springmvc沒有提供默認的處理器,我們也沒有手動編寫對應的處理器,此時按照springmvc的處理流程分析得知,它down了 -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

springmvc.xml

路徑:src/main/resources/springmvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 處理器類的掃描 -->
    <context:component-scan
        base-package="com.cyb.springmvc.controller"></context:component-scan>
    <!-- 註解映射器 @Controller和@RequestMapping組合這種方式的註解映射的解析 -->
    <!-- <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"></bean> -->
    <!-- 註解適配器 -->
    <!-- <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"></bean> -->
    <!-- 配置註釋的適配器和映射器,同時還注入其他很多的bean -->
    <!-- <mvc:annotation-driven></mvc:annotation-driven> -->
    <!-- 显示配置視圖解析器 -->
    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
</beans>

ItemController.java

路徑:/src/main/java/com/cyb/springmvc/controller/ItemController.java

package com.cyb.springmvc.controller;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import com.cyb.springmvc.po.item;

/**
 * 處理器的開發方式有多種,比如實現HttpRequestHandler接口、Controller接口的方式、還有註解的方式 企業中使用的一般都是註解的方式
 * 註解的注意事項
 *  1、類上加上@Controller註解(必須是Controller,可以通過源碼找到答案)
 *  2、類上或者方法上面要加上@RequestMapping(必須)
 * 
 * @author apple
 *
 */
@Controller
public class ItemController {
    //@RequestMapping此時填寫的是url
    //ModelAndView:Model標識的是數據類型,View就是最終要展示給用戶的視圖
    @RequestMapping("queryItem")
    public ModelAndView queryItem() {
        //用靜態數據模型
        List<item> itemList=new ArrayList<item>();
        
        item item_1=new item();
        item_1.setName("蘋果手機");
        item_1.setPrice(5000);
        item_1.setDetail("iphoneX蘋果手機!");
        itemList.add(item_1);
        
        item item_2=new item();
        item_2.setName("華為手機");
        item_2.setPrice(6000);
        item_2.setDetail("華為5G網速就是快!");
        itemList.add(item_2);
        ModelAndView mvAndView=new ModelAndView();
        //設置數據模型,相當於request的setAttribute方法,實質上,底層確實也是轉成了request()
        //先將k/v數據放入map中,最終根據視圖對象不同,再進行後續處理
        mvAndView.addObject("itemList",itemList);
        //設置view視圖
        mvAndView.setViewName("/WEB-INF/jsp/item/item-list.jsp");
        return mvAndView;
    }
}

item.java

路徑:src/main/java/com/cyb/springmvc/po/item.java

package com.cyb.springmvc.po;

public class item {
    private String name;
    private double price;
    private String detail;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public String getDetail() {
        return detail;
    }

    public void setDetail(String detail) {
        this.detail = detail;
    }
}

item-list.jsp

 路徑:src/webapp/WEB-INF/jsp/item/item-list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>查詢商品列表</title>
</head>
<body>
    <form action="${pageContext.request.contextPath }/itemList.do"
        method="post">
        查詢條件:
        <table width="100%" border=1>
            <tr>
                <td><input type="submit" value="查詢" /></td>
            </tr>
        </table>
        商品列表:
        <table width="100%" border=1>
            <tr>
                <td>商品名稱</td>
                <td>商品價格</td>
                <td>商品描述</td>
                <td>操作</td>
            </tr>
            <c:forEach items="${itemList }" var="item">
                <tr>
                    <td>${item.name }</td>
                    <td>${item.price }</td>
                    <td>${item.detail }</td>
                    <td><a
                        href="${pageContext.request.contextPath }/itemEdit.do?id=${item.name}">修改</a></td>
                </tr>
            </c:forEach>

        </table>
    </form>
</body>

</html>

 項目結構圖

 運行

 完整項目

 SpringMVC 框架源碼分析

 框架結構

 程序入口

一、初始化Servlet

二、處理器映射,渲染頁面

 注:標記的方法體,跟蹤進去讀源碼就好啦!~~

默認配置文件

 

 

 

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
    org.springframework.web.servlet.function.support.RouterFunctionMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
    org.springframework.web.servlet.function.support.HandlerFunctionAdapter


org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

 

架構流程

  1. 用戶發送請求至前端控制器DispatcherServlet
  2. DispatcherServlet收到請求調用HandlerMapping處理器映射器
  3. 處理器映射器根據請求url找到具體的處理器,生成處理器對象及處理器攔截器(如果有則生成)一併返回給DispatcherServlet
  4. DispatcherServlet通過HandlerAdapter處理器適配器調用處理器
  5. HandlerAdapter執行處理器(handler,也叫後端控制器)
  6. Controller執行完成返回ModelAndView
  7. HandlerAdapter將handler執行結果ModelAndView返回給DispatcherServlet
  8. DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器
  9. ViewReslover解析后返回具體View對象
  10. DispatcherServlet對View進行渲染視圖(即將模型數據填充至視圖種)
  11. DispatcherServlet響應用戶

 組件說明

 DispatcherServlet:前端控制器

   用戶請求到達前端控制器,它就相當於mvc模式中的C,DispatcherServlet是整個流程控制的中心,由它調用其他組件處理用戶的請求,DispatcherServlet的存在降低了組件之間的耦合性。

HandlerMapping:處理器映射器

   HandlerMapping負責根據用戶請求找到Handler即處理器,springmvc提供了不同的映射器實現不同的映射方式,例如:配置文件方式,實現接口方式,註解方式等。

Handler:處理器

  Handler是繼DispatcherServlet前端控制器的後端控制器,在DispatcherServlet的控制下,Handler對具體的用戶請求進行處理。

  由於Handler涉及到具體的用戶業務請求,所以一般情況需要程序員根據業務需求開發Handler。

HandlerAdapter:處理器適配器

  通過HandlerAdapter對處理器進行執行,這是適配器模式的應用,通過擴展適配器可以對更多類型的處理器進行執行。

 View Resolver:視圖解析器

  View Resolver負責將處理結果生成View視圖,View Resolver首先根據邏輯視圖名解析成物理視圖名即具體的頁面地址,再生成View視圖對象,最後對View進行渲染將處理結果通過頁面展示給用戶。

View:視圖

  springmvc框架提供了很多View視圖類型的支持,包括:jstlView、freemarkerView、pdfView等。我們最常用的視圖就是jsp。

  一般情況下需要通過頁面標籤或頁面模板技術將模型數據通過頁面展示給用戶,需要由程序員根據業務需求開發具體的頁面。

說明

  再springmvc的各個組件中,處理器映射器、處理器適配器、視圖解析器稱為springmvc的三大組件。需要用戶開發的組件有:處理器、視圖

三大組件配置(註解方式)

註解映射器和適配器

通過bean標籤配置

RequestMappingHandlerMapping:註解式處理器映射器

  對類中標記@ResquestMapping的方式進行映射,根據ResquestMapping定義的url匹配ResquestMapping標記的方法,匹配成功返回HandlerMethod對象給前端控制器,HandlerMethod對象中封裝url對應的方法Method。

配置如下:

<!--註解映射器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<!--註解適配器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

通過mvc標籤配置(推薦)

<mvc:annotation-drivern />

  mvc:annotation-drivern標籤的作用,詳見AnnotationDrivenBeanDefinitionParser類的parse方法。分析源碼可知:mvc:annotation-drivern往spring容器中註冊以下的一些BeanDefinition

  • ContentNegotiationManagerFactoryBean
  • RequestMappingHandlerMapping
  • ConfigurableWebBindingInitializer
  • RequestMappingHandlerAdapter
  • CompositeUriComponentsContributorFactoryBean
  • ConversionServiceExposingInterceptor
  • MappedInterceptor
  • ExceptionHandlerExceptionResolver
  • ResponseStatusExceptionResolver
  • DefaultHandlerExceptionResolver
  • BeanNameUrlHandlerMapping
  • HttpRequestHandlerAdapter
  • SimpleControllerHandlerAdapter
  • HandlerMappingIntrospector

視圖解析器

再springmvc.xml文件配置如下:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <!-- 該視圖解析器,默認的視圖類就是JstlView,可以不寫 -->
        <property name="viewClass"
            value="org.springframework.web.servlet.view.JstlView" />
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>
  • InternalResourceViewResolver:默認支持JSP視圖解析
  •  viewClass:JstlView表示JSP模板頁面需要使用JSTL標籤庫,所以classpath中必須包含jstl的相關jar 包。此屬性可以不設置,默認為JstlView
  • prefix suffix:查找視圖頁面的前綴和後綴,最終視圖的址為:前綴+邏輯視圖名+後綴,邏輯視圖名需要在controller中返回的ModelAndView指定,比如邏輯視圖名為hello,則最終返回的jsp視圖地址 “WEB-INF/jsp/hello.jsp”

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品在網路上成為最夯、最多人討論的話題?

※高價收購3C產品,價格不怕你比較

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

Python 深入淺出支持向量機(SVM)算法

相比於邏輯回歸,在很多情況下,SVM算法能夠對數據計算從而產生更好的精度。而傳統的SVM只能適用於二分類操作,不過卻可以通過核技巧(核函數),使得SVM可以應用於多分類的任務中。

本篇文章只是介紹SVM的原理以及核技巧究竟是怎麼一回事,最後會介紹sklearn svm各個參數作用和一個demo實戰的內容,盡量通俗易懂。至於公式推導方面,網上關於這方面的文章太多了,這裏就不多進行展開了~

1.SVM簡介

支持向量機,能在N維平面中,找到最明顯得對數據進行分類的一個超平面!看下面這幅圖:

如上圖中,在二維平面中,有紅和藍兩類點。要對這兩類點進行分類,可以有很多種分類方法,就如同圖中多條綠線,都可以把數據分成兩部分。

SVM做的,是找到最好的那條線(二維空間),或者說那個超平面(更高維度的空間),來對數據進行分類。這個最好的標準,就是最大間距

至於要怎麼找到這個最大間距,要找到這個最大間距,這裏大概簡單說一下,兩個類別的數據,到超平面的距離之和,稱之為間隔。而要做的就是找到最大的間隔。

這最終就變成了一個最大化間隔的優化問題。

2.SVM的核技巧

核技巧,主要是為了解決線性SVM無法進行多分類以及SVM在某些線性不可分的情況下無法分類的情況。

比如下面這樣的數據:

這種時候就可以使用核函數,將數據轉換一下,比如這裏,我們手動定義了一個新的點,然後對所有的數據,計算和這個新的點的歐式距離,這樣我們就得到一個新的數據。而其中,離這個新點距離近的數據,就被歸為一類,否則就是另一類。這就是核函數。

這是最粗淺,也是比較直觀的介紹了。通過上面的介紹,是不是和Sigmoid有點像呢?都是通過將數據用一個函數進行轉換,最終得到結果,其實啊,Sigmoid就是一鍾核函數來着,而上面說的那種方式,是高斯核函數。

這裏補充幾點:

  • 1.上面的圖中只有一個點,實際可以有無限多個點,這就是為什麼說SVM可以將數據映射到多維空間中。計算一個點的距離就是1維,2個點就是二維,3個點就是三維等等。。。
  • 2.上面例子中的紅點是直接手動指定,實際情況中可沒辦法這樣,通常是用隨機產生,再慢慢試出最好的點。
  • 3.上面舉例這種情況屬於高斯核函數,而實際常見的核函數還有多項式核函數,Sigmoid核函數等等。

OK,以上就是關於核技巧(核函數)的初步介紹,更高級的這裏也不展開了,網上的教程已經非常多了。

接下來我們繼續介紹sklearn中SVM的應用方面內容。

3.sklearn中SVM的參數

def SVC(C=1.0, 
             kernel='rbf', 
             degree=3, 
             gamma='auto_deprecated',
             coef0=0.0, 
             shrinking=True, 
             probability=False,
             tol=1e-3, 
             cache_size=200, 
             class_weight=None,
             verbose=False, 
             max_iter=-1, 
             decision_function_shape='ovr',
             random_state=None)
 
- C:類似於Logistic regression中的正則化係數,必須為正的浮點數,默認為 1.0,這個值越小,說明正則化效果越強。換句話說,這個值越小,越訓練的模型更泛化,但也更容易欠擬合。
- kernel:核函數選擇,比較複雜,稍後介紹
- degree:多項式階數,僅在核函數選擇多項式(即“poly”)的時候才生效,int類型,默認為3。
- gamma:核函數係數,僅在核函數為高斯核,多項式核,Sigmoid核(即“rbf“,“poly“ ,“sigmoid“)時生效。float類型,默認為“auto”(即值為 1 / n_features)。
- coef0:核函數的獨立項,僅在核函數為多項式核核Sigmoid核(即“poly“ ,“sigmoid“)時生效。float類型,默認為0.0。獨立項就是常數項。
- shrinking:不斷縮小的啟髮式方法可以加快優化速度。 就像在FAQ中說的那樣,它們有時會有所幫助,有時卻沒有幫助。 我認為這是運行時問題,而不是收斂問題。
- probability:是否使用概率評估,布爾類型,默認為False。開啟的話會評估數據到每個分類的概率,不過這個會使用到較多的計算資源,慎用!!
- tol:停止迭代求解的閾值,單精度類型,默認為1e-3。邏輯回歸也有這樣的一個參數,功能都是一樣的。
- cache_size:指定使用多少內存來運行,浮點型,默認200,單位是MB。
- class_weight:分類權重,也是和邏輯回歸的一樣,我直接就搬當時的內容了:分類權重,可以是一個dict(字典類型),也可以是一個字符串"balanced"字符串。默認是None,也就是不做任何處理,而"balanced"則會去自動計算權重,分類越多的類,權重越低,反之權重越高。也可以自己輸出一個字典,比如一個 0/1 的二元分類,可以傳入{0:0.1,1:0.9},這樣 0 這個分類的權重是0.1,1這個分類的權重是0.9。這樣的目的是因為有些分類問題,樣本極端不平衡,比如網絡攻擊,大部分正常流量,小部分攻擊流量,但攻擊流量非常重要,需要有效識別,這時候就可以設置權重這個參數。
- verbose:輸出詳細過程,int類型,默認為0(不輸出)。當大於等於1時,輸出訓練的詳細過程。僅當"solvers"參數設置為"liblinear"和"lbfgs"時有效。
- max_iter:最大迭代次數,int類型,默認-1(即無限制)。注意前面也有一個tol迭代限制,但這個max_iter的優先級是比它高的,也就如果限制了這個參數,那是不會去管tol這個參數的。
- decision_function_shape:多分類的方案選擇,有“ovo”,“ovr”兩種方案,也可以選則“None”,默認是“ovr”,詳細區別見下面。
- random_state:隨時數種子。

sklearn-SVM參數,kernel特徵選擇

kernel:核函數選擇,字符串類型,可選的有“linear”,“poly”,“rbf”,“sigmoid”,“precomputed”以及自定義的核函數,默認選擇是“rbf”。各個核函數介紹如下:
“linear”:線性核函數,最基礎的核函數,計算速度較快,但無法將數據從低維度演化到高維度
“poly”:多項式核函數,依靠提升維度使得原本線性不可分的數據變得線性可分
“rbf”:高斯核函數,這個可以映射到無限維度,缺點是計算量比較大
“sigmoid”:Sigmoid核函數,對,就是邏輯回歸裏面的那個Sigmoid函數,使用Sigmoid的話,其實就類似使用一個一層的神經網絡
“precomputed”:提供已經計算好的核函數矩陣,sklearn不會再去計算,這個應該不常用
“自定義核函數”:sklearn會使用提供的核函數來進行計算
說這麼多,那麼給個不大嚴謹的推薦吧
樣本多,特徵多,二分類,選擇線性核函數
樣本多,特徵多,多分類,多項式核函數
樣本不多,特徵多,二分類/多分類,高斯核函數
樣本不多,特徵不多,二分類/多分類,高斯核函數

當然,正常情況下,一般都是用交叉驗證來選擇特徵,上面所說只是一個較為粗淺的推薦。

sklearn-SVM參數,多分類方案

其實這個在邏輯回歸裏面已經有說過了,這裏還是多說一下。

原始的SVM是基於二分類的,但有些需求肯定是需要多分類。那麼有沒有辦法讓SVM實現多分類呢?那肯定是有的,還不止一種。

實際上二元分類問題很容易推廣到多元邏輯回歸。比如總是認為某種類型為正值,其餘為0值

舉個例子,要分類為A,B,C三類,那麼就可以把A當作正向數據,B和C當作負向數據來處理,這樣就可以用二分類的方法解決多分類的問題,這種方法就是最常用的one-vs-rest,簡稱OvR。而且這種方法也可以方便得推廣到其他二分類模型中(當然其他算法可能有更好的多分類辦法)。

另一種多分類的方案是Many-vs-Many(MvM),它會選擇一部分類別的樣本和另一部分類別的樣本來做二分類

聽起來很不可思議,但其實確實是能辦到的。比如數據有A,B,C三個分類。

我們將A,B作為正向數據,C作為負向數據,訓練出一個分模型。再將A,C作為正向數據,B作為負向數據,訓練出一個分類模型。最後B,C作為正向數據,C作為負向數據,訓練出一個模型。

通過這三個模型就能實現多分類,當然這裏只是舉個例子,實際使用中有其他更好的MVM方法。限於篇幅這裏不展開了。

MVM中最常用的是One-Vs-One(OvO)。OvO是MvM的特例。即每次選擇兩類樣本來做二元邏輯回歸。

對比下兩種多分類方法,通常情況下,Ovr比較簡單,速度也比較快,但模型精度上沒MvM那麼高。MvM則正好相反,精度高,但速度上比不過Ovr。

4.sklearn SVM實戰

我們還是使用鳶尾花數據集,不過這次只使用其中的兩種花來進行分類。首先準備數據:

import matplotlib.pyplot as plt
import numpy as np
from sklearn import svm,datasets
import pandas as pd
tem_X = iris.data[:, :2]
tem_Y = iris.target
new_data = pd.DataFrame(np.column_stack([tem_X,tem_Y]))
#過濾掉其中一種類型的花
new_data = new_data[new_data[2] != 1.0]
#生成X和Y
X = new_data[[0,1]].values
Y = new_data[[2]].values

然後用數據訓練,並生成最終圖形


# 擬合一個SVM模型
clf = svm.SVC(kernel='linear')
clf.fit(X, Y)

# 獲取分割超平面
w = clf.coef_[0]
# 斜率
a = -w[0] / w[1]
# 從-5到5,順序間隔採樣50個樣本,默認是num=50
# xx = np.linspace(-5, 5)  # , num=50)
xx = np.linspace(-2, 10)  # , num=50)
# 二維的直線方程
yy = a * xx - (clf.intercept_[0]) / w[1]
print("yy=", yy)

# plot the parallels to the separating hyperplane that pass through the support vectors
# 通過支持向量繪製分割超平面
print("support_vectors_=", clf.support_vectors_)
b = clf.support_vectors_[0]
yy_down = a * xx + (b[1] - a * b[0])
b = clf.support_vectors_[-1]
yy_up = a * xx + (b[1] - a * b[0])

# plot the line, the points, and the nearest vectors to the plane
plt.plot(xx, yy, 'k-')
plt.plot(xx, yy_down, 'k--')
plt.plot(xx, yy_up, 'k--')

plt.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1], s=80, facecolors='none')


plt.scatter(X[:, 0].flat, X[:, 1].flat, c='#86c6ec', cmap=plt.cm.Paired)
# import operator
# from functools import reduce
# plt.scatter(X[:, 0].flat, X[:, 1].flat, c=reduce(operator.add, Y), cmap=plt.cm.Paired)

plt.axis('tight')
plt.show()

最終的SVM的分類結果如下:

以上~

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※高價收購3C產品,價格不怕你比較

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

3c收購,鏡頭 收購有可能以全新價回收嗎?

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!