您好,登錄后才能下訂單哦!
這篇文章主要介紹了Netty中Redant怎么用,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
Redant 是一個(gè)基于 Netty 的 Web 容器,類(lèi)似 Tomcat 和 WebLogic 等容器
只需要啟動(dòng)一個(gè) Server,默認(rèn)的實(shí)現(xiàn)類(lèi)是 NettyHttpServer 就能快速啟動(dòng)一個(gè) web 容器了,如下所示:
public final class ServerBootstrap { public static void main(String[] args) { Server nettyServer = new NettyHttpServer(); // 各種初始化工作 nettyServer.preStart(); // 啟動(dòng)服務(wù)器 nettyServer.start(); }}
我們可以直接啟動(dòng) redant-example 模塊中的 ServerBootstrap 類(lèi),因?yàn)?redant-example 中有很多示例的 Controller,我們直接運(yùn)行 example 中的 ServerBootstrap,啟動(dòng)后你會(huì)看到如下的日志信息:
在 redant-example 模塊中,內(nèi)置了以下幾個(gè)默認(rèn)的路由:
啟動(dòng)成功后,可以訪問(wèn) http://127.0.0.1:8888/ 查看效果,如下圖所示:
如果你可以看到 "Welcome to redant!" 這樣的消息,那就說(shuō)明你啟動(dòng)成功了。
框架實(shí)現(xiàn)了自定義路由,通過(guò) @Controller @Mapping 注解就可以唯一確定一個(gè)自定義路由。如下列的 UserController 所示:
和 Spring 的使用方式一樣,訪問(wèn) /user/list 來(lái)看下效果,如下圖所示:
目前支持 json、html、xml、text 等類(lèi)型的結(jié)果渲染,用戶只需要在 方法的 @Mapping 注解上通過(guò) renderType 來(lái)指定具體的渲染類(lèi)型即可,如果不指定的話,默認(rèn)以 json 類(lèi)型范圍。
如下圖所示,首頁(yè)就是通過(guò)指定 renderType 為 html 來(lái)返回一個(gè) html 頁(yè)面的:
從 UserController 的代碼中,我們看到 userServerce 對(duì)象是通過(guò) @Autowired 注解自動(dòng)注入的,這個(gè)功能是任何一個(gè) IOC 容器基本的能力,下面我們來(lái)看看如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 IOC 容器。
首先定義一個(gè) BeanContext 接口,如下所示:
public interface BeanContext { /** * 獲得Bean * @param name Bean的名稱(chēng) * @return Bean */ Object getBean(String name); /** * 獲得Bean * @param name Bean的名稱(chēng) * @param clazz Bean的類(lèi) * @param <T> 泛型 * @return Bean */ <T> T getBean(String name,Class<T> clazz);}
然后我們需要在系統(tǒng)啟動(dòng)的時(shí)候,掃描出所有被 @Bean 注解修飾的類(lèi),然后對(duì)這些類(lèi)進(jìn)行實(shí)例化,然后把實(shí)例化后的對(duì)象保存在一個(gè) Map 中即可,如下圖所示:
代碼很簡(jiǎn)單,通過(guò)在指定路徑下掃描出所有的類(lèi)之后,把實(shí)例對(duì)象加入map中,但是對(duì)于已經(jīng)加入的 bean 不能繼續(xù)加入了,加入之后要獲取一個(gè) Bean 也很簡(jiǎn)單了,直接通過(guò) name 到 map 中去獲取就可以了。
現(xiàn)在我們已經(jīng)把所有 @Bean 的對(duì)象管理起來(lái)了,那對(duì)于依賴到的其他的 bean 該如何注入呢,換句話說(shuō)就是將我們實(shí)例化好的對(duì)象賦值給 @Autowired 注解修飾的變量。
簡(jiǎn)單點(diǎn)的做法就是遍歷 beanMap,然后對(duì)每個(gè) bean 進(jìn)行檢查,看這個(gè) bean 里面的每個(gè) setter 方法和屬性,如果有 @Autowired 注解,那就找到具體的 bean 實(shí)例之后將值塞進(jìn)去。
BeanContext 已經(jīng)實(shí)現(xiàn)了,那怎么獲取 BeanContext 的實(shí)例呢?想到 Spring 中有很多的 Aware 接口,每種接口負(fù)責(zé)一種實(shí)例的回調(diào),比如我們想要獲取一個(gè) BeanFactory 那只要將我們的類(lèi)實(shí)現(xiàn) BeanFactoryAware 接口就可以了,接口中的 setBeanFactory(BeanFactory factory) 方法參數(shù)中的 BeanFactory 實(shí)例就是我們所需要的,我們只要實(shí)現(xiàn)該方法,然后將參數(shù)中的實(shí)例保存在我們的類(lèi)中,后續(xù)就可以直接使用了。
那現(xiàn)在我就來(lái)實(shí)現(xiàn)這樣的功能,首先定義一個(gè) Aware 接口,所有其他需要回調(diào)塞值的接口都繼承自該接口,如下所示:
public interface Aware {
}
public interface BeanContextAware extends Aware{
/**
* 設(shè)置BeanContext
* @param beanContext BeanContext對(duì)象
*/
void setBeanContext(BeanContext beanContext);
}
接下來(lái)需要將 BeanContext 的實(shí)例注入到所有 BeanContextAware 的實(shí)現(xiàn)類(lèi)中去。BeanContext 的實(shí)例很好得到,BeanContext 的實(shí)現(xiàn)類(lèi)本身就是一個(gè) BeanContext 的實(shí)例,并且可以將該實(shí)例設(shè)置為單例,這樣的話所有需要獲取 BeanContext 的地方都可以獲取到同一個(gè)實(shí)例。
拿到 BeanContext 的實(shí)例后,我們就需要掃描出所有實(shí)現(xiàn)了 BeanContextAware 接口的類(lèi),并實(shí)例化這些類(lèi),然后調(diào)用這些類(lèi)的 setBeanContext 方法,參數(shù)就傳我們拿到的 BeanContext 實(shí)例。
邏輯理清楚之后,實(shí)現(xiàn)起來(lái)就很簡(jiǎn)單了,如下圖所示:
基本上所有的 web 容器都會(huì)有 cookie 管理的能力,那我們的 redant 也不能落后。首先定義一個(gè) CookieManager 的接口,核心的操作 cookie 的方法如下:
public interface CookieManager {
Set<Cookie> getCookies();
Cookie getCookie(String name);
void addCookie(String name,String value);
void setCookie(Cookie cookie);
boolean deleteCookie(String name);
}
其中我只列舉了幾個(gè)核心的方法,另外有一些不同參數(shù)的重載方法,這里就不詳細(xì)介紹了。最關(guān)鍵的是兩個(gè)方法,一個(gè)是讀 Cookie 一個(gè)是寫(xiě) Cookie 。
Netty 中是通過(guò) HttpRequest 的 Header 來(lái)保存請(qǐng)求中所攜帶的 Cookie的,所以要讀取 Cookie 的話,最關(guān)鍵的是獲取到 HttpRequest。而 HttpRequest 可以在 ChannelHandler 中拿到,通過(guò) HttpServerCodec 編解碼器,Netty 已經(jīng)幫我們把請(qǐng)求的數(shù)據(jù)轉(zhuǎn)換成 HttpRequest 了。但是這個(gè) HttpRequest 只在 ChannelHandler 中才能訪問(wèn)到,而處理 Cookie 通常是用戶自定義的操作,并且對(duì)用戶來(lái)說(shuō)他是不關(guān)心 HttpRequest 的,他只需要通過(guò) CookieManager 去獲取一個(gè) Cookie 就行了。
這種情況下,最適合的就是將 HttpRequest 對(duì)象保存在一個(gè) ThreadLocal 中,在 CookieManager 中需要獲取的時(shí)候,直接到 ThreadLocal 中去取出來(lái)就可以了,如下列代碼所示:
@Overridepublic Set<Cookie> getCookies() { HttpRequest request = TemporaryDataHolder.loadHttpRequest(); Set<Cookie> cookies = new HashSet<>(); if(request != null) { String value = request.headers().get(HttpHeaderNames.COOKIE); if (value != null) { cookies = ServerCookieDecoder.STRICT.decode(value); } } return cookies;}
TemporaryDataHolder 就是那個(gè)通過(guò) ThreadLocal 保存了 HttpRequest 的類(lèi)。
寫(xiě) Cookie 和讀 Cookie 面臨著一樣的問(wèn)題,就是寫(xiě)的時(shí)候需要借助于 HttpResponse,將 Cookie 寫(xiě)入 HttpResponse 的 Header 中去,但是用戶執(zhí)行寫(xiě) Cookie 操作的時(shí)候,根本就不關(guān)心 HttpResponse,甚至他在寫(xiě)的時(shí)候,還沒(méi)有 HttpResponse。
這時(shí)的做法也是將需要寫(xiě)到 HttpResponse 中的 Cookie 保存在 ThreadLocal 中,然后在最后通過(guò) channel 寫(xiě)響應(yīng)之前,將 Cookie 拿出來(lái)塞到 HttpResponse 中去即可,如下列代碼所示:
@Override
public void setCookie(Cookie cookie) {
TemporaryDataHolder.storeCookie(cookie);
}
/**
* 響應(yīng)消息
*/
private void writeResponse(){
boolean close = isClose();
response.headers().add(HttpHeaderNames.CONTENT_LENGTH, String.valueOf(response.content().readableBytes()));
// 從ThreadLocal中取出待寫(xiě)入的cookie
Set<Cookie> cookies = TemporaryDataHolder.loadCookies();
if(!CollectionUtil.isEmpty(cookies)){
for(Cookie cookie : cookies){
// 將cookie寫(xiě)入response中
response.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie));
}
}
ChannelFuture future = channel.write(response);
if(close){
future.addListener(ChannelFutureListener.CLOSE);
}
}
攔截器是一個(gè)框架很重要的功能,通過(guò)攔截器可以實(shí)現(xiàn)一些通用的工作,比如登錄鑒權(quán),事務(wù)處理等等。記得在 Servlet 的年代,攔截器是非常重要的一個(gè)功能,基本上每個(gè)系統(tǒng)都會(huì)在 web.xml 中配置很多的攔截器。
攔截器的基本思想是,通過(guò)一連串的類(lèi)去執(zhí)行某個(gè)攔截的操作,一旦某個(gè)類(lèi)中的攔截操作返回了 false,那就終止后面的所有流程,直接返回。
這種場(chǎng)景非常適合用責(zé)任鏈模式去實(shí)現(xiàn),而 Netty 的 pipeline 本身就是一個(gè)責(zé)任鏈模式的應(yīng)用,所以我們就可以通過(guò) pipeline 來(lái)實(shí)現(xiàn)我們的攔截器。這里我定義了兩種類(lèi)型的攔截器:前置攔截器和后置攔截器。
前置攔截器是在處理用戶的業(yè)務(wù)邏輯之前的一個(gè)攔截操作,如果該操作返回了 false 則直接 return,不會(huì)繼續(xù)執(zhí)行用戶的業(yè)務(wù)邏輯。
后置攔截器就有點(diǎn)不同了,后置攔截器主要就是處理一些后續(xù)的操作,因?yàn)楹笾脭r截器再跟前置攔截器一樣,當(dāng)操作返回了 false 直接 return 的話,已經(jīng)沒(méi)有意義了,因?yàn)闃I(yè)務(wù)邏輯已經(jīng)執(zhí)行完了。
理解清楚了具體的邏輯之后,實(shí)現(xiàn)起來(lái)就很簡(jiǎn)單了,如下列代碼所示:
有了實(shí)現(xiàn)之后,我們需要把他們加到 pipeline 中合適的位置,讓他們?cè)谡麄€(gè)責(zé)任鏈中生效,如下圖所示:
目前攔截器還沒(méi)有實(shí)現(xiàn)指定順序執(zhí)行的功能,其實(shí)也很簡(jiǎn)單,可以定義一個(gè) @InterceptorOrder 的注解應(yīng)用在所有的攔截器的實(shí)現(xiàn)類(lèi)上,掃描到攔截器的結(jié)果之后,根據(jù)該注解進(jìn)行排序,然后把拍完序之后的結(jié)果添加到 pipeline 中即可。
到目前為止,我描述的都是單節(jié)點(diǎn)模式,如果哪一天單節(jié)點(diǎn)的性能無(wú)法滿足了,那就需要使用集群了,所以我也實(shí)現(xiàn)了集群模式。
集群模式是由一個(gè)主節(jié)點(diǎn)和若干個(gè)從節(jié)點(diǎn)構(gòu)成的。主節(jié)點(diǎn)接收到請(qǐng)求后,將請(qǐng)求轉(zhuǎn)發(fā)給從節(jié)點(diǎn)來(lái)處理,從節(jié)點(diǎn)把處理好的結(jié)果返回給主節(jié)點(diǎn),由主節(jié)點(diǎn)把結(jié)果響應(yīng)給請(qǐng)求。
要想實(shí)現(xiàn)集群模式需要有一個(gè)服務(wù)注冊(cè)和發(fā)現(xiàn)的功能,目前是借助于 Zk 來(lái)做的服務(wù)注冊(cè)與發(fā)現(xiàn)。
因?yàn)橹鞴?jié)點(diǎn)需要把請(qǐng)求轉(zhuǎn)發(fā)給從節(jié)點(diǎn),所以主節(jié)點(diǎn)需要知道目前有哪些從節(jié)點(diǎn),我通過(guò) ZooKeeper 來(lái)實(shí)現(xiàn)服務(wù)注冊(cè)與發(fā)現(xiàn)。
如果你沒(méi)有可用的 Zk 服務(wù)端的話,那你可以通過(guò)運(yùn)行下面的 Main 方法來(lái)啟動(dòng)一個(gè) ZooKeeper 服務(wù)端:
public final class ZkBootstrap {
private static final Logger LOGGER = LoggerFactory.getLogger(ZkBootstrap.class);
public static void main(String[] args) {
try {
ZkServer zkServer = new ZkServer();
zkServer.startStandalone(ZkConfig.DEFAULT);
}catch (Exception e){
LOGGER.error("ZkBootstrap start failed,cause:",e);
System.exit(1);
}
}
}
這樣你就可以在后面啟動(dòng)主從節(jié)點(diǎn)的時(shí)候使用這個(gè) Zk 了。但是這并不是必須的,如果你已經(jīng)有一個(gè)正在運(yùn)行的 Zk 的服務(wù)端,那么你可以在啟動(dòng)主從節(jié)點(diǎn)的時(shí)候直接使用它,通過(guò)在 main 方法的參數(shù)中指定 Zk 的地址即可。
只需要運(yùn)行下面的代碼,就可以啟動(dòng)一個(gè)主節(jié)點(diǎn)了:
public class MasterServerBootstrap {
public static void main(String[] args) {
String zkAddress = ZkServer.getZkAddressArgs(args,ZkConfig.DEFAULT);
// 啟動(dòng)MasterServer
Server masterServer = new MasterServer(zkAddress);
masterServer.preStart();
masterServer.start();
}
}
如果在 main 方法的參數(shù)中指定了 Zk 的地址,就通過(guò)該地址去進(jìn)行服務(wù)發(fā)現(xiàn),否則會(huì)使用默認(rèn)的 Zk 地址。
只需要運(yùn)行下面的代碼,就可以啟動(dòng)一個(gè)從節(jié)點(diǎn)了:
public class SlaveServerBootstrap {
public static void main(String[] args) {
String zkAddress = ZkServer.getZkAddressArgs(args,ZkConfig.DEFAULT);
Node node = Node.getNodeWithArgs(args);
// 啟動(dòng)SlaveServer
Server slaveServer = new SlaveServer(zkAddress,node);
slaveServer.preStart();
slaveServer.start();
}
}
如果在 main 方法的參數(shù)中指定了 Zk 的地址,就通過(guò)該地址去進(jìn)行服務(wù)注冊(cè),否則會(huì)使用默認(rèn)的 Zk 地址。
實(shí)際上多節(jié)點(diǎn)模式具體的處理邏輯還是復(fù)用了單節(jié)點(diǎn)模式的核心功能,只是把原本一臺(tái)實(shí)例擴(kuò)展到多臺(tái)實(shí)例而已。
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“Netty中Redant怎么用”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。