Spring原始碼分析-BeanFactoryPostProcessors 應用之 PropertyPlaceholderConfigurer
BeanFactoryPostProcessors
介紹
BeanFactoryPostProcessors完整定義:
/** * Allows for custom modification of an application context's bean definitions, * adapting the bean property values of the context's underlying bean factory. * @see BeanPostProcessor * @see PropertyResourceConfigurer */ public interface BeanFactoryPostProcessor { /** * Modify the application context's internal bean factory after its standard * initialization. All bean definitions will have been loaded, but no beans * will have been instantiated yet. This allows for overriding or adding * properties even to eager-initializing beans. * @param beanFactory the bean factory used by the application context * @throws org.springframework.beans.BeansException in case of errors */ void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException; }
我們知道spring最大優點就是其可擴充套件性,BeanFactoryPostProcessor介面就是spring中提供給我們用於擴充套件的一個地方。我們看該介面上的javadoc其實非常的詳細,這也是我們看spring原始碼的一個技巧,就是看一個類是幹嘛的一定要先通讀其註釋。
結合介面上的註釋大致描述下BeanFactoryPostProcessor: 允許使用者通過修改applicationContext 中的bean定義(就是xml中定義的bean的資訊即:BeanDefinition是和xml有一對一的配置,比如是否是單利,以及propert 屬性的賦值等) 來調整applicationContext中bean工廠中bean屬性值。 也就是說執行到BeanFactoryPostProcessor時全部的BeanDefinition定義已經載入好了但是bean例項還沒有被建立,我們可以修補或者覆蓋bean屬性值。
我們可以看一下ApplicationContext中BeanFactoryPostProcessor的呼叫位置來印證是否如此
下面是ApplicationContext核心程式碼:
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); //獲取beanFactory例項 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); prepareBeanFactory(beanFactory); try { postProcessBeanFactory(beanFactory); //這裡正是我們的BeanFactoryPostProcessor執行的位置 invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. //建立非懶載入的所有單例 這裡是真正建立bean例項的地方 finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } } }
經過
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
這兩步我們已經拿到了beanFactory例項,也就是每一個bean對應的BeanDefinition已經載入好了。下面才是執行invokeBeanFactoryPostProcessors(beanFactory),也就印證了我們上面的結論。
下面我們通過一個BeanFactoryPostProcessor的典型應用PropertyPlaceholderConfigurer來詳細講解BeanFactoryPostProcessor執行原理
PropertyPlaceholderConfigurer
簡單使用的例子
PropertyPlaceholderConfigurer相信大家都使用過,我們在配置bean的屬性可以使用佔位符來賦值,然後通過調整properties檔案中對應的屬性值來修改。看一個使用PropertyPlaceholderConfigurer簡單的例子:
public class Student {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
<bean class="com.yalunwang.Student" id="student">
<property name="name" value="${student.name}"></property>
<property name="age" value="${student.age}"></property>
</bean>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:bean.properties"></property>
</bean>
bean.properties配置檔案:
student.name=anan
student.age=2
單元測試:
@Test
public void test_ioc_app(){
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-ioc.xml");
Student student =(Student) ac.getBean("student");
System.out.println("name: "+student.getName());
System.out.println("age: "+student.getAge());
}
輸出結果一切正常:
name: anan
age: 2
原始碼分析
先看一下PropertyPlaceholderConfigurer的類繼承圖:
可以看到PropertyPlaceholderConfigurer實現了BeanFactoryPostProcessor和 PriorityOrdered。
我們接著對上面的 invokeBeanFactoryPostProcessors(beanFactory)繼續進行分析:
/**
* Instantiate and invoke all registered BeanFactoryPostProcessor beans,
* respecting explicit order if given.
* 例項化並呼叫所有已註冊的BeanFactoryPostProcessor Bean,
* 如果繼承了Order介面按順序執行
* <p>Must be called before singleton instantiation.
*/
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
// Invoke BeanDefinitionRegistryPostProcessors first, if any.
Set<String> processedBeans = new HashSet<String>();
//如果是beanFactory實現了BeanDefinitionRegistry
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
List<BeanFactoryPostProcessor> regularPostProcessors = new LinkedList<BeanFactoryPostProcessor>();
List<BeanDefinitionRegistryPostProcessor> registryPostProcessors =
new LinkedList<BeanDefinitionRegistryPostProcessor>();
//遍歷硬編碼設定的beanFactory後置處理器
for (BeanFactoryPostProcessor postProcessor : getBeanFactoryPostProcessors()) {
//如果是BeanDefinitionRegistryPostProcessor型別先執行postProcessBeanDefinitionRegistry方法再將其新增到registryPostProcessors集合中進行後續postProcessBeanFactory方法的執行
if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
BeanDefinitionRegistryPostProcessor registryPostProcessor =
(BeanDefinitionRegistryPostProcessor) postProcessor;
registryPostProcessor.postProcessBeanDefinitionRegistry(registry);
registryPostProcessors.add(registryPostProcessor);
}
else {
//同理將正常的beanFactory後置處理器新增到regularPostProcessors集合中進行後續postProcessBeanFactory方法的執行
regularPostProcessors.add(postProcessor);
}
}
//找出配置的BeanDefinitionRegistryPostProcessor後置處理器
Map<String, BeanDefinitionRegistryPostProcessor> beanMap =
beanFactory.getBeansOfType(BeanDefinitionRegistryPostProcessor.class, true, false);
List<BeanDefinitionRegistryPostProcessor> registryPostProcessorBeans =
new ArrayList<BeanDefinitionRegistryPostProcessor>(beanMap.values());
OrderComparator.sort(registryPostProcessorBeans);
//執行BeanDefinitionRegistryPostProcessor後置處理器的postProcessBeanDefinitionRegistry方法
for (BeanDefinitionRegistryPostProcessor postProcessor : registryPostProcessorBeans) {
postProcessor.postProcessBeanDefinitionRegistry(registry);
}
//執行上面新增的beanFactory後置處理器的集合裡的方法
invokeBeanFactoryPostProcessors(registryPostProcessors, beanFactory);
invokeBeanFactoryPostProcessors(registryPostProcessorBeans, beanFactory);
invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
//處理後的新增到集合裡 防止後面重複執行
processedBeans.addAll(beanMap.keySet());
}
else {
// Invoke factory processors registered with the context instance.
invokeBeanFactoryPostProcessors(getBeanFactoryPostProcessors(), beanFactory);
}
//獲取配置的BeanFactoryPostProcessor
//以下按實現了 PriorityOrdered Ordered 沒有繼承 三種進行優先順序排序執行
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);
List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<BeanFactoryPostProcessor>();
List<String> orderedPostProcessorNames = new ArrayList<String>();
List<String> nonOrderedPostProcessorNames = new ArrayList<String>();
for (String ppName : postProcessorNames) {
//這個就是上面記錄的如果已經處理了配置的BeanFactoryPostProcessors就跳過
if (processedBeans.contains(ppName)) {
// skip - already processed in first phase above
}
else if (isTypeMatch(ppName, PriorityOrdered.class)) {
priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
}
else if (isTypeMatch(ppName, Ordered.class)) {
orderedPostProcessorNames.add(ppName);
}
else {
nonOrderedPostProcessorNames.add(ppName);
}
}
// First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered.
OrderComparator.sort(priorityOrderedPostProcessors);
//上面我們說了PropertyPlaceholderConfigurer 實現了BeanFactoryPostProcessor和 PriorityOrdered,所以會在這一步執行呼叫
invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
// Next, invoke the BeanFactoryPostProcessors that implement Ordered.
List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<BeanFactoryPostProcessor>();
for (String postProcessorName : orderedPostProcessorNames) {
orderedPostProcessors.add(getBean(postProcessorName, BeanFactoryPostProcessor.class));
}
OrderComparator.sort(orderedPostProcessors);
invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);
// Finally, invoke all other BeanFactoryPostProcessors.
List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<BeanFactoryPostProcessor>();
for (String postProcessorName : nonOrderedPostProcessorNames) {
nonOrderedPostProcessors.add(getBean(postProcessorName, BeanFactoryPostProcessor.class));
}
invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);
}
上面主要的執行邏輯我都添加了中文註釋方便大家理解。
總結一下改方法主要做的事情:
- 先判斷如果beanFactory是BeanDefinitionRegistry型別的話就新增對BeanDefinitionRegistryPostProcessor型別的呼叫,BeanDefinitionRegistryPostProcessor介面是BeanFactoryPostProcessor的子介面,BeanDefinitionRegistryPostProcessor的作用是方便我們可以手動註冊bean交給spring來管理,可以通過擴充套件其唯一的方法(void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;) 來註冊bean到spring裡,這個不是本次分析的重點後面再舉例講解。
- 先對硬編碼配置的BeanFactoryPostProcessor進行處理 如果是BeanDefinitionRegistryPostProcessor型別會進行postProcessBeanDefinitionRegistry呼叫和postProcessBeanFactory呼叫,如果不是則只進行postProcessBeanFactory呼叫。
- 再對配置的BeanDefinitionRegistryPostProcessor進行處理(postProcessBeanDefinitionRegistry呼叫和postProcessBeanFactory呼叫)
- 最後對配置BeanFactoryPostProcessor的進行處理會按 PriorityOrdered/Ordered/沒有繼承任何排序介面 三種進行優先順序排序執行postProcessBeanFactory呼叫。其中我們的PropertyPlaceholderConfigurer 實現了BeanFactoryPostProcessor和 PriorityOrdered,所以會在這一步執行呼叫
invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
- ** 我們繼續進行分析:**
invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
會執行PropertyPlaceholderConfigurer父類PropertyResourceConfigurer中的方法
/**
* {@linkplain #mergeProperties Merge}, {@linkplain #convertProperties convert} and
* {@linkplain #processProperties process} properties against the given bean factory.
* @throws BeanInitializationException if any properties cannot be loaded
*/
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
Properties mergedProps = mergeProperties();
// Convert the merged properties, if necessary.
convertProperties(mergedProps);
// Let the subclass process the properties.
processProperties(beanFactory, mergedProps);
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
mergeProperties()方法會先將 配置的properties載入到mergedProps裡
後面呼叫** processProperties(beanFactory, mergedProps)**;進行處理
- processProperties(beanFactory, mergedProps)分析
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
throws BeansException {
StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);
this.doProcessProperties(beanFactoryToProcess, valueResolver);
}
這裡只有兩行程式碼,第一行是建立StringValueResolver例項(用於替換佔位符的真正方法)
我們跨過千山萬水終於要到馬上要進行佔位符替換了,繼續分析
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
StringValueResolver valueResolver) {
//將建立的valueResolver設定到BeanDefinitionVisitor裡 用於最終替換邏輯 (替換佔位符為對應properties裡配置的值)
BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
//拿出ioc容器裡註冊的beans
String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
for (String curName : beanNames) {
//排除掉當前beanName也就是 PropertyPlaceholderConfigurer Bean 且beanFactoryToProcess必須是當前容器
if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
//獲取一個bean對應的bean定義
BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
try {
//進行bean定義元資料的替換操作
visitor.visitBeanDefinition(bd);
}
catch (Exception ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage());
}
}
}
// New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
beanFactoryToProcess.resolveAliases(valueResolver);
// New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}
上面我已經使用中文註釋寫的很清楚了接著進行分析
- visitor.visitBeanDefinition(bd);
public void visitBeanDefinition(BeanDefinition beanDefinition) {
visitParentName(beanDefinition);
visitBeanClassName(beanDefinition);
visitFactoryBeanName(beanDefinition);
visitFactoryMethodName(beanDefinition);
visitScope(beanDefinition);
visitPropertyValues(beanDefinition.getPropertyValues());
ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
visitIndexedArgumentValues(cas.getIndexedArgumentValues());
visitGenericArgumentValues(cas.getGenericArgumentValues());
}
這裡可以對應 bean的parentName beanClassName property等進行替換操作我們這裡只關注屬性的替換操作
- visitPropertyValues(beanDefinition.getPropertyValues());
protected void visitPropertyValues(MutablePropertyValues pvs) {
PropertyValue[] pvArray = pvs.getPropertyValues();
for (PropertyValue pv : pvArray) {
Object newVal = resolveValue(pv.getValue());
if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) {
pvs.add(pv.getName(), newVal);
}
}
}
- Object newVal = resolveValue(pv.getValue());
@SuppressWarnings("rawtypes")
protected Object resolveValue(Object value) {
if (value instanceof BeanDefinition) {
visitBeanDefinition((BeanDefinition) value);
}
else if (value instanceof BeanDefinitionHolder) {
visitBeanDefinition(((BeanDefinitionHolder) value).getBeanDefinition());
}
else if (value instanceof RuntimeBeanReference) {
RuntimeBeanReference ref = (RuntimeBeanReference) value;
String newBeanName = resolveStringValue(ref.getBeanName());
if (!newBeanName.equals(ref.getBeanName())) {
return new RuntimeBeanReference(newBeanName);
}
}
else if (value instanceof RuntimeBeanNameReference) {
RuntimeBeanNameReference ref = (RuntimeBeanNameReference) value;
String newBeanName = resolveStringValue(ref.getBeanName());
if (!newBeanName.equals(ref.getBeanName())) {
return new RuntimeBeanNameReference(newBeanName);
}
}
else if (value instanceof Object[]) {
visitArray((Object[]) value);
}
else if (value instanceof List) {
visitList((List) value);
}
else if (value instanceof Set) {
visitSet((Set) value);
}
else if (value instanceof Map) {
visitMap((Map) value);
}
else if (value instanceof TypedStringValue) {
TypedStringValue typedStringValue = (TypedStringValue) value;
String stringValue = typedStringValue.getValue();
if (stringValue != null) {
String visitedString = resolveStringValue(stringValue);
typedStringValue.setValue(visitedString);
}
}
else if (value instanceof String) {
return resolveStringValue((String) value);
}
return value;
}
這裡有很多型別,是因為spring支援很多型別的配置比如property的值我們可以配置為ref=xxxbean那麼value就是RuntimeBeanReference型別,
如果配置
<list>
<value>343</value>
<value>45</value>
</list>
那麼value就是List型別等等。這裡我們例子中配置的型別是TypedStringValue,那麼執行
else if (value instanceof TypedStringValue) {
TypedStringValue typedStringValue = (TypedStringValue) value;
//這裡拿到的值是原始的帶有佔位符的比如例子裡就是${student.name}、${student.age}這種
String stringValue = typedStringValue.getValue();
if (stringValue != null) {
//這一步就是去進行替換
String visitedString = resolveStringValue(stringValue);
typedStringValue.setValue(visitedString);
}
}
- String visitedString = resolveStringValue(stringValue);
/**
* Resolve the given String value, for example parsing placeholders.
* @param strVal the original String value
* @return the resolved String value
*/
protected String resolveStringValue(String strVal) {
if (this.valueResolver == null) {
throw new IllegalStateException("No StringValueResolver specified - pass a resolver " +
"object into the constructor or override the 'resolveStringValue' method");
}
//呼叫我們之前傳進來的valueResolver也就是上面 (BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver); 這裡傳進來的) 進行替換操作
String resolvedValue = this.valueResolver.resolveStringValue(strVal);
// Return original String if not modified.
return (strVal.equals(resolvedValue) ? strVal : resolvedValue);
}
valueResolver其實就是PlaceholderResolvingStringValueResolver例項,它又委託PropertyPlaceholderHelper進行操作
也就是
/**
* Replaces all placeholders of format {@code ${name}} with the value returned
* from the supplied {@link PlaceholderResolver}.
* @param value the value containing the placeholders to be replaced.
* @param placeholderResolver the {@code PlaceholderResolver} to use for replacement.
* @return the supplied value with placeholders replaced inline.
*/
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "Argument 'value' must not be null.");
return parseStringValue(value, placeholderResolver, new HashSet<String>());
}
protected String parseStringValue(
String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
StringBuilder buf = new StringBuilder(strVal);
int startIndex = strVal.indexOf(this.placeholderPrefix);
while (startIndex != -1) {
int endIndex = findPlaceholderEndIndex(buf, startIndex);
if (endIndex != -1) {
//將ex. ${student.name} 轉成 student.name
String placeholder = buf.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
// 遞迴呼叫直到沒有佔位符
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// 獲取properties配置檔案對應的值 就是獲取student.name 對應在 properties裡的值
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
if (propVal == null && this.valueSeparator != null) {
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
String actualPlaceholder = placeholder.substring(0, separatorIndex);
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
if (propVal == null) {
propVal = defaultValue;
}
}
}
if (propVal != null) {
//再次遞迴呼叫 確保最後的值沒有佔位符
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
//將原先的StringBuff的原始值替換為拿到的值
buf.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
//這裡因為已經替換成真正的值 拿不到佔位符(${)所以值就是-1 會跳出迴圈返回
startIndex = buf.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
else if (this.ignoreUnresolvablePlaceholders) {
// Proceed with unprocessed value.
startIndex = buf.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
else {
throw new IllegalArgumentException("Could not resolve placeholder '" +
placeholder + "'" + " in string value \"" + strVal + "\"");
}
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;
}
}
return buf.toString();
}
該方法是真正替換的操作所在,改方法已經新增中文註釋應該很好理解了,總結就是做了一下兩件事:
1.首先會將ex. ${student.name} 轉成 student.name
2.將student.name通過 tring propVal = placeholderResolver.resolvePlaceholder(placeholder);獲取真正的值然後返回。
protected String resolvePlaceholder(String placeholder, Properties props, int systemPropertiesMode) {
String propVal = null;
if (systemPropertiesMode == SYSTEM_PROPERTIES_MODE_OVERRIDE) {
propVal = resolveSystemProperty(placeholder);
}
if (propVal == null) {
propVal = resolvePlaceholder(placeholder, props);
}
if (propVal == null && systemPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) {
propVal = resolveSystemProperty(placeholder);
}
return propVal;
}
該方法就比較簡單了就是根據不同的模式做處理,systemPropertiesMode預設是SYSTEM_PROPERTIES_MODE_FALLBACK
- 如果systemPropertiesMode = SYSTEM_PROPERTIES_MODE_OVERRIDE 是指 系統配置檔案優先順序大於我們的配置檔案。
- 拿不到或者配置不等於SYSTEM_PROPERTIES_MODE_OVERRIDE 就去我們配置檔案裡進行獲取
- 如果還獲取不到且如果systemPropertiesMode=SYSTEM_PROPERTIES_MODE_FALLBACK就再嘗試去系統檔案裡查詢。
結論
經過以上各個步驟最終BeanDefinition裡的parentName beanClassName property中的佔位符都會被我們propertis配置檔案中對應的值所替換掉,這就為後續例項化bean後做bean例項屬性填充時做好了準備。
我們再進行 Student student =(Student) ac.getBean("student"); 時就可以正常列印對應的值了。
看原始碼的一點分享tip
- 看原始碼可能會比較枯燥,堅持很重要,如果看一遍是懵的那就是歇一歇再繼續多看幾遍這樣慢慢就會找到感覺
- 看的過程中一定要寫例子進行除錯,然後再繼續反覆看慢慢就會明白其中的含義
- 看原始碼可以看某一個核心類的繼承圖,以及javadoc。還可以嘗試畫出核心的時序圖,這些都能對我們看懂原始碼起到事半功倍的作用。
至此本篇原始碼分析就結束了,後續繼續spring原始碼分析的文章。加油!!