RBAC是什麼,能解決什麼難題?
RBAC是Role-Based Access Control的首字母,譯成中文即基於角色的權限訪問控制,說白了也就是用戶通過角色與權限進行關聯[其架構靈感來源於操作系統的GBAC(GROUP-Based Access Control)的權限管理控制]。簡單的來說,一個用戶可以擁有若干角色,每一個角色擁有若干權限。這樣,就構造成“用戶-角色-權限”的授權模型。在這種模型中,用戶與角色之間,角色與權限之間,一般者是多對多的關係。其對應關係如下:
在許多的實際應用中,系統不只是需要用戶完成簡單的註冊,還需要對不同級別的用戶對不同資源的訪問具有不同的操作權限。且在企業開發中,權限管理系統也成了重複開發效率最高的一個模塊之一。而在多套系統中,對應的權限管理只能滿足自身系統的管理需要,無論是在數據庫設計、權限訪問和權限管理機制方式上都可能不同,這種不致性也就存在如下的憋端:
- 維護多套系統,重複造輪子,時間沒用在刀刃上
- 用戶管理、組織機制等數據重複維護,數據的完整性、一致性很難得到保障
- 權限系統設計不同,概念理解不同,及相應技術差異,系統之間集成存在問題,單點登錄難度大,也複雜的企業系統帶來困難
RBAC是基於不斷實踐之後,提出的一個比較成熟的訪問控制方案。實踐表明,採用基於RBAC模型的權限管理系統具有以下優勢:由於角色、權限之間的變化比角色、用戶關係之間的變化相對要慢得多,減小了授權管理的複雜性,降低管理開銷;而且能夠靈活地支持應用系統的安全策略,並對應用系統的變化有很大的伸縮性;在操作上,權限分配直觀、容易理解,便於使用;分級權限適合分層的用戶級形式;重用性強。
ThinkPHP中RBAC實現體系
ThinkPHP中RBAC基於Java的Spring的Acegi安全系統作爲參考原型,並做了相應的簡化處理,以適應當前的ThinkPHP結構,提供一個多層、可定製的安全體系來爲應用開發提供安全控制。安全體系中主要有以下幾部分:
- 安全攔截器
- 認證管理器
- 決策訪問管理器
- 運行身份管理器
安全攔截器
安全攔截器就好比一道道門,在系統的安全防護系統中可能存在很多不同的安全控制環節,一旦某個環節你未通過安全體系認證,那麼安全攔截器就會實施攔截。
認證管理器
防護體系的第一道門就是認證管理器,認證管理器負責決定你是誰,一般它通過驗證你的主體(通常是一個用戶名)和你的憑證(通常是一個密碼),或者更多的資料來做到。更簡單的說,認證管理器驗證你的身份是否在安全防護體系授權範圍之內。
訪問決策管理
雖然通過了認證管理器的身份驗證,但是並不代表你可以在系統裏面肆意妄爲,因爲你還需要通過訪問決策管理這道門。訪問決策管理器對用戶進行授權,通過考慮你的身份認證信息和與受保護資源關聯的安全屬性決定是是否可以進入系統的某個模塊,和進行某項操作。例如,安全規則規定只有主管才允許訪問某個模塊,而你並沒有被授予主管權限,那麼安全攔截器會攔截你的訪問操作。
決策訪問管理器不能單獨運行,必須首先依賴認證管理器進行身份確認,因此,在加載訪問決策過濾器的時候已經包含了認證管理器和決策訪問管理器。
爲了滿足應用的不同需要,ThinkPHP 在進行訪問決策管理的時候採用兩種模式:登錄模式和即時模式。登錄模式,系統在用戶登錄的時候讀取改用戶所具備的授權信息到 Session,下次不再重新獲取授權信息。也就是說即使管理員對該用戶進行了權限修改,用戶也必須在下次登錄後才能生效。即時模式就是爲了解決上面的問題,在每次訪問系統的模塊或者操作時候,進行即使驗證該用戶是否具有該模塊和操作的授權,從更高程度上保障了系統的安全。
運行身份管理器
運行身份管理器的用處在大多數應用系統中是有限的,例如某個操作和模塊需要多個身份的安全需求,運行身份管理器可以用另一個身份替換你目前的身份,從而允許你訪問應用系統內部更深處的受保護對象。這一層安全體系目前的 RBAC 中尚未實現。
ThinkPHP中RBAC認證流程
對應上面的安全體系,ThinkPHP 的 RBAC 認證的過程大致如下:
- 判斷當前模塊的當前操作是否需要認證
- 如果需要認證並且尚未登錄,跳到認證網關,如果已經登錄 執行5
- 通過委託認證進行用戶身份認證
- 獲取用戶的決策訪問列表
- 判斷當前用戶是否具有訪問權限
權限管理的具體實現過程
RBAC相關的數據庫介紹
在ThinkPHP完整包,包含了RBAC處理類RBAC.class.php文件,
位於Extend/Library/ORG/Util
。打開該文件,其中就包含了使用RBAC必備的4張表,SQL語句如下(複製後請替換表前綴):
CREATE TABLE IF NOT EXISTS `ly_access` (
`role_id` smallint(6) unsigned NOT NULL,
`node_id` smallint(6) unsigned NOT NULL,
`level` tinyint(1) NOT NULL,
`module` varchar(50) DEFAULT NULL,
KEY `groupId` (`role_id`),
KEY `nodeId` (`node_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `ly_node` (
`id` smallint(6) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`title` varchar(50) DEFAULT NULL,
`status` tinyint(1) DEFAULT '0',
`remark` varchar(255) DEFAULT NULL,
`sort` smallint(6) unsigned DEFAULT NULL,
`pid` smallint(6) unsigned NOT NULL,
`level` tinyint(1) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `level` (`level`),
KEY `pid` (`pid`),
KEY `status` (`status`),
KEY `name` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `ly_role` (
`id` smallint(6) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`pid` smallint(6) DEFAULT NULL,
`status` tinyint(1) unsigned DEFAULT NULL,
`remark` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `pid` (`pid`),
KEY `status` (`status`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ;
CREATE TABLE IF NOT EXISTS `ly_role_user` (
`role_id` mediumint(9) unsigned DEFAULT NULL,
`user_id` char(32) DEFAULT NULL,
KEY `group_id` (`role_id`),
KEY `user_id` (`user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
下面對RBAC相關的數據庫表及字段作一下介紹:
表名 | 字段名 | 字段類型 | 作用 |
---|---|---|---|
ly_user | id | INT | 用戶ID(唯一識別號) |
username | VARCHAR(16) | 用戶名 | |
password | VARCHAR(32) | 密碼 | |
VARCHAR(100) | 用戶郵箱 | ||
create_time | TIMESTAMP | 創建時間(時間戳) | |
logintime | TIMESTAMP | 最近一次登錄時間(時間戳) | |
loginip | VARCHAR(15) | 最近登錄的IP地址 | |
status | TINYINT(1) | 啓用狀態:0:表示禁用;1:表示啓用 | |
remark | VARCHAR(255) | 備註信息 | |
ly_role | id | INT | 角色ID |
name | VARCHAR(20) | 角色名稱 | |
pid | SMALLINT(6) | 父角色對應ID | |
status | TINYINT(1) | 啓用狀態(同上) | |
remark | VARCHAR(255) | 備註信息 | |
ly_node | id | SMALLINT(6) | 節點ID |
name | VARCHAR(20) | 節點名稱(英文名,對應應用控制器、應用、方法名) | |
title | VARCHAR(50) | 節點中文名(方便看懂) | |
status | TINYINT(1) | 啓用狀態(同上) | |
remark | VARCHAR(255) | 備註信息 | |
sort | SMALLINT(6) | 排序值(默認值爲50) | |
pid | SMALLINT(6) | 父節點ID(如:方法pid對應相應的控制器) | |
level | TINYINT(1) | 節點類型:1:表示應用(模塊);2:表示控制器;3:表示方法 | |
ly_role_user | user_id | INT | 用戶ID |
role_id | SMALLINT(6) | 角色ID | |
ly_access | role_id | SMALLINT(6) | 角色ID |
node_id | SMALLINT(6) | 節點ID | |
level | |||
module |
實現RBAC管理的前導性工作
基於ThinkPHP實現RBAC的權限管理系統中,首先要做一些前導性的工作(系統數據庫設計TP已經爲我們完成了),主要分以下幾個方面:
- 用戶(增、刪、改、查)
- 角色(增、刪、改、查)
- 節點(增、刪、改、查)
- 配置權限(更新權限)
具體實現的代碼如下(相關解釋均在註釋之中):
<?php
/**
* Created by PhpStorm.
* User: LiuYang
* Date: 14-9-6
* Time: 下午9:54
* Description: 基於ThinkPHP實現的權限管理系統
*/
class RbacAction extends CommonAction {
//初始化操作
function _initialize(){
if(!IS_AJAX) $this->error('你訪問的頁面不存在,請稍後再試');
}
//用戶列表
public function index(){
$db = M('user');
//當前頁碼
$pageNum = I('post.pageNum',1,'int');
//每頁顯示條數
$numPerPage = I('post.numPerPage',C("numPerPage"),'int');
//總頁碼數
$totalCount = $db->count();
$this->totalCount = $totalCount;
$this->numPerPage = $numPerPage;
$this->items = D('UserRelation')->relation(true)->page($pageNum, $numPerPage)->select();
$this->display();
}
//添加編輯用戶彈層表單
public function addUser(){
//如果設置了uid,則爲編輯用戶,否則爲增加用戶
$this->role = M('role')->where('status = 1')->field('id,name')->select();
if(isset($_GET['uid'])) {
$this->userinfo = M('user')->where( "id = $_GET[uid]" )->find();
}
$this->display();
}
//添加及編輯用戶表單處理
public function addUserHandler(){
$db = M('user');
if($_POST['id']) {
//如果存在ID,即表示更新
$data = array(
'id' => I('post.id','','int'),
'username' => I('username', '', 'string'),
'status' => I('status','', 'int'),
'remark' => I('remark'),
'logintime' => time(),
'loginip' => get_client_ip()
);
if($_POST['password']) $data['password'] = I('password','', 'md5');
if($db->save($data)) {
$roleuser = M('role_user');
$roleuser->where("id = $data[id]")->delete();
$roleuser->add(array(
'role_id' => I('role','','intval'),
'user_id' => $data[id]
));
$this->ajaxReturn(array(
'statusCode' => 200,
'message' => '更新成功'
));
} else {
$this->ajaxReturn(array(
'statusCode' => 300,
'message' => '操作失敗'
));
}
return ;
}
//添加表單處理
$data = array(
'username' => I('username', '', 'string'),
'password' => I('password', '', 'md5'),
'status' => I('status','', 'int'),
'remark' => I('remark'),
'logintime' => time(),
'loginip' => get_client_ip()
);
if($uid = M('user')->add($data)) {
$roleuser = M('role_user');
$roleuser->where("id = $uid")->delete();
$roleuser->add(array(
'role_id' => I('role','','intval'),
'user_id' => $uid
));
$this->ajaxReturn(array(
'statusCode' => 200,
'message' => '操作成功',
'navTabId' => '',
'rel' => '',
'callbackType' => '',
'forwardUrl' => '',
'confirmMsg' => ''
));
} else {
$this->ajaxReturn(array(
'statusCode' => 300,
'message' => '操作失敗'
));
}
}
//啓用或楚用用戶
public function resume(){
$id = I('get.id','0','int');
$db = M('user');
$status = $db->where("id = $id")->getField('status');
$status = ($status == 1)? 0 : 1 ;
if($db->where("id = $id")->setField('status', $status)){
$this->ajaxReturn(array(
'statusCode' => 1,
'message' => '操作成功',
'navTabId' =>$_GET['navTabId']
));
} else {
$this->ajaxReturn(array(
'statusCode' => 0,
'message' => '操作失敗'
));
}
}
//刪除用戶
public function deleteUserHandler(){
$id = I('get.uid',0,'int');
if( M('user')->delete($id) ) {
$this->ajaxReturn(array(
'statusCode' => 1,
'message' => '刪除成功',
'navTabId' => $_GET['navTabId']
));
} else {
$this->ajaxReturn(array(
'statusCode' => 0,
'message' => '刪除成功',
'navTabId' => $_GET['navTabId']
));
}
}
//節點列表
public function node(){
$node = M('node')->where(array('status'=>1))->order('sort')->select();
$this->node = node_merge($node);
$this->display();
}
//添加及編輯節點彈層表單
public function addNode(){
//添加表單默認情況
$this->info = array(
'level' => I('get.level',1,'int'),
'pid' => I('get.pid',0,'int'),
'status' => 1,
'sort' => 50
);
switch ($this->info['level']){
case 1: {
$this->label = "應用";
break;
}
case 2: {
$this->label = "控制器";
break;
}
case 3: {
$this->label = "方法";
break;
}
}
if($_GET['id']) {
//編輯模式
$this->info = M('node')->where(array('id'=>$_GET['id']))->find();
}
$this->display();
}
//添加及編輯節點表單處理
public function addNodeHandler(){
$id = $_POST['id'];
$db = M('node');
if($id) {
//更新
if($db->save($_POST)) {
$this->ajaxReturn(array(
'statusCode' => 200,
'message' => '添加成功',
'navTabId' => $_GET['navTabId']
));
} else {
$this->ajaxReturn(array(
'statusCode' => 300,
'message' => '更新失敗',
'navTabId' => $_GET['navTabId']
));
}
}else {
//保存
if($db->add($_POST)) {
$this->ajaxReturn(array(
'statusCode' => 200,
'message' => '添加成功',
'navTabId' => $_GET['navTabId']
));
} else {
$this->ajaxReturn(array(
'statusCode' => 300,
'message' => '添加失敗',
'navTabId' => $_GET['navTabId']
));
}
}
}
//刪除節點
public function deleteNodeHandler(){
$id = I('get.id','0','int');
$db = M('node');
$data = $db->where(array('pid'=>$id))->field('id')->find();
if($data) {
$this->ajaxReturn(array(
'statusCode' => 0,
'message' => '你請求刪除的節點存在子節點,不可直接刪除'
));
} else {
if($db->delete($id)) {
$this->ajaxReturn(array(
'statusCode'=> 1,
'message' => '刪除成功'
));
} else {
$this->ajaxReturn(array(
'statusCode' => 0,
'message' => '刪除失敗'
));
}
}
//if($data['level'] === 3)
}
//角色管理
public function role(){
$this->role = M('role')->select();
$this->display();
}
//添加及編輯角色
public function addRole(){
if($_GET['rid']) {
$id = I('get.rid',0,'int');
$this->role = M('role')->find($id);
}
$this->display();
}
//添加角色表單處理
public function addRoleHandler(){
$db = M('role');
if($_POST['id']) {
//更新
if($db->save($_POST)) {
$this->ajaxReturn(array(
'statusCode'=> 200,
'message' => "角色信息更新成功"
));
} else {
$this->ajaxReturn(array(
'statusCode' => "300",
'message' => '角色信息更新失敗'
));
}
} else {
//添加表單處理
if($db ->add($_POST)){
$this->ajaxReturn(array(
'statusCode'=> 200,
'message' => "角色添加成功"
));
}else {
$this->ajaxReturn(array(
'statusCode' => 300,
'message' => '角色添加失敗'
));
}
}
}
//刪除角色
public function deleteRole(){
}
//快束啓用或楚用用戶
public function resumeRole(){
$id = I('get.rid',0, 'int');
$db = M('role');
$status = $db->where("id = $id")->getField('status');
$status = ($status == 1)? 0 : 1 ;
if($db->where("id = $id")->setField('status', $status)){
$this->ajaxReturn(array(
'statusCode' => 1,
'message' => '操作成功',
'navTabId' =>$_GET['navTabId']
));
} else {
$this->ajaxReturn(array(
'statusCode' => 0,
'message' => '操作失敗'
));
}
}
//給用戶添加節點權限
public function access(){
$rid = I('rid',0 ,'intval');
$node = M('node')->where(array('status'=>1))->field(array('id','title','pid','name','level'))->order('sort')->select();
//獲取原有權限
$access = M('access')->where("role_id = $rid")->getField('node_id',true);
$this->node = node_merge($node,$access);
$this->assign('rid',$rid)->display();
}
//添加節點權限表單處理
public function accessHandler(){
$rid = I('rid', '', 'intval');
$db = M('access');
//清空原有權限
$db->where("role_id = $rid")->delete();
//插入新的權限
$data = array();
foreach ($_POST['access'] as $v) {
$tmp = explode('_', $v);
$data[] = array(
'role_id'=> $rid,
'node_id'=> $tmp[0],
'level'=>$tmp[1]
);
}
if($db->addAll($data)) {
$this->ajaxReturn(array(
'statusCode'=> 200,
'message' => '權限更新成功'
));
} else {
$this->ajaxReturn(array(
'statusCode' => 300,
'message' => '權限更新失敗'
));
}
}
}
ThinkPHP中RBAC類的詳解
在ThinkPHP處理權限管理中,真正的難點並不是在RBAC類的使用上,上面相關的處理(權限配置,節點管理等);所以當完成以上工作,其實RBAC系統已經完成了90%。下面先從ThinkPHP中RBAC的配置說起(詳細請參看對應的註釋內容): <?php
/**
* Created by PhpStorm.
* User: LiuYang
* Date: 14-9-29
* Time: 下午9:36
* Description: ThinkPHP中RBAC處理類的配置文件
*/
return array(
"USER_AUTH_ON" => true, //是否開啓權限驗證(必配)
"USER_AUTH_TYPE" => 1, //驗證方式(1、登錄驗證;2、實時驗證)
"USER_AUTH_KEY" => 'uid', //用戶認證識別號(必配)
"ADMIN_AUTH_KEY" => 'superadmin', //超級管理員識別號(必配)
"USER_AUTH_MODEL" => 'user', //驗證用戶表模型 ly_user
'USER_AUTH_GATEWAY' => '/Public/login', //用戶認證失敗,跳轉URL
'AUTH_PWD_ENCODER'=>'md5', //默認密碼加密方式
"RBAC_SUPERADMIN" => 'admin', //超級管理員名稱
"NOT_AUTH_MODULE" => 'Index,Public', //無需認證的控制器
"NOT_AUTH_ACTION" => 'index', //無需認證的方法
'REQUIRE_AUTH_MODULE' => '', //默認需要認證的模塊
'REQUIRE_AUTH_ACTION' => '', //默認需要認證的動作
'GUEST_AUTH_ON' => false, //是否開啓遊客授權訪問
'GUEST_AUTH_ID' => 0, //遊客標記
"RBAC_ROLE_TABLE" => 'ly_role', //角色表名稱(必配)
"RBAC_USER_TABLE" => 'ly_role_user', //用戶角色中間表名稱(必配)
"RBAC_ACCESS_TABLE" => 'ly_access', //權限表名稱(必配)
"RBAC_NODE_TABLE" => 'ly_node', //節點表名稱(必配)
);
注意:
- 以上有的配置項並非必須的,但標有“必配”,則必須配置
- 無需認證的權限和方法與需要認證的模塊和動作可以根據需要選擇性配置,還有部分權限相關配置並未列表出
RBAC處理類提供靜態的方法
ThinkPHP的RBAC處理類提供給我們很多相關的靜態方法如下:
方法名 | 接收參數說明 | 返回值 | 說明 |
---|---|---|---|
RBAC::authenticate($map,$model=”); |
|
array |
返回值是在用戶表中,以$map爲條件 |
0RBAC::saveAccessList($authId=null); |
|
返回一個空值 | 如果驗證方式爲登錄驗證,則將權限寫入session中,否則不作任何處理 |
RBAC::getRecordAccessList($authId=null,$module=”); |
|
Array | 返回一個包含權限的ID的數組 |
RBAC::checkAccess() | 無 | 返回true或false | 檢查當前操作是否需要認證(根據配置中需要認證和不需要評論的模塊或方法得出) |
RBAC::checkLogin() | 無 | true | 如果當前操作需要認證且用戶沒有登錄,繼續檢測是否開啓遊客授權。如果開啓遊客授權,則寫入遊客權限;否則跳到登錄頁 |
RBAC::AccessDecision($appName=APP_NAME) |
|
|
AccessDecision($appName=APP_NAME)方法,檢測當前項目模塊操作,是否存在於$_SESSION[‘_ACCESS_LIST’]數組中$_SESSION[‘_ACCESS_LIST’][‘當前操作’][‘當前模塊’][‘當前操作’]是否存在。如果存在表示有權限,返回true;否則返回flase。 |
RBAC::getAccessList($authId) |
|
Array | 通過數據庫查詢取得當前認證號的所有權限列表 |
RBAC::getModuleAccessList($authId,$module) |
|
Array | 返回指定用戶可訪問的節點權限數組 |
RBAC::AccessDecision()
方法時,如果你開啓了項目分組,則必須傳入當前分組,代碼如下: //權限驗證
if(C('USER_AUTH_ON') && !$notAuth) {
import('ORG.Util.RBAC');
//使用了項目分組,則必須引入GROUP_NAME
RBAC::AccessDecision(GROUP_NAME) || $this->error("你沒有對應的權限");
}
RBAC處理類的實際應用
在完成用戶登錄,角色創建,節點增刪改查的工作後,就只剩下了RBAC如何在對應程序代碼中應用了。挻簡單的,只用在原來的代碼其他上改動幾個地方即可。
- 用戶登錄時,寫入用戶權限
- 用戶操作時,進行權限驗證
下面是用戶登錄時的實現代碼:
<?php
/**
* Created by PhpStorm.
* User: LiuYang
* Date: 14-8-24
* Time: 下午5:23
* Description: 用戶登戶及退出控制器
*/
class LoginAction extends Action {
//用戶登錄視圖
public function index(){
//...
}
//用戶登錄處理表單
public function loginHandle(){
if(!IS_POST) halt('頁面不存在,請稍後再試');
if(session('verify') != I('param.verify','','md5')) {
$this->error('驗證碼錯誤', U('Admin/Login/index'));
}
$user = I('username','','string');
$passwd = I('password','','md5');
$db = M('user');
$userinfo = $db->where("username = '$user' AND password = '$passwd'")->find();
if(!$userinfo) $this->error('用戶名或密碼錯誤', U('Admin/Login/index'));
if(!$userinfo['status']) $this->error('該用戶被鎖定,暫時不可登錄', U('Admin/Login/index'));
//更新登錄信息
$db->save(array("id"=> $userinfo["id"], "logintime"=> time(), "loginip" => get_client_ip()));
//寫入session值
session(C("USER_AUTH_KEY"), $userinfo["id"]);
session("username", $userinfo["username"]);
session("logintime", $userinfo["logintime"]);
session("loginip",$user["loginip"]);
//如果爲超級管理員,則無需驗證
if($userinfo['username'] == C('RBAC_SUPERADMIN')) {
session(C('ADMIN_AUTH_KEY'), true);
}
//載入RBAC類
import('ORG.Util.RBAC');
//讀取用戶權限
RBAC::saveAccessList();
$this->success('登錄成功', U('Admin/Index/index'));
}
//登出登錄
public function logOut(){
//...
}
//驗證碼
public function verify(){
//...
}
}
接着在控制器目錄創建一個
CommonAction.class.php
文件,然後改寫所有要權限驗證的類,讓其繼承自CommonAction
。代碼如下: <?php
/**
* Created by PhpStorm.
* User: LiuYang
* Date: 14-9-3
* Time: 下午8:40
* Description: 操作權限驗證
*/
class CommonAction extends Action {
function _initialize(){
if(!isset($_SESSION[C('USER_AUTH_KEY')])) {
$this->redirect('Admin/Login/index');
}
$notAuth = in_array(MODULE_NAME, explode(',', C('NOT_AUTH_MODULE'))) || in_array(ACTION_NAME, C('NOT_AUTH_ACTION'));
//權限驗證
if(C('USER_AUTH_ON') && !$notAuth) {
import('ORG.Util.RBAC');
//使用了項目分組,則必須引入GROUP_NAME
RBAC::AccessDecision(GROUP_NAME) || $this->error("你沒有對應的權限");
}
}
}
在
ThinkPHP
中提供了一個_initialize()
方法,是在類初始化就會執行的,也就是隻要後面控制器繼承自CommonAction
類,就會在作對應操作時,執行_initialize()
方法。