Action的三種實現方式,struts.xml配置的詳細解釋及其簡單執行過程(二)

勿以惡小而為之,勿以善小而不為————————–劉備

勸諸君,多行善事積福報,莫作惡

上一章簡單介紹了Struts2的’兩個蝴蝶飛,你好’ (一),如果沒有看過,請觀看上一章

一 Action的三種實現方式

上一章開發的HelloAction和HelloAction2,並沒有繼承任何類或者實現任何接口,但是必須有一個execute() 方法,方法返回值是String類型。

這樣的代碼不容易理解,更並不能使人看得出這個類是干什麼的,甚至不能區分這個控制器類與普通的Java類有什麼區別,通常開發中不這樣做。

我們開發者在開發Struts2框架的時候,希望自己寫的這個Action類能夠具有易理解性,且已經支持某些功能,如參數接收,文件上傳等。

一.一 第一種實現方式(普通Java類,裏面只包含execute()方法)

package com.yjl.web.action;
import org.apache.log4j.Logger;
/**
* @author 兩個蝴蝶飛
* @version 創建時間:2018年8月23日 上午9:41:32
* @description 第一種實現方式,普通java類,
* 有一個execute()方法,也可以多寫幾個方法,用action中的標籤method來控制,可以正常訪問。
*/
public class Hello1Action {
	private static Logger logger=Logger.getLogger(Hello1Action.class);
	public String execute() {
		logger.info("兩個蝴蝶飛,web層你好");
		return "success";
	}
}

不具有開發時要求的規範性,且不支持某些struts2自身提供的功能。

方法名稱只有一個 execute()

一.二 第二種實現方式(實現Action接口)

package com.yjl.web.action;
import com.opensymphony.xwork2.Action;
/**
* @author 兩個蝴蝶飛
* @version 創建時間:2018年8月23日 上午10:54:03
* @description 第二種實現方式,實現Action接口,重寫裏面的execute()方法
* 有一個execute()方法和五個String類型的常量
*/
public class Hello2Action implements Action{
	@Override
	public String execute() throws Exception {
		return Action.SUCCESS;
		//return Action.ERROR;
		//return Action.LOGIN;
		//return Action.NONE;
		//return Action.INPUT;
	}
}

注意,Action接口是xwork2包下的接口。

實現了Action接口,使開發者能夠看出來這是一個Action,具有了一定程度上的開發規範,

但是實現了Action接口,所以必須要重寫execute()方法。

一般自己寫Action,構思好之後上來就直接add(), edit(), delete(). select() 這些業務方法,

每次都要重寫execute()方法,不太方便。 而且這種方式不具有struts2中某些功能,如驗證框架和國際化。

Action中接口中有五個常用的結果字符串(好多方法都返回success,error,login,input,none,故將其封裝了一下) .

這些字符串雖然是大寫,然而真實的值是全部小寫.

package com.opensymphony.xwork2;

public abstract interface Action
{
  public static final String SUCCESS = "success";
  public static final String NONE = "none";
  public static final String ERROR = "error";
  public static final String INPUT = "input";
  public static final String LOGIN = "login";
  
  public abstract String execute()
    throws Exception;
}

一.三 繼承ActionSupport類(官方推薦)

	package com.yjl.web.action;
	import com.opensymphony.xwork2.ActionSupport;
	/**
	* @author 兩個蝴蝶飛
	* @version 創建時間:2018年8月23日 上午11:04:20
	* @description 第三種方式,繼承ActionSupport類。
	* ActionSupport類實現了Action接口,也有Action中的五個常量.
	*/
	public class Hello3Action extends ActionSupport{
		public String list() {
			return "list";
		}
	}

繼承了ActionSupport類,不需要重新寫execute()方法,直接寫業務方法即可。

ActionSupport類,已經實現了 Action接口。 其具備Action中的五個常量,並且該類還實現了其他接口,

源代碼:

	public class ActionSupport implements Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable{
    ...
	public String execute() throws Exception
  	{
		//默認返回的是 success 字符串 
  		 return "success";
 	}
 	...
}

如驗證框架(Validateable,ValidationAware),國際化(LocaleProvider)。

以後開發中,使用 繼承 ActionSupport 類的形式。

二 配置文件 struts.xml中節點的詳細解釋

在src下有一個struts.xml的配置文件,它配置了開發者自己編寫實現的Action,是struts2框架的核心,不能改變文件名稱。(注意,是struts.xml,並不是struts2.xml,並沒有那個2)。

在struts.xml中,最上面是一個約束, 是一個根節點。

二.一 修改常量節點

在struts-core.jar核心包下,有一個包org.apache.struts2包下,有一個default.properties屬性文件,裏面記錄了很多常用的常量,

其中常見的有:

struts.i18n.encoding=UTF-8 
struts.multipart.maxSize=2097152
struts.action.extension=action,,
struts.enable.DynamicMethodInvocation = false
struts.devMode = false
struts.ui.theme=xhtml
struts.ognl.allowStaticMethodAccess=false

建議修改后的值為:

###國際化操作,編碼格式為UTF-8
struts.i18n.encoding=UTF-8
###上傳文件時最大的上傳大小,默認為2M. 根據項目情況具體填寫值,建議後面加兩個00
struts.multipart.maxSize=209715200
###struts的訪問後綴名, struts1框架默認的是 .do 
struts.action.extension=action,,
###struts是否可以訪問靜態方法
struts.enable.DynamicMethodInvocation =true
###struts是否是開發者模式
struts.devMode =true
###struts中ui標籤的主題,建議為simple
struts.ui.theme=simple
###ognl中是否可以訪問靜態方法,為true
struts.ognl.allowStaticMethodAccess=true

可以在struts.xml中進行相應的修改,如

 <!--修改國際化編碼 -->
<constant name="struts.i18n.encoding" value="UTF-8"></constant>
<!--修改是否為開發者模式 -->
<constant name="struts.devMode" value="true"></constant>

按照name,value值的形式進行填寫。

也可以在src下新建一個struts.properties,然後將這些值放置進去,struts也會自動struts.propeties中的常量值的。

也可以在web.xml中,在 中,以 局部參數的形式傳遞進去。

建議使用第一種方式,在struts.xml中用 ,畢竟這個文件常常打開,出錯了也容易發現。

二.二 分模塊開發

在實際的項目中,有很多的模塊,如果所有的配置都放在一個struts.xml,那麼一旦這個struts.xml被其他人誤操作導致了錯誤,那麼其他人的項目將無法運行的,當配置內容過多時,struts.xml的內容太長,不便於維護,所以最好是分模塊開發,一個模塊用一個配置文件,然後再利用 進行導入, 類似 於jsp中的 靜態包含一樣。

所以建議每一個模塊都寫一個模塊.xml,然後在struts.xml中引入即可。如有三個模塊 User模塊和Class,Course,那麼可以將User的配置放置在user.xml中,Class配置放置在class.xml中,course模塊放置在course.xml,在struts.xml中只需要

	<include file="user.xml"></include>
	<include file="class.xml"></include>
	<include file="course.xml"></include>

靜態包含即可。 注意,file的文件路徑引用是否輸入正確。

正確的位置引用,點擊ctrl+模塊.xml時,可以跳轉到相應的.xml文件中。如果沒有跳轉和反應,那說明位置引用錯誤,需要重新檢查一下。

二.三 包節點

在struts.xml配置文件中,最重要的節點就是package節點。 package,分包。 可以將action進行分包處理。

這樣每一個action或者每一組action用package進行隔開,便於維護,類似於java中package的概念。

二.三.一 <package> 節點的使用

<package name="hello" extends="struts-default" namespace="/">
        <!--具體的Action-->
</package>

package中name節點是package的名字,是獨一無二的,不能夠重複。 最好與模塊名相同或者起一個有意義的名稱。

extends節點表示繼承,即package之間可以相互的繼承,來避免重複化功能的編寫。 默認為struts-default。

struts-default中struts已經定義了很多功能,開發者自己寫的包只需要extends 這個包名struts-default,

就擁有了struts已經定義好的功能。 如攔截器功能,文件上傳功能。

用戶也可以自己繼承自己所寫的包 。如父包名為

那麼子包只需要 , 這樣child包不但擁有struts-default的功能,也擁有parent包中的特殊功能,這也是Java的多重繼承的體現。 所以package的name 要符合標識符的規範,具有可讀性。

namespace節點表示命名空間,以/開頭,默認是”/” 。是為了在訪問路徑和訪問請求url方面體現package的分包作用. package中的name是在配置文件中體現分包,namespace是在url中體現分包。 建議開發中,namespace的路徑名與name保持一致。 package中的namespace的值與子節點action中name的值,共同構成了完整的訪問請求路徑。

二.三.二 <package></package> 子節點<action></action>節點的使用

在Hello3Action中定義兩個方法,一個是list()查詢,一個是add()添加的方法。

package com.yjl.web.action;
import org.apache.log4j.Logger;
import com.opensymphony.xwork2.ActionSupport;
/**
* @author 兩個蝴蝶飛
* @version 創建時間:2018年8月23日 上午11:04:20
* @description 測試action標籤中method的方法訪問
*/
public class Hello3Action extends ActionSupport{
	private static final long serialVersionUID = 8737138848863458260L;
	Logger logger=Logger.getLogger(Hello3Action.class);
	public String list() {
		logger.info("執行list方法");
		return "list";
	}
	public String add() {
		logger.info("執行add方法");
		return "add";
	}
}

標籤,有三個基本的屬性,

	<action name="list" class="com.yjl.web.action.Hello3Action"
        method="list">

</action>

其中name為action的名字,表示區別一個package包下的不同的action。 其中這個name的值,不應該隨便取,應該是要訪問的方法名。

在瀏覽器客戶端請求的url為 /項目名/package的namespace名稱/action的name名稱.action;

class為要訪問的那個Action的全限定名稱,是class,用.(點)進行分隔。

其中,class 可以省略, 省略默認為 ActionSupport 類, 全限定名稱為: com.opensymphony.xwork2.ActionSupport
method為要訪問的那個方法名稱,類 extends ActionSupport 后,有很多很多的方法,如list(), add(), delete()等,那麼怎麼知道具體要訪問哪個方法呢? 用method這個屬性. method=”要方法的方法名” ,是方法名。

action還有一個節點是converter,表示所用的是哪一個類型轉換器。(後面會有相應的解釋)

很清楚, action 中的 class指定了訪問的是哪一個action, method 指定了訪問的是哪一個具體的方法, 利用了反射技術實現。

在本實例了有兩個方法,所以要進行寫兩個Action, 一個Action類中會有多個方法,難道要一個個配置多個Action嗎?

Struts2提供了一些簡單的方式

二.三.三 配置Action的三種形式

二.三.三.一 通過配置method的屬性完成

簡單舉例如下:

	<action name="list" class="com.yjl.web.action.Hello3Action"
		method="list">
			
	</action>
  <action name="add" class="com.yjl.web.action.Hello3Action"
		method="add">
            
    </action>

缺點: 有幾個方法,就要配置有幾個action,當方法過多時,不易維護。

二.三.三.二 通過配置 通配符完成。

簡單舉例如下:

	<action name="Hello3_*" class="com.yjl.web.action.Hello3Action"
		method="{1}">
			
		</action>

name的值為: 類簡寫名(去掉Action后)_* method中的值取第一個{1},從1開始,不是從0開始。

這樣訪問Hello3Action中的list方法,訪問路徑就是 Hello3_list

訪問Hello3Action中的add方法,訪問路徑就是Hello3_add

簡化了action的相關配置。

也有的人配置的更狠, 會配置成_, 即:

	<action name="*_*" class="com.yjl.web.action.{1}Action"
		method="{2}">
			
		</action>

User類中的list就是User_list, User類中的add就是User_add,

Class類中的list就是Class_list,Class類中的add就是Class_add

這樣雖說簡化了開發,但卻不利用 result 節點的維護 ,不建議這樣配置。

好多類的好多方法返回值,都寫在這一個action 下面,會亂。

二.三.三.三 動態方法訪問

不是用 * 通配符,而是用! 號。 即:

想訪問UserAction中list方法() 前端寫url為 userAction!list.action
想訪問UserAction中add方法() 前端寫url為 userAction!add.action
想訪問ClassAction中list方法() 前端寫url為 classAction!list.action
想訪問ClassAction中add方法() 前端寫url為 classAction!add.action

這樣訪問也特別的方便。

這樣的話, action中只需要配置name和class即可。 method已經由外部指定了,不需要寫method的值了。

需要先添加變量 struts.enable.DynamicMethodInvocation, 使其變成 true,開啟。

	<constant name="struts.enable.DynamicMethodInvocation" value="true"></constant>

如果是UserAction的話,配置應該是:

<action name="userAction" class="com.yjl.web.action.UserAction" >
			
</action>

ClassAction的話,配置應該是

<action name="classAction" class="com.yjl.web.action.ClassAction" >
			
</action>

二.三.四 action子節點result的配置

result表示結果,是對方法的返回值進行相應的分析。有兩個屬性,name和type

	<result name="success" type="dispatcher">/index.jsp</result>

其中name的值要與方法的返回值保持一致。

如 list方法返回值是return SUCCESS,那麼這個list方法的返回值對應的result的值就是 ,

如果返回是”hello”, 那麼這個name的返回值就是

如果在action中配置通配符, name=Hello3_*形式,method=”{1}”, 那麼為了簡化result的配置,可以將result配置成 name={1},

相應的.jsp,可以變成 /{1}.jsp。

但這樣必須保證Action中方法的名稱與返回值的名稱相同,並且與跳轉到的jsp的名稱也要相同, 這樣不太好。

result中type五種常見的形式, dispatcher(轉發到jsp),redirect(重定向到jsp), chain(轉發到另外一個方法),redirectAction(重定向到另外一個方法),stream(上傳和下載流)

其中dispathcer和redirect是跳轉到jsp,如果想要傳遞數據,用dispather,

如果不想傳遞數據,用redirect (dispathcer是轉發,redirect是重定向)

chain,redirectAction是跳轉到action的操作,一般用於這同一個類中兩個方法之間的跳轉,

如add()添加成功之後,需要跳轉到list()方法進行显示結果,這時就可以配置成:

	<result name="add" type="redirectAction">Hello3_list</result>

地址url也會相應的改變,如果是chain的話,地址欄是不會改變的。 chain是轉發到action, redirectAction是重定向到action.

也可以在不同包之間的action進行的跳轉 。

如 add 方法 想到跳轉到 /class 命名空間下的 Hello2Action 的 list 方法。

<result name="add" type="redirectAction">
	<!-- 要跳轉到哪一個命名空間,即哪一個包 -->
	<param name="namespace">/class</param>
	<!-- 要跳轉到哪一個Action 不加後綴 -->
	<param name="actionName">Hello2Action</param>
	<!-- 跳轉到哪一個方法 -->
	<param name="method">list</param>
	<!-- 可能要傳遞的參數. 用ognl表達式,根據情況添加 -->
	<param name="id">${id}</param>
</result>

通過 param 標籤來配置帶參還是不帶參。

二.四 全局結果頁面與局部結果頁面。

這個全局是相對於package來說的,是package中的全局,並不是所有的struts.xml中的全局,所以全局結果的節點位置應該放在package節點裏面,與action節點平行。 用 節點。

常用的全局結果頁面有兩種:

error錯誤頁面,頁面出錯了都显示這個頁面,

login 登錄頁面, 如果沒有登錄,輸入任何url都會跳轉到login頁面(認證時用)

noprivilege 沒有權限頁面,如果用戶沒有權限訪問了某一個頁面,會給出相應的提示(授權時用)

<global-results>
			<result name="error">/error/error.jsp</result>
			<result name="login">/login.jsp</result>
            <result name="noprivilege">/noprivilege.jsp</result>
</global-results>

當全局結果頁面與局部結果頁面發生衝突時,以局部結果頁面為準。

全局配置時:

<global-results>
			<result name="success">/successGlobal.jsp</result>
</global-results>

在該包下的某個action 的方法result 也返回了 success

	<result name='success'>success.jsp</result>

那麼,當邏輯視圖為 success時,最終將返回 success.jsp

二.五 配置跳轉頁面

在開發中,常常有這麼一種情況,

請求login.jsp 時,為 /login, 那麼就跳轉到 login.jsp 頁面,

語法為 register.jsp 時,為 /register, 那麼就跳轉到 register 頁面。

這個時候,配置 為:

	<action name="*">
			<result>/WEB-INF/content/{1}.jsp</result>
	</action>

將頁面放置在 content 文件夾下面,避免用戶直接訪問 jsp頁面。

注意,要將此 action 放置在最後, 當所有上面的action都不匹配時,才匹配這一個action.

三 Struts2的執行流程

當用戶在客戶端發送一個請求后,如常用的標準的http://localhost:8080/Struts_Hello/user/User_add.action時,

會經過前端控制器(StrutsPrepareAndExecuteFilter) 過濾器,執行一連串的過濾器鏈,然後根據user 找到了對應的package的namespape,進入到具體的package包下。 利用通配符的方式進行訪問,User_add會進行匹配相應的action,根據class和method找到是哪一個類的哪一個方法,在實例化類Action之前,會先執行攔截器。通過反射實例化類,運行方法, 方法運行成功之後,有一個返回值,這個返回值會與剛才action下的 中的name進行相應的匹配,匹配到哪一個,就執行哪一個result。 如果是diapatcher或者redirect,就显示到相應的.jsp頁面(帶有數據), 如果是chain或者redirectAction,那麼就去執行那一個方法,之後進行返回具體的視圖。

執行過程圖如下:

謝謝您的觀看!!!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※教你寫出一流的銷售文案?

※別再煩惱如何寫文案,掌握八大原則!

程序員實用JDK小工具歸納,工作用得到

在JDK的安用裝目錄bin下,有一些有非常實用的小工具,可用於分析JVM初始配置、內存溢出異常等問題,我們接下來將對些常用的工具進行一些說明。

JDK小工具簡介

在JDK的bin目錄下面有一些小工具,如javac,jar,jstack,jstat等,在日常編譯運行過程中有着不少的“額外”功能,那麼它們是怎麼工作的呢?雖然這些文件本身已經被編譯成可執行二進制文件了,但是其實它們的功能都是由tools.jar這個工具包(配合一些dll或者so本地庫)完成的,每個可執行文件都對應一個包含main函數入口的java類(有興趣可以閱讀openJDK相關的源碼,它們的對應關係如下(更多可去openJDK查閱):

javac com.sun.tools.javac.Main
jar sun.tools.jar.Main
jps sun.tools.jps.Jps
jstat sun.tools.jstat.Jstat
jstack    sun.tools.jstack.JStack
...

tools.jar的使用

我們一般開發機器上都會安裝JDK+jre,這時候,要用這些工具,直接運行二進制可執行文件就行了,但是有時候,機器上只有jre而沒有JDK,我們就無法用了么?

如果你知道如上的對應關係的話,我們就可以”構造”出這些工具來(當然也可以把JDK安裝一遍,本篇只是介紹另一種選擇),比如我們編寫

//Hello.java
public class Hello{
    public static void main(String[] args)throws Exception{
        while(true){
            test1();
            Thread.sleep(1000L);
        }
    }
    public static void test1(){
        test2();
    }
    public static void test2(){
        System.out.println("invoke test2");
    }
}

可以驗證如下功能轉換關係

1.編譯源文件:

javac Hello.java => java -cp tools.jar com.sun.tools.javac.Main Hello.java

結果一樣,都可以生成Hello.class文件
然後我們開始運行java -cp . Hello

2.查看java進程:

jps => java -cp tools.jar sun.tools.jps.Jps

結果一樣,如下:

4615 Jps
11048 jar
3003 Hello

3.動態查看內存:

jstat -gcutil 3003 100 3 => java -cp tools.jar sun.tools.jstat.Jstat -gcutil 3003 100 3

發現結果是一樣的

  S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
  0.00 0.00 4.00 0.00 17.42 19.65 0 0.000 0 0.000 0.000
  0.00 0.00 4.00 0.00 17.42 19.65 0 0.000 0 0.000 0.000
  0.00 0.00 4.00 0.00 17.42 19.65 0 0.000 0 0.000 0.000

4.查看當前運行棧信息
正常情況,執行如下命令結果也是一樣,可以正常輸出

jstack 3003 =》 java -cp tools.jar sun.tools.jstack.JStack 3003

但是有的jre安裝不正常的時候,會報如下錯誤

Exception in thread "main" java.lang.UnsatisfiedLinkError: no attach in java.library.path

這是因為jstack的運行需要attach本地庫的支持,我們需要在系統變量裏面配置上其路徑,假如路徑為/home/JDK/jre/bin/libattach.so
命令轉換成

jstack 3003 =》 java -Djava.library.path=/home/JDK/jre/bin -cp tools.jar sun.tools.jstack.JStack 3003

就可以實現了
在linux系統中是libattach.so,而在windows系統中是attach.dll,它提供了一個與本機jvm通信的能力,利用它可以與本地的jvm進行通信,許多java小工具就可能通過它來獲取jvm運行時狀態,也可以對jvm執行一些操作

attach使用

1. 編寫agent.jar代理包

  • 編寫一個Agent類
//Agent.java
public class Agent{
    public static void agentmain(String args, java.lang.instrument.Instrumentation inst) {
        System.out.println("agent : " + args);
    }
}
  • 編譯Agent
java -cp tools.jar com.sun.tools.javac.Main Agent.java
//或者
javac Agent.java
  • 再編manifest.mf文件
//manifest.mf
Manifest-Version: 1.0
Agent-Class: Agent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
  • 把Agent.class和manifest.mf進行打包成agent.jar
java -cp tools.jar sun.tools.jar.Main -cmf manifest.mf agent.jar Agent.class
//或者
jar -cmf manifest.mf agent.jar Agent.class

2.attach進程

  • 編寫如下attach類,編譯並執行
//AttachMain.java
public class AttachMain {
    public static void main(String[] args) throws Exception {
        com.sun.tools.attach.VirtualMachine vm = com.sun.tools.attach.VirtualMachine.attach(args[0]);
        vm.loadAgent("agent.jar", "inject params");
        vm.detach();
    }
}
  • 編譯:
java -cp tools.jar com.sun.tools.javac.Main -cp tools.jar AttachMain.java
//或者
javac -cp tools.jar AttachMain.java
  • 執行attach
java -cp .:tools.jar AttachMain 3003
  • 查看Hello進程有如下輸出:
invoke test2
invoke test2
invoke test2
invoke test2
invoke test2
invoke test2
invoke test2
agent : inject params
invoke test2

說明attach成功了,而且在目標java進程中引入了agent.jar這個包,並且在其中一個線程中執行了manifest文件中agentmain類的agentmain方法,詳細原理可以見JVMTI的介紹,例如oracle的介紹

3. 用attach製作小工具

  • 寫一個使進程OutOfMemory/StackOverFlow的工具
    有了attach的方便使用,我們可以在agentmain中新起動一個線程(為避免把attach線程污染掉),在裏面無限分配內存但不回收,就可以產生OOM或者stackoverflow
    代碼如下:
//Agent.java for OOM
public class Agent{
    public static void agentmain(String args, java.lang.instrument.Instrumentation inst) {
        new Thread() {
            @Override
            public void run() {
                java.util.List<byte[]> list = new java.util.ArrayList<byte[]>();
                try {
                    while(true) {
                        list.add(new byte[100*1024*1024]);
                        Thread.sleep(100L);
                    }
                } catch (InterruptedException e) {
                }
            }
        }.start();
    }
}
//Agent.java for stackoverflow
public class Agent{
    public static void agentmain(String args, java.lang.instrument.Instrumentation inst) {
        new Thread() {
            @Override
            public void run() {
                stackOver();
            }
            private void stackOver(){
                stackOver();
            }
        }.start();
    }
}

當測試OOM的時候,hello進程的輸出為:

invoke test2
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
        at Agent$1.run(Agent.java:9)
invoke test2
invoke test2
invoke test2

說明發生OOM了, 但是OOM線程退出了,其它線程還在正常運行。

如果我們需要進程在OOM的時候產生一些動作,我們可以在進程啟動的時候增加一些OOM相關的VM參數

  • OOM的時候直接kill掉進程:-XX:OnOutOfMemoryError=”kill -9 %p”
    結果如下:
invoke test2
invoke test2
#
# java.lang.OutOfMemoryError: Java heap space
# -XX:OnOutOfMemoryError="kill -9 %p"
#   Executing /bin/sh -c "kill -9 26829"...
Killed
  • OOM的時候直接退出進程:-XX:+ExitOnOutOfMemoryError
    結果如下:
invoke test2
invoke test2
Terminating due to java.lang.OutOfMemoryError: Java heap space
  • OOM的時候進程crash掉:-XX:+CrashOnOutOfMemoryError
    結果如下:
invoke test2
invoke test2
Aborting due to java.lang.OutOfMemoryError: Java heap space
invoke test2#
# A fatal error has been detected by the Java Runtime Environment:
#
#  Internal Error (debug.cpp:308)
, pid=42675, tid=0x00007f3710bf4700
#  fatal error: OutOfMemory encountered: Java heap space
#
# JRE version: Java(TM) SE Runtime Environment (8.0_171-b11) (build 1.8.0_171-b11)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.171-b11 mixed mode linux-amd64 compressed oops)
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /root/hanlang/test/hs_err_pid42675.log
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
#
Aborted
  • OOM的時候dump內存:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/dump.hprof
    結果生成dump文件

asm的應用

1.asm使用原理

asm是一個java字節碼工具,提供一種方便的函數/屬性級別修改已經編譯好的.class文件的方法, asm的簡單使用原理介紹如下:

  • 通過ClassReader讀取.class文件的字節碼內容,並生成語法樹;
  • ClassReader的方法accept(ClassVisitor classVisitor, int parsingOptions)功能是讓classVisitor遍歷語法樹,默認ClassVisitor是一個代理類,需要有一個具體的實現在遍歷語法樹的時候做一些處理;
  • 用ClassWriter是ClassVisitor的一個實現,它的功能是把語法樹轉換成字節碼;
  • 通常我們會定義一個自己的ClassVisitor,可以重寫裏面的一些方法來改寫類處理邏輯,然後讓ClassWriter把處理之後的語法樹轉換成字節碼;

2.下面是具體的實現步驟:

  • 引入asm依賴包
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>7.0</version>
</dependency>
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm-commons</artifactId>
    <version>7.0</version>
</dependency>
//或者引入如下包
asm-commons-7.0.jar
asm-analysis-7.0.jar
asm-tree-7.0.jar
asm-7.0.jar
  • 定義一個ClassVisitor,功能是在所有方法調用前和調用後分別通過System.out.println打印一些信息
    輸入為字節碼,輸出也是字節碼
//MyClassVisitor.java
public class MyClassVisitor extends ClassVisitor {
    private static final Type SYSTEM;
    private static final Type OUT;
    private static final Method PRINTLN;
    static {
        java.lang.reflect.Method m = null;
        try {
            m = PrintStream.class.getMethod("println", new Class<?>[] {String.class});
        } catch (Exception e) {
        }
        SYSTEM = Type.getType(System.class);
        OUT = Type.getType(PrintStream.class);
        PRINTLN = Method.getMethod(m);
    }

    private String cName;

    public MyClassVisitor(byte[] bytes) {
        super(Opcodes.ASM7, new ClassWriter(ClassWriter.COMPUTE_FRAMES));
        new ClassReader(bytes).accept(this, ClassReader.EXPAND_FRAMES);
    }
    String format(String name) {
        return name.replaceAll("<", "_").replaceAll("\\$|>", "");
    }
    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        cName = format(name);
        super.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        if ((access & 256) != 0) {
            return super.visitMethod(access, name, desc, signature, exceptions);
        }
        return new MyMethodAdapter(super.visitMethod(access, name, desc, signature, exceptions), access, name, desc);
    }

    public byte[] getBytes() {
        return ((ClassWriter) cv).toByteArray();
    }

    class MyMethodAdapter extends AdviceAdapter {
        private String mName;

        public MyMethodAdapter(MethodVisitor methodVisitor, int acc, String name, String desc) {
            super(Opcodes.ASM7, methodVisitor, acc, name, desc);
            this.mName = format(name);
        }

        @Override
        protected void onMethodEnter() {
            getStatic(SYSTEM, "out", OUT);
            push(cName + "." + mName + " start");
            this.invokeVirtual(OUT, PRINTLN);
        }

        @Override
        protected void onMethodExit(int opcode) {
            getStatic(SYSTEM, "out", OUT);
            push(cName + "." + mName + " end");
            this.invokeVirtual(OUT, PRINTLN);
        }
    }
}
  • 定義一個簡單的classLoader來加載轉換后的字節碼
//MyLoader.java
class MyLoader extends ClassLoader {
    private String cname;
    private byte[] bytes;
    public MyLoader(String cname, byte[] bytes) {
        this.cname = cname;
        this.bytes = bytes;
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class<?> clazz = null;
        if (clazz == null && cname.equals(name)) {
            try {
                clazz = findClass(name);
            } catch (ClassNotFoundException e) {
            }
        }
        if (clazz == null) {
            clazz = super.loadClass(name, resolve);
        }
        return clazz;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> clazz = this.findLoadedClass(name);
        if (clazz == null) {
            clazz = defineClass(name, bytes, 0, bytes.length);
        }
        return clazz;
    }
}
  • 加載轉換Hello類,然後反向調用其方法

//將如下main函數加入MyClassVisitor.java中

public static void main(String[] args) throws Exception {
    try (InputStream in = Hello.class.getResourceAsStream("Hello.class")) {
        byte[] bytes = new byte[in.available()];
        in.read(bytes);
        String cname = Hello.class.getName();
        Class<?> clazz = new MyLoader(cname, new MyClassVisitor(bytes).getBytes()).loadClass(cname);
        clazz.getMethod("test1").invoke(null);
    }
}
  • 編譯
java -cp tools.jar com.sun.tools.javac.Main -cp asm-commons-7.0.jar:asm-analysis-7.0.jar:asm-tree-7.0.jar:asm-7.0.jar:. *.java
//或者
javac -cp asm-commons-7.0.jar:asm-analysis-7.0.jar:asm-tree-7.0.jar:asm-7.0.jar:. *.java
  • 運行
java -cp asm-commons-7.0.jar:asm-analysis-7.0.jar:asm-tree-7.0.jar:asm-7.0.jar:. MyClassVisitor
//結果如下:
Hello.test1 start
Hello.test2 start
invoke test2
Hello.test2 end
Hello.test1 end

asm的使用很廣泛,最常用的是在spring aop裏面切面的功能就是通過asm來完成的

3. 利用asm與Instrument製作調試工具

  • Instrument工具

Instrument類有如下方法,可以增加一個類轉換器

addTransformer(ClassFileTransformer transformer, boolean canRetransform)

執行如下方法的時候,對應的類將會被重新定義

retransformClasses(Class<?>... classes)
  • 與asm配合使用
    當我們修改Agent.java代碼為下面內容
//Agent
public class Agent {
    public static void agentmain(String args, Instrumentation inst) {
        try {
            URLClassLoader loader = (URLClassLoader)Agent.class.getClassLoader();
            Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
            method.setAccessible(true);//代碼級引入依賴包
            method.invoke(loader, new File("asm-7.0.jar").toURI().toURL());
            method.invoke(loader, new File("asm-analysis-7.0.jar").toURI().toURL());
            method.invoke(loader, new File("asm-tree-7.0.jar").toURI().toURL());
            method.invoke(loader, new File("asm-commons-7.0.jar").toURI().toURL());
            inst.addTransformer(new ClassFileTransformer() {
                @Override
                public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                    ProtectionDomain protectionDomain, byte[] bytes) {
                    return new MyClassVisitor(bytes).getBytes();
                }
            }, true);
            inst.retransformClasses(Class.forName("Hello"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 編譯並打包成agent.jar
//編譯
javac -cp asm-commons-7.0.jar:asm-analysis-7.0.jar:asm-tree-7.0.jar:asm-7.0.jar:. *.java
//打包
jar -cmf manifest.mf agent.jar MyLoader.class MyClassVisitor.class MyClassVisitor\$MyMethodAdapter.class Agent.class Agent\$1.class
  • attach進程修改字節碼
//執行
java -cp .:tools.jar AttachMain 3003
//執行前後Hello進程的輸出變化為
invoke test2
invoke test2
invoke test2
Hello.test1 start
Hello.test2 start
invoke test2
Hello.test2 end
Hello.test1 end
Hello.test1 start
Hello.test2 start
invoke test2
Hello.test2 end
Hello.test1 end

利用asm及instrument工具來實現熱修改字節碼現在有許多成熟的工具,如btrace(https://github.com/btraceio/btrace,jvm-sandbox https://github.com/alibaba/jvm-sandbox)

 

點擊關注,第一時間了解華為雲新鮮技術~

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

NetAnalyzer筆記 之 十四 NetAnalyzer 6.0 的使用方法 — 3.協議分析與統計

數據分析

完成了數據的抓取,那麼接下來就是NetAnalyzer的第二個重點部分了,協議分析作為整個軟件的核心之一,在最新的NetAnalyzer中已經得到了巨大的提升。NetAnalyzer中協議分析分為單數據包分析,和聯合分析兩種分析方式,對於聯合分析會根據不同的協議特性進行形成不同的分析方案,目前支持傳輸協議(TCP/UDP)協議分析, HTTP協議分析。在數據統計部分部分還增加了針對ARP協議的圖形化分析。對於協議分析,需要了解相關的網絡知識或是有相關專業背景支持。

單數據包分析,在獲取到數據包后,軟件工作界面數據包列表框中會显示所獲取的所用數據包,並且對這次數據做了一些簡單的分析,我們可以憑藉這些數據簡單判斷所對應的的數據包類型。

 

數據包列表

當我們選中一行,即選中一個數據包,我們可以看到對該數據包詳細的數據分析信息,並一樹狀結構樹呈現出來,並在右側显示該數據包原始信息。當我們選中協議樹中一個字段時,右側的數據就會定位到當前字端所分析數據的位置。

 

數據分析

然後通過對應的協議格式進行匹配與分析,如這部分的IP協議。

 

IPv4協議格式

需要注意的是,NetAnalyzer目前對於選中的字段只能精確到字節層次,對於一些協議,其中一個字節可能包含了多個字段,或是跨字節的字段,則會選擇全部的字節數據,比如IPv6協議。

 

IPv6協議格式

其中的版本字段只佔用了4bit(1字節為8bit),通信類型佔了8bit 也就是1字節,但是因為其中前面部分使用了版本字段所在字節後面的4bit,所以改字段為一個典型的跨字節字段,同樣流標籤字段使用了20bit,佔用第二個字節的4bit加上後面自身的2個字節(16bit)。

 

解析后的IPv6數據

對於類型的字段因為NetAnalyzer使用十六進制显示數據,並不能清晰表達bit層次的信息所以當選定字段后默認選中改字段所在的字節,如點擊版本選中方式如下,

 

IPv6版本信息

選中通信類型和流標籤則呈現方式如下。

 

通信類型和流標籤共用數據

數據分析標籤

雖然NetAnalyzer盡可能多分析每個數據包所包含的信息,但是依舊存在很多數據需要我們手動去解析。所以軟件增加了數據標籤。

 

數據分析

數據標籤頁點擊 显示 按鈕 就可以打開數據轉換窗口,當然也可以在常規轉換中點擊任意功能可以打開轉換窗口

 

轉換窗口管理

 

關閉按鈕為關閉轉換窗口,清空則是清空當前窗口內的數據。

點擊清空按鈕,則清空轉換信息。

 

常規轉換工具

NetAnalyzer中提供了一部分簡單的轉換功能,這些功能只有在載荷數據被選中的情況的才可以啟用,

如點擊二進制按鈕,則對所選的數據轉換為對應的二進制字符串。如下圖所示。

 

常規數據轉換窗口

除了一些簡單的轉換功能,還集成了MangoScript擴展方式和插件擴展方式(無可用插件的

時候不显示)的轉換。

 

擴展MangoScript的解析

如下面通過MangoScript針對某即時通信軟件的數據分析。

針對於MangoScript和插件兩種方式的轉換,將會在在《NetAnalyzer使用說明書 二 擴展與開發》中詳細說明,此處不再贅述。

 

 

定位轉換功能需要配合常規轉換進行使用,有時候我們確定某個字節會在一個確定的位置出現,比如IP地址字段,我們選中該位置,位置字段就會出現一串代碼 (10,1) [26]-4

(x,y)[offset] – length

 x: 十六進制編輯器水平方向的偏移量

 y :   十六進制編輯器垂直方向的偏移量

offset : 字節偏移量,offset = y * 16 + x

length :  當前選擇的數據長度

 

數據轉換

所以代碼 (10,1) [26]-4 確定了當前IP地址的位置,此時點擊 常規轉換 -> IPv4地址 則會在模式中記錄當前的轉換模式,然後點擊定位轉換,就會在當前數據包列表中針對每個數據包這個位置執行定位操作,這對於尋找所需要的數據非常重要。

 

選擇了IPv4轉換

 

執行定位轉換

對於MangoScript和插件擴展依然支持定位轉換。

 

區塊複製,主要是對一些已經選中的字節進行複製轉為代碼,字節數組,以及保存的功能,以及數據做手動分析,腳本分析以及自定義轉換等,後續將會說明,此處不再詳細介紹。

 

數據塊操作

 

字節定位,與定位轉換類似,但是字節定位主要是用來在數據包列表中查找相同位置出現相同字節序列的數據包。算作一個查找功能。

 

字節定位

分析標籤

分析標籤下個功能依託於數據包列表,分別有載荷數據提取,數據包標記,編碼轉換,數據查找,統計等相關功能,是聯合分析的主要功能,下面將會着重對一下功能進行說明。

 

數據分析標籤

TCP/UDP協議分析   前面介紹的都是基於單包的數據分析,而在協議分析中,我們大部分分析的數據都是依託於TCP/UDP的長連接數據,這部分數據的特點就是有多個數據包通過tcp或udp相關協議完成數據重組后才可以使用(基於udp的連接數據可能不是很嚴格)。

NetAnalyzer 除了提供基於單包的數據分析,更提供了基於連接數據的分析,而分析出來的數據不僅僅是在窗口上呈現一堆亂碼,更可以通過DocBar將獲取的數據提取出來進行使用。

開始 標籤最後一部分就是基於長連接的分析。點擊TCP/UDP 按鈕

 

基於TCP/UDP載荷數據查看

此時NetAnalyzer便會切換到載荷數據模式(該過程可以通過配置,使用獨立窗口打開)。在該模式下會打開專有的載荷數據菜單,數據區域也會變為對於載荷數據的分析,這裏先介紹一個NetAnalyzer中的DocBar工具,如下圖

 

DocBar

在文本模式下,分析載荷數據會显示該工具條,該工具條會提供針對當前數據塊的各種操作,當然在不動情況下,显示的工具和數量,都有所不同,下面是對當前各個功能的說明。

l   對當前數據塊進行摺疊

l   選中當前的分析數據

l   保存當前原始數據

l   查看原始數據(bytes數據)

l   MangScript解析數據

l   手動測試數據

 

對於其他情況下的工具在這裏不會一一介紹,但是碰到的時候會有說明,並且隨着後續功能點的增加,DocBar可能會有更多的功能添加進來。

 

tcp/udp 的分析分為 文本模式原始模式 ,文本模式主要是用於分析載荷數據為文本的數據,我們可以通過下面兩種方式更改文本編碼方式,分析數據。

文本模式下,呈現方式如下:

 

查看載荷數據

原始模式分析如下,可用通過TCP/UDP的下拉菜單命令 字節數據 切換為原始數據

 

 

字節查詢方式

 

字節方式呈現

對於在該功能下針對TCP的所有數據都已經進行過TCP重組,所以最終分析完成的數據並不是按照數據包方式做簡單呈現就可以的,都會做數據的篩查與整理。如果需要單包分析的使用者需要注意一下。

 

HTTP數據分析 http作為最有網絡代表意義的協議,NetAnalyzer提供了更加完善的分析,http基於tcp協議,所以數據還原等都建立在tcp數據還原的基礎之上。通過http分析,我們可以還原很多有意義的數據,如獲取到Http所傳輸的的html、js、css數據文件,還可以獲取到基於http協議分析得到的圖片,文件等信息,如下圖分別為還原后的圖片和zip壓縮包。

 

http方式分析出的圖片

 

http方式分析出的文件

對於常規的字符串或圖片可以直接在NetAnalyzer呈現,但是對於其他類型的文件,如視頻、音樂、以及上面提到的zip壓縮包文件,在在NetAnalyzer會簡單显示為二進制數據,該數據如果過長,則會截斷显示,但是在後面會加入【全部數據】下鑽選項,當點擊該數據后則會打開原始數據對話框,並且會完整显示當前的數據,如下圖所示。

 

查看原始數據

原始數據對話框中,提供了簡單的數據另存為和數據識別相關的功能。

 

原始數據保存

保存 保存當前窗口中的數據為一個文件。

保存選擇數據 是當選擇對話框中其中的一段數據保存為文件,有時候數據可能存在偏差,或者我們需要提取選定的數據保存為文件,可以通過下拉保存選定的數據進行保存。

數據識別功能。

轉為… 則是將當前的數據轉到編碼轉換工具中進行進一步分析。

自動識別 為了更加快速的實現數據提取,NetAnalyzer增加了數據識別模塊,通過整理不同文件的頭部或尾部字節形成數據識別特徵,當進行自動識別的時候,可以快速定位字節。

 

文件識別

添加特徵 將選定的指定字節添加為文件識別頭,並且添加相關信息,形成一個特徵。

 

添加文件識別

識別管理 管理特徵庫,在後續將詳細介紹該功能點。

 

載荷數據分析出的文件

除了使用常規的識別方式,在載荷數據提取中也加入了數據識別功能。在使用的時候點擊數據識別就可以在下方显示被識別到的數據類型,有時候可能會存在多個類型和誤識別的情況,使用的時候請務必注意。

有時候通過HTTP協議還原部分二進制數據,如下面還原ZIP文件,文檔會以二進制數據呈現,而我們可以通過0x50 0x4B(PK)推斷出該文件很有可能是zip文件 ,所以我們點擊全部數據 ,打開原始數據窗口,這部分數據正好是zip的全部數據。

   

保存的zip文件內容

此時點擊將當前數據保存為zip文件。減壓就可以看到對應的文件內容。

 

在載荷數據模式下,菜單會自動切換為,載荷模式菜單

 

載荷數據標籤

該菜單下提供了很多常用的字符串轉換工具

 

格式轉換工具

如下面通過通過Cookie格式化,格式化了http頭中的cookie字段

 

Cookie格式化

需要注意的是使用這些字段首先需要選中被轉換的文本,然後點擊需對應的功能項。其中如果點擊轉換為…,則啟動NetAnalyzer附帶的編碼轉換工具,進行集中處理。

 

編碼轉換工具

針對html字符串數據,還提供了過濾標籤和HTML預覽功能,因為該部分功能都很類型,且使用簡單,用戶自行嘗試使用即可。

 

 

時序圖 在數據分析中,除了對於數據本身的分析之外,有時候我們還要去評測一些數據質量等方面的內容。並且可以通過圖像化的方式表現出來。

 

TCP時序圖分析

時序圖模擬TCP/UDP在數據網絡中的數據傳輸過程,還原網絡通信場景,如該圖可以完整的反映TCP三次握手以及斷開連接四次揮手的情景。可以作為對當前分析數據從另外一個方面的反饋,更具有參考意義。

點擊

 

時序圖選項

就可以看到針對於當前tcp/udp 數據交互的情況。

 

 

數據標記

在分析標籤下面,有標記功能,實現對當前採集會話數據連接的進行快速識別。

 

數據標記

NetAnalyzer提供了四中顏色對數據包鏈接進行區分。

如TCP數據包,就會通過源IP地址+源端口地址+目標IP地址+目標端口 作為一個特徵來進行識別,此處的源和目標具有相對性。

注*  ctrl+鼠標左鍵 可以實現對數據會話的快速標記 顏色為紅色

 

標記完成的數據

通過點擊清理標記,可還原數據。

 

 

數據包查找 

 

數據包查找

在數據包列表模式下使用Ctrl+F即可以打開數據包查找功能。

該功能主要是實現快速查找數據包的功能,可以通過編號,協議,地址(mac/ip),端口,關鍵字等五種方式查找數據包。還可以通過數據列表導航按鈕進行數據包列表瀏覽。

 

 

編碼方式

在通過TCP/UDP 或HTTP 功能還原數據的時候,有時候會出現亂碼,尤其是對非英文字符。在HTTP協議中通常都會在頭部信息中攜帶編碼方法,通過提取就可以獲取到編碼方式,但是仍然後部分服務並不提供編碼字段,這時候就需要我們通過手動切換,來嘗試還原相關信息。

通過菜單欄或者是狀態欄都可以對編碼方案進行切換

 

字符編碼

 

狀態欄字符編碼

這裏需要注意的是如果http頭部包含了編碼方式,則使用頭部提供的編碼方式。

 

 

數據統計

目前NetAnalyzer显示了大量的統計方式,涵蓋了數據報表、流量分析、主機通信矩,傳輸報告、ARP報告等多種統計方式。

 

數據報表

 

報表信息

對當前捕獲的數據表中的數據進行統計與歸類。呈現方式如有圖所示。

 

報表內容

包含一些基本信息,數據量與時間直線圖,數據量佔比,關係圖等信息

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

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

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

為什麼阿里巴巴Java開發手冊中不允許魔法值出現在代碼中?

在閱讀《阿里巴巴Java開發手冊》時,發現有一條關於關於常量定義的規約,具體內容如下:

圖中的反例是將數據緩存起來,並使用魔法值加鏈路 id 組成 key,這就可能會出現其他開發人員在複製粘貼的時候,少複製 _ 的情況發生,這種錯誤很難去檢查到,因為讀取緩存不存在,可能會去數據庫讀取,很難察覺到。

如果在生產環境中,大量的請求進來,緩存全部失效,直接請求數據庫,導致數據庫連接過多,查詢效率變低的問題發生,因此看來魔法值確實應該避免出現在代碼中。

另外在 《Clean Code》 和 《重構》 等書中也提到了類似的問題,在代碼中出現原始形態数字通常來說是壞現象,應該用命名良好的常量類隱藏它。

靜態常量取代魔法值

像下面這個例子:

if (billCount > 75) {
    //todo
} else {
    //todo
}

如果在不了解這塊的業務的同事,在讀到這塊代碼的時候,可能會想,75 是什麼鬼,為啥和這個數比較,背後深藏着什麼秘密嗎?可能只有當時的開發人員記得了,導致代碼可讀性和可維護性極差。

如果聲明一個常量,來替換該魔法值,可能就會使代碼的可讀性和可維護性大大增加。

static final Integer BASIC_BILL_COUNT = 75;

還有些魔法表達式,比如:

if (value > 60 && value <= 80 && type = 1) {
    // todo
}

比如這個表達式是表示狀態為正常且項目活躍,就可以定義:

boolean isActiveProject = value > 60 && value <= 80 && type = 1;

這樣是不是可讀性就提高了,一眼就可以看出來這塊代碼的邏輯。

枚舉類取代魔法值

還有一種消除魔法值的方式是使用枚舉類代替,下面讓我們舉個例子:

if (eventId == 1) {
    System.out.println("睡覺");
} else if (eventId == 2) {
    System.out.println("吃飯");
} else if (eventId == 3) {
    System.out.println("打豆豆");
}

如上代碼是針對事件 id 去執行相應的事件,如果事件比較少,大家還可以勉強記住每個 eventId 對應的含義,但是隨着事件 id 的增多,很可能會發生,新來的員工把事件 id 給搞混了,導致執行錯誤的事件,發生 bug。

那麼我們可以使用枚舉類來表示相應的事件:

public enum EventEnum {

    /**
     * 睡覺
     */
    SLEEP_EVENT(1, "睡覺"),

    /**
     * 吃飯
     */
    EAT_EVENT(2, "吃飯"),

    /**
     * 打豆豆
     */
    FIGHT_PEA_EVENT(3, "打豆豆");

    private int eventId;
    private String desc;

    EventEnum(int eventId, String desc) {
        this.eventId = eventId;
        this.desc = desc;
    }

    public int getEventId() {
        return eventId;
    }

    public String getDesc() {
        return desc;
    }
}

修改完之後的代碼如下:

if (eventId == EventEnum.SLEEP_EVENT.getEventId()) {
    System.out.println("睡覺");
} else if (eventId == EventEnum.EAT_EVENT.getEventId()) {
    System.out.println("吃飯");
} else if (eventId == EventEnum.FIGHT_PEA_EVENT.getEventId()) {
    System.out.println("打豆豆");
}

是不是可讀性急劇提升,還不快看看自己代碼中有沒有這樣的魔法值出現,有的話趕緊改造起來。

還有如果你需要在不同的地點引用同一數值,魔法數會讓你煩惱不已,因為一旦這些数字發生改變,就必須在程序中找到所有的魔法值,並將它們全部修改一遍,這樣就太費時費力了。

其實不只是 Java 不應該在代碼中使用魔法值,其他語言亦是如此。

總結

本文主要介紹了為什麼不允許在代碼中出現魔法值以及如何將代碼中已有的魔法值去除掉。

代碼可讀性還是比較重要的,你肯定不希望別人在接手你的代碼的時候,罵到這数字啥意思,這代碼寫得跟粑粑一樣。

最好的關係就是互相成就,大家的在看、轉發、留言三連就是我創作的最大動力。

參考

《Java開發手冊》泰山版

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

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

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

※回頭車貨運收費標準

最近面試遇到的種種應聘者,你是這樣的嗎?

原文鏈接

很久沒有寫文章了,一時間竟不知如何開篇?為什麼沒有寫呢?是因為太忙了。最近在忙什麼呢?工作學習還有就是招人。上班時間不忙的時候大多是在看技術文章、技術文檔,上下班公交車上也是,還有就是最近兩個月在面試一些人。其實我是不太想面的,原因有三。一是耽誤我自己的時間,二是面了十幾個只有一兩個能讓我很稱心的。還有就是太費錢了公司又不給報銷,所以我最近都會用一些會議軟件來面試。

 

昨天面試了一個2012年開始工作的30歲程序員,面試前我心裏打鼓,畢竟我才工作三年但是說實話面下來不太理想,首先簡歷寫的一般,簡歷排版格式有點亂,多處字體不一致,還有技術棧很老,項目很小大多是內部用的,沒用過Redis,分佈式相關的東西沒有,也沒自己去了解過項目之外的東西,其次面試問到的問題回答的不到一半,但是態度還是不錯的,臨了還問我面試情況,我說了我的感受,也給了一些建議。

 

其實稍微看看他的簡歷,待過的公司,做過的項目也,就能知道為什麼工作七八年的30歲程序員水平這麼一般了。工作這麼多年一共待過兩個公司,看樣子都是外包公司,寫的幾個項目也大多數是內部使用的一些管理系統,併發量不大,沒有技術挑戰,對自己提升不高。

 

那麼程序員如何突破自己呢?怎麼才能擺脫中年危機呢?首先要跳出舒適圈,人都是有惰性的,都喜歡安逸的活着,如果生活過得去,沒有太大的壓力,誰又願意再努力一把呢?但是成功往往屬於那些肯逼迫自己的人,肯走出舒適圈、有目標的人。即使是30多了也是可以拼一把的,如果你是該技術的,那也可以再把技術深造深造,搞的紮實一點;如果你已經考慮轉管理了,那你就往管理方面靠,多看看管理方面的書籍,有空再考個管理的證,但是技術你也不能落下,不要求你把技術搞的多精通,但最起碼你要知道這個技術,了解一下他的基本原理,要不然有一天你要你下屬引進一個技術,他告訴你太難要花好多時間,或者說搞不了,你都不知道他說的是真的還是假的,如果你相信了,那他以後背地里就笑話你不懂技術,那以後這樣的事情還會多着呢。

 

另外不建議搞技術的過早的去轉管理,比如你剛工作3年,你的經理建議你去轉管理,這是不建議的,原因上面也說了,你的技術還不透徹,對技術的把控你完全不懂,到時候讓你評估一個技術引入的工作量,難度等,你搞不定的話又可能還會鬧出笑話。

 

 

 

今天遇到一個應聘者,工作經歷三年,四個項目全都是管理類的、內部使用的項目,但是人家簡歷寫的技術都是熟悉啊,符合公司的招聘標準啊。OK,面吧,來唄。

 

專業技能這塊寫的都是熟悉,我一看會這麼多還挺棒的GOOD BOY

 

 

 

 

廢話不多說,上來我就問,Java基礎你掌握的熟練嗎?對方說還行吧,我就先問了幾個Java語法的概念,然後問了HashMap的put操作的流程、擴容機制,什麼時候擴容的?做什麼操作的時候會發生線程不安全?統統回答的不好。

 

我:如果想使用線程安全的Map,用哪個?

應聘者:ConcurrentHashMap

 

我:ConcurrentHashMap怎麼保證線程安全的?

應聘者:這個…我平時用的少,不太知道底層

 

然後接着我就問什麼是Spring?對方的回答是Spring是一個框架,核心是AOP和IOC。這就回答完了。

 

我:spring有什麼優點呢?

應聘者:有 什麼優點?…嗯…這些概念性的東西我忘了…

 

我:那你說一下什麼是Spring AOP,可以干什麼用?使用什麼技術實現的?

應聘者:AOP就是面向切面編程,可以用來記錄日誌,安全管理,用動態代理實現的

 

我:Spring AOP使用的哪種動態代理?

應聘者:JDK動態代理,CGLIB動態代理

 

我:什麼時候用JDK動態代理,什麼還是用CGLIB動態代理

應聘者:它有一個判斷,好像是沒有繼承類時用JDK動態代理

 

我:BeanFactory和ApplicationContext有什麼區別?

應聘者:….我們項目spring用的很少,用的是springboot

 

然後我簡單的問了幾個springboot的基礎問題,還都能回答上來,可以看出來確實用了springboot。

看他簡歷上寫的熟悉spring cloud,我心想做這些管理系統還需要微服務嗎?就問他在哪個項目里用到了,他說沒用過,是自己自學過。

 

因為我們也沒有這套技術,我就沒再問。

我對MQ感興趣,就問他RabbitMQ的問題。

我:使用RabbitMQ有什麼好處啊?

應聘者:我們發郵件使用了RabbitMQ,往MQ里發郵件。

 

我:為什麼要用RabbitMQ啊?(我問有什麼好處,他剛沒回答,我換個問法)

應聘者:你是說為什麼不用別的MQ嗎?項目里用的就是RabbitMQ我就用了

 

我:發郵件不用MQ也能實現,為什麼要引入MQ呢?有什麼好處嗎?

應聘者:不用MQ也能實現嗎?我不知道,我們發郵件就是用RabbitMQ,我就用了。

我:(跳過這個問題吧)那你能說一下RabbitMQ的消息是基於什麼傳輸的?

應聘者:基於什麼傳輸?你這問的好官方啊,我不知道問的啥,你能問的通俗點嗎?

 

我:(算了跳過)那你知道RabbitMQ它的消息怎麼路由嗎?

應聘者:這個…我不太清楚,記不清了,上個項目用到了,好久沒有用了,但是我如果有項目要使用的話,基本上再看看就能很快上手了。

 

我:哦,我看你技術寫的都是熟悉。那你Redis用的多嗎?

應聘者:用的少,我買過視頻看過。

 

我:那你說一下Redis的數據類型都有哪些?各自的使用場景

應聘者:string,hash,list,set,zset

 

我:(這就完了?明明問的還有使用場景呢)怎麼使用Redis實現分佈式鎖呢?

應聘者:這…嗯…我不太清楚,項目中不怎麼用redis,都是內部使用的很少用redis

 

我:redis有哪幾種架構模式啊?

應聘者:架構模式…呃…不知道,對redis了解的不多。你問我點業務。(老是問我不會的,你問點業務啊?)

 

(不多,你簡歷寫熟悉⊙﹏⊙b汗,還教我問你,你是面試官還是我是啊?你的項目有毛的業務)

 

我:那你說一下你在項目中怎麼使用Spring security的

應聘者:….

 

我:那你講一下SSO的流程

應聘者:….

 

這幾個問題,我已經沒仔細在聽他回答的是什麼了

最後又問了幾個問題,我已經不想問了,已經快四十分鐘了。

 

我:我今天就這麼多問題,你有什麼要問我的嗎?

應聘者:咱們公司是在北京嗎我看手機號是北京的(…等一些關於項目的幾個問題)

 

這個應聘者存在一個什麼問題?眼高手低,高估自己,面試準備不充分。建議近期找工作的把Java基礎,JVM,集合,併發,數據庫,redis,框架,dubbo,zookeeper弄懂,準備充分,這樣才能百戰不殆,成為offer收割機。

 

還有的應聘者問題回答的賊6,問道到在項目中怎麼使用的,哪些地方用到了,就卡殼了。很顯然這樣的就是簡單粗暴的背面試題,所以也要結合自己的項目去準備面試,把面試題嵌入到項目中,能說出在項目里哪些地方用到了,有什麼優點等,盡量準備充分。

 

好了今天就分享到這裏,有什麼需要交流的歡迎留言哦~

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

Azure AD(四)知識補充-服務主體

一,引言

  又到了新的一周了,也到了我新的分享的時間了,還記得上一周立得Flag,其中 “保證每周輸出一篇文章” ,讓我特別“在意”(這裏用詞不太恰當)。主要是我的一個大學舍友,他突然問了我一個關於寫博的事情,自己也在上周開通了賬號,也想着堅持寫博客。在我看來,這確實是一件好事,寫博不僅僅是分享的過程;也是自己提煉寫博的一個過程,以及文章組織的能力,對自己還是很有好處的。這不僅僅要寫內容要精鍊,同時也要讓別人能看的懂。加油,默默的在這裏給他打氣。(ง •_•)ง

好了,開始今天的分析

————————————我是分割線————————————

  上一周有介紹到Azure AD資源託管標識的內容,其實就包括如何去操作開啟系統分配的託管標識,以及通過開啟託管標識,VM如何去訪問Azure 中的一些資源,如 “Key Vault” 等。今天去分析一波關於“Service Principal”(服務主體)。

二,正文

1,服務主體對象

  若要訪問受 Azure AD 租戶保護的資源,需要訪問的實體必須由安全主體來表示。 這同時適用於用戶(用戶主體)和應用程序(服務主體)。安全主體定義 Azure AD 租戶中用戶/應用程序的訪問策略和權限。 這樣便可實現核心功能,如在登錄時對用戶/應用程序進行身份驗證,在訪問資源時進行授權。當應用程序被授予了對租戶中資源的訪問權限時(根據註冊或許可),將創建一個服務主體對象。 Microsoft Graph ServicePrincipal 實體定義服務主體對象屬性的架構。

2,應用程序和服務主體的關係

可以將應用程序對象視為應用程序的全局表示形式(供所有租戶使用),將服務主體視為本地表示形式(在特定租戶中使用)。

應用程序對象用作模板,常見屬性和默認屬性從其中派生,以便在創建相應服務主體對象時使用。 因此,應用程序對象與軟件應用程序存在 1 對 1 關係,而與其對應的服務主體對象存在 1 對多關係。

必須在將使用應用程序的每個租戶中創建服務主體,讓它能夠建立用於登錄和/或訪問受租戶保護的資源的標識。 單租戶應用程序只有一個服務主體(在其宿主租戶中),在應用程序註冊期間創建並被允許使用。 多租戶 Web 應用程序/API 還會在租戶中的某個用戶已同意使用它的每個租戶中創建服務主體。

下圖演示了應用程序的應用程序對象和對應的服務主體對象之間的關係,其上下文是在名為 HR 應用的示例多租戶應用程序中。 此示例方案中有三個 Azure AD 租戶:

  • Adatum -開發HR 應用的公司使用的租戶
  • Contoso -contoso 組織使用的租戶,即HR 應用的使用者
  • Fabrikam -fabrikam 組織使用的租戶,它也使用HR 應用

在此示例方案中:

表 1

步驟 說明
1 是在應用程序的宿主租戶中創建應用程序對象和服務主體對象的過程。
2 當 Contoso 和 Fabrikam 的管理員完成同意並嚮應用程序授予訪問權限時,會在其公司的 Azure AD 租戶中創建服務主體對象,並向其分配管理員所授予的權限。 另請注意,HR 應用可能配置/設計為允許由用戶同意以供個人使用。
3 HR 應用程序的使用者租戶(例如 Contoso 和 Fabrikam)各有自己的服務主體對象。 每個對象代表其在運行時使用的應用程序實例,該實例受相關管理員同意的權限控制。

3,使用Azure CLI創建Azure服務主體(示例)

使用 az ad sp create-for-rbac 命令創建服務主體創建服務主體時,請選擇其使用的登錄身份驗證的類型。

 注意

如果您的帳戶無權創建服務主體,將返回一條錯誤消息,其中包含“權限不足,無法完成操作”。請與您的Azure Active Directory管理員聯繫以創建服務主體。

3.1,在 “azure portal” 中驗證當前的Azure訂閱

az account show

3.2,显示訂閱名稱ID值的列表

az account list --query "[].{name:name, subscriptionId:id}"

 

3.3,使用 az ad sp create-for-rbac 命令,將其替換<subscription_id>為要使用的訂閱帳戶的ID

az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/<subscription_id>"

注意:我們將創建一個具有 “Contributor” (貢獻者角色:默認角色)的服務主體。該 “Contributor” 角色具有完全的權限讀取和寫入到Azure的賬戶,

成功完成后,該命令將显示幾個值,包括自動生成的密碼  

 

同時,我們可以在 “azure portal” 中可以找到對應的設置 選擇=》Azure Active Directory

 

點擊 “App registrations”

 

 同時,我們可以在當前訂閱下的 “IAM”中找到對應的角色訪問權限信息。當然了,上面我創建服務主體的時候給的 scope 是整個訂閱,也就是我們可以通過這個服務主體去訪問azure的任何資源。

例如 “azure devops Pipeline” 在CD的過程,需要配置 “Service Principal”

 

 例如使用Terraform 構建基礎架構資源的時候,需要配置 Service Principal

三,總結

  使用Azure服務的自動化工具應始終具有受限權限。Azure提供服務主體,而不是讓應用程序以完全特權用戶身份登錄。Azure服務主體是為與應用程序,託管服務和自動化工具一起使用而創建的身份,以訪問Azure資源。這種訪問受到分配給服務主體的角色的限制,使您可以控制可以訪問哪些資源以及可以訪問哪個級別。出於安全原因,始終建議將服務主體與自動化工具一起使用,而不是允許他們使用用戶身份登錄。

服務主體的默認角色是Contributor該角色具有讀取和寫入Azure帳戶的完整權限

參考資料:RBAC內置角色:https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles

作者:Allen 

版權:轉載請在文章明顯位置註明作者及出處。如發現錯誤,歡迎批評指正。

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

台中搬家公司費用怎麼算?

微前端與項目實施方案研究

一、前言

微前端(micro-frontends)是近幾年在前端領域出現的一個新概念,主要內容是將前端應用分解成一些更小、更簡單的能夠獨立開發、測試、部署的小塊,而在用戶看來仍然是內聚的單個產品。微前端的理念源於微服務,是將龐大的整體拆成可控的小塊,並明確它們之間的依賴關係,而它的價值在於能將低耦合的代碼與組件進行組合,基座+基礎協議模式能接入大量應用,進行統一的管理和輸出,許多公司與團隊也都在不斷嘗試和優化相關解決技術與設計方案,為這一概念的落地和推廣添磚加瓦。結合自身遇到的問題,適時引用微前端架構能起到明顯的提效賦能作用。

二、背景

目前我司擁有大量的內部系統,這些系統採用相同的技術棧,在實際開發和使用過程中,逐漸暴露出如下幾個問題:

1.有大量可復用的部分,雖然有組件庫,但是依賴版本難統一;
2.靜態資源體積過大,影響頁面加載和渲染速度;
3.應用切換目前是通過鏈接跳轉的方式實現,會有白屏和等待時長的問題,對用戶體驗不夠友好;
針對上述幾個問題,決定採用微前端架構對內部系統進行統一的管理,本文也是圍繞微前端落地的技術預研方案。

三、方案調研

目前業界有多種解決方案,有各自的優缺點,具體如下:

  • 路由轉發:路由轉發嚴格意義上不屬於微前端,多個子模塊之間共享一個導航即可 簡單,易實現 體驗不好,切換應用整個頁面刷新;

  • 嵌套 iframe:每個子應用一個 iframe 嵌套 應用之間自帶沙箱隔離 重複加載腳本和樣式;

  • 構建時組合:獨立倉儲,獨立開發,構建時整體打包,合併應用 方便依賴管理,抽取公共模塊 無法獨立部署,技術棧,依賴版本必須統一;

  • 運行時組合:每個子應用獨立構建,運行時由主應用負責應用管理,加載,啟動,卸載,通信機制 良好的體驗,真正的獨立開發,獨立部署 複雜,需要設計加載,通信機制,無法做到徹底隔離,需要解決依賴衝突,樣式衝突問題;

    開源微前端框架也有多種,例如阿里出品的qiankun,icestark,還有針對angular提出的mooa等,都能快速接入項目,但結合公司內部系統的特點,直接採用會有有些限制,例如要實現定製界面,無刷新加載應用,且不能對現有項目的開發和部署造成影響,因此決定自研相關技術。

四、架構設計

4.1 應用層

應用層包括所有接入微服務工作台的內部系統,他們各自開發與部署,接入前後沒有多大影響,只是需要針對微服務層單獨輸出打包一份靜態資源;

4.2 微服務層

微服務層作為核心模塊,擁有資源加載、路由管理、狀態管理和用戶認證管理幾大功能,具體內容將在後面詳細闡述,架構整體工作流程如下:

4.3 基礎支撐層

基礎支撐層作為基座,提供微服務運行的環境和容器,同時接入其他後端服務,豐富實用場景和業務功能;

五、技術重難點

要實現自定義微前端架構,難點在於需要管理和整合多個應用,確保應用之間獨立運行,彼此不受影響,需要解決如下幾個問題:

5.1 資源管理

5.1.1資源加載

每個應用有一個應用資源管理和註冊的文件(app.regiser.js),其中包含路由信息,應用配置信息(configs.js)和靜態資源清單,當首次切換到某應用時,首先加載app.register.js文件,完成路由和應用信息的註冊,然後根據當前瀏覽器路由地址加載對應的靜態文件,完成頁面渲染,從而將各應用的靜態資源串聯起來,其中註冊入口文件通過webpack插件來實現,具體實現如下:
FuluAppRegisterPlugin.prototype.apply = function(compiler) {
   appId = extraAppId();
   var entry = compiler.options.entry;
   if (isArray(entry)) {
            for (var i = 0; i &lt; entry.length; i++) {
                if (isIndexFile(entry[i])) { // 入口文件
                    indexFileEdit(entry[i]);
                    entry[i] = entry[i].replace(indexEntryRegx, indeEntryTemp); // 替換入口文件
                    i = entry.length;
                }
            }
    } else {
            if (isIndexFile(entry)) { // 入口文件
                indexFileEdit(entry); // 重新生成和編輯入口文件
                compiler.options.entry = compiler.options.entry.replace(indexEntryRegx, indeEntryTemp); // 替換入口文件
            }
    }
    compiler.hooks.done.tap('fulu-app-register-done', function(compilation) {
            fs.unlinkSync(tempFilePath); // 刪除臨時文件
            return compilation;
    });
    compiler.hooks.emit.tap('fulu-app-register', function(compilation) {
        var contentStr = 'window.register("'+ appId + '", {\nrouter: [ \n ' + extraRouters() + ' \n],\nentry: {\n'; // 全局註冊方法
        var entryCssArr = [];
        var entryJsArr = [];
        for (var filename in compilation.assets) {
            if (filename.match(mainCssRegx)) { // 提取css文件
                entryCssArr.push('\"' + filename + '\"');
            } else if (filename.match(mainJsRegx) || filename.match(manifestJsRegx) || filename.match(vendorsJsRegx)) { // 提取js文件
                entryJsArr.push('\"' + filename + '\"');
            }
        }
        contentStr += ('css: ['+ entryCssArr.join(', ') +'],\n'); // css資源清單
        contentStr += ('js: ['+ entryJsArr.join(', ') +'],\n }\n});\n'); // js資源清單
        compilation.assets['resources/js/' + appId + '-app-register.js'] = { // 生成appid-app-register.js入口文件
            source: function() {
                return contentStr;
            },
            size: function() {
                return contentStr.length;
            }
        };
        return compilation;
    });
};
5.1.2資源文件名
微服務輸出打包模式下,靜態資源統一打包形式以項目id開頭,形如10000092-main.js, 文件名稱的修改通過webpack的插件實現;

核心實現代碼如下:

FuluAppRegisterPlugin.prototype.apply = function(compiler) {
    ......
    compiler.options.output.filename = addIdToFileName(compiler.options.output.filename, appId);
    compiler.options.output.chunkFilename = addIdToFileName(compiler.options.output.chunkFilename, appId);
    compiler.options.plugins.forEach((c) =&gt; {
        if (c.options) {
            if (c.options.filename) {
                c.options.filename = addIdToFileName(c.options.filename, appId);
            }
            if (c.options.chunkFilename) {
                c.options.chunkFilename = addIdToFileName(c.options.chunkFilename, appId);
            }
        }
    });
   ......
};

5.2 路由管理

路由分為應用級和菜單級兩大類,應用類以應用id為前綴,將各應用區分開,避免路由地址重名的情況,菜單級的路由由各應用的路由系統自行管理,結構如下:

5.3 狀態分隔

前端項目通過狀態管理庫來進行數據的管理,為了保證各應用彼此間獨立,因此需要修改狀態庫的映射關係,這一部分需要藉助於webpack插件來進行統一的代碼層面調整,包括model和view兩部分代碼,model定義了狀態對象,view藉助工具完成狀態對象的映射,調整規則為【應用id+舊狀態對象名稱】,下面來講解一下插件的實現;

插件的實現原理是藉助AST的搜索語法匹配源代碼中的狀態編寫和綁定的相關代碼,然後加上應用編號前綴,變成符合預期的AST,最後輸出成目標代碼:
module.exports = function(source) {
      var options = loaderUtils.getOptions(this);
	stuff = 'app' + options.appId;
	isView = !!~source.indexOf('React.createElement'); // 是否是視圖層
	allFunc = [];
	var connectFn = "function connect(state) {return Object.keys(state).reduce(function (obj, k) { var nk = k.startsWith('"+stuff+"') ? k.replace('"+stuff+"', '') : k; obj[nk] = state[k]; return obj;}, {});}";
	connctFnAst = parser.parse(connectFn);
	const ast = parser.parse(source, { sourceType: "module", plugins: ['dynamicImport'] });
	traverse(ast, {
		CallExpression: function(path) {
			if (path.node.callee && path.node.callee.name === 'connect') { // export default connext(...)
				if (isArray(path.node.arguments)) {
					var argNode = path.node.arguments[0];
					if (argNode.type === 'FunctionExpression') { // connect(() => {...})
						traverseMatchFunc(argNode);
					} else if (argNode.type === 'Identifier' && argNode.name !== 'mapStateToProps') { // connect(zk)
						var temp_node = allFunc.find((fnNode) => {
							return fnNode.id.name === argNode.name;
						});
						if (temp_node) {
							traverseMatchFunc(temp_node);
						}
					}
				}
			} else if (path.node.callee && path.node.callee.type === 'SequenceExpression') {
				if (isArray(path.node.callee.expressions)) {
					for (var i = 0; i < path.node.callee.expressions.length; i++) {
						if (path.node.callee.expressions[i].type === 'MemberExpression'
							&& path.node.callee.expressions[i].object.name === '_dva'
							&& path.node.callee.expressions[i].property.name === 'connect') {
								traverseMatchFunc(path.node.arguments[0]);
								i = path.node.callee.expressions.length;
						}
					}
				}
			}
		},
		FunctionDeclaration: function(path) {
			if (path.node.id.name === 'mapStateToProps' && path.node.body.type === 'BlockStatement') {
				traverseMatchFunc(path.node);
			}
			allFunc.push(path.node);
		},
		ObjectExpression: function(path) {
			if (isView) {
				return;
			}
			if (isArray(path.node.properties)) {
				var temp = path.node.properties;
				for (var i = 0; i < temp.length; i++) {
					if (temp[i].type === 'ObjectProperty' && temp[i].key.name === 'namespace') {
						temp[i].value.value = stuff + temp[i].value.value;
						i = temp.length;
					}
				}
			}
		}
	});
	return core.transformFromAstSync(ast).code;
};

5.4 框架容器渲染

完成以上步驟的改造,就可以實現容器中的頁面渲染,這一部分涉及到組件庫框架層面的調整,大流程如下圖:

六、構建流程

6.1 使用插件

構建過程中涉及到兩款自開發的插件,分別是fulu-app-register-plugin和fulu-app-loader;

6.1.1 安裝
npm i fulu-app-register-plugin fulu-app-loader -D;
6.1.2 配置

webpack配置修改:

const FuluAppRegisterPlugin = require('fulu-app-register-plugin');
module: {
   rules: [{
         test: /\.jsx?$/,
         loader: 'fulu-app-loader',
      }
   ]
}
plugins: [
    new FuluAppRegisterPlugin(),
    ......
]

6.2.編譯

編譯過程與目前項目保持一致,相比以前,多輸出了一份微前端項目編譯代碼,流程如下:

七、遺留問題

7.1 js環境隔離

由於各應用都加載到同一個運行環境,因此如果修改了公共的部分,則會對其他系統產生不可預知的影響,目前沒有比較好的辦法來解決,後續將持續關注這方面的內容,逐漸優化達到風險可制的效果。

7.2.獲取token

目前應用切換使用重定向來完成token獲取,要實現如上所述的微前端效果,需要放棄這種方式,改用接口調用異步獲取,或者其他解決方案。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※教你寫出一流的銷售文案?

※別再煩惱如何寫文案,掌握八大原則!

用隊列實現棧,用棧實現隊列,聽起來有點繞,都搞懂了就掌握了精髓!

目錄

  • 一、背景
  • 二、概念
    • 2.1 棧
    • 2.2 隊列
  • 三、棧和隊列的相互實現
    • 3.1 用隊列實現棧
    • 3.2 用棧實現隊列
  • 四、總結

一、背景

棧和隊列是數據結構中最常用到的兩種結構,有非常廣泛的運用,該篇文章將通過動畫的手段,展示棧和隊列相互實現的底層原理,讓我們真正搞懂棧和隊列的特性。

二、概念

2.1 棧

棧[Stack]:是一種限定僅在表尾進行插入和刪除操作的線性表;即後進先出(LIFO-last in first out),最後插入的元素最先出來

  • 入棧(push)
  • 出棧 (pop)
2.2 隊列

 隊列[Queue]:是一種限定僅在表頭進行刪除操作,僅在表尾進行插入操作的線性表;即先進先出(FIFO-first in first out):最先插入的元素最先出來。

  • 入隊(enqueue)
  • 出隊(dequeue)

三、棧和隊列的相互實現

3.1 用隊列實現棧
  • 模擬入棧的實現原理
    — 棧的特性是新加入的元素出現在棧頂,保證後進先出。
    — 隊列的特性為新加入的元素出現在隊尾,隊列的隊尾元素最後出隊。
    按以上兩個前提,我們可以讓隊頭至隊尾前的其它所有元素依次出隊再入隊,直至在隊尾新加入的元素被移到隊頭,也即實現了讓新壓入的元素保留在棧頂

  • 模擬出棧的實現原理
    — 由於在入棧時保證隊列中新加入隊尾的元素被移到了隊頭,出棧只需彈出隊頭元素即可。

  • 完整代碼實現

/**
 * 用隊列模擬實現棧
 *
 * @author zhuhuix
 * @date 2020-06-09
 */
public class QueueImplStack {

    // 定義隊列
    private Queue<Integer> queue;

    public QueueImplStack() {
        queue = new LinkedList();
    }

    // 入棧--在隊尾加入元素后,讓其他元素按順序出隊再入隊,保持新加入的元素永遠在隊頭
    public void push(Integer e) {
        queue.offer(e);
        int size = queue.size();
        int i = 0;
        while (i < size - 1) {
            queue.offer(queue.poll());
            i++;
        }
    }

    // 出棧--將隊尾前的其它所有元素出隊再入隊,直至隊尾元素移到隊頭
    public Integer pop() {
        return queue.poll();
    }

    // 查看棧頂元素--即隊頭元素
    public Integer peek() {
        return queue.peek();
    }

    // 是否為空
    public boolean isEmpty() {
        return queue.isEmpty();
    }

    public static void main(String[] args) {
        QueueImplStack stack = new QueueImplStack();
        stack.push(1);
        System.out.println(stack.peek());
        stack.push(2);
        System.out.println(stack.peek());
        stack.push(3);
        System.out.println(stack.peek());
        System.out.println("=============");
        System.out.println(stack.pop());
        System.out.println(stack.pop());
        System.out.println(stack.pop());
        System.out.println(stack.isEmpty());

    }
}

3.2 用棧實現隊列
  • 模擬入隊的實現原理
    — 隊列的特性最新入隊的元素需排在隊尾,最先入隊的元素排在隊頭,按隊頭到隊尾的順序依次出隊。
    — 對應到棧的數據結構上,也即需將新加入的元素保留在棧頂,保證先進先出。
    按以上兩個前提,需在存放數據的棧的基礎上再增加一個輔助棧,在每次入隊時,先將存放數據的棧彈入輔助棧,再把需加入的新元素壓入數據棧底,最後把輔助棧中的元素彈出依次壓入數據棧,這樣保證了新加入的元素,沉在棧底。

    模擬出隊的實現原理
    — 由於在入隊時,通過數據棧與輔助棧的交換,實現了后加入的元素沉在棧底,先進入的元素保留在棧頂,直接通過出棧彈出即可。

    • 完整代碼實現
/**
 * 用棧模擬實現隊列
 *
 * @author zhuhuix
 * @date 2020-06-09
 */
public class StackImplQueue {
    // 數據棧
    private Stack<Integer> stack;
    // 輔助棧
    private Stack<Integer> aux;

    StackImplQueue() {
        stack = new Stack<>();
        aux = new Stack<>();
    }

    // 入隊--通過數據棧與輔助棧相互交換,保證新加入的元素沉在數據棧底
    public void enqueue(Integer e) {
        while (!stack.isEmpty()) {
            aux.push(stack.pop());
        }
        stack.push(e);
        while(!aux.isEmpty()){
            stack.push(aux.pop());
        }

    }

    // 出隊--彈出數據棧元素
    public Integer dequeue(){
        return stack.pop();
    }

    // 查看隊頭元素
    public Integer peek(){
        return stack.peek();
    }

    // 是否為空
    public boolean isEmpty(){
        return stack.isEmpty();
    }

    public static void main(String[] args) {
        StackImplQueue queue = new StackImplQueue();
        queue.enqueue(1);
        System.out.println(queue.peek());
        queue.enqueue(2);
        System.out.println(queue.peek());
        queue.enqueue(3);
        System.out.println(queue.peek());
        System.out.println("=============");
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());

    }
}

四、總結

通過以上棧和隊列相互交叉的實踐,我們對棧和隊列的重大特性有了深入了解:

  • 棧和隊列都是線性連續結構,增加和刪除元素不會影響破此連續性
  • 棧通過棧頂的操作實現元素的增加與刪除,也即只能在一端進行操作
  • 隊列通過隊尾增加元素,隊頭刪除元素,也即可以在兩端操作

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

1.5T超大空間只要6.98萬起的自主SUV車主滿意嗎

9萬首先我是五菱的忠實粉絲,第一輛車就是五菱,跑了20萬公里發動機和變速箱還沒動過,這質量是我選擇560的原因,其次我要經常拉點貨,後排放倒后空間完全可以滿足我,而且一家人出去玩也方便很多,1。8L+5AMT開起來比手動擋省力得多,雖然有時候會有些頓挫感,但畢竟不是全自動,也能接受了。

前言

有什麼車上市不到一年,就可以做到超過330000輛的銷量成績呢,沒錯它就是自主SUV陣型中的又一神車-寶駿560,對於任何品牌和車型來說,這都是一個了不起的成績,從全系沒有自動擋,到增加了5AMT,隨後又推出了1.5T的車型,為了更適應消費者需求一直在改進,那麼車主對它的評價如何呢,一起來探討一下。

上汽通用五菱-寶駿560

指導價:6.98-9.48萬

車主:菜鳥凌凌漆

購買車型:2016款 1.8L 手動精英型

裸車價格:7.68萬

五菱的車子實用性是經過市場的長時間考驗的,性價比也是較高的,圓潤飽滿的外觀是非常耐看的,雖說沒什麼太大的亮點,內飾中控布局也趨向轎車化的設計水平,無論是前後排,空間都非常寬敞,後排想怎麼坐就怎麼坐,1.8L的動力還是挺不錯的,起油比較快,操控性精準,尤其是在鄉村道路,底盤高的優勢就很明顯。

10月份提的車,目前行駛了2000多公里,油耗7.5L左右,續航能力很好。

車主:三石哥

購買車型:2016款 1.8L AMT智能手動豪華型

裸車價格:8.9萬

首先我是五菱的忠實粉絲,第一輛車就是五菱,跑了20萬公里發動機和變速箱還沒動過,這質量是我選擇560的原因,其次我要經常拉點貨,後排放倒后空間完全可以滿足我,而且一家人出去玩也方便很多,1.8L+5AMT開起來比手動擋省力得多,雖然有時候會有些頓挫感,但畢竟不是全自動,也能接受了。

11月份提的車,目前走了1800公里,市區油耗7.8左右,高速6.7左右,比之前麵包車要省油。

車主:青春的奧秘

購買車型:2016款 1.5T 手動豪華型

裸車價格:8.88萬

首先外觀時尚大氣,寶駿560 1.5T手動豪華版配置很齊全,功能強大,無鑰匙進入/啟動、倒車影像、定速巡航什麼的都有配備,動力非常充足,渦輪介入得比較早,加速很順暢,後排空間秒殺同級,北京癱什麼的隨便坐,電動助力的方向盤很輕盈,一個手也可以輕鬆操控,轉向很精準,這麼低的價格買到這麼好的車子,超值了。

9月份提的車,目前行駛4000公里,綜合油耗6.7,還是非常省油的。

總結:寶駿560一上市的歡迎程度就非常誇張,從單一的動力總成擴展到兩個,從純手動擋車型發展到增加了5AMT,現在唯一欠缺的就是全自動擋車型,上升的空間還有很大,逐步完善後再創銷量奇迹也是指日可待。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※教你寫出一流的銷售文案?

※別再煩惱如何寫文案,掌握八大原則!

這台後驅SUV只要5萬多,而且還是大廠出品

實際動力表現來看,這副發動機的低扭會略有不足,在2000轉時感覺動力並不是很充沛,上到3000轉以後動力會有所改善。不過,這種小排量發動機最大的問題就是後勁不足,上到100km/h之後就很難有什麼作為。底盤表現幻速S2的前懸架使用麥弗遜式獨立懸架,而後懸架為五連桿式非獨立懸架。

各個級別的SUV市場早已進入百家爭鳴的狀態,小型SUV市場更是如此,各路廠商都會使出渾身解數想搶佔這個市場,北汽也不例外。它們旗下的幻速S2可以說是一款重要產品,下面編者我就給大家分析一下幻速S2。

之所以要寫幻速S2是因為熱門的小型SUV大家已聽了不少,而幻速S2這種中規中矩的產品大家可能了解較少,但銷量並不能很好地反應出其真實產品力,因此編者我想給大家好好科普一下。

北汽銀翔-北汽幻速S2

指導價:5.18-6.08萬

外觀設計

幻速S2的車身尺寸為4250*1730*1735mm,軸距為2560mm。從前臉看,引擎蓋上由兩側向中間匯聚的凹線讓前臉有個很好的中心感。兩側向側面上揚的大燈組,看起來有一點點關二哥的威武感。

側面來看,車身顯得較為緊湊,門把手上方的腰線讓側面不至於過於單調。不過,輪胎的尺寸略微偏小。

後方來看,備胎外漏的設計野性十足,地台的高度較低,容易裝卸貨物。整個尾部給人一種方正厚實的感覺。

內飾設計

中控台的設計相當平庸也符合這個價位應有的水平,不過布局還是比較整齊,上手難度不高。擋把上的烤黑鋼琴漆帶來一絲亮點。

動力總成

幻速S2搭載了1.5L自然吸氣發動機+5擋手動變速箱,最大輸出113馬力和150牛米。實際動力表現來看,這副發動機的低扭會略有不足,在2000轉時感覺動力並不是很充沛,上到3000轉以後動力會有所改善。不過,這種小排量發動機最大的問題就是後勁不足,上到100km/h之後就很難有什麼作為。

底盤表現

幻速S2的前懸架使用麥弗遜式獨立懸架,而後懸架為五連桿式非獨立懸架。具體表現來看,幻速S2的底盤會偏硬一些,對震動的過濾並不是很到位,尤其面對連續震動會有點應付不過來。

乘坐空間

SUV在空間表現中會有一定的優勢,幻速S2也不例外。身高175cm的體驗者坐於後排,能有兩拳左右的腿部空間和4指左右的頭部空間,這個表現還是不錯的。

由於幻速S2是一款后驅車,所以它的後排中央拱起有點高,對乘坐有點影響,同時後排中央也缺乏頭枕,看來對後排中央的乘客並不友好。

油耗與保養費用

多位車主反映的幻速S2百公里綜合油耗為8.3L,對於它這個級別的產品來說,還是有一點點偏高。

保養費用方面,幻速S2的6萬公里總保養費用為4914元,其中更換機油機濾的價格為220元,這個價錢還是比較實惠的。

配置分析

幻速S2總共有8個車型可以買,但是只有4個版本,每個版本都有國四和國五的車型,這點要注意。

經過分析,我推薦買指導價為5.78萬的手動舒適型,這也是次低配的車型。因為它雖然比最低配貴了5000元,但是多了副駕駛安全氣囊、車頂行李架、倒車雷達、后視鏡電動調節和后雨刷。這堆配置之中,我覺得最重要的是副駕駛安全氣囊,這也是給家人朋友的一個保障。

編者總結:

幻速S2是小型SUV中少有的后驅車型,所以它的操控性會好一點,前輪胎的磨損也不會那麼快。如果你有一個后驅夢,而預算又正好卡在這個區間,何不買台幻速S2。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!