組件設計 —— 重新認識受控與非受控組件

重新定義受控與非受控組件的邊界

對非受控組件與受控組件作了如圖中下劃線的邊界定義。一經推敲, 該定義是缺乏了些完整性嚴謹性的, 比如針對非表單組件(彈框、輪播圖)如何劃分受控與非受控的邊界? 又比如非受控組件是否真的如文案上所說的數據的展示與變更都由 dom 自身接管呢?

在非受控組件中, 通常業務調用方只需傳入一個初始默認值便可使用該組件。以 Input 組件為例:

// 組件提供方
function Input({ defaultValue }) {
  return <input defaultValue={defaultValue} />
}

// 調用方
function Demo() {
  return <Input defaultValue={1} />
}

在受控組件中, 數值的展示與變更則分別由組件的 statesetState 接管。同樣以 Input 組件為例:

// 組件提供方
function Input() {
  const [value, setValue] = React.useState(1)
  return <input value={value} onChange={e => setValue(e.target.value)} />
}

// 調用方
function Demo() {
  return <Input />
}

有意思的一個問題來了, Input 組件到底是受控的還是非受控的? 我們甚至還可以對代碼稍加改動成 <Input defaultValue={1} /> 的最初調用方式:

// 組件提供方
function Input({ defaultValue }) {
  const [value, setValue] = React.useState(defaultValue)
  return <input value={value} onChange={e => setValue(e.target.value)} />
}

// 調用方
function Demo() {
  return <Input defaultValue={1} />
}

儘管此時 Input 組件本身是一個受控組件, 但與之相對的調用方失去了更改 Input 組件值的控制權, 所以對調用方而言, Input 組件是一個非受控組件。值得一提的是, 以非受控組件的使用方式去調用受控組件是一種反模式, 在下文中會分析其中的弊端。

如何做到不管對於組件提供方還是調用方 Input 組件都為受控組件呢? 提供方讓出控制權即可, 調整代碼如下:

// 組件提供方
function Input({ value, onChange }) {
  return <input value={value} onChange={onChange} />
}

// 調用方
function Demo() {
  const [value, setValue] = React.useState(1)
  return <Input value={value} onChange={e => setValue(e.target.value)} />
}

經過上述代碼的推演后, 概括如下: 受控以及非受控組件的邊界劃分取決於當前組件對於子組件值的變更是否擁有控制權。如若有則該子組件是當前組件的受控組件; 如若沒有則該子組件是當前組件的非受控組件。

職能範圍

基於調用方對於受控組件擁有控制權這一認知, 因此受控組件相較非受控組件能賦予調用方更多的定製化職能。這一思路與軟件開發中的有異曲同工之妙, 同時讓筆者受益匪淺的 也是類似的思想。

藉助受控組件的賦能, 以 Input 組件為例, 比如調用方可以更為自由地對值進行校驗限制, 又比如在值發生變更時執行一些額外邏輯。

// 組件提供方
function Input({ value, onChange }) {
  return <input value={value} onChange={onChange} />
}

// 調用方
function Demo() {
  const [value, setValue] = React.useState(1)
  return <Input value={value} onChange={e =>
    // 只支持數值的變更
    if (/\D/.test(e.target.value)) return
    setValue(e.target.value)}
  />
}

因此綜合基礎組件擴展性通用性的考慮, 受控組件的職能相較非受控組件更加寬泛, 建議優先使用受控組件來構建基礎組件。

反模式 —— 以非受控組件的使用方式調用受控組件

首先何謂反模式? 筆者將其總結為增大隱性 bug 出現概率的模式, 該模式是最佳實踐的對立經驗。如若使用了反模式就不得不花更多的精力去避免潛在 bug。官網對反模式也有很好的。

緣何上文提到以非受控組件的使用方式去調用受控組件是一種反模式? 觀察 Input 組件的第一行代碼, 其將 defaultValue 賦值給 value, 這種將 props 賦值給 state 的賦值行為在一定程度上會增加某些隱性 bug 的出現概率。

比如在切換導航欄的場景中, 恰巧兩個導航中傳進組件的 defaultValue 是相同的值, 在導航切換的過程中便會將導航一中的 Input 的狀態值帶到導航二中, 這顯然會讓使用方感到困惑。

// 組件提供方
function Input({ defaultValue }) {
  // 反模式
  const [value, setValue] = React.useState(defaultValue);
  React.useEffect(() => {
    setValue(defaultValue);
  }, [defaultValue]);
  return <input value={value} onChange={e => setValue(e.target.value)} />;
}

// 調用方
function Demo({ defaultValue }) {
  return <Input defaultValue={defaultValue} />;
}

function App() {
  const [tab, setTab] = React.useState(1);
  return (
    <>
      {tab === 1 ? <Demo defaultValue={1} /> : <Demo defaultValue={1} />}
      <button onClick={() => (tab === 1 ? setTab(2) : setTab(1))}>
        切換 Tab
      </button>
    </>
  );
}

如何避免使用該反模式同時有效解決問題呢? 官方提供了兩種較為優質的解法, 將其留給大家作為思考。

  1. 方法一: (更為推薦)
  2. 方法二:

歡迎關注

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

SpringBoot 配置文件與依賴庫分離打包配置

一、應用場景

一般情況下我們對springboot應用打包時使用springboot的maven插件spring-boot-maven-plugin的maven進行打包,打包完成得到一個fatjar,fatjar的優點是可以直接運行,缺點是體積太大,不利於傳輸,springboot應用打出來的fatjar體積少則幾十M,多則上百M,在往服務器部署傳輸時十分不便,可能只改了某個類文件,都需要重新將整個fatjar重新傳輸一次,特別是走公網傳輸的時候,可能上傳速度只有幾百甚至幾十KB,而整個fatjar中真正我們項目的代碼文件可能也就幾百KB或幾兆的大小,所以有必要將fatjar中的依賴庫與我們項目的class進行分離打包,這樣每次更換項目class就方便很多,而將配置文件也分離出來的原因在於我們可能經常需要更改配置文件的內容,如果放在fatjar中這樣修改是非常不方便的,所以也需要將配置文件也分離出來。

 >  fatjar 即將項目需要的所有依賴庫及配置文件等打進一個jar或war,該文件可直接運行

 

二、配置

2.1 POM配置

下面對pom.xml進行配置,來實現分離打包,配置如下

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>chenyb</groupId>
    <artifactId>demo</artifactId>
    <version>v1.2-release</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- springboot 打包插件 -->
            <!--
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.xx.xx</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            -->

            <!-- maven 打包插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <!-- MANIFEST.MF 中 Class-Path 加入前綴 -->
                            <classpathPrefix>lib/</classpathPrefix>
                            <!-- jar包不包含唯一版本標識 -->
                            <useUniqueVersions>false</useUniqueVersions>
                            <!-- 指定入口類 -->
                            <mainClass>cn.test.DemoApplication</mainClass>
                        </manifest>
                    </archive>
                    <outputDirectory>${project.build.directory}</outputDirectory>
                </configuration>
            </plugin>

            <!-- 拷貝依賴 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
                            <overWriteReleases>true</overWriteReleases>
                            <overWriteSnapshots>true</overWriteSnapshots>
                            <overWriteIfNewer>true</overWriteIfNewer>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>

</project>

關鍵配置說明:

(1) 去掉了spring-boot-maven-plugin打包插件

(2) 添加 maven-jar-plugin (maven標準打包插件)

(3) maven-dependency-plugin(依賴拷貝插件,主要用於將maven依賴庫拷貝出來)

插件具體的配置,pom.xml中已添加備註說明

 

2.2 打包

執行maven package 命令進行打包,得到的結果如下

 將 lib目錄 及 項目jar 文件拷貝到同一目錄下,我為了測試方便,先全部拷貝到桌面上,(放置服務器上時也需保證在同一目錄下)

 打開demo-v1.2-release可以看到,並沒有將依賴jar打進來,大小隻有不到4KB

 

2.3 config目錄創建

以上做完還還需要將項目配置文件拷貝出來,在與jar包平級目錄建立config目錄,將項目中的application.properties或yaml文件拷貝進來

  config 下的文件

經過以上步驟,全部配置完畢,下面進行一下簡單的測試

 

三、測試

 為了保證加載的是外部config目錄的配置文件,我將application-test.yaml中的server.port改為8085, 打開命令行輸入

C:\Users\Administrator\Desktop>java -jar -Dspring.profiles.active=dev -Dspring.location.config=config/ C:\Users\Administrator\Desktop\demo-v1.2-release.jar

回車運行,能正常啟動說明外部依賴可以正常加載進來

 可以看到啟動完成后tomcat監聽端口為8085,說明外部配置加載成功。

PS : 如果外部配置文件加載失敗,會使用項目jar中的配置文件,如下圖,也就是啟動後會是8080端口

application-dev.yaml中配置的端口是8080

 

而我已將外部config目錄下application-dev.yaml中端口做了修改,使用外部配置文件啟動後會是8085端口

 

四、一點小坑

默認情況下window命令行打開后,是在當前用戶目錄下,像這樣

 而我的config、lib、項目jar拷貝在桌面上,實際路徑是

一開始我在  C:\Users\Administrator> 直接執行下方命令,一直加載不到配置文件

java -jar -Dspring.profiles.active=dev -Dspring.location.config=config/ C:\Users\Administrator\Desktop\demo-v1.2-release.jar

原因就在於程序與配置文件不在同一目錄下,我在C:\Users\Administrator>運行啟動命令,而程序實際目錄在 C:\Users\Administrator\Desktop> 下,因為程序使用了絕對路徑,可以找到文件,所以程序的實際運行路徑為C:\Users\Administrator\Desktop,而我使用的配置 spring.location.config=config/ 使用的是相對路徑,,這個相對路徑又是相對 C:\Users\Administrator> 目錄,所以就會出現找不到配置文件的情況。

 

解決辦法一:

命令行切換到 C:\Users\Administrator\Desktop 目錄,即項目jar所在目錄,運行 java -jar 命令

 

解決辦法二:

將config拷貝到C:/Users/Administrator下,保證C:/Users/Administrator相對路徑下存在config目錄及配置文件(該方法可解決問題,但是不建議)

 

解決方法三:

spring.location.config=config/ 處使用絕對路徑,即C:/Users/Administrator/Desktop/config/ 

 

所以很重要一點,一定保證 執行命令 的目錄 與項目jar、lib、config都在同一目錄下。

 

五、完整demo地址

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

dbstructsync -mysql表、字段、索引差異解析工具(原創)

最近寫了一個工具(比較兩套測試環境mysql數據庫中表、表字段、索引的差異,基於python)通過文章簡單介紹下工具的相關內容

  1. 工具名稱
  2. 主要功能
  3. 具體使用方法
  4. 部分實現代碼
  5. 後續

一、工具名稱:

dbstructsync (python庫)

二、主要功能:

比較兩套環境中mysql指定庫中表、表字段及索引的差異,返回同步的sql ,裡面包含建表,修改索引,修改字段的sql .

A環境的數據庫db 作為sourcedb, B環境的數據庫db targetdb ,程序邏輯比較的是 sourcedb 與targetdb 的差異,然後返回一個list的數據類型 

list中包含新建表sql,修改、增加字段sql, 刪除、新增索引sql 

現在總共有3個版本,0.0.1 和0.0.2 存在一定的bug, 所以請使用最新的0.0.3版本

 

 

其他說明:由於是剛完成不久的程序,所以暫時不對最終結果sql進行執行,避免對使用過程中產生不好的影響,這個版本大家可以通過python 自行選擇需要執行哪些操作;隨着之後程序的逐步深入修改和演變,會將執行sql這一步也都加進去

同時也會優化使用方式,讓使用這個工具的小夥伴更容易操作

三、具體使用方法:

1、 pip install  -i https://pypi.python.org/pypi  dbstructsync   

在代碼里引入使用,

from DbStructSync import cli 

result=cli.db_sync(sourcedb, targetdb) 
#sourcedb,targetdb是兩個dict的參數,具體參數看下面
# 這裏得到的    result = ['use 庫;', 
#             'CREATE TABLE `test_async` (\n  `test_async` #varchar(30) NOT NULL,\n  `aa` varchar(400) DEFAULT NULL,\n  #PRIMARY KEY (`test_async`)\n) ENGINE=InnoDB DEFAULT #CHARSET=utf8;',
#             'drop index `index_chaxx` on chanxx_auto_puxx_conf;',
#             'create index `index_chaxx` on #chanxx_auto_puxx_conf(`channel_nxx`,`channel_prxx`) USING #BTREE;']
#result 中包含  use 庫;
#              如果有少的表,會有 create table的數據; 如果有不同的索引,會#存在drop index 和create index的sql;
#              如果有不同的字段,會有alter table的sql ;
#只需要對這個結果,再通過pymysql的一些數據庫操作就可以保證 sourcedb #的內容與taragetdb一致。

2、同時還支持命令行操作,代碼寫入到x.py代碼中

result = cli.db_sync_commandline()
python x.py --source  host=10.1.1.x,port=3306,user=root,passwd=root,db=investx --target host=10.1.1.x,port=3306,user=root,passwd=root,db=investx

 

命令行中  –source  key=value;key2=value2;key3=value3  –target key=value;key2=value2;key3=value3

–source, –target 是兩給必輸的參數,後續的值會作為一個dict類型傳入程序。 –source是源庫的信息, –target是目標庫的信息

還包括其他幾個命令參數 –only-index , –only-fields ; –only-index 只比較索引差異, –only-fields 只比較字段差異, 非必填,默認都為False 

四、部分實現代碼:

 

def diff_tables(sourcetable, targettable):
    '''

    :param sourcetable:  源數據庫的表名列表
    :param targettable:  目的數據庫的表名列表
    :return: 返回dict,包含三種結果,源庫多的表,目標庫多的表,相同的表
    '''
    logger.info('開始比較兩個庫中表的差異,源庫表{},目標庫表{}'.format(sourcetable, targettable))
    table_result={}
    if not isinstance(sourcetable, list) or not isinstance(targettable, list):
         raise  TypeError('sourcetable , targettable的類型不是list')
    source_diff = set(sourcetable) - set(targettable)
    target_diff = set(targettable) - set(sourcetable)
    same_tables = set(sourcetable)& set(targettable)
    table_result['source'] = source_diff
    table_result['target'] = target_diff
    table_result['same'] = same_tables
    logger.info('兩個庫中表的差異結果{}'.format(table_result))
    return  table_result
 

def  diff_indexs_fields(sourcesql, targetsql, type=1):
    '''
    :param sourcesql: 源數據庫表的創建sql
    :param targetsql: 目標數據表的創建sql
    :return: 記錄下索引不一致的地方
    '''
    result = {}
    logger.info('解析語句中的索引字段,並進行比較索引')
    sourcesql = parse_str(sourcesql)  # 從括號中提取需要的內容
    #logger.info('從括號中提取出來的信息數據{}'.format(sourcesql))
    sourcesql = lists2str(sourcesql)  #將list轉換為str,並對數據的空格數據進行處理
    logger.info('解析完的數據的信息{}'.format(sourcesql))
    sourcesql = sourcesql.split('\n') #將str按照'\\n'進行分割
    logger.info('解析完數據之後的信息{}'.format(sourcesql))
    targetsql = parse_str(targetsql)
    targetsql = lists2str(targetsql)
    targetsql = targetsql.split('\n')
    if type ==1:
        source_index = parse_fields(sourcesql,type)
        target_index = parse_fields(targetsql,type)

        result= compare_indexs_field(source_index, target_index, type)
    elif type ==2:
        source_field_sql = parse_fields(sourcesql, type)
        target_field_sql = parse_fields(targetsql, type)
        result = compare_indexs_field(source_field_sql, target_field_sql, type)
    return  result

def dict2sql(dict_str):
    '''
    將解析完成的數據轉換為對應的可執行sql
    :param dict_str:
    :return:
    '''
    dict_str = copy.deepcopy(dict_str) # 做一個深度copy,可以放心的進行相關數據處理

    if not isinstance(dict_str, dict):
        raise  TypeError('調用方法{}參數不是dict類型,請確認'.format('dict2sql'))
    #獲取db名字
    for key ,value in dict_str.items():
        dbname = key
        logger.info('數據庫名{}'.format(dbname))
        for table, table_desc  in  value.get('source').items():
               if table =='create_table':
                   #create_table_sql = lists2str(table_desc)
                   dict_str[dbname]['source'][table] = table_desc
                   #其他的都是table的名字
                   logger.info('數據庫的修改語句:{}'.format(table_desc))
               else:
                  logger.info('對於索引和字段的解析原始數據{}'.format(table_desc))
                  if table_desc.get('index'):
                      create_index_sql_lists=[]
                      #create_index_sql_lists.append('use {};'.format(dbname))
                      index_lists= (table_desc.get('index'))
                      result_index= parse_comma_split(str(index_lists)[1:-1])
                      for i in result_index:
                           if i.strip().startswith('\'KEY'):
                               #print(i.strip())
                               index_values = parse_space_split(i.strip())
                               drop_index_sql= 'drop index {} on {}'.format(index_values[1],table )
                               if len(index_values)<=3:
                                  create_index_sql='create index {} on {}{} '.format(index_values[1], table, index_values[2])
                               else:
                                  create_index_sql='create index {} on {}{} {}'.format(index_values[1], table, index_values[2], ' '.join(index_values[3:]))
                               create_index_sql_lists.append(drop_index_sql)
                               create_index_sql_lists.append(create_index_sql)

                           if i.strip().startswith('\'UNIQUE KEY'):
                                 index_values = parse_space_split(i.strip())
                                 drop_index_sql = 'drop index {} on {}'.format(index_values[2], table)
                                 if len(index_values) <= 4:
                                     create_index_sql = 'create unique index {} on {}{} '.format(index_values[2], table,
                                                                                          index_values[3])
                                 else:
                                     create_index_sql = 'create unique index {} on {}{} {}'.format(index_values[2], table,
                                                                                               index_values[3],
                                                                                               ' '.join(index_values[4:]),
                                                                                               )
                                 create_index_sql_lists.append(drop_index_sql)
                                 create_index_sql_lists.append(create_index_sql)
                      logger.info('表{}解析出來的索引的修改sql{}'.format(table, create_index_sql_lists))
                      dict_str[dbname]['source'][table]['index'] = create_index_sql_lists
                  if table_desc.get('fields'):
                      create_fields_sql_lists=[]
                      #create_fields_sql_lists.append('use {};'.format(dbname))
                      modify_field_sqls = table_desc.get('fields').get('modify',None)
                      create_field_sqls=table_desc.get('fields').get('lose',None)

                      if modify_field_sqls:
                           for modify_field_sql in modify_field_sqls:
                               sql_indexs = parse_space_split(str(modify_field_sql)[0:-1])
                               #print(sql_indexs)
                               alter_fields_sql='alter table {} modify column {} {} {}'.format(table, sql_indexs[0],sql_indexs[1],' '.join(sql_indexs[2:]))
                               create_fields_sql_lists.append(alter_fields_sql)
                      if create_field_sqls:
                           for  create_field_sql in create_field_sqls:
                                sql_indexs = parse_space_split(str(create_field_sql)[0:-1])
                                create_fields_sql='alter table {} add column {} {}'.format(table, sql_indexs[0],' '.join(sql_indexs[2:]))
                                create_fields_sql_lists.append(create_fields_sql)
                      logger.info('表{}解析出來的字段的修改sql{}'.format(table,create_fields_sql_lists))
                      dict_str[dbname]['source'][table]['fields'] = create_fields_sql_lists

    return  dict_str  # 返回給一個全部是可執行sql的dict

五、後續:

1、對使用過程中遇到對bug進行修復

2、對代碼進行優化

3、增加其他相關功能,讓工具越來越好用

4、希望使用的小夥伴多提意見,未來成為一個好用的小工具

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

車拚鋰電池!鹼性電池新突破 可重複充電、以鋁代鋅

 

鹼性電池售價便宜、也比鋰電池更不容易起火燃燒,唯一缺點是無法重複充電使用。不過,新創公司Ionic Materials即將在本週四(8月3日)發表一項突破性的新發現,讓固態的鹼性電池成為鋰電池等高儲電科技的替代品,直接應用到PC、智慧型手機或電動車。

紐約時報1日報導(),Ionic發明的新技術,可讓鹼性電池重複充電,其推出的原型,最多已能充電400次。Ionic創辦人兼材料科學家Mike Zimmerman說,該公司開發的鹼性電池,目前雖比市面上的鋰電池還要重,但其成本優勢和更優秀的儲電能力,將彌補這項缺點。

不只如此,鋰電池的關鍵材料是鈷,不但毒害環境,非洲還有不少礦場違法使用童工採鈷,令人憂心。相較之下,鹼性電池使用的是蘊藏量相對豐富的鋅和錳。

另外,Ionic還修正鹼性電池的設計,以價格更為親民的鋁取代鋅。在過去,鋁因為容易被腐蝕的問題,並未被採納,若鹼性電池真的能以鋁為材料,那麼重量有機會比鋰電池還要輕,也會比當前市面上的鹼性電池還要便宜。

報導稱,Ionic計畫在週四於永續能源研究機構Rocky Mountain Institute舉行的35週年大會上,宣布研究成果。

鹼性電池若能重複充電、將有諸多好處,但鋰電池之父John Goodenough也不是省油的燈。Goodenough雖已高齡94歲,卻寶刀未老,他今(2017)年3月已經帶領德州大學(University of Texas)奧斯汀分校團隊,打造出「全固態」(all-solid-state)鋰電池,不但更加安全,容量還是三倍大,而且只要數分鐘就可充電完畢,不必再像從前那樣等待數小時。

Apple Insider、CNET、CleanTechnica等多家外電3月6日報導,Goodenough採用全新技術打造的鋰電池可適用幾乎所有裝置,包括iPhone、MacBook Pro、車用電池或是類似特斯拉「Powerwall」家用電力儲存系統等,都可使用這種電池。

Goodenough團隊開發的最新技術,使用了玻璃製的電解質(不是以往的液體 ),可解決電池充電過快時激發「金屬鬚晶」(metal whiskers),導致正負兩極相互接觸、繼而起火爆炸的問題。

玻璃電解質是以鋰、鈉或鉀製成,可大幅增加電池的能量密度,據研究人員估算,即使充放電超過1,200次,電池續航力也幾乎不受影響。不僅如此,即使環境急凍到零下20度C,新款電池依舊能正常運作。

研發團隊指出,玻璃電解質可簡化電池製造流程,採用的材料也都對環境友善,能輕鬆回收再利用,另外更能降低稀土金屬的使用量。不過,德州大學科技商業化辦公室目前還在跟多家電池大廠洽談授權事宜,因此短期內恐怕還無法上市。

(本文內容由授權使用。圖片出處:Ionic Materials)  

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

2018第九屆廣州國際新能源汽車工業展覽會

時間:2018年4月13-15日

地點:廣州琶洲國際採購中心

組織單位:廣州中汽展覽有限公司

 

21世紀是一個面臨能源和環境巨大挑戰的世紀,新能源汽車具有良好的環保性能和燃料經濟性好、運行成本低等優勢,既可以保護環境,又可以緩解能源的短缺並能調整能源的結構,保障能源的安全。傳統燃油汽車將向高效低排放的電動汽車及混合動力車方向發展。大力發展新能源汽車是能源與環境的必然要求,加快培育和發展新能源汽車產業,是我國應對能源和環境挑戰、推動傳統汽車產業轉型升級的緊迫任務,也是我國搶佔未來競爭制高點、加快經濟發展方式轉變的戰略舉措。未來十年僅中央財政就投入上千億元用來支持以純電動車、混合動力汽車為代表的節能與新能源汽車的研發與推廣。

 

2014年5月24日上午,國家主席習近平在上海汽車集團考察時強調,發展新能源汽車是我國從汽車大國邁向汽車強國的必由之路,要加大研發力度,認真研究市場,用好用活政策,開發適應各種需求的產品,使之成為一個強勁的增長點。2014年以來多地出臺補貼政策,國務院發文:2014年9月1日起免征新能源汽車車輛購置稅。2016年9月23日和9月29日,國務院總理李克強連續在北京主持兩場國務院常務會議時強調:發展新能源汽車是國家戰略!會議出臺了對電動汽車行業利好的重大舉措:1.各地不得對新能源汽車實行限行、限購,已實行的應當取消;2.要求新建住宅停車位建設或預留安裝充電設施的比例應達到100%,大型公共建築物、公共停車場不低於10%。明確了對中國電動汽車行業發展的全面支持,電動汽車行業迎來國家戰略層面的全面重視和力挺,各種利好消息層出不窮。

 

2018年是實施“十三五”規劃的重要一年,《中共中央關於制定國民經濟和社會發展第十三個五年規劃的建議》把新能源汽車推廣列入國家的重要計畫之中,要求提高電動汽車產業化水準。這表明在“十三五”期間,新能源汽車發展在整個國民經濟和社會發展中將處在十分重要的地位,明確了新能源汽車在國民經濟和社會發展中的戰略定位。“十三五”期間,中國將成為世界最大的新能源汽車市場,成為世界新能源汽車的核心主戰場。中央新能源汽車“十三五”規劃明確指出,加快充電基礎設施建設,培育良好的新能源汽車服務和應用環境,財政部、科技部、工業和資訊化部、發展改革委、國家能源局等五部委緊跟研究起草了《關於“十三五”新能源汽車充電設施獎勵政策及加強新能源汽車推廣應用的通知》,各車企也紛紛按照該藍圖部署企業的“十三五”規劃。有機構統計,未來五年,僅八大車企集團合計將推出的新能源車型便多達91款,合計目標銷量超300萬輛。到2020年,我國新能源車保有量將超500萬台。可以預見,未來我國新能源汽車產業必將會爆發式發展。

 

為順應高速發展的新能源汽車,在各級主管部門的領導下,廣州中汽展覽有限公司聯合行業權威機構定于2018年4月13-15日在廣州琶洲國際採購中心舉辦“2018第九屆廣州國際新能源汽車工業展覽會”(NEA CHINA 2018)。我們將深化活動內涵,秉乘推動行業發展,以“突出品牌、開拓創新、注重實效、強化服務”的辦展宗旨,憑藉獨特的創意,科學的組織管理和卓越的服務,以全新的理念為廣大中外參展商提供一個“高水準、高品味、高品質”的拓展業務、技術交流、展示實力、獲取資訊、結交客戶、推廣新產品、尋找合作夥伴的國際商貿平臺。為全球新能源汽車行業提供更多的合作機會,有力推動中國新能源汽車相關產品全面進入全球採購體系,與世界各國新能源汽車產業協調合作、互利共贏、共同發展進步。

 

展品範圍

純電動車:轎車、大巴、公車、各旅行車、各種純電動特種車(環衛車、電力車、郵政車、小型客貨車、高爾夫車、房車、叉車、搬運車、旅遊觀光車、摩托車、三輪車等);

混合動力車:轎車、大巴、公車、各型旅行車等;

其他能源車:超級電容、燃料電池、氫能、生物燃料、太陽能及氫能源、天然氣、空氣動力汽車等各種新能源、清潔燃料及低排放、環保節能型車等;

零部件及動力驅動系統:低排放節能型發動機、混合動力發動機及清潔燃料發動機;動力電池與管理系統、燃料電池、混合動力系統;整車匯流排與控制系統;驅動電機、電動控制系統;充電裝置;儲能裝置等;能源管理系統;電力電容器、超級電容器、飛輪、逆變器、電熱泵、電動助力轉向、電動空調、輪胎、線連接、電磁技術、相關材料、功率模組等;相關材料、工藝、技術;相關檢測、監控、試驗、監控、安全防護裝備;維修、製造設備和工具等;

充電設施:充電站智慧型網路專案規劃及成果展示,加油站擴建充(換)電站、加油充電綜合服務站展示,太陽能、風能互補新能源汽車充電站技術產品,充電站充電機、充電樁、配電設備、變壓器、更換設備、電能、監控系統、有源濾波裝置、配電櫃、電覽、直接充電設備、管理輔助設備、充換電池及電池管理系統、停車場充電設施、智慧監控、充電站供電解決方案、充電站等。

其他:新能源汽車的整車及系統控制設計,智慧汽車及車聯網產品等。

 

目標觀眾

主辦單位將重點邀請的目標觀眾包括:

1、商務部、發改委、科技部、工信部、國家環保局等各局、司、中心、所領導;

2、全國各省市主管部門領導、大型企事業、機關單位領導;

3、全國各高校、科研單位、設計院、研究院、協(學)會領導;

4、公交、出租、環衛、郵政等單位負責人;車站、機場、碼頭、房地產、大型物業公司、高爾夫球場、旅遊景點、公園、體育場館、大專院校、醫院、療養院、度假村等單位負責人;

5、國內外著名生產、代理、經銷商、貿易公司等業內人士參觀、參展、技術交流。

 

組委會聯系方式:

陳敏婷 135 5603 1997   易嘉敏 135 3976 9616

電 話:+86-20-2919 8988  2919 8977

傳 真:+86-20-2919 8989

E-mail:

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

特斯拉超威!電動車飆贏跑車、藍寶堅尼吃灰塵

特斯拉電動車好威,旗下兩款車種速度之快,讓藍寶堅尼(Lamborghini)跑車也汗顏,只能在後面吃灰塵。

CNBC兩篇稱,Drag Times在美國佛州棕櫚灘舉辦賽事,特斯拉「Model S」轎車和「Model X」休旅車,雙雙派出具備「P100D Ludicrous」超快加速模式的車款,對戰藍寶堅尼Aventador跑車。在0.25英里(約400公尺)的直線加速賽上,特斯拉兩車款都大獲全勝,Model X更打破了休旅車的飆速紀錄。

電動車能立刻獲得扭力,加速之快勝過靠內燃機推動的傳統汽車。特斯拉的Ludicrous模式更能讓車輛在3秒內,速度從0飆至60英里(約96公里)。儘管部分跑車速度快過特斯拉電動車,但是價格遠高於特斯拉。以藍寶堅尼Aventador而言,價格為53萬美元,超過特斯拉的16.5萬美元。

JPMorgan看好電動車前景,預估2025年電動車將在全球車市奪下35%市佔、2030年續增至48%。這會帶來巨大衝擊,汽車經銷商和維修商將受重創。報告稱,電動車維修費用低廉,主因電動車移動零件少,只有20個,低於傳統汽車的2千個,會大幅壓低維修費用、並延長車輛壽命。仰賴售後服務利潤的汽車經銷商等將面臨可觀風險。

不只如此,車貸業者也會拉警報。電動車盛行之後,傳統汽車的剩餘價值會減少,如果車貸公司回收汽車、再次出售,價格將低於現值。而且電動車壽命較長,車貸業者承作的新貸款也會萎縮。

此外,原油需求也會受到威脅。CNBC 5月23日報導,Tony Seba是獨立智庫RethinkX共同創辦人兼史丹福大學教授,他預言自駕電動車崛起會衝擊原油需求。他說,原油需求估計在2020到2021年觸頂,十年之內,原油需求會從1億桶降至7,000萬桶,原油均衡價格(equilibrium price)大跌至25美元。

Sebe做此預言,主因預期自駕電動車將蔚為風潮。他說,自駕車獲得許可之後,叫車服務、電動車和自動駕駛結合,叫車會比自己買車開車便宜十倍。若是如此,車輛總數將大減、停車需求也暴跌。

(本文內容由授權使用。圖片出處:Tesla)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

北京推廣新能源車,擬大力新建公用充電樁

北京日報消息,北京城市管理委員會表示,已正式發文要求加強電動汽車充電基礎設施建設和管理,其中,新建停車場必須預留配建充電設施條件,全市機關單位內部停車場應含充電停車位,新建社區停車位須100%具有安裝充電樁條件。

資料顯示,截至今年7月底,北京市新能源汽車數量達14.14萬輛,全市已累計建成約9.75萬個充電樁,基本形成六環內平均服務半徑5公里的公用充電網,其中,個人自用樁約6.87萬個,配建比約75%,商場商圈、交通樞紐等公共停車區域建成1,895處、約1.75萬個。

北京市城管委相關負責人表示,大力新建公用充電樁是破解新能源車推廣困難的重要工作之一,日前市政府已發佈《關於進一步加強電動汽車充電基礎設施建設和管理的實施意見》,首次對新建、既有建築的停車位配建充電設施作出了量化要求。

新規定將充電設施配建指標納入規劃設計規範,明確新建建築配建停車場及公共停車場中充電設施的建設比例或預留建設安裝條件。其中,辦公類建築不低於配建停車位的25%,商業類及公共停車場庫(含P+R停車場)不低於20%,居住類按照配建停車位的100%規劃建設,其他類公建如醫院、學校、文體設施等不低於15%規劃建設。

另外,新建社區100%規劃建設,係指每個停車位都要具備安裝條件,讓車主買車後就能直接裝充電樁,不用再申請改電增容。

同時,對於具備電源條件的既有辦公區、大型商場等公用停車場,新規要求必須配建不低於車位數量10%的公用充電樁。對於老舊社區住戶而言,購買電動車最難的關卡就是無法安裝充電樁,而此次頒佈的新規將充電設施建設納入北京市老舊社區綜合改造範圍內,引導物業公司、業主委員會積極支持和配合充電設施建設。

此次新規也提出,北京全市各級機關、企事業單位內部停車場都要配建公用充電設施。按規定,北京市各級公共機構(包括各級政府機關、事業單位、社會組織)和國有企業新建內部停車場,充電樁數量應不低於車位數的25%,或預留建設安裝條件。

(本文內容由授權使用。圖片出處:public domain CC0)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

.NET開發者的機遇與Web Blazor基礎(有彩蛋),.NET Core 又一殺器! Web Blazor框架橫空出世!

 一.嘮嘮WebAssembly的發展歷程

  目前有很多支持WebAssembly的項目,但發展最快的是Blazor,這是一個構建單頁面的.NET技術,目前已經從Preview版本升級到了beta版本,微軟計劃在2020年5月發布Blazor的第一個版本。

  Blazor是什麼?它是一項將C#和.NET都放入瀏覽器的Microsoft技術。它使用WebAssembly來工作,WebAssembly是一種高性能的管道,可以將代碼預編譯為緊湊的二進制格式。最重要的是,每個主流瀏覽器(包括移動版本)都支持WebAssembly。

  十年前,JavaScript統治世界還不是很明顯。Flash和Silverlight也正在運行。這二個都需要使用瀏覽器插件來完成工作,並且都以不同的用戶界面方法替換了HTML。這種方法使他們在功能方面遙遙領先於JavaScript,但隨着移動互聯網的出現,他們就慢慢過時。

  但隨後從最初的Javascript再到微軟的JScript和CEnvi的ScriptEase三足鼎立,再到最後的統一標準,當時微軟憑藉Windows系統捆綁Internet Explorer的先天優勢擊潰Netscape后,兩大巨頭就此進入了長達數年的靜默期,JavaScript就是在這樣的情況下被構想出來的,當時的瀏覽器之王,Netscape Navigator創始人Marc Andreessen認為Netscape需要一種“glue language”來支持HTML,讓Web設計師和兼職程序員可以很容易地使用它來組裝諸如圖像和插件之類的組件,且代碼是可以直接寫在網頁標記中。除此之外微軟的步步緊逼也迫使Andreessen不得不聘請Brendan Eich,及早將Scheme編程語言嵌入到Netscape Navigator中。1995年,JavaScript以Mocha為名開發,並於9月在Netscape Navigator 2.0的測試版中首次發布,當時被稱為LiveScript,12月,在Netscape Navigator 2.0 beta 3中部署時被重命名為JavaScript 。雖然Netscape Navigator在Chrome、Internet Explorer和Firefox等多款瀏覽器的圍追堵截中最終落敗,但是JavaScript卻推動了網頁的發展,並一直被沿用至今。

  這是一個諷刺。在JavaScript征服世界的同時,播下了一顆很小的種子,這可能會在將來的某個時候暗示JavaScript的終結。那顆種子是名為asm.js的實驗技術。

  這是Mozilla的開發人員在2013年完成的一個古怪的實驗。他們正在尋找在瀏覽器中運行高性能代碼的方法。但是與插件不同,asm.js並未嘗試在瀏覽器旁邊運行。相反,它的目的是直接通過Javascript的虛擬化。

  從本質上講,asm.js是簡潔,優化的JavaScript語法。它比普通的JavaScript運行得更快,因為它避免了該語言的慢動態部分。但是認識到它的網絡瀏覽器也可以應用其他優化,從而大大提高性能。換句話說,asm.js遵循黃金法則- 不要破壞網絡 -同時提供通往未來改進的途徑。Firefox團隊使用asm.js以及名為的轉碼工具來獲取用C ++構建的實時3D遊戲,並將其放入Web瀏覽器中,並且僅在JavaScript和原始野心上運行。

  有人問為什麼asm.js好在哪裡,簡單而言,它的性能比JavaScript高几百倍,當然是在沒有谷歌的V8引擎之下,因為JavaScript是弱類型語言,它需要猜測你的數據類型來進行編譯,這樣的情況下,在我看來它肯定需要遍歷完一個方法,然後再進行運算,與其這樣我為什麼不打個標識呢?當然在不破壞JavaScript的情況下,arm.js選擇了一個騷氣的想法,如果你想你的數據類型是int,那麼聲明一個值就變成了變量名|0,就這樣它的目的就達到了。

  儘管asm.js實驗產生了一些令人眼花撩亂的演示,但工作的開發人員基本上忽略了它。對他們來說,這隻是超越現代的一個有趣方面。但這隨着WebAssembly的創建而改變。

  WebAssembly既是asm.js的後繼產品,又是一項截然不同的技術。這是一種緊湊的二進制代碼格式。像asm.js一樣,WebAssembly代碼也被輸入到JavaScript執行環境中。它具有相同的沙箱和相同的運行時環境。與asm.js一樣,WebAssembly的編譯方式也可以提高效率。但是現在,這些效率比有以前更加明顯,並且瀏覽器可以完全跳過JavaScript解析階段。對於普通的邏輯,WebAssembly遠比常規JavaScript快,幾乎與本機編譯的代碼一樣快。

   WebAssembly於2015年首次出現。如今,桌面和移動設備上的四大瀏覽器(Chrome,Edge,Safari和Firefox)已完全支持它。儘管可以通過將WebAssembly代碼轉換為asm.js來實現向後兼容,但Internet Explorer不支持它。就讓IE涼透吧!但需要注意的是WebAssembly無法迴避JavaScript,因為它已鎖定在JavaScript運行時環境中。實際上,WebAssembly需要與至少一些普通的JavaScript代碼一起運行,因為它不能直接訪問頁面。這意味着如果不通過JavaScript層,就無法操縱DOM或接收事件。

   聽我說起來,這是一個限制,但聰明的微軟開發者已經找到了走私的方法,在瀏覽器中下載一個微型.NET運行時,作為已編譯的WASM文件。此運行時處理JavaScript互操作,並提供基本服務,它能給我們提供GC或者其它用法。Blazor不是唯一一個由WebAssembly支持的實驗。考慮一下,它旨在將Python放入瀏覽器中,並帶有用於數據分析的高級數學工具包。據我所知這應該使用emscripten的編譯器。

   人們常說,何時Javascript能夠替代服務器端語言,又有人說什麼時候可以代替桌面級應用程序,所以WebAssembly並不是用來代替JavaScript的。而是為了解決現代問題,如果它做到了,那就真的做到了!所以作為一個程序員,你應該對WebAssembly引起足夠的重視,未來快速加載Web應用程序的需求肯定會增加。

   就現在我們的.NET Core提供了兩種Blazor模板,包括Blazor Server 以及 Blazor WebAssembly。

  • Blazor Server使用熟悉的.NET環境在Web服務器上運行代碼。訣竅是瀏覽器和服務器之間的通信方式。當用戶與頁面進行交互時,JavaScript代碼將回調到發生實際頁面生命周期的服務器。(要建立此連接,該頁面使用名為的Microsoft API )運行服務器端代碼后,Blazor Server呈現該頁面並將更改發送回Web頁面,該Web頁面將相應地進行更新。
  • Blazor WebAssembly使用由WebAssembly提供支持的微型.NET運行時在瀏覽器中運行代碼。您的客戶端代碼可以訪問許多熟悉的.NET庫,並且您使用C#語言編寫它,您仍然可以像在JavaScript頁面中一樣在Web服務器上調用API。

  Blazor Server是一種具有一些有趣用例的技術,但是由於不斷的通信,您顯然會犧牲一些性能-甚至不用問脫機功能。Blazor WebAssembly是受到最多宣傳的一種,也是我們在本文中探討的一種。

  關於Blazor,程序員最常見的誤解是將其C#代碼編譯為WebAssembly,然後發送到瀏覽器,然後執行。這種方法並非不可能-Blazor的創建者暗示他們將來可能會嘗試這種技術。但是如今Blazor的工作方式並不是如此。

  換句話說,如今的Blazor是當您訪問使用Blazor的網頁時,該頁面將從下載按比例縮小的.NET運行時開始。然後它將下載您的應用程序以及您的應用程序使用的任何其他.NET庫,所有這些都在其本機IL中。最後,Blazor運行時執行IL。

二.配置您的開發環境

   由於Blazor是一個預發布的早期Beta產品。基礎結構的關鍵部分正在發生變化,您將無法獲得與其他類型的Microsoft項目相同級別的工具支持。我嘗試在Visual Studio 2019中進行編碼,需要注意的是您需要勾選.NET FrameWork 4.8 以及 .NET Core 3.0 + ,這樣您才具有Web Assembly的項目。完成設置后,您可以輕鬆創建Blazor項目。只需啟動Visual Studio,創建一個新項目,然後選擇“ Blazor App”項目即可。Visual Studio會詢問您是否需要Blazor Server應用程序或Blazor WebAssembly應用程序.

 三.Blazor的數據綁定與組件傳值

  由於關於Blazor的一篇我編寫的文章,未能提及更深入的內容,那麼現在我將要介紹一下高級的Blazor用法,到最後還會有一個糖果,園友力作的Blazor UI!多麼激動人心的時刻,那麼趕快開始吧.

3.1 Child Component

  在Blazor的Child Component中可以使用[Parameter] 關鍵字,來進行傳值的定義,我們可以這麼來做,現在只是提一下這個概念,下面會仔細說下組件之間如何進行跨組件綁定值。

<div>
    <p>標題:@title</p>
</div>
@code{
    [Parameter]
    public string title { get; set; }
}

隨後在調用時,Visual Studio IDE 就可以直接向您的視覺進行提示輸入相關屬性。

<Demorazor title="Hello 博客園的兄弟們!"></Demorazor>

運行效果如下:

3.2 single Bind and Two-way binding

single bind就不用說了,新建項目自帶的模板Counter示例那就是如此。

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

此處 @currentCount 值根據點擊按鈕的數量遞增Click me。<p>標記元素中的值會自動刷新,無需任何組件刷新。

two-way binding 我們可以自定義我們的事件 一共分為二中綁定方式 包括@bind 和 @Bind-Value,值得一提的是還可以通過使用event參數指定@bind-value屬性, 使用其他事件來綁定屬性或字段。例如第四個文本框就是綁定changeString採用oninput事件的屬性,以到達在文本框的值更改時激發,經過我的測試如果你的綁定事件是Javascript中不存在的,那麼也無妨,不會報出系統級別的異常,我想如果是從IL轉換到WebAssembly中,就會直接過濾掉,但是Visual Studio 2019 沒有給我們提示,也讓我們編譯通過,即使是當前的最高16.0.4 預覽版也是如此,這個是令我詫異的。

<p>
    <span>在這裏可以使用bind-value 或者 bind 當然這裏確保您不使用其它事件!</span>
    <input @bind-value="changeString" />
    <p>這是我輸入的內容: @changeString</p>
</p>
<p>
    <span>oninput</span>
    <input @bind-value="changeString" @bind-value:event="oninput" />
</p>

@code {
    string changeString = "";
}  

運行效果如下:

 3.3 Component bindings

   想要跨組件進行綁定屬性值,可以使用,@bind-{property}可在其中跨組件綁定屬性值,我們試着嘗試,首先我們創建一個子控件,這個blazor就叫Baby,有一個身份證Id的屬性和出生地址。

   EventCallback的用法非常廣泛,它可以跨組件共享方法和屬性,如不寫下面的兩個屬性,則就會報錯。

@page "/baby"
<h2>Child Compoent</h2>
<p>出生的Baby IdentityCard:@Baby_IdentityCrad_Id</p>
<h3>在{@Baby_new_Address} 生的</h3>
@code {
    [Parameter]
    public string Baby_IdentityCrad_Id{ get; set; }

    /// <summary>
    /// 這個屬性也是牛的雅皮~~~ hhh
    /// </summary>
    [Parameter]
    public string Baby_new_Address{ get; set; }
    
    [Parameter]
    public EventCallback<string> Baby_IdentityCrad_IdChanged { get; set; }

    [Parameter]
    public EventCallback<string> Baby_new_AddressChanged { get; set; }
}

   有什麼樣的兒子就會有什麼樣的爸爸? 現在我們創建出父親,那就直接叫做一個Father.razor吧~

@page "/father"
<h3>Father</h3>

<Baby @bind-Baby_IdentityCrad_Id="@id_Card"
      @bind-Baby_new_Address="@address">
</Baby>
<button class="btn btn-primary" @onclick="@ChangeTheYear">new baby()</button>
@code {
    public string id_Card { get; set; }
    public string address { get; set; }
    private void ChangeTheYear()
    {
        id_Card = Guid.NewGuid().ToString();
        address = "老張";
    }
}

運行效果如下:

 

 如果要在子組件中定義事件,則可以MouseEventArgs來接受設備上的事件,然後再進行附加事件。

[Parameter]
public EventCallback<MouseEventArgs> OnClick { get; set; }

四.級聯傳值

   在某些情況下, 使用組件參數將數據從祖先組件流式傳輸到附屬組件是不方便的, 尤其是在有多個組件層時。 級聯值和參數通過提供一種方便的方法, 使上級組件為其所有子代組件提供值。 級聯值和參數還提供了一種方法來協調組件。我們試着去構建一個例子,首先創建一個最頂層的組件。

@page "/myDome"
<p><span>姓名:</span><input @bind="@pName" /></p>
<p><span>年齡:</span><input @bind-value="@pAge" @bind-value:event="oninput"/></p>
<CascadingValue Value="@pName" Name="ProfileName">
    <CascadingValue Value="@pAge" Name="ProfileAge">
        <ParentComponent />
    </CascadingValue>
</CascadingValue>
@code {
    private string pName { get; set; } = "張三";
    private int pAge { get; set; } = 35;
}

ParentComponent.razor:

<div style="background-color:darkgray;width:200px;">
    <p>Parent Component</p>
    <div style="padding:10px;">
        <p> 年齡 :@Age</p>
        <ChildComponent />
    </div>
</div>
@code{
    [CascadingParameter(Name = "ProfileAge")]
    int Age { get; set; }
}

ChildComponent.razor:

<div style="background-color:beige;width:200px;">
    <p>Child Component</p>
    <p>名稱 : @Name.ToString()</p>
</div>

@code{
    [CascadingParameter(Name = "ProfileName")]
    string Name { get; set; }
}

 運行效果如下:

 

 可以發現,一級直接將二級和三級的組件進行了數據穿透,不過需要注意的是CascadingValue的Name一定要和CascadingParameter的Name相同,否則將會執行錯誤。

五.路由

   從古至今,任何大型的開發框架,都是具有路由的,否則可能將會無法工作,其實Blazor的啟動頁也就使用了路由,這是毋庸置疑的。當你的組件帶有 @page 指令時,將為生成的類指定  指定路由模板的。 在運行時,路由器將使用 RouteAttribute 查找組件類,並呈現哪個組件包含與請求的 URL 匹配的路由模板。

@page "/luyou"
@page "/luyou/{text}"

<h1>Blazor is @Text!</h1>

@code {
    [Parameter]
    public string Text { get; set; }

    protected override void OnInitialized()
    {
        Text = Text ?? "fantastic";
    }
}

運行效果如下:

在上面的示例中應用了兩個 @page 指令。 第一個允許導航到沒有參數的組件。 第二個 @page 指令採用 {text} 路由參數,並將該值分配給 Text 屬性。

關於Blazor的基礎入門咱們這篇就說到這裏,相信你一定覺得Blazor了不起!它是一個現代的開源框架。它也由一家擁有悠久歷史的公司擁有,該公司放棄了昨天的閃亮新技術。因此,大多數開發人員都應該謹慎對待Blazor。只要JavaScript能夠執行Blazor可以做的所有事情,而沒有下載大小,性能和新工具堆棧帶來的額外挑戰,大多數開發人員將一如既往。

這並不意味着Blazor不能在所有這些領域都佔有一席之地。它甚至可能成為.NET Web應用程序開發中的主導力量。但是如果我今天必須下注,這就是我要依靠的東西。WebAssembly是未來。但就目前而言,Blazor只是一種有趣的可能性。

六.彩蛋

就現在!我的好朋友宇辰正在開發一款名為Blazui的UI組件。它為什麼叫Blazui?

Blazor + Element UI = Blazui,Element UI 的blazor版本,無JS,無TS,用 .Net 寫前端的 UI 框架,非 Silverlight,非 WebForm,開箱即用!!

Blazui 演示地址:。QQ群:74522853,碼雲地址:

參考Blazor使用的前提條件:

  1. 安裝 .Net Core 3.0
  2. 安裝 VS2019
  3. 安裝所有 VS2019 Blazor Extension

現在Blazor正在逐漸變好,讓我們即刻出發!.NET Core 不只是開源!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

7. SOFAJRaft源碼分析—如何實現一個輕量級的對象池?

前言

我在看SOFAJRaft的源碼的時候看到了使用了對象池的技術,看了一下感覺要吃透的話還是要新開一篇文章來講,內容也比較充實,大家也可以學到之後運用到實際的項目中去。

這裏我使用RecyclableByteBufferList來作為講解的例子:

RecyclableByteBufferList

public final class RecyclableByteBufferList extends ArrayList<ByteBuffer> implements Recyclable {

    private transient final Recyclers.Handle handle;

    private static final Recyclers<RecyclableByteBufferList> recyclers = new Recyclers<RecyclableByteBufferList>(512) {

        @Override
        protected RecyclableByteBufferList newObject(final Handle handle) {
            return new RecyclableByteBufferList(
                    handle);
        }
    };

      //獲取一個RecyclableByteBufferList實例
    public static RecyclableByteBufferList newInstance(final int minCapacity) {
        final RecyclableByteBufferList ret = recyclers.get();
        //容量不夠的話,進行擴容
        ret.ensureCapacity(minCapacity);
        return ret;
    }
      //回收RecyclableByteBufferList對象
    @Override
    public boolean recycle() {
        clear();
        this.capacity = 0;
        return recyclers.recycle(this, handle);
    }
}

我在上面將RecyclableByteBufferList獲取對象的方法和回收對象的方法給列舉出來了,獲取實例的時候會通過recyclers的get方法去獲取,回收對象的時候會去調用list的clear方法清空list裏面的內容之後再去調用recyclers的recycle方法進行回收。
如果recyclers裏面沒有對象可以獲取,那麼會調用newObject方法創建一個對象,然後將handle對象傳入構造器中進行實例化。

對象池Recyclers

數據結構

  1. 每一個 Recyclers 對象包含一個 ThreadLocal<Stack<T>> threadLocal實例;
    每一個線程包含一個 Stack 對象,該 Stack 對象包含一個 DefaultHandle[],而 DefaultHandle 中有一個屬性 T value,用於存儲真實對象。也就是說,每一個被回收的對象都會被包裝成一個 DefaultHandle 對象
  2. 每一個 Recyclers 對象包含一個ThreadLocal<Map<Stack<?>, WeakOrderQueue>> delayedRecycled實例;
    每一個線程對象包含一個 Map<Stack<?>, WeakOrderQueue>,存儲着為其他線程創建的 WeakOrderQueue 對象,WeakOrderQueue 對象中存儲一個以 Head 為首的 Link 數組,每個 Link 對象中存儲一個 DefaultHandle[] 數組,用於存放回收對象。

假設線程A創建的對象

  1. 線程A回收RecyclableByteBufferList時,直接將RecyclableByteBufferList的DefaultHandle 對象壓入 Stack 的 DefaultHandle[] 中;
  2. 線程B回收RecyclableByteBufferList時,會首先從其 Map<Stack<?>, WeakOrderQueue> 對象中獲取 key=線程A的Stack 對象的 WeakOrderQueue,然後直接將RecyclableByteBufferList的DefaultHandle 對象(內部包含RecyclableByteBufferList對象)壓入該 WeakOrderQueue 中的 Link 鏈表中的尾部 Link 的 DefaultHandle[]中,同時,這個 WeakOrderQueue 會與線程 A 的 Stack 中的 head 屬性進行關聯,用於後續對象的 pop 操作;
  3. 當線程 A 從對象池獲取對象時,如果線程 A 的 Stack 中有對象,則直接彈出;如果沒有對象,則先從其 head 屬性所指向的 WeakorderQueue 開始遍歷 queue 鏈表,將 RecyclableByteBufferList 對象從其他線程的 WeakOrderQueue 中轉移到線程 A 的 Stack 中(一次 pop 操作只轉移一個包含了元素的 Link),再彈出。

Recyclers靜態代碼塊

private static final int DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD = 4 * 1024; // Use 4k instances as default.
private static final int DEFAULT_MAX_CAPACITY_PER_THREAD;
private static final int INITIAL_CAPACITY;

static {
    // 每個線程的最大對象池容量
    int maxCapacityPerThread = SystemPropertyUtil.getInt("jraft.recyclers.maxCapacityPerThread", DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD);
    if (maxCapacityPerThread < 0) {
        maxCapacityPerThread = DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD;
    }

    DEFAULT_MAX_CAPACITY_PER_THREAD = maxCapacityPerThread;
    if (LOG.isDebugEnabled()) {
        if (DEFAULT_MAX_CAPACITY_PER_THREAD == 0) {
            LOG.debug("-Djraft.recyclers.maxCapacityPerThread: disabled");
        } else {
            LOG.debug("-Djraft.recyclers.maxCapacityPerThread: {}", DEFAULT_MAX_CAPACITY_PER_THREAD);
        }
    }
    // 設置初始化容量信息
    INITIAL_CAPACITY = Math.min(DEFAULT_MAX_CAPACITY_PER_THREAD, 256);
}

 public static final Handle NOOP_HANDLE = new Handle() {};

Recyclers會在靜態代碼塊中做一些對象池容量初始化的工作,初始化了最大對象池容量和初始化容量信息。

從對象池中獲取對象

Recyclers#get

// 線程變量,保存每個線程的對象池信息,通過 ThreadLocal 的使用,避免了不同線程之間的競爭情況
private final ThreadLocal<Stack<T>> threadLocal = new ThreadLocal<Stack<T>>() {

    @Override
    protected Stack<T> initialValue() {
        return new Stack<>(Recyclers.this, Thread.currentThread(), maxCapacityPerThread);
    }
};

public final T get() {
    if (maxCapacityPerThread == 0) {
        return newObject(NOOP_HANDLE);
    }
    //從threadLocal中獲取一個棧對象
    Stack<T> stack = threadLocal.get();
    //拿出棧頂元素
    DefaultHandle handle = stack.pop();
    //如果棧裏面沒有元素,那麼就實例化一個
    if (handle == null) {
        handle = stack.newHandle();
        handle.value = newObject(handle);
    }
    return (T) handle.value;
}

Get方法會從threadLocal中去獲取數據,如果獲取不到,那麼會初始化一個Stack,並傳入當前Recyclers實例,當前線程,與最大容量。然後從stack中pop拿出棧頂元素,如果獲取的元素為空,那麼直接調用newHandle新建一個DefaultHandle實例,並調用Recyclers實現類的newObject獲取實現類的實例。也就是說DefaultHandle是用來封裝真正的對象的實例。

從stack中申請一個對象

Stack(Recyclers<T> parent, Thread thread, int maxCapacity) {
    this.parent = parent;
    this.thread = thread;
    this.maxCapacity = maxCapacity;
    elements = new DefaultHandle[Math.min(INITIAL_CAPACITY, maxCapacity)];
}

DefaultHandle pop() {
    int size = this.size;
    if (size == 0) {
        if (!scavenge()) {
            return null;
        }
        size = this.size;
    }
    //size表示整個stack中的大小
    size--;
    //獲取最後一個元素
    DefaultHandle ret = elements[size];
    if (ret.lastRecycledId != ret.recycleId) {
        throw new IllegalStateException("recycled multiple times");
    }
    // 清空回收信息,以便判斷是否重複回收
    ret.recycleId = 0;
    ret.lastRecycledId = 0;
    this.size = size;
    return ret;
}

獲取對象的邏輯也比較簡單,當 Stack 中的 DefaultHandle[] 的 size 為 0 時,需要從其他線程的 WeakOrderQueue 中轉移數據到 Stack 中的 DefaultHandle[],即 scavenge方法,該方法下面再聊。當 Stack 中的 DefaultHandle[] 中最終有了數據時,直接獲取最後一個元素

對象池回收對象

我們再來看看RecyclableByteBufferList是怎麼回收對象的。
RecyclableByteBufferList#recycle

public boolean recycle() {
    clear();
    this.capacity = 0;
    return recyclers.recycle(this, handle);
}

RecyclableByteBufferList回收對象的時候首先會調用clear方法清空屬性,然後調用recyclers的recycle方法進行對象回收。

Recyclers#recycle

public final boolean recycle(T o, Handle handle) {
    if (handle == NOOP_HANDLE) {
        return false;
    }

    DefaultHandle h = (DefaultHandle) handle;
    //stack在實例化的時候會在構造器中傳入一個Recyclers作為parent
    //所以這裡是校驗一下,如果不是當前線程的, 直接不回收了
    if (h.stack.parent != this) {
        return false;
    }
    if (o != h.value) {
        throw new IllegalArgumentException("o does not belong to handle");
    }
    h.recycle();
    return true;
}

這裡會接着調用DefaultHandle的recycle方法進行回收

DefaultHandle

static final class DefaultHandle implements Handle {
    //在WeakOrderQueue的add方法中會設置成ID
    //在push方法中設置成為OWN_THREAD_ID
    //在pop方法中設置為0
    private int lastRecycledId;
    //只有在push方法中才會設置OWN_THREAD_ID
    //在pop方法中設置為0
    private int recycleId;
    //當前的DefaultHandle對象所屬的Stack
    private Stack<?> stack;
    private Object value;

    DefaultHandle(Stack<?> stack) {
        this.stack = stack;
    }

    public void recycle() {
        Thread thread = Thread.currentThread();
        //如果當前線程正好等於stack所對應的線程,那麼直接push進去
        if (thread == stack.thread) {
            stack.push(this);
            return;
        }
        // we don't want to have a ref to the queue as the value in our weak map
        // so we null it out; to ensure there are no races with restoring it later
        // we impose a memory ordering here (no-op on x86)
        // 如果不是當前線程,則需要延遲回收,獲取當前線程存儲的延遲回收WeakHashMap
        Map<Stack<?>, WeakOrderQueue> delayedRecycled = Recyclers.delayedRecycled.get();
        // 當前 handler 所在的 stack 是否已經在延遲回收的任務隊列中
        // 並且 WeakOrderQueue是一個多線程間可以共享的Queue
        WeakOrderQueue queue = delayedRecycled.get(stack);
        if (queue == null) {
            delayedRecycled.put(stack, queue = new WeakOrderQueue(stack, thread));
        }
        queue.add(this);
    }
}

DefaultHandle在實例化的時候會傳入一個stack實例,代表當前實例是屬於這個stack的。
所以在調用recycle方法的時候,會判斷一下,當前的線程是不是stack所屬的線程,如果是那麼直接push到stack裏面就好了,不是則調用延遲隊列delayedRecycled;
從delayedRecycled隊列中獲取Map<Stack<?>, WeakOrderQueue> delayedRecycled ,根據stack作為key來獲取WeakOrderQueue,然後將當前的DefaultHandle實例放入到WeakOrderQueue中。

同線程回收對象

Stack#push

void push(DefaultHandle item) {
    // (item.recycleId | item.lastRecycleId) != 0 等價於 item.recycleId!=0 && item.lastRecycleId!=0
    // 當item開始創建時item.recycleId==0 && item.lastRecycleId==0
    // 當item被recycle時,item.recycleId==x,item.lastRecycleId==y 進行賦值
    // 當item被pop之後, item.recycleId = item.lastRecycleId = 0
    // 所以當item.recycleId 和 item.lastRecycleId 任何一個不為0,則表示回收過
    if ((item.recycleId | item.lastRecycledId) != 0) {
        throw new IllegalStateException("recycled already");
    }
    // 設置對象的回收id為線程id信息,標記自己的被回收的線程信息
    item.recycleId = item.lastRecycledId = OWN_THREAD_ID;

    int size = this.size;
    if (size >= maxCapacity) {
        // Hit the maximum capacity - drop the possibly youngest object.
        return;
    }
    // stack中的elements擴容兩倍,複製元素,將新數組賦值給stack.elements
    if (size == elements.length) {
        elements = Arrays.copyOf(elements, Math.min(size << 1, maxCapacity));
    }

    elements[size] = item;
    this.size = size + 1;
}

同線程回收對象 DefaultHandle#recycle 步驟:

  1. stack 先檢測當前的線程是否是創建 stack 的線程,如果不是,則走異線程回收邏輯;如果是,則首先判斷是否重複回收,然後判斷 stack 的 DefaultHandle[] 中的元素個數是否已經超過最大容量(4k),如果是,直接返回;
  2. 判斷當前的 DefaultHandle[] 是否還有空位,如果沒有,以 maxCapacity 為最大邊界擴容 2 倍,之後拷貝舊數組的元素到新數組,然後將當前的 DefaultHandle 對象放置到 DefaultHandle[] 中
  3. 最後重置 stack.size 屬性

異線程回收對象

WeakOrderQueue

static final class Stack<T> {
    //使用volatile可以立即讀取到該queue
      private volatile WeakOrderQueue head;
}
WeakOrderQueue(Stack<?> stack, Thread thread) {
    head = tail = new Link();
    //使用的是WeakReference ,作用是在poll的時候,如果owner不存在了
    // 則需要將該線程所包含的WeakOrderQueue的元素釋放,然後從鏈表中刪除該Queue。
    owner = new WeakReference<>(thread);
    //假設線程B和線程C同時回收線程A的對象時,有可能會同時創建一個WeakOrderQueue,就坑同時設置head,所以這裏需要加鎖
    synchronized (stackLock(stack)) {
        next = stack.head;
        stack.head = this;
    }
}

創建WeakOrderQueue對象的時候會初始化一個WeakReference的owner,作用是在poll的時候,如果owner不存在了, 則需要將該線程所包含的WeakOrderQueue的元素釋放,然後從鏈表中刪除該Queue。

然後給stack加鎖,假設線程B和線程C同時回收線程A的對象時,有可能會同時創建一個WeakOrderQueue,就坑同時設置head,所以這裏需要加鎖。

以head==null的時候為例
加鎖:
線程B先執行,則head = 線程B的queue;之後線程C執行,此時將當前的head也就是線程B的queue作為線程C的queue的next,組成鏈表,之後設置head為線程C的queue
不加鎖:
線程B先執行 next = stack.head此時線程B的queue.next=null->線程C執行next = stack.head;線程C的queue.next=null-> 線程B執行stack.head = this;設置head為線程B的queue -> 線程C執行stack.head = this;設置head為線程C的queue,此時線程B和線程C的queue沒有連起來。

WeakOrderQueue#add

void add(DefaultHandle handle) {
    // 設置handler的最近一次回收的id信息,標記此時暫存的handler是被誰回收的
    handle.lastRecycledId = id;

    Link tail = this.tail;
    int writeIndex;
    // 判斷一個Link對象是否已經滿了:
    // 如果沒滿,直接添加;
    // 如果已經滿了,創建一個新的Link對象,之後重組Link鏈表,然後添加元素的末尾的Link(除了這個Link,前邊的Link全部已經滿了)
    if ((writeIndex = tail.get()) == LINK_CAPACITY) {
        this.tail = tail = tail.next = new Link();
        writeIndex = tail.get();
    }
    tail.elements[writeIndex] = handle;
    // 如果使用者在將DefaultHandle對象壓入隊列后,將Stack設置為null
    // 但是此處的DefaultHandle是持有stack的強引用的,則Stack對象無法回收;
    //而且由於此處DefaultHandle是持有stack的強引用,WeakHashMap中對應stack的WeakOrderQueue也無法被回收掉了,導致內存泄漏
    handle.stack = null;
    // we lazy set to ensure that setting stack to null appears before we unnull it in the owning thread;
    // this also means we guarantee visibility of an element in the queue if we see the index updated
    // tail本身繼承於AtomicInteger,所以此處直接對tail進行+1操作
    tail.lazySet(writeIndex + 1);
}

Stack異線程push對象流程

  1. 首先獲取當前線程的 Map<Stack<?>, WeakOrderQueue> 對象,如果沒有就創建一個空 map;
  2. 然後從 map 對象中獲取 key 為當前的 Stack 對象的 WeakOrderQueue;
  3. 如果獲取的WeakOrderQueue對象為null,那麼創建一個WeakOrderQueue對象,並將對象放入到map中,最後調用WeakOrderQueue#add添加對象

WeakOrderQueue 的創建流程:

  1. 創建一個Link對象,將head和tail的引用都設置為此對象
  2. 創建一個WeakReference指向owner對象,設置當前的 WeakOrderQueue 所屬的線程為當前線程。
  3. 先將原本的 stack.head 賦值給剛剛創建的 WeakOrderQueue 的 next 節點,之後將剛剛創建的 WeakOrderQueue 設置為 stack.head(這一步非常重要:假設線程 A 創建對象,此處是線程 C 回收對象,則線程 C 先獲取其 Map<Stack<?>, WeakOrderQueue> 對象中 key=線程A的stack對象的 WeakOrderQueue,然後將該 Queue 賦值給線程 A 的 stack.head,後續的 pop 操作打基礎),形成 WeakOrderQueue 的鏈表結構。

WeakOrderQueue#add添加對象流程

  1. 首先設置 item.lastRecycledId = 當前 WeakOrderQueue 的 id
  2. 然後看當前的 WeakOrderQueue 中的 Link 節點鏈表中的尾部 Link 節點的 DefaultHandle[] 中的元素個數是否已經達到 LINK_CAPACITY(16)
  3. 如果不是,則直接將當前的 DefaultHandle 元素插入尾部 Link 節點的 DefaultHandle[] 中,之後置空當前的 DefaultHandle 元素的 stack 屬性,最後記錄當前的 DefaultHandle[] 中的元素數量;
  4. 如果是,則新建一個 Link,並且放在當前的 Link 鏈表中的尾部節點處,與之前的 tail 節點連起來(鏈表),之後進行第三步的操作。

從異線程獲取對象

我再把pop方法搬下來一次:

DefaultHandle pop() {
    int size = this.size;
    // size=0 則說明本線程的Stack沒有可用的對象,先從其它線程中獲取。
    if (size == 0) {
        // 當 Stack<T> 此時的容量為 0 時,去 WeakOrder 中轉移部分對象到 Stack 中
        if (!scavenge()) {
            return null;
        }
        //由於在transfer(Stack<?> dst)的過程中,可能會將其他線程的WeakOrderQueue中的DefaultHandle對象傳遞到當前的Stack,
        //所以size發生了變化,需要重新賦值
        size = this.size;
    }
    //size表示整個stack中的大小
    size--;
    //獲取最後一個元素
    DefaultHandle ret = elements[size];
    if (ret.lastRecycledId != ret.recycleId) {
        throw new IllegalStateException("recycled multiple times");
    }
    // 清空回收信息,以便判斷是否重複回收
    ret.recycleId = 0;
    ret.lastRecycledId = 0;
    this.size = size;
    return ret;
}
  1. 首先獲取當前的 Stack 中的 DefaultHandle 對象中的元素個數。
  2. 如果為 0,則從其他線程的與當前的 Stack 對象關聯的 WeakOrderQueue 中獲取元素,並轉移到 Stack 的 DefaultHandle[] 中(每一次 pop 只轉移一個有元素的 Link),如果轉移不成功,說明沒有元素可用,直接返回 null;
  3. 如果轉移成功,則重置 size屬性 = 轉移后的 Stack 的 DefaultHandle[] 的 size,之後直接獲取 Stack 對象中 DefaultHandle[] 的最後一位元素,之後做防護性檢測,最後重置當前的 stack 對象的 size 屬性以及獲取到的 DefaultHandle 對象的 recycledId 和 lastRecycledId 回收標記,返回 DefaultHandle 對象。

scavenge轉移

Stack#scavenge

boolean scavenge() {
    // continue an existing scavenge, if any
    // 掃描判斷是否存在可轉移的 Handler
    if (scavengeSome()) {
        return true;
    }
    
    // reset our scavenge cursor
    prev = null;
    cursor = head;
    return false;
}

調用scavengeSome掃描判斷是否存在可轉移的 Handler,如果沒有,那麼就返回false,表示沒有可用對象

Stack#scavengeSome

boolean scavengeSome() {
    WeakOrderQueue cursor = this.cursor;
    if (cursor == null) {
        cursor = head;
        // 如果head==null,表示當前的Stack對象沒有WeakOrderQueue,直接返回
        if (cursor == null) {
            return false;
        }
    }

    boolean success = false;
    WeakOrderQueue prev = this.prev;
    do {
        // 從當前的WeakOrderQueue節點進行 handler 的轉移
        if (cursor.transfer(this)) {
            success = true;
            break;
        }
        // 遍歷下一個WeakOrderQueue
        WeakOrderQueue next = cursor.next;
        // 如果 WeakOrderQueue 的實際持有線程因GC回收了
        if (cursor.owner.get() == null) {
            // If the thread associated with the queue is gone, unlink it, after
            // performing a volatile read to confirm there is no data left to collect.
            // We never unlink the first queue, as we don't want to synchronize on updating the head.
            // 如果當前的WeakOrderQueue的線程已經不可達了
            //如果該WeakOrderQueue中有數據,則將其中的數據全部轉移到當前Stack中
            if (cursor.hasFinalData()) {
                for (;;) {
                    if (cursor.transfer(this)) {
                        success = true;
                    } else {
                        break;
                    }
                }
            }
            //將當前的WeakOrderQueue的前一個節點prev指向當前的WeakOrderQueue的下一個節點,
            // 即將當前的WeakOrderQueue從Queue鏈表中移除。方便後續GC
            if (prev != null) {
                prev.next = next;
            }
        } else {
            prev = cursor;
        }

        cursor = next;

    } while (cursor != null && !success);

    this.prev = prev;
    this.cursor = cursor;
    return success;
}
  1. 首先設置當前操作的 WeakOrderQueue cursor,如果為 null,則賦值為 stack.head 節點,如果 stack.head 為 null,則表明外部線程沒有回收過當前線程創建的 對象,外部線程在回收對象的時候會創建一個WeakOrderQueue,並將stack.head 指向新創建的WeakOrderQueue對象,則直接返回 false;如果不為 null,則繼續向下執行;
  2. 首先對當前的 cursor 進行元素的轉移,如果轉移成功,則跳出循環,設置 prev 和 cursor 屬性;
  3. 如果轉移不成功,獲取下一個線程 Y 中的與當前線程的 Stack 對象關聯的 WeakOrderQueue,如果該 queue 所屬的線程 Y 還可達,則直接設置 cursor 為該 queue,進行下一輪循環;如果該 queue 所屬的線程 Y 不可達了,則判斷其內是否還有元素,如果有,全部轉移到當前線程的 Stack 中,之後將線程 Y 的 queue 從查詢 queue 鏈表中移除。

transfer轉移

    boolean transfer(Stack<?> dst) {
        //尋找第一個Link
        Link head = this.head;
        // head == null,沒有存儲數據的節點,直接返回
        if (head == null) {
            return false;
        }
        // 讀指針的位置已經到達了每個 Node 的存儲容量,如果還有下一個節點,進行節點轉移
        if (head.readIndex == LINK_CAPACITY) {
            //判斷當前的Link節點的下一個節點是否為null,如果為null,說明已經達到了Link鏈表尾部,直接返回,
            if (head.next == null) {
                return false;
            }
            // 否則,將當前的Link節點的下一個Link節點賦值給head和this.head.link,進而對下一個Link節點進行操作
            this.head = head = head.next;
        }
        // 獲取Link節點的readIndex,即當前的Link節點的第一個有效元素的位置
        final int srcStart = head.readIndex;
        // 獲取Link節點的writeIndex,即當前的Link節點的最後一個有效元素的位置
        int srcEnd = head.get();
        // 本次可轉移的對象數量(寫指針減去讀指針)
        final int srcSize = srcEnd - srcStart;
        if (srcSize == 0) {
            return false;
        }
        // 獲取轉移元素的目的地Stack中當前的元素個數
        final int dstSize = dst.size;
        // 計算期盼的容量
        final int expectedCapacity = dstSize + srcSize;
        // 期望的容量大小與實際 Stack 所能承載的容量大小進行比對,取最小值
        if (expectedCapacity > dst.elements.length) {
            final int actualCapacity = dst.increaseCapacity(expectedCapacity);
            srcEnd = Math.min(srcStart + actualCapacity - dstSize, srcEnd);
        }

        if (srcStart != srcEnd) {
            // 獲取Link節點的DefaultHandle[]
            final DefaultHandle[] srcElems = head.elements;
            // 獲取目的地Stack的DefaultHandle[]
            final DefaultHandle[] dstElems = dst.elements;
            // dst數組的大小,會隨着元素的遷入而增加,如果最後發現沒有增加,那麼表示沒有遷移成功任何一個元素
            int newDstSize = dstSize;
            //// 進行對象轉移
            for (int i = srcStart; i < srcEnd; i++) {
                DefaultHandle element = srcElems[i];
                // 表明自己還沒有被任何一個 Stack 所回收
                if (element.recycleId == 0) {
                    element.recycleId = element.lastRecycledId;
                //  避免對象重複回收
                } else if (element.recycleId != element.lastRecycledId) {
                    throw new IllegalStateException("recycled already");
                }
                // 將可轉移成功的DefaultHandle元素的stack屬性設置為目的地Stack
                element.stack = dst;
                // 將DefaultHandle元素轉移到目的地Stack的DefaultHandle[newDstSize ++]中
                dstElems[newDstSize++] = element;
                // 設置為null,清楚暫存的handler信息,同時幫助 GC
                srcElems[i] = null;
            }
            // 將新的newDstSize賦值給目的地Stack的size
            dst.size = newDstSize;

            if (srcEnd == LINK_CAPACITY && head.next != null) {
                // 將Head指向下一個Link,也就是將當前的Link給回收掉了
                // 假設之前為Head -> Link1 -> Link2,回收之後為Head -> Link2
                this.head = head.next;
            }
            // 設置讀指針位置
            head.readIndex = srcEnd;
            return true;
        } else {
            // The destination stack is full already.
            return false;
        }
    }
}
  1. 尋找 cursor 節點中的第一個 Link如果為 null,則表示沒有數據,直接返回;
  2. 如果第一個 Link 節點的 readIndex 索引已經到達該 Link 對象的 DefaultHandle[] 的尾部,則判斷當前的 Link 節點的下一個節點是否為 null,如果為 null,說明已經達到了 Link 鏈表尾部,直接返回,否則,將當前的 Link 節點的下一個 Link 節點賦值給 head ,進而對下一個 Link 節點進行操作;
  3. 獲取 Link 節點的 readIndex,即當前的 Link 節點的第一個有效元素的位置
  4. 獲取 Link 節點的 writeIndex,即當前的 Link 節點的最後一個有效元素的位置
  5. 計算 Link 節點中可以被轉移的元素個數,如果為 0,表示沒有可轉移的元素,直接返回
  6. 獲取轉移元素的目標 Stack 中當前的元素個數(dstSize)並計算期盼的容量 expectedCapacity,如果 expectedCapacity 大於目標Stack 的長度(dst.elements.length),則先對目的地 Stack 進行擴容,計算 Link 中最終的可轉移的最後一個元素的下標;
  7. 如果發現目的地 Stack 已經滿了( srcStart != srcEnd為false),則直接返回 false
  8. 獲取 Link 節點的 DefaultHandle[] (srcElems)和目標 Stack 的 DefaultHandle[](dstElems)
  9. 根據可轉移的起始位置和結束位置對 Link 節點的 DefaultHandle[] 進行循環操作
  10. 將可轉移成功的 DefaultHandle 元素的stack屬性設置為目標 Stack(element.stack = dst),將 DefaultHandle 元素轉移到目的地 Stack 的 DefaultHandle[newDstSize++] 中,最後置空 Link 節點的 DefaultHandle[i]
  11. 如果當前被遍歷的 Link 節點的 DefaultHandle[] 已經被掏空了(srcEnd == LINK_CAPACITY),並且該 Link 節點還有下一個 Link 節點
  12. 重置當前 Link 的 readIndex

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

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

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

SpringSecurity退出功能實現的正確方式

本文將介紹在Spring Security框架下如何實現用戶的”退出”logout的功能。其實這是一個非常簡單的功能,我見過很多的程序員在使用了Spring Security之後,仍然去自己寫controller方法實現logout功能,這種做法就好像耕地,你有机械設備你不用,你非要用牛。

一、logout最簡及最佳實踐

其實使用Spring Security進行logout非常簡單,只需要在spring Security配置類配置項上加上這樣一行代碼:http.logout()。關於spring Security配置類的其他很多實現、如:HttpBasic模式、formLogin模式、自定義登錄驗證結果、使用權限表達式、session會話管理,在本號的之前的文章已經都寫過了。本節的核心內容就是在原有配置的基礎上,加上這樣一行代碼:http.logout()。

@Configuration
@EnableWebSecurity
public class SecSecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http.logout();
   }

}

加上logout配置之後,在你的“退出”按鈕上使用/logtou作為請求登出的路徑。

<a href="/logout" >退出</a>

logout功能我們就完成了。實際上的核心代碼只有兩行。

二、默認的logout做了什麼?

雖然我們簡簡單單的實現了logout功能,是不是還不足夠放心?我們下面就來看一下Spring Security默認在logout過程中幫我們做了哪些動作。

  • 當前session失效,即:logout的核心需求,session失效就是訪問權限的回收。
  • 刪除當前用戶的 remember-me“記住我”功能信息
  • clear清除當前的 SecurityContext
  • 重定向到登錄頁面,loginPage配置項指定的頁面

通常對於一個應用來講,以上動作就是logout功能所需要具備的功能了。

三、個性化配置

雖然Spring Security默認使用了/logout作為退出處理請求路徑,登錄頁面作為退出之後的跳轉頁面。這符合絕大多數的應用的開發邏輯,但有的時候我們需要一些個性化設置,如下:

 http.logout()
     .logoutUrl("/signout")
     .logoutSuccessUrl("/aftersignout.html")
     .deleteCookies("JSESSIONID")
  • 通過指定logoutUrl配置改變退出請求的默認路徑,當然html退出按鈕的請求url也要修改
  • 通過指定logoutSuccessUrl配置,來顯式指定退出之後的跳轉頁面
  • 還可以使用deleteCookies刪除指定的cookie,參數為cookie的名稱

四、LogoutSuccessHandler

如果上面的個性化配置,仍然滿足不了您的應用需求。可能您的應用需要在logout的時候,做一些特殊動作,比如登錄時長計算,清理業務相關的數據等等。你可以通過實現LogoutSuccessHandler 接口來實現你的業務邏輯。

@Component
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
    
    @Override
    public void onLogoutSuccess(HttpServletRequest request, 
                                HttpServletResponse response, 
                                Authentication authentication) 
                                throws IOException, ServletException {
        //這裏書寫你自己的退出業務邏輯
        
        // 重定向到登錄頁
        response.sendRedirect("/login.html");
    }
}

然後進行配置使其生效,核心代碼就是一行logoutSuccessHandler。注意logoutSuccessUrl不要與logoutSuccessHandler一起使用,否則logoutSuccessHandler將失效。

@Configuration
@EnableWebSecurity
public class SecSecurityConfig extends WebSecurityConfigurerAdapter {
    
@Autowired
    private MyLogoutSuccessHandler myLogoutSuccessHandler;

    @Override
    protected void configure(final HttpSecurity http) throws Exception {
         http.logout()
             .logoutUrl("/signout")
             //.logoutSuccessUrl(``"/aftersignout.html"``)
             .deleteCookies("JSESSIONID")
              //自定義logoutSuccessHandler
             .logoutSuccessHandler(myLogoutSuccessHandler);   
   }
}

期待您的關注

  • 博主最近新寫了一本書:
  • 本文轉載註明出處(必須帶連接,不能只轉文字):。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!