Netty源碼學習系列之3-ServerBootstrap的初始化_網頁設計公司

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

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

前言

    根據前文我們知道,NioEventLoopGroup和NioEventLoop是netty對Reactor線程模型的實現,而本文要說的ServerBootstrap是對上面二者的整合與調用,是一個統籌者和協調者。具體netty使用的是Reactor單線程模型還是多線程模型、抑或者主從多線程模型,都是ServerBootstrap的不同配置決定的。

    下面照例粘貼一下示例demo(以Reactor多線程模式構建),開始正文。

 1 public class NettyDemo1 {
 2     // netty服務端的一般性寫法
 3     public static void main(String[] args) {
 4         EventLoopGroup boss = new NioEventLoopGroup(1);
 5         EventLoopGroup worker = new NioEventLoopGroup();
 6         try {
 7             ServerBootstrap bootstrap = new ServerBootstrap();
 8             bootstrap.group(boss, worker).channel(NioServerSocketChannel.class)
 9                     .option(ChannelOption.SO_BACKLOG, 100)
10                     .childHandler(new ChannelInitializer<SocketChannel>() {
11                         @Override
12                         protected void initChannel(SocketChannel socketChannel) throws Exception {
13                             ChannelPipeline pipeline = socketChannel.pipeline();
14                             pipeline.addLast(new StringDecoder());
15                             pipeline.addLast(new StringEncoder());
16                             pipeline.addLast(new NettyServerHandler());
17                         }
18                     });
19             ChannelFuture channelFuture = bootstrap.bind(90);
20             channelFuture.channel().closeFuture().sync();
21         } catch (Exception e) {
22             e.printStackTrace();
23         } finally {
24             boss.shutdownGracefully();
25             worker.shutdownGracefully();
26         }
27     }
28 }

 

一、ServerBootstrap的初始化

    ServerBootstrap的無參構造器啥都沒做,它使用的build模式給屬性賦值,即上面示例中看到的,每執行一個賦值方法都會返回當前對象的引用使得可以繼續鏈式調用。下面挨個方法追蹤。

1 public ServerBootstrap() { }

 

1、ServerBootstrap.group方法

    ServerBootstrap有兩個可用重載group方法(如下的兩個),其中接收一個group入參的方法會調用有兩個入參的group方法,只是兩個參數傳同一個group。這兩個group方法決定了netty使用的Reactor線程模型的類型,一個group入參的方法對應Reactor單線程模型,兩個入參且不是同一個group的方法對應Reactor多線程模型或主從多線程模型(具體是哪一種取決於實例化parentGroup時的線程數)。此處只是提一下,先有個印象,後面會對線程模型進行詳細研究。

1 public ServerBootstrap group(EventLoopGroup group) {
2         return group(group, group);
3     }
1 public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
2         super.group(parentGroup);
3         ObjectUtil.checkNotNull(childGroup, "childGroup");
4         if (this.childGroup != null) {
5             throw new IllegalStateException("childGroup set already");
6         }
7         this.childGroup = childGroup;
8         return this;
9     }

    可以看到上述group方法對兩個入參進行了不同位置的賦值,將第一個參數parentGroup傳給了父類AbstractBootstrap的group方法,如下,即最終賦值給了AbstractBootstrap中的group屬性。第二個參數直接賦值給了ServerBootstrap的childGroup屬性。

1 public B group(EventLoopGroup group) {
2         ObjectUtil.checkNotNull(group, "group");
3         if (this.group != null) {
4             throw new IllegalStateException("group set already");
5         }
6         this.group = group;
7         return self();
8     }

 

2、ServerBootstrap.option/childOption方法和ServerBootstrap.attr/childAttr方法

    這四個方法只是做了屬性的賦值,分別賦值給了AbstractBootstrap的options屬性和attrs屬性以及ServerBootstrap的childOptions屬性和childAttrs屬性。

 1 public <T> B option(ChannelOption<T> option, T value) {
 2         ObjectUtil.checkNotNull(option, "option");
 3         if (value == null) {
 4             synchronized (options) {
 5                 options.remove(option);
 6             }
 7         } else {
 8             synchronized (options) {
 9                 options.put(option, value);
10             }
11         }
12         return self();
13     }
 1 public <T> B attr(AttributeKey<T> key, T value) {
 2         ObjectUtil.checkNotNull(key, "key");
 3         if (value == null) {
 4             synchronized (attrs) {
 5                 attrs.remove(key);
 6             }
 7         } else {
 8             synchronized (attrs) {
 9                 attrs.put(key, value);
10             }
11         }
12         return self();
13     }

 

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

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

3、ServerBootstrap.channel方法

    調用的是父類AbstractBootstrap的channel方法:

1 public B channel(Class<? extends C> channelClass) {
2         return channelFactory(new ReflectiveChannelFactory<C>(
3                 ObjectUtil.checkNotNull(channelClass, "channelClass")
4         ));
5     }

    可以看到先封裝成了一個ReflectiveChannelFactory對象,然後調用channelFactory方法,下面挨個看。ReflectiveChannelFactory的構造器如下,可見就是將傳入class對象的構造器取出來賦值,此時constructor存放的就是NioServerSocketChannel的構造器。

public ReflectiveChannelFactory(Class<? extends T> clazz) {
        ObjectUtil.checkNotNull(clazz, "clazz");
        try {
            this.constructor = clazz.getConstructor();
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) +
                    " does not have a public non-arg constructor", e);
        }
    }

    channelFactory方法的工作是將上面創建的ReflectiveChannelFactory對象賦值給AbstractBootstrap的channelFactory屬性:

1 public B channelFactory(ChannelFactory<? extends C> channelFactory) {
2         ObjectUtil.checkNotNull(channelFactory, "channelFactory");
3         if (this.channelFactory != null) {
4             throw new IllegalStateException("channelFactory set already");
5         }
6 
7         this.channelFactory = channelFactory;
8         return self();
9     }

 

4、ServerBootstrap.handler方法和ServerBootstrap.childHandler方法

    handler方法的入參賦值給了AbstractBootstrap的handler屬性,childHandler方法的入參賦值給了ServerBootstrap的childHandler屬性。看到這裏想必園友們也能看出ServerBootstrap的賦值規律了,凡是child開頭的都放在ServerBootstrap中,而不帶child的大多放在其父類ABstractBootstrap中。

1 public B handler(ChannelHandler handler) {
2         this.handler = ObjectUtil.checkNotNull(handler, "handler");
3         return self();
4     }
1 public ServerBootstrap childHandler(ChannelHandler childHandler) {
2         this.childHandler = ObjectUtil.checkNotNull(childHandler, "childHandler");
3         return this;
4     }

 

5、完成賦值后ServerBootstrap的快照圖

 

 

小結

    ServerBootstrap的初始化過程看起來賦了很多值,但都只是做了準備工作,看起來輕鬆又簡單,但請注意,這是暴風雨前寧靜。前面的各種賦值到底有什麼用處?很多屬性分為有child前綴和沒有child前綴,這樣設置又有什麼意圖?下一期將進入ServerBootstrap的bind方法,這是netty的深水區,很多謎底也將在這裏得到揭曉,敬請期待!

 

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

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

Cypress系列(6)- Cypress 的重試機制_如何寫文案

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

銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。

如果想從頭學起Cypress,可以看下面的系列文章哦

https://www.cnblogs.com/poloyy/category/1768839.html

 

前言

重試(Retry-ability)是 Cypress 的核心概念之一,有助於我們寫出更加健壯的測試

 

命令和斷言

Cypress 測試中經常被調用的兩種類型,仍以前面說到的 testLogin.js 為栗子

最後的斷言解析

檢查標籤為 h1 的元素是否包含 jane.lane

 

斷言的一般步驟

  1. 用 cy.get() 查詢應用程序的DOM,找到元素
  2. 針對元素或元素列表進行斷言嘗試 ,我們示例中為 .should(“contain”, “jane.lane”) 

 

關於實際工作中的靈魂拷問

現在的 web 應用基本都是異步的,如果出現以下情況又應該怎麼處理呢?

  1. 如果斷言發生時,應用程序尚未更新DOM怎麼辦?
  2. 如果斷言發生時,應用程序正在等待其後端響應,而導致頁面暫無結果怎麼辦?
  3. 如果斷言發生時,應用程序正在進行密集計算,而導致頁面未及時更新怎麼辦?

上述情況再測試中經常會發生,一般處理方法是在斷言前價格固定等待時間(或像 selenium 一樣顯式、隱式等待),但仍有可能會發生測試失敗

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

 

Cypress 如何優美的解決上述問題

  1.  cy.get() 命令之後的斷言通過,則該命令成功執行完成
  2.  cy.get() 命令之後的斷言失敗,則 cy.get() 命令會自動重新查詢 web 應用程序的 DOM 樹,然後 Cypress 將再次嘗試對 cy.get() 返回的元素進行斷言
  3. 如果斷言仍然失敗, cy.get() 仍然會重新查詢 DOM 樹….以此類推
  4. 直到斷言成功 或 cy.get() 命令超時

總結

  • 其實很像selenium 的顯式等待,只不過 Cypress 是全局的,不用針對元素去單獨識別
  • Cypress 這種自動重試機制避免了在測試代碼中編寫硬編碼等待(強制等待),使測試代碼更加健壯

 

多重斷言

  • 在日常測試中,有時候需要多重斷言,即獲取元素後跟多個斷言
  • 在多重斷言中,Cypress 將按順序進行斷言,即當第一個斷言通過後,會進行第二個斷言,通過後進行第三個斷言…以此類推

 

列表的栗子

需求

  • 假設一個下拉列表,存在兩個選項,第一個選項是“iTesting”,第二個選項是“testerTalk”
  • 我們需要驗證兩個選項的存在,並且順序正確,代碼片段如下

代碼解析

  1. 總共有三個斷言:一個 should() ,兩個 expect() 
  2. and() 斷言實際上是 should() 斷言的別名,它是 should() 的自定義回調斷言,其中包含兩個 expect() 斷言
  3. 在測試執行過程中,如果第二個斷言失敗了,那第三個斷言永遠不會執行
  4. 如果導致第二個斷言失敗的原因被找到且修復了,且此時整個命令還沒有超時,則在進行第三個斷言時,還會再次重試第一、第二個斷言

 

重試(Retry-ability)的條件

前言

  • Cypress 並不會重試所有命令,當命令可能改變被測應用程序的狀態時,該命令將不會重試(如: click() ,畢竟要點擊)
  • Cypress 僅會重試那些查詢 DOM 的命令: cy.get() 、 find() 、 contains() 等
  • 可以通過官方文檔 Assertions 部分來檢查是否重試了特定命令:https://docs.cypress.io/zh-cn/guides/references/assertions.html#Chai

 

常用的可重試命令

 

重點啦!

重試的超時時間默認是 4秒,對應的配置項是: defaultCommondTimeout ,如果想改重試的超時時間,在 cypress.json 文件改對應的字段值即可

 

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

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

什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。

ASP.NET Core MVC+Layui使用EF Core連接MySQL執行簡單的CRUD操作_網頁設計公司

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

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

前言:

  本章主要通過一個完整的示例講解ASP.NET Core MVC+EF Core對MySQL數據庫進行簡單的CRUD操作,希望能夠為剛入門.NET Core的小夥伴們提供一個完整的參考實例。關於ASP.NET Core MVC+EF操作MsSQL Server詳情請參考官方文檔(https://docs.microsoft.com/zh-cn/aspnet/core/data/ef-mvc/?view=aspnetcore-3.1)。

示例實現功能預覽:

 博客實例源碼下載地址:

https://github.com/YSGStudyHards/ASP.NET-Core-MVC-Layui-EF-Core-CRUD_Sample

一、創建ASP.NET Core Web應用程序:

注意,本章節主要以APS.NET Core 3.1版本作為博客的樣式實例!

 

二、添加EF Core NuGet包:

  若要在項目中使用EF Core操作MySQL數據庫,需要安裝相應的數據庫驅動包。 本章教程主要使用 MySQL數據庫,所以我們需要安裝相關驅動包MySql.Data.EntityFrameworkCore。

安裝方式:

點擊工具=>NuGet包管理器=>程序包管理器控制台輸入以下命令:

Install-Package MySql.Data.EntityFrameworkCore -Version 8.0.20

點擊工具=>NuGet包管理器=>管理解決方案的NuGet程序包:

搜索:MySql.Data.EntityFrameworkCore  點擊安裝。

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

三、創建對應數據庫表的實體模型:

   注意該篇博客使用的是手動模型優先的方式進行數據庫表字段與模型屬性映射,當然如果大家覺得這樣子比較麻煩的話可以真正意義上的模型優先,直接創建模型在program.cs中配置創建對應模型的數據庫邏輯代碼即可無需手動創建數據庫,可參考官網文檔教程(https://docs.microsoft.com/zh-cn/aspnet/core/data/ef-rp/intro?view=aspnetcore-3.1&tabs=visual-studio#create-the-database)。

創建用戶模型(UserInfo):

注意:屬性大小寫和數據庫中的表字段保持一致,Id 屬性成為此類對應的數據庫表的主鍵列。 默認情況下,EF Core 將名為 Id 或 xxxID 的屬性視為主鍵。 有關詳細信息,請參閱 F Core – 密鑰。

    /// <summary>
    /// 學生信息模型
    /// </summary>
    public class UserInfo
    {
        /// <summary>
        /// 學生編號
        /// </summary>
        [Description("學生編號")]
        public int? Id { get; set; }

        /// <summary>
        /// 學生姓名
        /// </summary>
        [Description("學生姓名")]
        public string UserName { get; set; }

        /// <summary>
        /// 學生性別
        /// </summary>
        [Description("學生性別")]
        public string Sex { get; set; }

        /// <summary>
        /// 學生聯繫電話
        /// </summary>
        [Description("學生聯繫電話")]
        public string Phone { get; set; }

        /// <summary>
        /// 學生描述
        /// </summary>
        [Description("學生描述")]
        public string Description { get; set; }

        /// <summary>
        /// 學生愛好
        /// </summary>
        [Description("學生愛好")]
        public string Hobby { get; set; }
    }

四、將數據庫連接字符串添加到 appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
        "MySqlConnection":"Data Source=127.0.0.1;User ID=root;Password=root;DataBase=SchoolUserInfo_db"
  }
}

五、創建數據庫上下文:

概述:

 數據庫上下文類是為給定數據模型協調 EF Core 功能的主類。 上下文派生自 Microsoft.EntityFrameworkCore.DbContext。 上下文指定數據模型中包含哪些實體。 在此項目中將數據庫上下文類命名為 SchoolUserInfoContext。

創建:

using Microsoft.EntityFrameworkCore;
using Model;

namespace Dal
{
    public class SchoolUserInfoContext : DbContext
    {
        public SchoolUserInfoContext(DbContextOptions<SchoolUserInfoContext> options)
            : base(options)
        {
        }

        /// <summary>
        /// DbSet實體集屬性對應數據庫中的表(注意實體集名必須與表明一致)
        /// </summary>
        public DbSet<UserInfo> UserInfos { get; set; }

        /// <summary>
        /// TODO:當數據庫創建完成后, EF 創建一系列數據表,表名默認和 DbSet 屬性名相同。 集合屬性的名稱一般使用複數形式,但不同的開發人員的命名習慣可能不一樣,
/// 開發人員根據自己的情況確定是否使用複數形式。 在定義 DbSet 屬性的代碼之後,添加下面代碼,對DbContext指定單數的表名來覆蓋默認的表名。
/// </summary> /// <param name="modelBuilder"></param> protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<UserInfo>().ToTable("UserInfo"); } } }

六、將上下文添加到 Startup.cs 中的依賴項注入:

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            //注入EF Core數據庫上下文服務
            services.AddDbContext<SchoolUserInfoContext>(options =>
                options.UseMySQL(Configuration.GetConnectionString("MySqlConnection")));

            services.AddControllersWithViews();
        }

七、引入Layui樣式和js:

前往官網下載Layui相關樣式和js包,下載地址:https://www.layui.com/

Layui彈出層插件layer.js(有很多地方需要用到彈窗),下載地址:https://layer.layui.com/

將相關文件存放到wwwroot文件下:

 

將相關文件引入默認布局頁面中:

八、 ASP.NET Core MVC 和 EF Core實現MySQL  CRUD功能:

注意在這裏主要展示的EF Core與數據庫操作的部分代碼,詳細代碼可下載實例源碼查看。

Create:

        /// <summary>
        /// 學生信息添加
        /// </summary>
        /// <param name="addUserInfo"></param>
        /// <returns></returns>
        public async Task<bool> Create(AddUserInfoViewModel addUserInfo)
        {
            try
            {
                var userInfo=new UserInfo()
                {
                    UserName = addUserInfo.UserName,
                    Sex = addUserInfo.Sex,
                    Hobby = addUserInfo.Hobby,
                    Phone = addUserInfo.Phone,
                    Description = addUserInfo.Description
                };

                _shoSchoolUserInfoContext.UserInfos.Add(userInfo);

                await _shoSchoolUserInfoContext.SaveChangesAsync();

                return true;
            }
            catch
            {
                return false;
            }
        }

Retrieve:

        /// <summary>
        /// 獲取用戶信息
        /// </summary>
        /// <param name="page">當前頁碼</param>
        /// <param name="limit">显示條數</param>
        /// <param name="userName">用戶姓名</param>
        /// <returns></returns>
        public async Task<PageSearchModel> GetPageListData(int page = 1, int limit = 15, string userName = "")
        {
            try
            {
                List<UserInfo> listData;
                var totalCount = 0;
                if (!string.IsNullOrWhiteSpace(userName))
                {
                    listData = await _shoSchoolUserInfoContext.UserInfos.Where(x => x.UserName.Contains(userName)).OrderByDescending(x => x.Id).Skip((page - 1) * limit).Take(limit).ToListAsync();

                    totalCount = _shoSchoolUserInfoContext.UserInfos
                        .Count(x => x.UserName.Contains(userName));
                }
                else
                {
                    listData = await _shoSchoolUserInfoContext.UserInfos.OrderByDescending(x => x.Id).Skip((page - 1) * limit).Take(limit).ToListAsync();

                    totalCount = _shoSchoolUserInfoContext.UserInfos.Count();
                }

                return new PageSearchModel()
                {
                    ResultMsg = "success",
                    Code = 200,
                    TotalCount = totalCount,
                    DataList = listData
                };
            }
            catch (Exception e)
            {
                return new PageSearchModel() { Code = 400, ResultMsg = e.Message };
            }
        }

Update:

        /// <summary>
        /// 學生信息修改
        /// </summary>
        /// <param name="userInfo"></param>
        /// <returns></returns>
        public async Task<bool> Update(UserInfo userInfo)
        {

            try
            {
                _shoSchoolUserInfoContext.UserInfos.Update(userInfo);

                await _shoSchoolUserInfoContext.SaveChangesAsync();

                return true;
            }
            catch
            {
                return false;
            }
        }

Delete:

        /// <summary>
        /// 學生信息刪除
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public async Task<bool> Delete(int? id)
        {
            try
            {
                var searchUserInfo = await _shoSchoolUserInfoContext.UserInfos.FindAsync(id);

                if (searchUserInfo == null)
                {
                    return false;
                }

                _shoSchoolUserInfoContext.UserInfos.Remove(searchUserInfo);
                await _shoSchoolUserInfoContext.SaveChangesAsync();

                return true;
            }
            catch
            {
                return false;
            }
        }

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

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

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

SpringAOP使用及源碼分析(SpringBoot下)_網頁設計

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

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

一、SpringAOP應用

  1. 先搭建一個SpringBoot項目
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.7.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.mmc</groupId>
	<artifactId>springboot-study</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>springboot-study</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		
	</dependencies>
	
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>
  1. 定義一個業務邏輯類,作為切面
public interface CalculationService {

    /**
     * 加法運算
     * @param x
     * @param y
     * @return
     */
    public Integer add(Integer x,Integer y);
}

/**
 * @description:
 * @author: mmc
 * @create: 2020-06-01 14:22
 **/
@Service
public class CalculationServiceImpl implements CalculationService {

    @Override
    public Integer add(Integer x, Integer y) {
        if(x==null||y==null){
            throw  new NullPointerException("參數不能為空");
        }
        return x+y;
    }
}
  1. 定義一個切面類,添加通知方法
  • 前置通知(@Before):logStart:在目標方法(div)運行之前運行
  • 後置通知(@After):logEnd:在目標方法(add)運行結束之後運行(無論方法正常結束還是異常結束)
  • 返回通知(@AfterReturning):logReturn:在目標方法(add)正常返回之後運行
  • 異常通知(@AfterThrowing):logException:在目標方法(add)出現異常以後運行
  • 環繞通知(@Around):動態代理,手動推進目標方法運行(joinPoint.procced())

/**
 * @description:  切面類
 * @author: mmc
 * @create: 2020-06-01 14:24
 **/
@Aspect
@Component
public class LogAspects {

    //抽取公共的切入點表達式
    //1、本類引用
    //2、其他的切面引用
    @Pointcut("execution(public Integer com.mmc.springbootstudy.service.CalculationService.*(..))")
    public void pointCut(){};

    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        System.out.println(""+joinPoint.getSignature().getName()+"運行。。。@Before:參數列表是:{"+Arrays.asList(args)+"}");
    }

    @After("pointCut()")
    public void logEnd(JoinPoint joinPoint){
        System.out.println(""+joinPoint.getSignature().getName()+"結束。。。@After");
    }


    //JoinPoint一定要出現在參數表的第一位
    @AfterReturning(value="pointCut()",returning="result")
    public void logReturn(JoinPoint joinPoint,Object result){
        System.out.println(""+joinPoint.getSignature().getName()+"正常返回。。。@AfterReturning:運行結果:{"+result+"}");
    }

    @AfterThrowing(value="pointCut()",throwing="exception")
    public void logException(JoinPoint joinPoint,Exception exception){
        System.out.println(""+joinPoint.getSignature().getName()+"異常。。。異常信息:{"+exception+"}");
    }
}
  1. 寫一個controller測試
@RequestMapping("/testaop")
   @ResponseBody
    public Integer testaop(Integer x,Integer y){
       Integer result = calculationService.add(x, y);
       return result;
   }
  1. 測試

add運行。。。@Before:參數列表是:{[2, 3]}
add結束。。。@After
add正常返回。。。@AfterReturning:運行結果:{5}

二、源碼分析

主線流程圖:

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

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

  1. spring.factories文件里引入了AopAutoConfiguration類
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class, AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = false)
	//看配置文件,如果配置的spring.aop.proxy-target-class為false則引入JdkDynamicAutoProxyConfiguration
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
			matchIfMissing = false)
	public static class JdkDynamicAutoProxyConfiguration {

	}

	@Configuration
	//開啟AspectJAutoProxy
	@EnableAspectJAutoProxy(proxyTargetClass = true)
	//看配置文件,如果配置的spring.aop.proxy-target-class為true則引入CglibAutoProxyConfiguration 
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
			matchIfMissing = true)
	public static class CglibAutoProxyConfiguration {

	}

}

在包目錄下找到配置文件,並且發現他的值為true

在上面的方法上有EnableAspectJAutoProxy註解,並傳入了proxyTargetClass=true

  1. 進入@EnableAspectJAutoProxy註解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
//引入了AspectJAutoProxyRegistrar
@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {
    boolean proxyTargetClass() default false;

    boolean exposeProxy() default false;
}
  1. 進入AspectJAutoProxyRegistrar類
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    AspectJAutoProxyRegistrar() {
    }

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //註冊了自動自動代理類
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
        AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if (enableAspectJAutoProxy != null) {
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }

            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }

    }
}
  1. 進入registerAspectJAnnotationAutoProxyCreatorIfNecessary方法裏面
 public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) {
        return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
    }

可以看到返回了一個BeanDefinition,裏面的BeanClass類型是AnnotationAwareAspectJAutoProxyCreator,這個類看名字是一個AOP的動態代理創建類,裏面沒有啥可疑的方法。在IDEA里按Ctrl+H看他的繼承結構。有一個父類AbstractAutoProxyCreator,這個類實現了BeanPostProcessor接口。這個接口是Bean的擴展接口,在bean初始化完成後會調用到他的postProcessAfterInitialization(Object bean, String beanName)方法。

  1. 方法內容如下
 public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
            Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                //如果有必要,進行包裝  
                return this.wrapIfNecessary(bean, beanName, cacheKey);
            }
        }

        return bean;
    }
    
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
        //獲取切面的方法,第9點那裡展開討論
            Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
            if (specificInterceptors != DO_NOT_PROXY) {
                this.advisedBeans.put(cacheKey, Boolean.TRUE);
                //創建動態代理
                Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
                this.proxyTypes.put(cacheKey, proxy.getClass());
                return proxy;
            } else {
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return bean;
            }
        } else {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
    }
  1. 可以看出這裏已經在開始創建動態代理了
  protected Object createProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) {
        if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
            AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory)this.beanFactory, beanName, beanClass);
        }
        //動態代理工廠
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.copyFrom(this);
        if (!proxyFactory.isProxyTargetClass()) {
            if (this.shouldProxyTargetClass(beanClass, beanName)) {
                proxyFactory.setProxyTargetClass(true);
            } else {
                this.evaluateProxyInterfaces(beanClass, proxyFactory);
            }
        }

        Advisor[] advisors = this.buildAdvisors(beanName, specificInterceptors);
        //切面那裡的方法
        proxyFactory.addAdvisors(advisors);
        proxyFactory.setTargetSource(targetSource);
        this.customizeProxyFactory(proxyFactory);
        proxyFactory.setFrozen(this.freezeProxy);
        if (this.advisorsPreFiltered()) {
            proxyFactory.setPreFiltered(true);
        }
        //獲取動態代理類
        return proxyFactory.getProxy(this.getProxyClassLoader());
    }
  1. 學過AOP的人都知道動態代理的方式有兩種,一種JDK代理,一種CGLIB動態代理。那麼Spring裏面是怎麼選擇的呢?答案就在這裏:
 public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
   // 1.config.isOptimize()是否使用優化的代理策略,目前使用與CGLIB
        // config.isProxyTargetClass() 是否目標類本身被代理而不是目標類的接口
        // hasNoUserSuppliedProxyInterfaces()是否存在代理接口

        if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
            return new JdkDynamicAopProxy(config);
        } else {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
            } else {
                //目標類不是接口或不是代理類就使用cglib代理
                return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
            }
        }
    }
  1. Cglib的代理類是CglibAopProxy、ObjenesisCglibAopProxy,JDK的代理類是JdkDynamicAopProxy。在這些類裏面對目標類進行了代理,在執行方法的時候就是執行的代理類的方法,而實現了切面編程的效果。
  2. 主線流程就是這些了,還有一個沒說的就是我們如何獲取的切面方法,@Before(“pointCut()”)這些註解又是如何生效的?再回到AbstractAutoProxyCreator的wrapIfNecessary()方法
    裏面有這句代碼:
 Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
 
 
  @Nullable
    protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
        List<Advisor> advisors = this.findEligibleAdvisors(beanClass, beanName);
        return advisors.isEmpty() ? DO_NOT_PROXY : advisors.toArray();
    }
    
    protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
        //查找候選的要切面附加的方法,這裏加進去的
        List<Advisor> candidateAdvisors = this.findCandidateAdvisors();
        List<Advisor> eligibleAdvisors = this.findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
        this.extendAdvisors(eligibleAdvisors);
        if (!eligibleAdvisors.isEmpty()) {
            eligibleAdvisors = this.sortAdvisors(eligibleAdvisors);
        }

        return eligibleAdvisors;
    }
    
    
    
  1. 他會找到Aspect類,然後遍歷裏面的方法,並獲取Pointcut,然後構造出Advisor,加入到集合List advisors里,供動態代理時使用

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

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

Hystrix微服務容錯處理及回退方法源碼分析_貨運

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

前言

在 SpringCloud 微服務項目中,我們有了 Eureka 做服務的註冊中心,進行服務的註冊與發現和服務治理。使得我們可以摒棄硬編碼式的 ip:端口 + 映射路徑 來發送請求。我們有了 Feign 作為聲明式服務調用組件,可以像調用本地服務一樣來調用遠程服務。基於 Ribbon 我們又實現了客戶端負載均衡,輕鬆的在集群環境下選取合適的服務提供者。這樣看來我們的微服務貌似很完善了。是這樣的嗎?

並非如此,想想我們在編碼過程中進行的健壯性檢查。類比一下服務與服務調用是否也應該更加健壯一些呢?我們目前的微服務在正常運行的時候是沒有問題的,但若是某個偏下游的服務提供者不可用,造成服務積壓,接連引起上游的服務消費者宕機,引法雪崩效應。是不是就顯得我們的微服務不堪一擊呢?因此我們需要一個組件來解決這樣的問題,前輩們參考生活中保險絲的原理做出了微服務中的保險絲-Hystrix熔斷器。下面讓我們來一起使用一下

聲明:本文首發於博客園,作者:后青春期的Keats;地址:https://www.cnblogs.com/keatsCoder/ 轉載請註明,謝謝!

Hystrix簡介

Hystrix主要實現了下面的功能:

  • 包裹請求:使用 HystrixCommand(或 HystrixObservableCommand) 包裹對依賴的調用邏輯。每個命令在獨立的線程中執行,使用了設計模式中的‘命令模式’
  • 跳閘機制:當某微服務的錯誤率超過一定閾值時,可以自動跳閘,停止請求該服務一段時間
  • 資源隔離:Hystrix 為每個微服務都維護了一個小型的線程池(或信號量)如果該線程池已滿,發往該依賴的請求就會被立即拒絕
  • 監控:Hystrix 可以近乎實時的監控運行指標和配置的變化,例如成功、失敗、超時和被拒絕的請求等
  • 回退機制:當請求成功、失敗、超時和被拒絕或者斷路器打開時,執行回退邏輯。回退邏輯可由開發人員自行提供
  • 自我修復:斷路器打開一段時間后,會進入‘半開’狀態,允許一個請求訪問服務提供方,如果成功。則關閉斷路器

使用 Hystrix

引入依賴

        <!-- 熔斷器 hystrix -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

在啟動類上添加 @EnableHystrix

兩種情況下的回退方法

非 Feign 調用下的回退方法

編寫回退方法
/**
 * getUserByAge 方法 Hystrix 回退方法
 * @param age
 * @return
 */
public User getUserByAgeFallBack(Integer age){
    User user = new User();
    user.setName("默認用戶");
    user.setAge(age);
    return user;
}
在客戶端的方法上聲明
@HystrixCommand(fallbackMethod = "getUserByAgeFallBack")

測試:將服務提供方的代碼打斷點。調用服務消費方,會發現返回了默認用戶

需要注意:

  1. 回退方法的返回值類型需要和原來方法返回值類型相同(否則會報 FallbackDefinitionException: Incompatible return types)
  2. 回退方法的參數列表也要和原來方法相同(否則會報 FallbackDefinitionException: fallback method wasn’t found: getUserByAgeFallBack([class java.lang.Integer]))
  3. 當我寫下第二句時,發現書中下一節介紹說可以通過在回退方法中添加第二個參數:ThrowEable 來捕獲異常,分析調用失敗的原因,我就知道我錯了。為了避免繼續得到錯誤的結論,我決定讀一讀 Hystrix 處理回退方法的源碼
加點料:Hystrix 對回退方法的封裝的源碼如下:
com.netflix.hystrix.contrib.javanica.utils.MethodProvider
public FallbackMethod find(Class<?> enclosingType, Method commandMethod, boolean extended) {
	// 首先判斷該方法的 HystrixCommand 註解上有沒有 defaultFallback / fallbackMethod 配置回退方法名稱
    if (this.canHandle(enclosingType, commandMethod)) {
    	// 調用 doFind 方法
        return this.doFind(enclosingType, commandMethod, extended);
    } else {
    	// 沒有配置的化就接着下一個判斷
        return this.next != null ? this.next.find(enclosingType, commandMethod, extended) : FallbackMethod.ABSENT;
    }
}

find 方法在用戶所請求的方法的 HystrixCommand 註解上有用 defaultFallback / fallbackMethod 配置回退方法名稱的時候,會調用 doFind 方法來尋找回退方法。該方法的參數有兩個,enclosingType 是用戶所請求的方法的類字節碼文件,commandMethod 是用戶所請求的方法

首先通過 this.getFallbackName 獲取回退方法名稱,接着通過獲取 commandMethod 的參數類型們

接着分兩種情況:

  1. 回調方法繼承於 commandMethod 且最後一個參數類型是 Throwable,則去掉回退方法參數列表中的 Throwable 類型進行匹配
  2. 回調方法不繼承於 commandMethod ,則存在兩個可能的參數類型列表: fallbackParameterTypes 和 extendedFallbackParameterTypes 前者是 commandMethod 是參數列表,後者是前者 + Throwable。然後兩個都進行匹配。接着使用 Java8 Optional API,按順序選取前者匹配到的方法 / 後者 / 空返回
private FallbackMethod doFind(Class<?> enclosingType, Method commandMethod, boolean extended) {
    String name = this.getFallbackName(enclosingType, commandMethod);
    Class<?>[] fallbackParameterTypes = null;
    if (this.isDefault()) {
        fallbackParameterTypes = new Class[0];
    } else {
        fallbackParameterTypes = commandMethod.getParameterTypes();
    }

    if (extended && fallbackParameterTypes[fallbackParameterTypes.length - 1] == Throwable.class) {
        fallbackParameterTypes = (Class[])ArrayUtils.remove(fallbackParameterTypes, fallbackParameterTypes.length - 1);
    }

    Class<?>[] extendedFallbackParameterTypes = (Class[])Arrays.copyOf(fallbackParameterTypes, fallbackParameterTypes.length + 1);
    extendedFallbackParameterTypes[fallbackParameterTypes.length] = Throwable.class;
    Optional<Method> exFallbackMethod = MethodProvider.getMethod(enclosingType, name, extendedFallbackParameterTypes);
    Optional<Method> fMethod = MethodProvider.getMethod(enclosingType, name, fallbackParameterTypes);
    Method method = (Method)exFallbackMethod.or(fMethod).orNull();
    if (method == null) {
        throw new FallbackDefinitionException("fallback method wasn't found: " + name + "(" + Arrays.toString(fallbackParameterTypes) + ")");
    } else {
        return new FallbackMethod(method, exFallbackMethod.isPresent(), this.isDefault());
    }
}

由源碼可以得到結論:回退方法要麼參數列表和原始方法相同,要麼加且僅加一個類型為 Throwable 的參數。其他的都不行

Feign 客戶端下的回退方法

  1. 設置:feign.hystrix.enabled: true

    ※回頭車貨運收費標準

    宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

  2. Feign 客戶端接口上的 @FeignClient 添加 fallback 屬性,指向回退類

  3. 回退類實現客戶端接口

# feign的配置
feign:
  hystrix:
    enabled: true # 打開 feign 的 hystrix 支持

注意回退類加上 @Component 接口,避免因為 Spring 容器找不到該類而啟動報錯

// Feign 客戶端接口上的 @FeignClient 添加 fallback 屬性,指向回退類
@FeignClient(name = "SERVICE-PROVIDER", fallback = UserServiceFeignClientFallBack.class)
public interface UserServiceFeignClient {

    @GetMapping("/api/v1/user/{age}")
    User getUser(@PathVariable("age") Integer age);

    /**
     * 用戶列表
     * @return
     */
    @GetMapping("/api/v1/users")
    List<User> getUsers();
}
// 回退類實現客戶端接口
@Component 
public class UserServiceFeignClientFallBack implements UserServiceFeignClient {
    @Override
    public User getUser(Integer age) {
        return null;
    }

    @Override
    public List<User> getUsers() {
        return null;
    }
}

當採用 Feign 客戶端來實現回退的時候,前面的捕捉異常方法就不起作用了,那我們應該如何來處理異常呢?可以使用 @FeignClient 的 fallbackFactory 屬性

@FeignClient(name = "SERVICE-PROVIDER", fallbackFactory = UserServiceFallbackFactory.class)

@Component
@Slf4j
public class UserServiceFallbackFactory implements FallbackFactory<UserServiceFeignClient> {
    @Override
    public UserServiceFeignClient create(Throwable t) {
        // 日誌最好寫在各個 fallback 方法中,而不要直接卸載 create方法中
        // 否則引用啟動時就會打印該日誌

        return new UserServiceFeignClient() {
            @Override
            public User getUser(Integer age) {
                log.info("調用User服務提供者失敗", t);
                User user = new User();
                user.setName("默認用戶");
                user.setAge(age);
                return user;
            }

            @Override
            public List<User> getUsers() {
                return null;
            }
        };
    }
}

注意: **fallback 和 fallbackFactory 屬性同時存在時,fallback 的優先級更高。因此開發中如果需要處理異常,只需配置 fallbackFactory 屬性即可 **

避免業務異常走進回退方法

在某些場景下,當發生業務異常時,我們並不想觸發 fallback。例如業務中判斷年齡 age 不能小於 1,否則拋出異常

if(age < 1){
    throw new KeatsException(ExceptionEnum.NUM_LESS_THAN_MIN);
}

這時 Hystrix 會捕捉到異常然後執行 fallback 方法,我們可以通過下面兩個方法來避免:

  1. 繼承 HystrixBadRequestException 該類繼承自 RunntimeException
  2. 在 @HystrixCommand 添加屬性 ignoreExceptions = {KeatsException.class}

為 Feign 禁用 Hystrix

只要打開 feign 的 hystrix 支持開關,feign 就會使用斷路器包裹 feign 客戶端的所有方法,但很多場景並不需要這樣。該如何禁用呢?

  • 為指定客戶端禁用。需要藉助 Feign 的自定義配置。首先添加一個自定義配置類,然後配置到 @FeignClient 的 configuration 屬性中
@Configuration
public class FeignDisableHystrixConfiguration {
    @Bean
    @Scope("prototype")
    public Feign.Builder feignBuilder(){
        return Feign.builder();
    }
}

@FeignClient(name = "SERVICE-PROVIDER", configuration = {FeignDisableHystrixConfiguration.class})
  • 全局禁用: feign.hystrix.enabled: false

本博客中所有示例代碼都已上傳至 github倉庫: https://github.com/keatsCoder/cloud-cli

參考文獻:《Spring Cloud與Docker 微服務架構實戰》 — 周立

碼字不易,如果你覺得讀完以後有收穫,不妨點個推薦讓更多的人看到吧!

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

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

【Java8新特性】關於Java8中的日期時間API,你需要掌握這些!!_網頁設計公司

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

寫在前面

Java8之前的日期和時間API,存在一些問題,比如:線程安全的問題,跨年的問題等等。這些問題都在Hava8中的日期和時間API中得到了解決,而且Java8中的日期和時間API更加強大。立志成為架構師的你,必須掌握Java8中的日期和時間API。

本地時間和時間戳

主要方法:

  • now:靜態方法,根據當前時間創建對象
  • of:靜態方法,根據指定日期/時間創建對象
  • plusDays,plusWeeks,plusMonths,plusYears:向當前LocalDate 對象添加幾天、幾周、幾個月、幾年
  • minusDays,minusWeeks,minusMonths,minusYears:從當前LocalDate 對象減去幾天、幾周、幾個月、幾年
  • plus,minus:添加或減少一個Duration 或Period
  • withDayOfMonth,withDayOfYear,withMonth,withYear:將月份天數、年份天數、月份、年份修改為指定的值並返回新的LocalDate 對象
  • getDayOfYear:獲得年份天數(1~366)
  • getDayOfWeek:獲得星期幾(返回一個DayOfWeek枚舉值)
  • getMonth:獲得月份, 返回一個Month 枚舉值
  • getMonthValue:獲得月份(1~12)
  • getYear:獲得年份
  • until:獲得兩個日期之間的Period 對象,或者指定ChronoUnits 的数字
  • isBefore,isAfter:比較兩個LocalDate
  • isLeapYear:判斷是否是閏年

使用 LocalDate、 LocalTime、 LocalDateTime

LocalDate、 LocalTime、 LocalDateTime 類的實例是不可變的對象,分別表示使用 ISO-8601日曆系統的日期、時間、日期和時間。它們提供了簡單的日期或時間,並不包含當前的時間信息。也不包含與時區相關的信息。

注: ISO-8601日曆系統是國際標準化組織制定的現代公民的日期和時間的表示法

方法 描述 示例
now() 靜態方法,根據當前時間創建對象 LocalDate localDate = LocalDate.now(); LocalTime localTime = LocalTime.now(); LocalDateTime localDateTime = LocalDateTime.now();
of() 靜態方法,根據指定日期/時間創建 對象 LocalDate localDate = LocalDate.of(2016, 10, 26); LocalTime localTime = LocalTime.of(02, 22, 56); LocalDateTime localDateTime = LocalDateTime.of(2016, 10, 26, 12, 10, 55);
plusDays, plusWeeks, plusMonths, plusYears 向當前 LocalDate 對象添加幾天、 幾周、 幾個月、 幾年
minusDays, minusWeeks, minusMonths, minusYears 從當前 LocalDate 對象減去幾天、 幾周、 幾個月、 幾年
plus, minus 添加或減少一個 Duration 或 Period
withDayOfMonth, withDayOfYear, withMonth, withYear 將月份天數、 年份天數、 月份、 年 份 修 改 為 指 定 的 值 並 返 回 新 的 LocalDate 對象
getDayOfMonth 獲得月份天數(1-31)
getDayOfYear 獲得年份天數(1-366)
getDayOfWeek 獲得星期幾(返回一個 DayOfWeek 枚舉值)
getMonth 獲得月份, 返回一個 Month 枚舉值
getMonthValue 獲得月份(1-12)
getYear 獲得年份
until 獲得兩個日期之間的 Period 對象, 或者指定 ChronoUnits 的数字
isBefore, isAfter 比較兩個 LocalDate
isLeapYear 判斷是否是閏年

示例代碼如下所示。

// 獲取當前系統時間
LocalDateTime localDateTime1 = LocalDateTime.now();
System.out.println(localDateTime1);
// 運行結果:2019-10-27T13:49:09.483

// 指定日期時間
LocalDateTime localDateTime2 = LocalDateTime.of(2019, 10, 27, 13, 45,10);
System.out.println(localDateTime2);
// 運行結果:2019-10-27T13:45:10

LocalDateTime localDateTime3 = localDateTime1
        // 加三年
        .plusYears(3)
        // 減三個月
        .minusMonths(3);
System.out.println(localDateTime3);
// 運行結果:2022-07-27T13:49:09.483

System.out.println(localDateTime1.getYear());       // 運行結果:2019
System.out.println(localDateTime1.getMonthValue()); // 運行結果:10
System.out.println(localDateTime1.getDayOfMonth()); // 運行結果:27
System.out.println(localDateTime1.getHour());       // 運行結果:13
System.out.println(localDateTime1.getMinute());     // 運行結果:52
System.out.println(localDateTime1.getSecond());     // 運行結果:6

LocalDateTime localDateTime4 = LocalDateTime.now();
System.out.println(localDateTime4);     // 2019-10-27T14:19:56.884
LocalDateTime localDateTime5 = localDateTime4.withDayOfMonth(10);
System.out.println(localDateTime5);     // 2019-10-10T14:19:56.884

Instant 時間戳

用於“時間戳”的運算。它是以Unix元年(傳統的設定為UTC時區1970年1月1日午夜時分)開始所經歷的描述進行運算 。

示例代碼如下所示。

Instant instant1 = Instant.now();    // 默認獲取UTC時區
System.out.println(instant1);
// 運行結果:2019-10-27T05:59:58.221Z

// 偏移量運算
OffsetDateTime offsetDateTime = instant1.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);
// 運行結果:2019-10-27T13:59:58.221+08:00

// 獲取時間戳
System.out.println(instant1.toEpochMilli());
// 運行結果:1572156145000

// 以Unix元年為起點,進行偏移量運算
Instant instant2 = Instant.ofEpochSecond(60);
System.out.println(instant2);
// 運行結果:1970-01-01T00:01:00Z

Duration 和 Period

Duration:用於計算兩個“時間”間隔。

Period:用於計算兩個“日期”間隔 。

Instant instant_1 = Instant.now();
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
Instant instant_2 = Instant.now();

Duration duration = Duration.between(instant_1, instant_2);
System.out.println(duration.toMillis());
// 運行結果:1000

LocalTime localTime_1 = LocalTime.now();
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
LocalTime localTime_2 = LocalTime.now();

System.out.println(Duration.between(localTime_1, localTime_2).toMillis());
// 運行結果:1000
LocalDate localDate_1 = LocalDate.of(2018,9, 9);
LocalDate localDate_2 = LocalDate.now();

Period period = Period.between(localDate_1, localDate_2);
System.out.println(period.getYears());      // 運行結果:1
System.out.println(period.getMonths());     // 運行結果:1
System.out.println(period.getDays());       // 運行結果:18

日期的操縱

TemporalAdjuster : 時間校正器。有時我們可能需要獲取例如:將日期調整到“下個周日”等操作。

TemporalAdjusters : 該類通過靜態方法提供了大量的常用 TemporalAdjuster 的實現。

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

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

例如獲取下個周日,如下所示:

LocalDate nextSunday = LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));

完整的示例代碼如下所示。

LocalDateTime localDateTime1 = LocalDateTime.now();
System.out.println(localDateTime1);     // 2019-10-27T14:19:56.884

// 獲取這個第一天的日期
System.out.println(localDateTime1.with(TemporalAdjusters.firstDayOfMonth()));            // 2019-10-01T14:22:58.574
// 獲取下個周末的日期
System.out.println(localDateTime1.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)));       // 2019-11-03T14:22:58.574

// 自定義:下一個工作日
LocalDateTime localDateTime2 = localDateTime1.with(l -> {
    LocalDateTime localDateTime = (LocalDateTime) l;
    DayOfWeek dayOfWeek =  localDateTime.getDayOfWeek();

    if (dayOfWeek.equals(DayOfWeek.FRIDAY)) {
       return localDateTime.plusDays(3);
    } else if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {
       return localDateTime.plusDays(2);
    } else {
       return localDateTime.plusDays(1);
    }
});
System.out.println(localDateTime2);
// 運行結果:2019-10-28T14:30:17.400

解析與格式化

java.time.format.DateTimeFormatter 類:該類提供了三種格式化方法:

  • 預定義的標準格式
  • 語言環境相關的格式
  • 自定義的格式

示例代碼如下所示。

DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ISO_DATE;
LocalDateTime localDateTime = LocalDateTime.now();
String strDate1 = localDateTime.format(dateTimeFormatter1);
System.out.println(strDate1);
// 運行結果:2019-10-27

// Date -> String
DateTimeFormatter dateTimeFormatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd  HH:mm:ss");
String strDate2 = dateTimeFormatter2.format(localDateTime);
System.out.println(strDate2);
// 運行結果:2019-10-27  14:36:11

// String -> Date
LocalDateTime localDateTime1 = localDateTime.parse(strDate2, dateTimeFormatter2);
System.out.println(localDateTime1);
// 運行結果:2019-10-27T14:37:39

時區的處理

Java8 中加入了對時區的支持,帶時區的時間為分別為:ZonedDate、 ZonedTime、 ZonedDateTime。

其中每個時區都對應着 ID,地區ID都為 “{區域}/{城市}”的格式,例如 : Asia/Shanghai 等。

  • ZoneId:該類中包含了所有的時區信息
  • getAvailableZoneIds() : 可以獲取所有時區時區信息
  • of(id) : 用指定的時區信息獲取 ZoneId 對象

示例代碼如下所示。

// 獲取所有的時區
Set<String> set = ZoneId.getAvailableZoneIds();
System.out.println(set);
// [Asia/Aden, America/Cuiaba, Etc/GMT+9, Etc/GMT+8, Africa/Nairobi, America/Marigot, Asia/Aqtau, Pacific/Kwajalein, America/El_Salvador, Asia/Pontianak, Africa/Cairo, Pacific/Pago_Pago, Africa/Mbabane, Asia/Kuching, Pacific/Honolulu, Pacific/Rarotonga, America/Guatemala, Australia/Hobart, Europe/London, America/Belize, America/Panama, Asia/Chungking, America/Managua, America/Indiana/Petersburg, Asia/Yerevan, Europe/Brussels, GMT, Europe/Warsaw, America/Chicago, Asia/Kashgar, Chile/Continental, Pacific/Yap, CET, Etc/GMT-1, Etc/GMT-0, Europe/Jersey, America/Tegucigalpa, Etc/GMT-5, Europe/Istanbul, America/Eirunepe, Etc/GMT-4, America/Miquelon, Etc/GMT-3, Europe/Luxembourg, Etc/GMT-2, Etc/GMT-9, America/Argentina/Catamarca, Etc/GMT-8, Etc/GMT-7, Etc/GMT-6, Europe/Zaporozhye, Canada/Yukon, Canada/Atlantic, Atlantic/St_Helena, Australia/Tasmania, Libya, Europe/Guernsey, America/Grand_Turk, US/Pacific-New, Asia/Samarkand, America/Argentina/Cordoba, Asia/Phnom_Penh, Africa/Kigali, Asia/Almaty, US/Alaska, Asia/Dubai, Europe/Isle_of_Man, America/Araguaina, Cuba, Asia/Novosibirsk, America/Argentina/Salta, Etc/GMT+3, Africa/Tunis, Etc/GMT+2, Etc/GMT+1, Pacific/Fakaofo, Africa/Tripoli, Etc/GMT+0, Israel, Africa/Banjul, Etc/GMT+7, Indian/Comoro, Etc/GMT+6, Etc/GMT+5, Etc/GMT+4, Pacific/Port_Moresby, US/Arizona, Antarctica/Syowa, Indian/Reunion, Pacific/Palau, Europe/Kaliningrad, America/Montevideo, Africa/Windhoek, Asia/Karachi, Africa/Mogadishu, Australia/Perth, Brazil/East, Etc/GMT, Asia/Chita, Pacific/Easter, Antarctica/Davis, Antarctica/McMurdo, Asia/Macao, America/Manaus, Africa/Freetown, Europe/Bucharest, Asia/Tomsk, America/Argentina/Mendoza, Asia/Macau, Europe/Malta, Mexico/BajaSur, Pacific/Tahiti, Africa/Asmera, Europe/Busingen, America/Argentina/Rio_Gallegos, Africa/Malabo, Europe/Skopje, America/Catamarca, America/Godthab, Europe/Sarajevo, Australia/ACT, GB-Eire, Africa/Lagos, America/Cordoba, Europe/Rome, Asia/Dacca, Indian/Mauritius, Pacific/Samoa, America/Regina, America/Fort_Wayne, America/Dawson_Creek, Africa/Algiers, Europe/Mariehamn, America/St_Johns, America/St_Thomas, Europe/Zurich, America/Anguilla, Asia/Dili, America/Denver, Africa/Bamako, Europe/Saratov, GB, Mexico/General, Pacific/Wallis, Europe/Gibraltar, Africa/Conakry, Africa/Lubumbashi, Asia/Istanbul, America/Havana, NZ-CHAT, Asia/Choibalsan, America/Porto_Acre, Asia/Omsk, Europe/Vaduz, US/Michigan, Asia/Dhaka, America/Barbados, Europe/Tiraspol, Atlantic/Cape_Verde, Asia/Yekaterinburg, America/Louisville, Pacific/Johnston, Pacific/Chatham, Europe/Ljubljana, America/Sao_Paulo, Asia/Jayapura, America/Curacao, Asia/Dushanbe, America/Guyana, America/Guayaquil, America/Martinique, Portugal, Europe/Berlin, Europe/Moscow, Europe/Chisinau, America/Puerto_Rico, America/Rankin_Inlet, Pacific/Ponape, Europe/Stockholm, Europe/Budapest, America/Argentina/Jujuy, Australia/Eucla, Asia/Shanghai, Universal, Europe/Zagreb, America/Port_of_Spain, Europe/Helsinki, Asia/Beirut, Asia/Tel_Aviv, Pacific/Bougainville, US/Central, Africa/Sao_Tome, Indian/Chagos, America/Cayenne, Asia/Yakutsk, Pacific/Galapagos, Australia/North, Europe/Paris, Africa/Ndjamena, Pacific/Fiji, America/Rainy_River, Indian/Maldives, Australia/Yancowinna, SystemV/AST4, Asia/Oral, America/Yellowknife, Pacific/Enderbury, America/Juneau, Australia/Victoria, America/Indiana/Vevay, Asia/Tashkent, Asia/Jakarta, Africa/Ceuta, Asia/Barnaul, America/Recife, America/Buenos_Aires, America/Noronha, America/Swift_Current, Australia/Adelaide, America/Metlakatla, Africa/Djibouti, America/Paramaribo, Europe/Simferopol, Europe/Sofia, Africa/Nouakchott, Europe/Prague, America/Indiana/Vincennes, Antarctica/Mawson, America/Kralendijk, Antarctica/Troll, Europe/Samara, Indian/Christmas, America/Antigua, Pacific/Gambier, America/Indianapolis, America/Inuvik, America/Iqaluit, Pacific/Funafuti, UTC, Antarctica/Macquarie, Canada/Pacific, America/Moncton, Africa/Gaborone, Pacific/Chuuk, Asia/Pyongyang, America/St_Vincent, Asia/Gaza, Etc/Universal, PST8PDT, Atlantic/Faeroe, Asia/Qyzylorda, Canada/Newfoundland, America/Kentucky/Louisville, America/Yakutat, Asia/Ho_Chi_Minh, Antarctica/Casey, Europe/Copenhagen, Africa/Asmara, Atlantic/Azores, Europe/Vienna, ROK, Pacific/Pitcairn, America/Mazatlan, Australia/Queensland, Pacific/Nauru, Europe/Tirane, Asia/Kolkata, SystemV/MST7, Australia/Canberra, MET, Australia/Broken_Hill, Europe/Riga, America/Dominica, Africa/Abidjan, America/Mendoza, America/Santarem, Kwajalein, America/Asuncion, Asia/Ulan_Bator, NZ, America/Boise, Australia/Currie, EST5EDT, Pacific/Guam, Pacific/Wake, Atlantic/Bermuda, America/Costa_Rica, America/Dawson, Asia/Chongqing, Eire, Europe/Amsterdam, America/Indiana/Knox, America/North_Dakota/Beulah, Africa/Accra, Atlantic/Faroe, Mexico/BajaNorte, America/Maceio, Etc/UCT, Pacific/Apia, GMT0, America/Atka, Pacific/Niue, Australia/Lord_Howe, Europe/Dublin, Pacific/Truk, MST7MDT, America/Monterrey, America/Nassau, America/Jamaica, Asia/Bishkek, America/Atikokan, Atlantic/Stanley, Australia/NSW, US/Hawaii, SystemV/CST6, Indian/Mahe, Asia/Aqtobe, America/Sitka, Asia/Vladivostok, Africa/Libreville, Africa/Maputo, Zulu, America/Kentucky/Monticello, Africa/El_Aaiun, Africa/Ouagadougou, America/Coral_Harbour, Pacific/Marquesas, Brazil/West, America/Aruba, America/North_Dakota/Center, America/Cayman, Asia/Ulaanbaatar, Asia/Baghdad, Europe/San_Marino, America/Indiana/Tell_City, America/Tijuana, Pacific/Saipan, SystemV/YST9, Africa/Douala, America/Chihuahua, America/Ojinaga, Asia/Hovd, America/Anchorage, Chile/EasterIsland, America/Halifax, Antarctica/Rothera, America/Indiana/Indianapolis, US/Mountain, Asia/Damascus, America/Argentina/San_Luis, America/Santiago, Asia/Baku, America/Argentina/Ushuaia, Atlantic/Reykjavik, Africa/Brazzaville, Africa/Porto-Novo, America/La_Paz, Antarctica/DumontDUrville, Asia/Taipei, Antarctica/South_Pole, Asia/Manila, Asia/Bangkok, Africa/Dar_es_Salaam, Poland, Atlantic/Madeira, Antarctica/Palmer, America/Thunder_Bay, Africa/Addis_Ababa, Asia/Yangon, Europe/Uzhgorod, Brazil/DeNoronha, Asia/Ashkhabad, Etc/Zulu, America/Indiana/Marengo, America/Creston, America/Punta_Arenas, America/Mexico_City, Antarctica/Vostok, Asia/Jerusalem, Europe/Andorra, US/Samoa, PRC, Asia/Vientiane, Pacific/Kiritimati, America/Matamoros, America/Blanc-Sablon, Asia/Riyadh, Iceland, Pacific/Pohnpei, Asia/Ujung_Pandang, Atlantic/South_Georgia, Europe/Lisbon, Asia/Harbin, Europe/Oslo, Asia/Novokuznetsk, CST6CDT, Atlantic/Canary, America/Knox_IN, Asia/Kuwait, SystemV/HST10, Pacific/Efate, Africa/Lome, America/Bogota, America/Menominee, America/Adak, Pacific/Norfolk, Europe/Kirov, America/Resolute, Pacific/Tarawa, Africa/Kampala, Asia/Krasnoyarsk, Greenwich, SystemV/EST5, America/Edmonton, Europe/Podgorica, Australia/South, Canada/Central, Africa/Bujumbura, America/Santo_Domingo, US/Eastern, Europe/Minsk, Pacific/Auckland, Africa/Casablanca, America/Glace_Bay, Canada/Eastern, Asia/Qatar, Europe/Kiev, Singapore, Asia/Magadan, SystemV/PST8, America/Port-au-Prince, Europe/Belfast, America/St_Barthelemy, Asia/Ashgabat, Africa/Luanda, America/Nipigon, Atlantic/Jan_Mayen, Brazil/Acre, Asia/Muscat, Asia/Bahrain, Europe/Vilnius, America/Fortaleza, Etc/GMT0, US/East-Indiana, America/Hermosillo, America/Cancun, Africa/Maseru, Pacific/Kosrae, Africa/Kinshasa, Asia/Kathmandu, Asia/Seoul, Australia/Sydney, America/Lima, Australia/LHI, America/St_Lucia, Europe/Madrid, America/Bahia_Banderas, America/Montserrat, Asia/Brunei, America/Santa_Isabel, Canada/Mountain, America/Cambridge_Bay, Asia/Colombo, Australia/West, Indian/Antananarivo, Australia/Brisbane, Indian/Mayotte, US/Indiana-Starke, Asia/Urumqi, US/Aleutian, Europe/Volgograd, America/Lower_Princes, America/Vancouver, Africa/Blantyre, America/Rio_Branco, America/Danmarkshavn, America/Detroit, America/Thule, Africa/Lusaka, Asia/Hong_Kong, Iran, America/Argentina/La_Rioja, Africa/Dakar, SystemV/CST6CDT, America/Tortola, America/Porto_Velho, Asia/Sakhalin, Etc/GMT+10, America/Scoresbysund, Asia/Kamchatka, Asia/Thimbu, Africa/Harare, Etc/GMT+12, Etc/GMT+11, Navajo, America/Nome, Europe/Tallinn, Turkey, Africa/Khartoum, Africa/Johannesburg, Africa/Bangui, Europe/Belgrade, Jamaica, Africa/Bissau, Asia/Tehran, WET, Europe/Astrakhan, Africa/Juba, America/Campo_Grande, America/Belem, Etc/Greenwich, Asia/Saigon, America/Ensenada, Pacific/Midway, America/Jujuy, Africa/Timbuktu, America/Bahia, America/Goose_Bay, America/Virgin, America/Pangnirtung, Asia/Katmandu, America/Phoenix, Africa/Niamey, America/Whitehorse, Pacific/Noumea, Asia/Tbilisi, America/Montreal, Asia/Makassar, America/Argentina/San_Juan, Hongkong, UCT, Asia/Nicosia, America/Indiana/Winamac, SystemV/MST7MDT, America/Argentina/ComodRivadavia, America/Boa_Vista, America/Grenada, Asia/Atyrau, Australia/Darwin, Asia/Khandyga, Asia/Kuala_Lumpur, Asia/Famagusta, Asia/Thimphu, Asia/Rangoon, Europe/Bratislava, Asia/Calcutta, America/Argentina/Tucuman, Asia/Kabul, Indian/Cocos, Japan, Pacific/Tongatapu, America/New_York, Etc/GMT-12, Etc/GMT-11, Etc/GMT-10, SystemV/YST9YDT, Europe/Ulyanovsk, Etc/GMT-14, Etc/GMT-13, W-SU, America/Merida, EET, America/Rosario, Canada/Saskatchewan, America/St_Kitts, Arctic/Longyearbyen, America/Fort_Nelson, America/Caracas, America/Guadeloupe, Asia/Hebron, Indian/Kerguelen, SystemV/PST8PDT, Africa/Monrovia, Asia/Ust-Nera, Egypt, Asia/Srednekolymsk, America/North_Dakota/New_Salem, Asia/Anadyr, Australia/Melbourne, Asia/Irkutsk, America/Shiprock, America/Winnipeg, Europe/Vatican, Asia/Amman, Etc/UTC, SystemV/AST4ADT, Asia/Tokyo, America/Toronto, Asia/Singapore, Australia/Lindeman, America/Los_Angeles, SystemV/EST5EDT, Pacific/Majuro, America/Argentina/Buenos_Aires, Europe/Nicosia, Pacific/Guadalcanal, Europe/Athens, US/Pacific, Europe/Monaco]

// 通過時區構建LocalDateTime
LocalDateTime localDateTime1 = LocalDateTime.now(ZoneId.of("America/El_Salvador"));
System.out.println(localDateTime1);
// 2019-10-27T00:46:21.268

// 以時區格式显示時間
LocalDateTime localDateTime2 = LocalDateTime.now();
ZonedDateTime zonedDateTime1 = localDateTime2.atZone(ZoneId.of("Africa/Nairobi"));
System.out.println(zonedDateTime1);
// 2019-10-27T14:46:21.273+03:00[Africa/Nairobi]

與傳統日期處理的轉換

寫在最後

如果覺得文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習Java8新特性。

最後,附上Java8新特性核心知識圖,祝大家在學習Java8新特性時少走彎路。

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

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

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

穩定性五件套-限流的原理和實現_租車

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

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

背景

 

最近了解到很多朋友對限流、熔斷、降級、隔離、超時重試的概念和應用場景理解的不是很到位,所以想用五篇的篇幅稍微系統的介紹一下。

 

本篇是第一篇,是限流做詳解,如果反饋好的話,我會繼續寫下面四篇。不好的話就算了,算我理解不夠,再自己總結總結。

 

限流的概念

 

有朋友問我限流和熔斷有什麼區別,我的理解很簡單。限流作用是防禦上游流量超過處理能力的手段,熔斷作用是容錯下游的快速失敗手段。

 

舉個生活中的限流例子:

 

小A最近打算找個女朋友,他拜託了很多朋友幫自己介紹,朋友們也很給力,很多姑娘都願意和小A聊一聊。小A發現時間忙不開了,他就制定了一個計劃,一天見2個。這就是限流。

 

舉個生活中的熔斷例子:

 

小A在見這些姑娘的時候,如果有的姑娘不守時,超過約定時間半小時還沒有出現,那小A就會離開。不然會耽誤見下一位姑娘,這是一種熔斷手段。另外,如果有的姑娘特別能說,聊天超過了3小時,小A也會打斷姑娘,把姑娘先送走,不然也會耽誤見下一位姑娘。這也是需要的熔斷措施。

 

限流的原理

 

不管任何編程語言的實現,目前主流的底層就是基於令牌桶算法和漏斗算法。這兩種算法達到的效果有所不同。

 

令牌桶算法

 

令牌桶算法是先有個固定容量的桶,一個任務會以固定的速率往桶里放token,請求來了會去取token。如果桶滿了,token就溢出了。多出來的token就不要了。如果請求太快,token生產速度跟不上消費速率,桶空了,有的請求取不到token,這時候就會直接返回錯誤而不繼續處理。

 

舉個例子:

 

比如小A最後找到了心儀的女朋友小C。他倆相處融洽,一起包餃子吃。小A負責擀皮,小C負責包。小A會把擀好的皮放到一塊案板上。這個案板可以放20張皮。如果皮擀多了,就放不下,這時候小A就會停下來等。如果皮擀的慢,小C沒的包,也就只能停下來。這裏的皮就相當於是token,包餃子就相當於是處理業務的請求。用圖表示如下:

 

 

 

漏斗算法

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

日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

 

漏斗算法也是先有個固定容量的桶,請求來了先經過桶,從桶里出去的速率是一定的。如果請求量讓桶滿了,多出來的請求就不處理了。如果桶是空的,新來的請求就能馬上處理。

 

事實上,各種MQ比如kafka就是典型漏斗算法。broker就是這個固定容量的桶,生產者會不斷的將數據寫到broker里,消費者是採用的拉取模式,總是以固定的速率來消費。

 

令牌桶算法和漏洞算法的比較

 

限流的實現

 

基礎實現

 

在Java中業界用的比較多的是Google出品的Guava RateLimiter和另外的一款resilience4j-ratelimiter來實現限流。原理差不多。

 

下面以RateLimiter為例進行講解。要實現一個限流總共需要用到RateLimiter的兩個方法:

 

1>RateLimiter.create() 靜態方法創建對象,初始化桶容量

 

2>acquire()或者tryAcquire()  獲取請求token,兩者使用一個即可。acquire方法是阻塞式的,用來實現漏斗算法;tryAcquire是非阻塞式的,用來實現令牌桶算法。

 

阻塞式是如果到達指定條件前一直不返回結果,通過下面的源碼可看到內部實際上是用sleep來實現的阻塞。因為所有的請求獲取權限時都會sleep固定的時間才返回,就達到了勻速的目的。

 

非阻塞是立即返回是否獲取到權限(token)。這時候請求如果獲取權限成功就處理請求,獲取權限失敗就直接返回一個自定義的快速失敗處理方式。平時請求速率小於token產生速率,桶漸漸滿了。一旦有突發流量,因為桶里有存量token,也可以直接獲取到權限,就是為什麼令牌桶算法可以應對突發流量的原理。

 

高階實現

 

上面實現里講的是工具組件,如果只使用工具組件有個問題。限流實際上需要定期進行容量評估,是一個動態的過程,如果只使用工具組件就需要每次修改代碼。當然也可以將每個值寫到一個統一配置里,比如zookeeper來進行管理。

 

如果規模大的情況下更好的一個解決方法是使用專門的平台。這個平台可以支撐更多維度的配置,比如集群維度的限流。集群維度和單機維度的區別是如果設置了一個總的閾值,系統可以根據機器資源情況自動計算出每台機器的限流情況。

 

在業界,阿里有個sentinel,有人稱為微服務哨兵。它是一套更完整的生態,除了我上面提到的功能之外,還提供了動態系統保護、熱點限流等功能。

 

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

※超省錢租車方案

商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!

漢字不能編程?只是看着有點豪橫!容易被開除!_包裝設計

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

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

作者:小傅哥
博客:https://bugstack.cn

沉澱、分享、成長,讓自己和他人都能有所收穫!

一、前言

在編程的路上你是否想過,用漢字寫一寫代碼?

最近有初學編程的小夥伴問小傅哥,漢字可以寫代碼嗎。自己英文不好,要是漢字可以寫代碼就好了。難道你要的是易語言?其實並不是,小夥伴也是學習 Java 的初學者,剛剛學習到 Spring 看着一片沒有註釋的代碼實在不好理解,要是都是漢字寫的,那不和讀作文一樣了嗎!

說道註釋,我想到大部分程序員討厭的兩件事

  1. 不喜歡寫註釋
  2. 不喜歡別人不寫註釋

其實對於學習編程來說,初學時寫寫案例,完成簡單的功能,反覆練習夯實基礎。數學和英文都還並不是你的絆腳石,因為你不需要做複雜的邏輯處理,比如算法。也不需要查閱大量的資料,比如原版的英文資料以及國內沒有翻譯的技術書籍等。所以這個時候對你來說,只是需要不斷的學,不斷的寫。並逐步強加自己的數學和英文能力。

回到我們的說的,既然你問漢字可以寫代碼嗎。其實在 java 里,原則上你可以寫漢字的屬性方法JVM虛擬機也是可以通過編譯執行的。只是這樣的代碼並不能很好的維護,甚至說亂碼了也很麻煩。再者,有人寫方言怎麼辦!

好!那麼我們接下來就使用漢字的方式來編寫一段關於 SpringAop 的案例!

二、開發環境

  1. JDK 1.8.0
  2. Spring 4.3.24.RELEASE
  3. 本篇涉及的源碼下載,可以關注公眾號bugstack蟲洞棧 獲取,並且還可以獲取更多原創案例。

三、技術實現

為了這個案例更加真實,我們模擬電影清朝韋小寶時期,太監入宮的過程。說白了也就是 SpringAOP 面向切面,的編程。

在做案例之前,我們先了解一下 AOP 的基本概念;

  1. @Aspect,定義切面的註解
  2. @Pointcut,切入點,一般會在方法上設定通配符表達式
  3. @Around,環繞,也就是你原本的方法會在這裏處理
  4. @Before,前置處理
  5. @After,後置處理

1. 定義切面

紫禁城.內務府.敬事房.臏.太監臏.凈身監管.java

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

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

@Aspect
@Component
public class 凈身監管 {

    @Pointcut("execution(public * 紫禁城.內務府.敬事房.利器庫..*.軍刺切(..))")
    public void 監管員(){

    }

    @Before("監管員()")
    public void 敬事前(){
        System.out.println("敬事前:---------準備下刀... ...");
    }

    @After("監管員()")
    public void 敬事後(){
        System.out.println("敬事後:---------切面完成... ...");
    }

    @Around("監管員()")
    public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable {

        System.out.println("待切身份:"+pjp.getArgs()[0]);
        System.out.println("執行工具:"+pjp.getSignature().getName());

        //獲得傳遞對象,並做處理
        太監臏 太監 = (太監臏) pjp.getArgs()[0];
        SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd");
        太監.set敬事日期(timeFormat.format(new Date()));

        //此處可以傳遞更改后的參數
        Object obj = pjp.proceed(new Object[]{太監});

        return obj;

    }

}
  • @Aspect,定義切面類,用於處理程序中的切面編程操作。
  • @Pointcut("execution(public * 紫禁城.內務府.敬事房.利器庫..*.軍刺切(..))"),定義切點處,對那些方法進行執行切面操作。除了這樣的操作外,還可以定義成自定義註解。那麼後續只要把某個你需要的方法上面添加這樣的自定義註解,就可以被 AOP 攔截。
  • @Before("監管員()")@After("監管員()"),記錄切面執行前後的記錄。
  • @Around("監管員()"),用於環繞方法增強,可以這裏去處理方法中的一些屬性信息,比如添加給某個字段添加時間。太監.set敬事日期(timeFormat.format(new Date()));

2. 設置切面可執行方法

紫禁城.內務府.敬事房.利器庫.切除器具.java

@Component("切除")
public class 切除器具 {

    public 太監臏 軍刺切(太監臏 太監){
        太監.set性別(宦官.太監.name());
        System.out.println("... 啊 ... ...老子被切面了!"+太監.get姓名());
        return 太監;
    }

}
  • 這裏類的方法就是上面定義的切點,Pointcut,也就是會被切面處理的方法。

3. 執行切面操作類

紫禁城.內務府.敬事房.執刀人.張三豐執刀.java

public class 張三豐執刀 {

    public static void main(String[] args) {

        ApplicationContext ctx = new ClassPathXmlApplicationContext("皇太極.xml");

        太監刑 太監行刑 = ctx.getBean("太監刑",太監刑.class);

        太監臏 太監 = new 太監臏();
        太監.set姓名("小德張");
        太監.set年齡("9");
        太監.set性別(宦官.男.name());

        太監 = 太監行刑.執行切除(太監);

        System.out.println("\r\n切除狀態:"+太監);
    }

}
  • 首先這裏定義了獲取 Spring 註解的 Application,用於我們獲取 Bean
  • 接下來定義一個對象類,主要傳遞具體參數信息交給執行切面的方法,進行操作。
  • 最後輸出結果信息,也就是我們可以看到具體被切面操作的方法。

四、測試結果

1. 執行內容

啟動方法;紫禁城.內務府.敬事房.執刀人.張三豐執刀.java

待切身份:太監臏 [姓名=小德張, 年齡=9, 性別=男, 敬事日期=]
執行工具:軍刺切
敬事前:---------準備下刀... ...
... 啊 ... ...老子被切面了!小德張
敬事後:---------切面完成... ...

切除狀態:太監臏 [姓名=小德張, 年齡=9, 性別=太監, 敬事日期=2020-05-05]

Process finished with exit code 0

2. 效果圖

五、總結

  • 漢字編程,好奇可以試試,但別真的用到項目里。本文也只是通過這樣的例子,向你展示學習過程的樂趣,建立一些學習過程的好感。

  • 最近加了很多剛入門學習編程的小夥伴,有很多小問號。比如;

    我是非常建議先跑起來,多寫代碼后再慢慢的去探究原理!

  • 最近聽到一首詩,不錯;廿四橋邊廿四風,憑欄猶憶舊江東。夕陽返照桃花渡,柳絮飛來片片紅。,白色的柳絮在夕陽桃花的映襯下就是成了片片紅。只要你敢學識淵博,就敢讓你擁有翻江倒海之力。

六、彩蛋

CodeGuide | 程序員編碼指南 Go!

本代碼庫是作者小傅哥多年從事一線互聯網 Java 開發的學習歷程技術匯總,旨在為大家提供一個清晰詳細的學習教程,側重點更傾向編寫Java核心內容。如果本倉庫能為您提供幫助,請給予支持(關注、點贊、分享)!

七、推薦閱讀

  • 用Java實現JVM虛擬機(漢字真的可以處理為關鍵字進行編碼)
  • 重學 Java 設計模式:實戰單例模式(Effective Java 作者推薦使用枚舉的方式解決單例模式)
  • 重學 Java 設計模式:實戰原型模式(多套試卷亂序題目)
  • 重學 Java 設計模式:實戰建造者模式(裝修套餐選配)
  • 重學 Java 設計模式:實戰抽象工廠模式(Redis集群使用升級)
  • 重學 Java 設計模式:實戰工廠方法模式(多種商品發獎)

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

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

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

MySql輕鬆入門系列——第一站 從源碼角度輕鬆認識mysql整體框架圖_台中搬家

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

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

一:背景

1. 講故事

最近看各大技術社區,不管是知乎,掘金,博客園,csdn基本上看不到有小夥伴分享sqlserver類的文章,看來在國內大環境下是不怎麼流行了,看樣子我再寫sqlserver是不可能再寫了,這輩子都不會寫了,只能靠技術輸出mysql維持生活這樣子。

二:了解架構圖

mysql最大的好處就是開源, 手握百萬源碼,有什麼問題搞不定呢? 這一點要比sqlserver爽多了,不用再dbcc搗來搗去。

1. 從架構圖入手

大家都知道做/裝修房子都要有一張圖紙,其實軟件也是一樣,只要有了這麼一張圖紙,大方向就定下來了,再深入到細節也不會亂了方向,然後給大家看一下我自己畫的架構圖,畫的不對請輕拍。

其實SqlServer,Oracle,MySql架構都大同小異,MySql的鮮明特點就是存儲引擎做成了插拔式,這就牛逼了,現行最常用的是InnoDB,這就讓我有了一個想法,有一套業務準備用 InMemory 模式跑一下,厲害了~~~

2. 功能點介紹

MySql其實就兩大塊,一塊是MySql Server層,一塊就是Storage Engines層。

<1> Client

不同語言的sdk遵守mysql協議就可以與mysqld進行互通。

<2> Connection/Thread Pool

MySql使用C++編寫,Connection是非常寶貴的,在初始化的時候維護一個池。

<3> SqlInterface,Parse,Optimizer,Cache

對sql處理,解析,優化,緩存等處理和過濾模塊,了解了解即可。

<4> Storage Engines

負責存儲的模塊,官方,第三方,甚至是你自己都可以自定義實現這個數據存儲,這就把生態做起來了,。

三: 源碼分析

關於怎麼去下載mysql源碼,這裏就不說了,大家自己去官網搗鼓搗鼓哈,本系列使用經典的 mysql 5.7.14版本。

1. 了解mysql是如何啟動監聽的

手握百萬行源碼,怎麼找入口函數呢??? ,其實很簡單,在mysqld進程上生成一個dump文件,然後看它的託管堆不就好啦。。。

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

從圖中可以看到,入口函數就是 mysqld!mysqld_main+0x227 中的 mysqld_main, 接下來就可以在源碼中全文檢索下。

<1> mysqld_main 入口函數 => sql/main.cc


extern int mysqld_main(int argc, char **argv);

int main(int argc, char **argv)
{
  return mysqld_main(argc, argv);
}

這裏大家可以用visualstudio打開C++源碼,使用查看定義功能,非常好用。

<2> 創建監聽


int mysqld_main(int argc, char **argv)
{
    //創建服務監聽線程
    handle_connections_sockets();
}

void handle_connections_sockets()
{
     //監聽連接
     new_sock= mysql_socket_accept(key_socket_client_connection, sock,
                                    (struct sockaddr *)(&cAddr), &length);

    if (mysql_socket_getfd(sock) == mysql_socket_getfd(unix_sock))
      thd->security_ctx->set_host((char*) my_localhost);

    //創建連接
    create_new_thread(thd);
}

//創建新線程處理處理用戶連接
static void create_new_thread(THD *thd){
   
   thd->thread_id= thd->variables.pseudo_thread_id= thread_id++;
   
   //線程進了線程調度器
   MYSQL_CALLBACK(thread_scheduler, add_connection, (thd));   
}

至此mysql就開啟了一個線程對 3306 端口進行監控,等待客戶端請求觸發 add_connection 回調。

2. 理解mysql是如何處理sql請求

這裏我以Insert操作為例稍微解剖下處理流程:

當用戶有請求sql過來之後,就會觸發 thread_scheduler的回調函數add_connection


static scheduler_functions one_thread_per_connection_scheduler_functions=
{
  0,                                     // max_threads
  NULL,                                  // init
  init_new_connection_handler_thread,    // init_new_connection_thread
  create_thread_to_handle_connection,    // add_connection
  NULL,                                  // thd_wait_begin
  NULL,                                  // thd_wait_end
  NULL,                                  // post_kill_notification
  one_thread_per_connection_end,         // end_thread
  NULL,                                  // end
};

scheduler_functions 中可以看到,add_connection 對應了 create_thread_to_handle_connection,也就是請求來了會觸發這個函數,從名字也可以看出,用一個線程處理一個用戶連接。

<1> 客戶端請求被 create_thread_to_handle_connection 接管及調用棧追蹤


void create_thread_to_handle_connection(THD *thd)
{
     if ((error= mysql_thread_create(key_thread_one_connection, &thd->real_id, &connection_attrib,
                                     handle_one_connection,(void*) thd))){}
}
//觸發回調函數  handle_one_connection
pthread_handler_t handle_one_connection(void *arg)
{
     do_handle_one_connection(thd);
}
//繼續處理
void do_handle_one_connection(THD *thd_arg){
    while (thd_is_connection_alive(thd))
    {
      mysql_audit_release(thd);
      if (do_command(thd))  break;  //這裏的 do_command 繼續處理
    }
}
//繼續分發
bool do_command(THD *thd)
{
    return_value= dispatch_command(command, thd, packet+1, (uint) (packet_length-1));
}
bool dispatch_command(enum enum_server_command command, THD *thd, char* packet, uint packet_length)
{
      switch (command) {
         case COM_INIT_DB: ....  break;
         ...
         case COM_QUERY:   //查詢語句:  insert xxxx
             mysql_parse(thd, thd->query(), thd->query_length(), &parser_state);  //sql解析
           break;
      }
}
//sql解析模塊
void mysql_parse(THD *thd, char *rawbuf, uint length, Parser_state *parser_state)
{
      error= mysql_execute_command(thd);
}

<2> 到這裏它的Parse,Optimizer,Cache都追完了,接下來看sql的CURD類型,繼續追。。。


//繼續執行
int mysql_execute_command(THD *thd)
{
  switch (lex->sql_command) 
  {
      case SQLCOM_SELECT:  res= execute_sqlcom_select(thd, all_tables);  break;

      //這個 insert 就是我要追的
      case SQLCOM_INSERT:   res= mysql_insert(thd, all_tables, lex->field_list, lex->many_values,
		                                      lex->update_list, lex->value_list,
                                              lex->duplicates, lex->ignore);
  }
}
//insert插入操作處理
bool mysql_insert(THD *thd,TABLE_LIST *table_list,List<Item> &fields, List<List_item> &values_list,
                  List<Item> &update_fields, List<Item> &update_values, 
                  enum_duplicates duplic, bool ignore)
{
      while ((values= its++))
      {
           error= write_record(thd, table, &info, &update);
      }
}
//寫入記錄
int write_record(THD *thd, TABLE *table, COPY_INFO *info, COPY_INFO *update)
{
    if (duplicate_handling == DUP_REPLACE || duplicate_handling == DUP_UPDATE)
    {
         // ha_write_row  重點是這個函數
         while ((error=table->file->ha_write_row(table->record[0])))
         {
             ....
         }
    }
}

可以看到,調用鏈還是挺深的,追到 ha_write_row 方法基本上算是追到頭了,再往下的話就是 MySql ServerStorage Engine提供的接口實現了,不信的話繼續看唄。。。

<3> 繼續挖 ha_write_row


int handler::ha_write_row(uchar *buf)
{
    MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_WRITE_ROW, MAX_KEY, 0,{ error= write_row(buf); })
}

//這是一個虛方法
virtual int write_row(uchar *buf __attribute__((unused)))
{
    return HA_ERR_WRONG_COMMAND;
}

看到沒有,write_row是個虛方法,也就是給底層方法實現的,在這裏就是給各大Storage Engines的哈。

3. 調用鏈圖

這麼多方法,看起來有點懵懵的吧,我來畫一張圖,幫助大家理解下這個調用堆棧。

三:總結

大家一定要熟讀架構圖,有了架構圖從源碼中找信息就方便多了,總之學習mysql成就感還是滿滿的。

如您有更多問題與我互動,掃描下方進來吧~

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

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

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

【asp.net core 系列】2 控制器與路由的恩怨情仇_台中搬家公司

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

0. 前言

在上一篇文章中,我們初步介紹了asp.net core,以及如何創建一個mvc項目。從這一篇開始,我將為大家展示asp.net core 的各種內容,並且嘗試帶領大家來挖掘其中的內在邏輯。

當然,那是以後的事情。這一篇將通過自定義一個控制器來為大家介紹asp.net core mvc 中控制器和路由的相關知識。

1. 控制器

先在Controllers目錄下添加一個類,名叫:

public class DemoController
{
    public string Index()
    {
        return "你好";
    }
}

訪問地址:

http://localhost:5006/demo/index

如果不出意外的話,你應該能看到網頁上的“你好”兩個字。

再新建一個類:

using Microsoft.AspNetCore.Mvc;
public class NoContrl : Controller
{
    public IActionResult Index()
    {
        return Content("Test");
    }
}

結合兩個不常規的控制器類,讓我們初窺asp.net core MVC是如何識別控制器的。這正是我之前說的,約定優於配置最好的體現。這個哲學最早也是為MVC提出來的,後來被.net framework引申到各個方面。

asp.net core mvc識別控制器,會在項目中發現 以Controller結尾的公開類或者繼承自Controller的公開類,並將這些類標記為控制器。當接到用戶或者界面轉交的請求時,程序從請求路徑中解析出控制器名稱,然後尋找 <控制器>Controller 或者 <控制器> : Controller 的類。

在默認情況下,一個訪問URL會在程序中解析成如下格式:

http://<HOST>:<PORT>/<Controller>/<Action>[其他參數]

在上文中,我們知道了控制器的解析規則,那麼現在看一下Action的解析規則:

在DemoController中添加如下方法:

public int TestInt()
{
    return 10;
}

public object TestObject()
{
    return new
    {
        Name = "TestObject",
        Age = 1
    };
}

public string TestPublic()
{
    return "成功訪問 TestPublic";
}

    protected string TestProtect()
{
    return "成功訪問 TestProtect";
}

private string TestPrivate()
{
    return "成功訪問 TestPrivate";
}

重新啟動,后依次訪問如下地址:

http://localhost:5006/Demo/TestInt
http://localhost:5006/Demo/TestObject
http://localhost:5006/Demo/TestPublic
http://localhost:5006/Demo/TestProtect
http://localhost:5006/Demo/TestPrivate

然後可以看到,TestInt、TestObject以及TestPublic均能正常訪問,但TestProtect和TestPrivate都提示找不到網頁或無法訪問。

可以看到,對於程序而言,Action就是控制器類里的公開類方法,與方法的返回值無關。也就是說,程序會找到 XXXController 或者名為XXX但繼承了Controller的類作為XXX的控制器,然後繼續在這個類里尋找到Action,如果沒有找到就會返回404的請求。

2. 路由

在第一節中,我們介紹了一下asp.net core mvc如何尋找控制器和Action,那這一節將介紹程序如何從請求鏈接中解析出控制器和Action的名稱,也就是路由映射。

路由(Routing)負責匹配傳入的HTTP請求,然後將這些請求發送給應用的可執行終結點。終結點是應用的可執行請求處理代碼單元,也就是我們控制器里的方法(Action)。

2.1 路由的配置

對於所有的asp.net core模板都包括生成在代碼中的路由。通常,我們要求路由在Startup.Configure方法中進行配置。

注意,Startup類里有且只有一個Configure方法,不能出現其重載版本。

該方法的聲明一般情況如下:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env);

如果想要設置路由,需要先註明項目啟用路由:

※推薦台中搬家公司優質服務,可到府估價

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

app.UseRouting();

然後使用如下方法配置路由:

app.UseEndpoints(endpoints =>
{
    // 配置路由
});

通常對於mvc項目而言,我們一般使用如下方式配置路由:

endpoints.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

這行代碼的意思是:創建一個名字為 default 的映射控制器的路由,映射規則為 {controller}/{action}/{id?},也就是第一個為控制器,第二個為Action,第三個是ID,其中ID可以不存在,當Action無法從請求地址中解析出來時默認為Index,控制器默認為Home。

通過這個解析,我們可以得知 我們之前訪問的

http://localhost:5000/

是哪個控制器里的什麼方法來處理了——HomeController.Index。

那麼我們修改一下HomeController.Index來驗證一下,我們理解是否有誤:

public IActionResult Index()
{
    return View();
}
//  修改為
public IActionResult Index()
{
    return Content("測試");
}

重新運行程序,訪問

http://localhost:5000/

然後看到頁面出現:測試字樣,可以看到路由系統自動為我們補全了控制器名和action名。如果方法中出現參數,則自動按照參數名1=值1&參數名2=值2這種形式查看參數。Id為特殊的,會自動按照目錄其映射。所以:

http://localhost:5000/控制器1/方法1/id值
http://localhost:5000/控制器1/方法1?id=id值

是一個請求鏈接。

2.2 添加路由配置

那麼,我們回過頭來看一下聲明路由的方法:

public static ControllerActionEndpointConventionBuilder MapControllerRoute(this IEndpointRouteBuilder endpoints, string name, string pattern, object defaults = null, object constraints = null, object dataTokens = null);

默認情況下,我們不會設置 defaults、constraints、dataTokens,這三個參數都有含義,這裏不對后兩個做介紹,簡單介紹一下第一個:

在路由配置的方法里,添加以下內容:

endpoints.MapControllerRoute(
                    name : "test",
                    pattern: "DemoTest/{action=Index}/{id?}",
                    defaults : new 
                    {
                        Controller = "Demo",

                    });

至此,我們沒有創建名為DemoTest的控制器,但是我們在訪問:

http://localhost:5006/DemoTest

仍然能得到響應,而且控制器被解析為Demo。

這就是defaults的意義,路由在解析的時候,系統會把defaults中的值自動填充到路由連接中沒有設置的值里。

當我們設置多個路由的時候,路由系統會優先嘗試匹配最容易解析的配置。比如說,當我們訪問:

http://localhost:5000/DemoTest/

的時候,路由系統會優先從名為test的配置表中解析,只有當無法從這裏找到時才會從其他路由中解析。

3. 總結

這一篇我們簡單介紹了控制器與路由映射,可以訪問我們自己添加的路由。在開發中,通常情況下,創建的控制器都是以Controller結尾並繼承Controller類。這是因為Controller類有很多有用的屬性和方法供我們使用,以Controller結尾是為了統一規則,可以讓我們一眼看出哪些是控制器。

更多內容煩請關注我的博客《高先生小屋》

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」