作者:小傅哥
部落格:https://bugstack.cn
沉澱、分享、成長,讓自己和他人都能有所收穫!?
一、前言
老司機,你的磚怎麼搬的那麼快?
是有勁?是技巧?是後門?總之,那個老司機的程式碼總是可以很快的完成產品每次新增的需求,就像他倆是一家似的!而你就不一樣了,不只產品經理還有運營、測試的小姐姐,都得給你買吃的,求著你趕緊把Bug修修,否則都來不及上線了。
那為啥別人的程式碼總是可以很快的擴充套件新功能,而你的程式碼從來不能被重構只能被重寫,小需求小改、大需求大改,沒需求呢?沒需求自己跑著跑著也能崩潰,半夜被運維薅起來:“你這怎麼又有資料庫慢查詢,把別人業務都拖拉胯了!”
有人說30歲的人都,還和剛畢業的做一樣的活,是沒進步的! 這太扯淡了,同樣是同樣的活,但做出來的結果可不一定是一樣的,有人能用ifelse
把產品功能湊出來,也有人可以把需求拆解成各個功能模組,定義介面、抽象類、實現和繼承,運用設計模式構建出一套新增需求時候能快速實現,出現問題能準確定位的程式碼邏輯。這就像有人問:“樹上有十隻鳥,一槍打死一隻,還有幾隻?”,你會想到什麼?槍聲大嗎、鳥籠了嗎、鳥被綁樹上了嗎、有鳥殘疾的嗎、鳥被打死了嗎、打鳥的人眼睛好使嗎、算肚子裡懷孕的鳥嗎、打鳥犯法嗎、邊上樹還有其他鳥嗎等等,這些都是一個職業技術人在一個行業磨練出來的經驗,不是1天2天看幾本書,刷幾個洗腦文章能吸收的。
二、目標
交給 Spring 管理的 Bean 物件,一定就是我們用類建立出來的 Bean 嗎?建立出來的 Bean 就永遠是單例的嗎,沒有可能是原型模式嗎?
在集合 Spring 框架下,我們使用的 MyBatis 框架中,它的核心作用是可以滿足使用者不需要實現 Dao 介面類,就可以通過 xml 或者註解配置的方式完成對資料庫執行 CRUD 操作,那麼在實現這樣的 ORM 框架中,是怎麼把一個資料庫操作的 Bean 物件交給 Spring 管理的呢。
因為我們在使用 Spring、MyBatis 框架的時候都可以知道,並沒有手動的去建立任何運算元據庫的 Bean 物件,有的僅僅是一個介面定義,而這個介面定義竟然可以被注入到其他需要使用 Dao 的屬性中去了,那麼這一過程最核心待解決的問題,就是需要完成把複雜且以代理方式動態變化的物件,註冊到 Spring 容器中。而為了滿足這樣的一個擴充套件元件開發的需求,就需要我們在現有手寫的 Spring 框架中,新增這一能力。
三、方案
關於提供一個能讓使用者定義複雜的 Bean 物件,功能點非常不錯,意義也非常大,因為這樣做了之後 Spring 的生態種子孵化箱就此提供了,誰家的框架都可以在此標準上完成自己服務的接入。
但這樣的功能邏輯設計上並不複雜,因為整個 Spring 框架在開發的過程中就已經提供了各項擴充套件能力的接茬
,你只需要在合適的位置提供一個接茬的處理介面呼叫和相應的功能邏輯實現即可,像這裡的目標實現就是對外提供一個可以二次從 FactoryBean 的 getObject 方法中獲取物件的功能即可,這樣所有實現此介面的物件類,就可以擴充自己的物件功能了。MyBatis 就是實現了一個 MapperFactoryBean 類,在 getObject 方法中提供 SqlSession 對執行 CRUD 方法的操作 整體設計結構如下圖:
- 整個的實現過程包括了兩部分,一個解決單例還是原型物件,另外一個處理 FactoryBean 型別物件建立過程中關於獲取具體呼叫物件的
getObject
操作。 SCOPE_SINGLETON
、SCOPE_PROTOTYPE
,物件型別的建立獲取方式,主要區分在於AbstractAutowireCapableBeanFactory#createBean
建立完成物件後是否放入到記憶體中,如果不放入則每次獲取都會重新建立。- createBean 執行物件建立、屬性填充、依賴載入、前置後置處理、初始化等操作後,就要開始做執行判斷整個物件是否是一個 FactoryBean 物件,如果是這樣的物件,就需要再繼續執行獲取 FactoryBean 具體物件中的
getObject
物件了。整個 getBean 過程中都會新增一個單例型別的判斷factory.isSingleton()
,用於決定是否使用記憶體存放物件資訊。
四、實現
1. 工程結構
small-spring-step-09
└── src
├── main
│ └── java
│ └── cn.bugstack.springframework
│ ├── beans
│ │ ├── factory
│ │ │ ├── config
│ │ │ │ ├── AutowireCapableBeanFactory.java
│ │ │ │ ├── BeanDefinition.java
│ │ │ │ ├── BeanFactoryPostProcessor.java
│ │ │ │ ├── BeanPostProcessor.java
│ │ │ │ ├── BeanReference.java
│ │ │ │ ├── ConfigurableBeanFactory.java
│ │ │ │ └── SingletonBeanRegistry.java
│ │ │ ├── support
│ │ │ │ ├── AbstractAutowireCapableBeanFactory.java
│ │ │ │ ├── AbstractBeanDefinitionReader.java
│ │ │ │ ├── AbstractBeanFactory.java
│ │ │ │ ├── BeanDefinitionReader.java
│ │ │ │ ├── BeanDefinitionRegistry.java
│ │ │ │ ├── CglibSubclassingInstantiationStrategy.java
│ │ │ │ ├── DefaultListableBeanFactory.java
│ │ │ │ ├── DefaultSingletonBeanRegistry.java
│ │ │ │ ├── DisposableBeanAdapter.java
│ │ │ │ ├── FactoryBeanRegistrySupport.java
│ │ │ │ ├── InstantiationStrategy.java
│ │ │ │ └── SimpleInstantiationStrategy.java
│ │ │ ├── support
│ │ │ │ └── XmlBeanDefinitionReader.java
│ │ │ ├── Aware.java
│ │ │ ├── BeanClassLoaderAware.java
│ │ │ ├── BeanFactory.java
│ │ │ ├── BeanFactoryAware.java
│ │ │ ├── BeanNameAware.java
│ │ │ ├── ConfigurableListableBeanFactory.java
│ │ │ ├── DisposableBean.java
│ │ │ ├── FactoryBean.java
│ │ │ ├── HierarchicalBeanFactory.java
│ │ │ ├── InitializingBean.java
│ │ │ └── ListableBeanFactory.java
│ │ ├── BeansException.java
│ │ ├── PropertyValue.java
│ │ └── PropertyValues.java
│ ├── context
│ │ ├── support
│ │ │ ├── AbstractApplicationContext.java
│ │ │ ├── AbstractRefreshableApplicationContext.java
│ │ │ ├── AbstractXmlApplicationContext.java
│ │ │ ├── ApplicationContextAwareProcessor.java
│ │ │ └── ClassPathXmlApplicationContext.java
│ │ ├── ApplicationContext.java
│ │ ├── ApplicationContextAware.java
│ │ └── ConfigurableApplicationContext.java
│ ├── core.io
│ │ ├── ClassPathResource.java
│ │ ├── DefaultResourceLoader.java
│ │ ├── FileSystemResource.java
│ │ ├── Resource.java
│ │ ├── ResourceLoader.java
│ │ └── UrlResource.java
│ └── utils
│ └── ClassUtils.java
└── test
└── java
└── cn.bugstack.springframework.test
├── bean
│ ├── UserDao.java
│ └── UserService.java
└── ApiTest.java
工程原始碼:公眾號「bugstack蟲洞棧」,回覆:Spring 專欄,獲取完整原始碼
Spring 單例、原型以及 FactoryBean
功能實現類關係,如圖 10-2
- 以上整個類關係圖展示的就是新增 Bean 的例項化是單例還是原型模式以及 FactoryBean 的實現。
- 其實整個實現的過程並不複雜,只是在現有的 AbstractAutowireCapableBeanFactory 類以及繼承的抽象類 AbstractBeanFactory 中進行擴充套件。
- 不過這次我們把 AbstractBeanFactory 繼承的 DefaultSingletonBeanRegistry 類,中間加了一層 FactoryBeanRegistrySupport,這個類在 Spring 框架中主要是處理關於 FactoryBean 註冊的支撐操作。
2. Bean的作用範圍定義和xml解析
cn.bugstack.springframework.beans.factory.config.BeanDefinition
public class BeanDefinition {
String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
private Class beanClass;
private PropertyValues propertyValues;
private String initMethodName;
private String destroyMethodName;
private String scope = SCOPE_SINGLETON;
private boolean singleton = true;
private boolean prototype = false;
// ...get/set
}
- singleton、prototype,是本次在 BeanDefinition 類中新增加的兩個屬性資訊,用於把從 spring.xml 中解析到的 Bean 物件作用範圍填充到屬性中。
cn.bugstack.springframework.beans.factory.xml.XmlBeanDefinitionReader
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
protected void doLoadBeanDefinitions(InputStream inputStream) throws ClassNotFoundException {
for (int i = 0; i < childNodes.getLength(); i++) {
// 判斷元素
if (!(childNodes.item(i) instanceof Element)) continue;
// 判斷物件
if (!"bean".equals(childNodes.item(i).getNodeName())) continue;
// 解析標籤
Element bean = (Element) childNodes.item(i);
String id = bean.getAttribute("id");
String name = bean.getAttribute("name");
String className = bean.getAttribute("class");
String initMethod = bean.getAttribute("init-method");
String destroyMethodName = bean.getAttribute("destroy-method");
String beanScope = bean.getAttribute("scope");
// 獲取 Class,方便獲取類中的名稱
Class<?> clazz = Class.forName(className);
// 優先順序 id > name
String beanName = StrUtil.isNotEmpty(id) ? id : name;
if (StrUtil.isEmpty(beanName)) {
beanName = StrUtil.lowerFirst(clazz.getSimpleName());
}
// 定義Bean
BeanDefinition beanDefinition = new BeanDefinition(clazz);
beanDefinition.setInitMethodName(initMethod);
beanDefinition.setDestroyMethodName(destroyMethodName);
if (StrUtil.isNotEmpty(beanScope)) {
beanDefinition.setScope(beanScope);
}
// ...
// 註冊 BeanDefinition
getRegistry().registerBeanDefinition(beanName, beanDefinition);
}
}
}
- 在解析 XML 處理類 XmlBeanDefinitionReader 中,新增加了關於 Bean 物件配置中 scope 的解析,並把這個屬性資訊填充到 Bean 定義中。
beanDefinition.setScope(beanScope)
3. 建立和修改物件時候判斷單例和原型模式
cn.bugstack.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();
@Override
protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {
Object bean = null;
try {
bean = createBeanInstance(beanDefinition, beanName, args);
// 給 Bean 填充屬性
applyPropertyValues(beanName, bean, beanDefinition);
// 執行 Bean 的初始化方法和 BeanPostProcessor 的前置和後置處理方法
bean = initializeBean(beanName, bean, beanDefinition);
} catch (Exception e) {
throw new BeansException("Instantiation of bean failed", e);
}
// 註冊實現了 DisposableBean 介面的 Bean 物件
registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);
// 判斷 SCOPE_SINGLETON、SCOPE_PROTOTYPE
if (beanDefinition.isSingleton()) {
addSingleton(beanName, bean);
}
return bean;
}
protected void registerDisposableBeanIfNecessary(String beanName, Object bean, BeanDefinition beanDefinition) {
// 非 Singleton 型別的 Bean 不執行銷燬方法
if (!beanDefinition.isSingleton()) return;
if (bean instanceof DisposableBean || StrUtil.isNotEmpty(beanDefinition.getDestroyMethodName())) {
registerDisposableBean(beanName, new DisposableBeanAdapter(bean, beanName, beanDefinition));
}
}
// ... 其他功能
}
- 單例模式和原型模式的區別就在於是否存放到記憶體中,如果是原型模式那麼就不會存放到記憶體中,每次獲取都重新建立物件,另外非 Singleton 型別的 Bean 不需要執行銷燬方法。
- 所以這裡的程式碼會有兩處修改,一處是 createBean 中判斷是否新增到 addSingleton(beanName, bean);,另外一處是 registerDisposableBeanIfNecessary 銷燬註冊中的判斷
if (!beanDefinition.isSingleton()) return;
。
4. 定義 FactoryBean 介面
cn.bugstack.springframework.beans.factory.FactoryBean
public interface FactoryBean<T> {
T getObject() throws Exception;
Class<?> getObjectType();
boolean isSingleton();
}
- FactoryBean 中需要提供3個方法,獲取物件、物件型別,以及是否是單例物件,如果是單例物件依然會被放到記憶體中。
5. 實現一個 FactoryBean 註冊服務
cn.bugstack.springframework.beans.factory.support.FactoryBeanRegistrySupport
public abstract class FactoryBeanRegistrySupport extends DefaultSingletonBeanRegistry {
/**
* Cache of singleton objects created by FactoryBeans: FactoryBean name --> object
*/
private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<String, Object>();
protected Object getCachedObjectForFactoryBean(String beanName) {
Object object = this.factoryBeanObjectCache.get(beanName);
return (object != NULL_OBJECT ? object : null);
}
protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName) {
if (factory.isSingleton()) {
Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) {
object = doGetObjectFromFactoryBean(factory, beanName);
this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT));
}
return (object != NULL_OBJECT ? object : null);
} else {
return doGetObjectFromFactoryBean(factory, beanName);
}
}
private Object doGetObjectFromFactoryBean(final FactoryBean factory, final String beanName){
try {
return factory.getObject();
} catch (Exception e) {
throw new BeansException("FactoryBean threw exception on object[" + beanName + "] creation", e);
}
}
}
- FactoryBeanRegistrySupport 類主要處理的就是關於 FactoryBean 此類物件的註冊操作,之所以放到這樣一個單獨的類裡,就是希望做到不同領域模組下只負責各自需要完成的功能,避免因為擴充套件導致類膨脹到難以維護。
- 同樣這裡也定義了快取操作 factoryBeanObjectCache,用於存放單例型別的物件,避免重複建立。在日常使用用,基本也都是建立的單例物件
- doGetObjectFromFactoryBean 是具體的獲取 FactoryBean#getObject() 方法,因為既有快取的處理也有物件的獲取,所以額外提供了 getObjectFromFactoryBean 進行邏輯包裝,這部分的操作方式是不和你日常做的業務邏輯開發非常相似。從Redis取資料,如果為空就從資料庫獲取並寫入Redis
6. 擴充套件 AbstractBeanFactory 建立物件邏輯
cn.bugstack.springframework.beans.factory.support.AbstractBeanFactory
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
protected <T> T doGetBean(final String name, final Object[] args) {
Object sharedInstance = getSingleton(name);
if (sharedInstance != null) {
// 如果是 FactoryBean,則需要呼叫 FactoryBean#getObject
return (T) getObjectForBeanInstance(sharedInstance, name);
}
BeanDefinition beanDefinition = getBeanDefinition(name);
Object bean = createBean(name, beanDefinition, args);
return (T) getObjectForBeanInstance(bean, name);
}
private Object getObjectForBeanInstance(Object beanInstance, String beanName) {
if (!(beanInstance instanceof FactoryBean)) {
return beanInstance;
}
Object object = getCachedObjectForFactoryBean(beanName);
if (object == null) {
FactoryBean<?> factoryBean = (FactoryBean<?>) beanInstance;
object = getObjectFromFactoryBean(factoryBean, beanName);
}
return object;
}
// ...
}
- 首先這裡把 AbstractBeanFactory 原來繼承的 DefaultSingletonBeanRegistry,修改為繼承 FactoryBeanRegistrySupport。因為需要擴充套件出建立 FactoryBean 物件的能力,所以這就想一個鏈條服務上,截出一個段來處理額外的服務,並把鏈條再連結上。
- 此處新增加的功能主要是在 doGetBean 方法中,新增了呼叫
(T) getObjectForBeanInstance(sharedInstance, name)
對獲取 FactoryBean 的操作。 - 在 getObjectForBeanInstance 方法中做具體的 instanceof 判斷,另外還會從 FactoryBean 的快取中獲取物件,如果不存在則呼叫 FactoryBeanRegistrySupport#getObjectFromFactoryBean,執行具體的操作。
五、測試
1. 事先準備
cn.bugstack.springframework.test.bean.IUserDao
public interface IUserDao {
String queryUserName(String uId);
}
- 這個章節我們刪掉 UserDao,定義一個 IUserDao 介面,之所這樣做是為了通過 FactoryBean 做一個自定義物件的代理操作。
cn.bugstack.springframework.test.bean.UserService
public class UserService {
private String uId;
private String company;
private String location;
private IUserDao userDao;
public String queryUserInfo() {
return userDao.queryUserName(uId) + "," + company + "," + location;
}
// ...get/set
}
- 在 UserService 新修改了一個原有 UserDao 屬性為 IUserDao,後面我們會給這個屬性注入代理物件。
2. 定義 FactoryBean 物件
cn.bugstack.springframework.test.bean.ProxyBeanFactory
public class ProxyBeanFactory implements FactoryBean<IUserDao> {
@Override
public IUserDao getObject() throws Exception {
InvocationHandler handler = (proxy, method, args) -> {
Map<String, String> hashMap = new HashMap<>();
hashMap.put("10001", "小傅哥");
hashMap.put("10002", "八杯水");
hashMap.put("10003", "阿毛");
return "你被代理了 " + method.getName() + ":" + hashMap.get(args[0].toString());
};
return (IUserDao) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{IUserDao.class}, handler);
}
@Override
public Class<?> getObjectType() {
return IUserDao.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
- 這是一個實現介面 FactoryBean 的代理類 ProxyBeanFactory 名稱,主要是模擬了 UserDao 的原有功能,類似於 MyBatis 框架中的代理操作。
- getObject() 中提供的就是一個 InvocationHandler 的代理物件,當有方法呼叫的時候,則執行代理物件的功能。
3. 配置檔案
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="userService" class="cn.bugstack.springframework.test.bean.UserService" scope="prototype">
<property name="uId" value="10001"/>
<property name="company" value="騰訊"/>
<property name="location" value="深圳"/>
<property name="userDao" ref="proxyUserDao"/>
</bean>
<bean id="proxyUserDao" class="cn.bugstack.springframework.test.bean.ProxyBeanFactory"/>
</beans>
- 在配置檔案中,我們把 proxyUserDao 這個代理物件,注入到 userService 的 userDao 中。與上一章節相比,去掉了 UserDao 實現類,轉而用代理類替換
4. 單元測試(單例&原型)
@Test
public void test_prototype() {
// 1.初始化 BeanFactory
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
applicationContext.registerShutdownHook();
// 2. 獲取Bean物件呼叫方法
UserService userService01 = applicationContext.getBean("userService", UserService.class);
UserService userService02 = applicationContext.getBean("userService", UserService.class);
// 3. 配置 scope="prototype/singleton"
System.out.println(userService01);
System.out.println(userService02);
// 4. 列印十六進位制雜湊
System.out.println(userService01 + " 十六進位制雜湊:" + Integer.toHexString(userService01.hashCode()));
System.out.println(ClassLayout.parseInstance(userService01).toPrintable());
}
- 在 spring.xml 配置檔案中,設定了 scope="prototype" 這樣就每次獲取到的物件都應該是一個新的物件。
- 這裡判斷物件是否為一個會看到列印的類物件的雜湊值,所以我們把十六進位制雜湊列印出來。
測試結果
cn.bugstack.springframework.test.bean.UserService$$EnhancerByCGLIB$$4cabb984@1b0375b3
cn.bugstack.springframework.test.bean.UserService$$EnhancerByCGLIB$$4cabb984@2f7c7260
cn.bugstack.springframework.test.bean.UserService$$EnhancerByCGLIB$$4cabb984@1b0375b3 十六進位制雜湊:1b0375b3
cn.bugstack.springframework.test.bean.UserService$$EnhancerByCGLIB$$4cabb984 object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 b3 75 03 (00000001 10110011 01110101 00000011) (58045185)
4 4 (object header) 1b 00 00 00 (00011011 00000000 00000000 00000000) (27)
8 4 (object header) 9f e1 01 f8 (10011111 11100001 00000001 11111000) (-134094433)
12 4 java.lang.String UserService.uId (object)
16 4 java.lang.String UserService.company (object)
20 4 java.lang.String UserService.location (object)
24 4 cn.bugstack.springframework.test.bean.IUserDao UserService.userDao (object)
28 1 boolean UserService$$EnhancerByCGLIB$$4cabb984.CGLIB$BOUND true
29 3 (alignment/padding gap)
32 4 net.sf.cglib.proxy.NoOp UserService$$EnhancerByCGLIB$$4cabb984.CGLIB$CALLBACK_0 (object)
36 4 (loss due to the next object alignment)
Instance size: 40 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
Process finished with exit code 0
- 物件後面的這一小段字串就是16進位制雜湊值,在物件頭雜湊值存放的結果上看,也有對應的數值。只不過這個結果是倒過來的。
- 另外可以看到 cabb984@1b0375b3、cabb984@2f7c7260,這兩個物件的結尾16進位制雜湊值並不一樣,所以我們的原型模式是生效的。
5. 單元測試(代理物件)
@Test
public void test_factory_bean() {
// 1.初始化 BeanFactory
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
applicationContext.registerShutdownHook();
// 2. 呼叫代理方法
UserService userService = applicationContext.getBean("userService", UserService.class);
System.out.println("測試結果:" + userService.queryUserInfo());
}
- 關於 FactoryBean 的呼叫並沒有太多不一樣,因為所有的不同都已經被 spring.xml 配置進去了。當然你可以直接呼叫 spring.xml 配置的物件
cn.bugstack.springframework.test.bean.ProxyBeanFactory
測試結果
測試結果:你被代理了 queryUserName:小傅哥,騰訊,深圳
Process finished with exit code 0
- 從測試結果來看,我們的代理類 ProxyBeanFactory 已經完美替換掉了 UserDao 的功能。
- 雖然看上去這一點實現並不複雜,甚至有點簡單。但就是這樣一點點核心內容的設計了,解決了所有需要和 Spring 結合的其他框架互動連結問題。如果對此類內容感興趣,也可以閱讀小傅哥《中介軟體設計和開發》
六、總結
- 在 Spring 框架整個開發的過程中,前期的各個功能介面類擴充套件的像膨脹了似的,但到後期在完善功能時,就沒有那麼難了,反而深入理解後會覺得功能的補充,都比較簡單。只需要再所遇領域範圍內進行擴充套件相應的服務實現即可。
- 當你仔細閱讀完關於 FactoryBean 的實現以及測試過程的使用,以後再需要使用 FactoryBean 開發相應的元件時候,一定會非常清楚它是如何建立自己的複雜 Bean 物件以及在什麼時候初始化和呼叫的。遇到問題也可以快速的排查、定位和解決。
- 如果你在學習的過程中感覺這些類、介面、實現、繼承,穿梭的很複雜,一時半會腦子還反應不過來。那麼你最好的方式是動手去畫畫這些類關係圖,梳理下實現的結構,看看每個類在幹什麼。看只能是知道,動手才能學會!