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