【課程11】圖片上傳模塊.xmind54.6KBhtml
【課程11】消息發...通知.xmind55.2KBreact
【課程11】異常處理分析.xmind95.4KBweb
【課程11預習】多...解讀.xmind0.4MBspring
【課程11】第三方...模塊.xmind0.2MB數據庫
【課程11】多人博...解讀.xmind0.3MBjson
項目簡介
mblog (mtons blog)開源免費的Java多人博客系統。設計模式
技術選型
項目結構
項目模塊切分
咱們先來想一下,咱們能不能一個項目就用一個模塊。這樣開起來很方便,簡單明瞭,那麼作起來呢,接下來咱們分析一下。api
假設咱們有這麼一個項目,整個項目構建一個war包,而每一層放到各自的Package裏面。以下:緩存
Itoo-Exam安全
com.tgb.itoo.exam.dao —–負責與數據庫的交互,封裝了hibernate的交互類
com.tgb.itoo.exam.service—-負責處理業務邏輯,放Service接口及其實現類
com.tgb.itoo.exam.web——-負責與客戶端的交互,主要放action/controller,jsp等等
com.tgb.itoo.exam.util——–工具類
那麼隨着咱們項目的擴大,Maven項目也會愈來愈大,那麼會遇到下面的幾個問題:
一、首先build整個項目的時間愈來愈長,儘管你一直在web層工做,但你不得不build整個項目。
二、模塊化體現不出來,若是咱們只負責維護某個模塊,由於咱們全部的模塊都在一個war包中,那麼咱們能夠隨意修改其餘模塊(權限的控制),致使版本管理混亂,衝突。同時由於模塊太多,太大,很差維護。不少人都在同時修改這個war包,將致使工做沒法順利進行。
三、pom.xml原本是能夠繼承、複用的,可是若是咱們新建一個項目,只能依賴於這個war包,那麼會把這個war包的相關的前臺的東西依賴過來,致使項目管理混亂。
這樣的管理是混亂的,沒有遵照一個設計模式原則:「高內聚,低耦合」。相反在代碼內部,全部的東西都耦合在了一塊兒。所以咱們須要劃分模塊。
另外,隨着技術的飛速發展和各種用戶對軟件的要求愈來愈高,軟件自己變得愈來愈複雜,設計人員開始採用各類方式進行開發,因而就有了咱們的分層架構、分層模塊來提升代碼的清晰和重用。從而實現了系統內部的高內聚、低耦合。
模塊化的好處
一、方便重用,當咱們再開發一條teacher線的時候,咱們只須要引用itoo-base,itoo-exam-api,這些包都是複用的,稱爲咱們平臺複用的基礎類庫,供全部的項目使用。這是模塊化最重要的一個目的。
二、靈活性。好比咱們這些公共的jar包,itoo-base,itoo-tool,itoo-exam-api等這些jar包,咱們不須要再當源碼,只須要deploy到nexus,其餘人從nexus下載便可。代碼的可維護性、可擴展性好,而且保證了項目獨立性與完整性。
使用模塊化配置,複用性強,防止pom變得過於龐大,方便構建;針對項目的管理更方便,每個模塊都是獨立的,抽象出一個父類來管理第三方jar的版本,開發人員只須要開發本身的線,其餘的都不用管,靈活;基於此種基礎咱們還能夠作分佈式。
聚合:
Maven聚合:當咱們的模塊很是多的時候,咱們想要一次構建多個項目,而不是到多個模塊的目錄下分別執行命令。Maven的聚合特性就是爲該需求服務的。
Maven構建: Maven首先解析聚合模塊pom、分析要構建的模塊、並計算出一個反應堆構建順序,而後根據這個順序依次構建各個模塊。反應堆是全部模塊組成的一個構建結構。
繼承
Maven繼承也是爲了防止重複,讓項目的jar包版本一致,在項目管理上起了很大的做用。
好比說相同的jar包咱們每一個人都須要依賴一遍,而且每一個人引用的版本號不一樣,勢必形成項目混亂,運行出問題。
一、子模塊省略grouopId和version,都會從父模塊依賴下來。
二、子模塊元素pom.xml。
總之,聚合是爲了方便快速構建項目,繼承是爲了消除重複配置,在簡化pom的同時還能促進各個模塊配置的一致性。
異常處理機制
關鍵類-HandlerExceptionResolver
Spring MVC經過HandlerExceptionResolver處理程序的異常,包括Handler的映射、數據綁定以及目標方法的執行。HandlerExceptionResolver時一個接口,該接口的實現類都有處理異常的功能。HandlerExceptionResolver是該接口應用普遍的一個實現類,而且DispatcherServlet默認裝配了HandlerExceptionResolver 的Bean。
SpringMVC 提供的異常處理主要有兩種方式:
- 一種是直接實現本身的HandlerExceptionResolver
經過註解的方式實現處理異常主要有如下兩種方式:
- 1 @ControllerAdvice+@ExceptionHandler:配置對全局異常進行處理
- 2 @Controller + @ExceptionHandler:配置對當前所在Controller的異常進行處理
在SpringMVC中,處理異常類其實是HandlerExceptionResolver子類。HandlerExceptionResolver處理全部controller類在執行過程當中拋出的未被處理的異常。
本文演示如何使用以上多種處理異常的方式,最後演示以不一樣的方式將異常結果返回給調用者
系統默認實現的HandlerExceptionResolver
如下是系統默認加載到spring mvc容器中的HandlerExceptionResolver
- ExceptionHandlerExceptionResolver: 根據@ExceptionHandler註解的方法處理對應的異常。其實上文的經過註解的方式處理異常,實際就是在這個類中實現
- ResponseStatusExceptionResolver: 根據@ResponseStatus註解的方法處理異常
- DefaultHandlerExceptionResolver: 將異常轉化爲特定的HTTP的狀態碼
- HandlerExceptionResolverComposite:此類經過列表包含以上3個HandlerExceptionResolver,當捕獲異常時,會循環調用以上3個HandlerExceptionResolver進行處理
ExceptionHandlerExceptionResolver處理過程總結一下:
- 根據用戶調用Controller中相應的方法獲得HandlerMethod,以後構造ExceptionHandlerMethodResolver,
- 構造ExceptionHandlerMethodResolver有2種選擇,
- 1.經過HandlerMethod拿到Controller,找出Controller中帶有@ExceptionHandler註解的方法(局部)
- 2.找到@ControllerAdvice註解配置的類中的@ExceptionHandler註解的方法(全局)。
- 這2種方式構造的ExceptionHandlerMethodResolver中都有1個key爲Throwable,value爲Method的緩存。以後經過發生的異常找出對應的Method,而後調用這個方法進行處理。
- 這裏異常還有個優先級的問題,好比發生的是NullPointerException,可是聲明的異常有Throwable和Exception,這時候ExceptionHandlerMethodResolver找Method的時候會根據異常的最近繼承關係找到繼承深度最淺的那個異常,即Exception。
發佈與通知
常見場景
在實際開發過程當中,經常遇到這種場景:
作完某一件事情之後,須要廣播一些消息或者通知,告訴其餘的模塊進行一些事件處理,通常來講,能夠一個一個發送請求去通知,可是這種方式比較消耗業務時間。那種更好解決方法呢,那就是事件監聽,事件監聽也是設計模式中 發佈-訂閱模式、觀察者模式的一種實現。
觀察者模式:在對象之間定義了一對多的依賴,這樣一來,當一個對象改變狀態,依賴它的對象會收到通知並自動更新。
Spring的事件爲Bean和Bean之間的消息傳遞提供支持。當一個對象處理完某種任務後,通知另外的對象進行某些處理,經常使用的場景有進行某些操做後發送通知,消息,操做記錄,發送短信、郵件等狀況。
Spring的事件遵循的流程
自定義事件,繼承ApplicationEvent(org.springframework.context.ApplicationEvent)
定義監聽事件,實現ApplicationListener(org.springframework.context.ApplicationListener)
使用容器觸發事件。
發佈事件,使用applicationContext發佈事件。
ApplicationEvent中,在自定義事件的構造方法中除了第一個source參數,其餘參數均可以去自定義,
能夠根據項目實際狀況進行監聽傳參,這裏就只定義個簡單的String字符串的透傳。
邏輯整理
這裏使用一種簡單的單機實現Spring的事件模型ApplicationEvent。
第一步、分別自定義訂閱和通知事件,繼承ApplicationEvent
第二步、分別定義事件監聽器,實現ApplicationListener
第三步、使用容器發佈事件(訂閱事件、通知事件)
項目運用
- FollowController.sendNotify()-->發送關注通知。
- NotifyEventHandler,關注事件監聽處理類,保存通知到數據庫
spring中的運用
知識拓展--@EventListener
有條件的事件處理
爲了使註釋@EventListener的功能更強大,Spring 4.2支持用SpEL表達式表達事件類型的方式
定義事件
public class TestEvent extends ApplicationEvent {
public boolean isImport;
public TestEvent(Object source, boolean isImport) {
super(source);
this.isImport = isImport;
}
public boolean isImport() {
return isImport;
}
public void setImport(boolean anImport) {
isImport = anImport;
}
@Override
public String toString() {
return "TestEvent{" +
"isImport=" + isImport +
'}';
}
}
定義監聽
@Component
public class EventHandler {
@EventListener(condition="#testEvent.isImport")
public void TestEventTest(TestEvent testEvent) {
System.out.println("==============TestEvent==============" + testEvent.toString());
}
}
知識拓展--guava的EventBus
定義事件
public class GuavaEvent {
private final int message;
public GuavaEvent(int message) {
this.message = message;
System.out.println("event message:"+message);
}
public int getMessage() {
return message;
}
}
定義事件監聽
public class GuavaEventListener {
public int lastMessage = 0;
@Subscribe
public void listen(GuavaEvent event) {
lastMessage = event.getMessage();
System.out.println("guava--------Message:"+lastMessage);
}
public int getLastMessage() {
return lastMessage;
}
}
發佈事件
EventBus eventBus = new EventBus();
GuavaEventListener listener = new GuavaEventListener();
eventBus.register(listener);
eventBus.post(new GuavaEvent(200));
eventBus.post(new GuavaEvent(300));
System.out.println("LastMessage:"+listener.getLastMessage());
事件監聽者[Listeners]
監聽特定事件(如,CustomerChangeEvent)
- 傳統實現:定義相應的事件監聽者類,如CustomerChangeEventListener;
- EventBus實現:以CustomerChangeEvent爲惟一參數建立方法,並用Subscribe註解標記。
把事件監聽者註冊到事件生產者:
- 傳統實現:調用事件生產者的registerCustomerChangeEventListener方法;這些方法不多定義在公共接口中,所以開發者必須知道全部事件生產者的類型,才能正確地註冊監聽者;
- EventBus實現:在EventBus實例上調用EventBus.register(Object)方法;請保證事件生產者和監聽者共享相同的EventBus實例。
按事件超類監聽(如,EventObject甚至Object):
- EventBus實現:EventBus自動把事件分發給事件超類的監聽者,而且容許監聽者聲明監聽接口類型和泛型的通配符類型(wildcard,如 ? super XXX)。
檢測沒有監聽者的事件:
- 傳統實現:在每一個事件分發方法中添加邏輯代碼(也可能適用AOP);
- EventBus實現:監聽DeadEvent;EventBus會把全部發布後沒有監聽者處理的事件包裝爲DeadEvent(對調試很便利)。
事件生產者[Producers]
管理和追蹤監聽者:
- 傳統實現:用列表管理監聽者,還要考慮線程同步;或者使用工具類,如EventListenerList;
- EventBus實現:EventBus內部已經實現了監聽者管理。
向監聽者分發事件:
- 傳統實現:開發者本身寫代碼,包括事件類型匹配、異常處理、異步分發;
- EventBus實現:把事件傳遞給 EventBus.post(Object)方法。異步分發能夠直接用EventBus的子類AsyncEventBus。
圖片上傳模塊
@PostContruct
是spring框架的註解,在方法上加該註解會在項目啓動的時候執行該方法,也能夠理解爲在spring容器初始化的時候執行該方法。
從這個文件開始看:
- 圖片上傳的入口,經過upload方法上傳圖片,並返回上傳結果
- 實現FileRepo接口,把實現類基礎公用部分的方法實現。
- 繼承抽象類AbstractFileRepo,重寫getRoot()方法,爲絕對路徑保存圖片
DateFormatUtils.format(new Date(), YYYYMMDDHHMMSS);
圖片壓縮關鍵類-- Thumbnails
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.8</version>
</dependency>
使用例子:
Oauth2.0協議運用
傳統受權方式缺點:
(1)網站爲了後續的服務,會保存用戶的密碼,這樣很不安全。
(2)Google不得不部署密碼登陸,而咱們知道,單純的密碼登陸並不安全。
(3)網站擁有了獲取用戶儲存在Google全部資料的權力,用戶無法限制網站得到受權的範圍和有效期。
(4)用戶只有修改密碼,才能收回賦予網站的權力。可是這樣作,會使得其餘全部得到用戶受權的第三方應用程序所有失效。
(5)只要有一個第三方應用程序被破解,就會致使用戶密碼泄漏,以及全部被密碼保護的數據泄漏。
OAuth就是爲了解決上面這些問題而誕生的。
協議原理
(A)用戶打開客戶端之後,客戶端要求用戶給予受權。
(B)用戶贊成給予客戶端受權。
(C)客戶端使用上一步得到的受權,向認證服務器申請令牌。
(D)認證服務器對客戶端進行認證之後,確認無誤,贊成發放令牌。
(E)客戶端使用令牌,向資源服務器申請獲取資源。
(F)資源服務器確認令牌無誤,贊成向客戶端開放資源。
OAuth的校驗流程爲何這麼複雜,直接受權以後redirect回accessToken不就結了嗎?爲何還要返回auth_code以後請求accessToken?
首先,redirect是不安全的,隨時能夠暫停回調而拿到accessToken,拿到了accessToken也就意味着拿到了受權,可是auth_code是和client相對應的,那麼即便拿到了auth_code還須要再次申請accessToken,申請accessToken時須要校驗Client和state。同時協議設計的原則就是隻有Client能拿到accessToken而用戶是拿不到的。
Oauth2.0安全使用建議
資源提供方:
- 獲取access token的code僅能使用一次,且與受權用戶關聯
資源使用方:
- 使用Authorization Code方式進行受權
- 受權過程使用state隨機哈希,並在服務端進行判斷
- 最後,對oauth2.0有詳細的瞭解,嚴格按照流程進行開發。
QQ受權登陸
QQ登陸OAuth2.0整體處理流程以下:
Step1:申請接入,獲取appid和apikey;
Step2:開發應用,並設置協做者賬號進行測試聯調;
Step3:放置QQ登陸按鈕;
Step4:經過用戶登陸驗證和受權,獲取Access Token;
Step5:經過Access Token獲取用戶的OpenID;
Step6:調用OpenAPI,來請求訪問或修改用戶受權的資源。
QQ互聯官網開發攻略:http://wiki.connect.qq.com/%E5%BC%80%E5%8F%91%E6%94%BB%E7%95%A5_server-side
項目接入:
- http://localhost:8080/oauth/callback/call_qq
- https://graph.qq.com/oauth2.0/authorize?response_type=code&redirect_uri=&state=0vnuc37nwskcs9cr3yo1wvaq&client_id=
- 經過Authorization Code獲取Access Token
- https://graph.qq.com/oauth2.0/token?code
- https://graph.qq.com/oauth2.0/me?access_token=
- 經過accessToken和openid獲取用戶信息
- https://graph.qq.com/user/get_user_info?access_token=&oauth_consumer_key=&openid=&format=json
- 判斷是否已經註冊,爲註冊跳轉到/bind_oauth方法進行帳號註冊與綁定。而後使用shiro登陸。