1. 程式人生 > 其它 >Spring的BeanFactoryPostProcessor和BeanPostProcessor

Spring的BeanFactoryPostProcessor和BeanPostProcessor

BeanFactoryPostProcessor和BeanPostProcessor,這兩個介面,都是Spring初始化bean時對外暴露的擴充套件點。兩個介面名稱看起來很相似,但作用及使用場景卻不同,分析如下:

1、BeanFactoryPostProcessor介面

該介面的定義如下:


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的bean建立之前,修改bean的定義屬性。也就是說,Spring允許BeanFactoryPostProcessor在容器例項化任何其它bean之前讀取配置元資料,並可以根據需要進行修改,例如可以把bean的scope從singleton改為prototype,也可以把property的值給修改掉。可以同時配置多個BeanFactoryPostProcessor,並通過設定'order'屬性來控制各個BeanFactoryPostProcessor的執行次序。
注意:BeanFactoryPostProcessor是在spring容器載入了bean的定義檔案之後,在bean例項化之前執行的。介面方法的入參是ConfigurrableListableBeanFactory,使用該引數,可以獲取到相關bean的定義資訊,例子:
1)spring bean的定義:


<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"
default-autowire="byName">

<bean id="myJavaBean" class="com.ali.caihj.postprocessor.MyJavaBean">
<property name="desc" value="測試一下啦" />
<property name="remark" value="這是備註資訊啦啦啦" />
</bean>
<bean id="myBeanFactoryPostProcessor" class="com.ali.caihj.postprocessor.MyBeanFactoryPostProcessor" />
</beans>
2)自定義的BeanFactoryPostProcessor:


public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("呼叫MyBeanFactoryPostProcessor的postProcessBeanFactory");
BeanDefinition bd = beanFactory.getBeanDefinition("myJavaBean");
System.out.println("屬性值============" + bd.getPropertyValues().toString());
MutablePropertyValues pv = bd.getPropertyValues();
if (pv.contains("remark")) {
pv.addPropertyValue("remark", "把備註資訊修改一下");
}
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
}

}
spring中,有內建的一些BeanFactoryPostProcessor實現類,常用的有:
org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
org.springframework.beans.factory.config.PropertyOverrideConfigurer
org.springframework.beans.factory.config.CustomEditorConfigurer:用來註冊自定義的屬性編輯器


2、BeanPostProcessor介面

該介面的定義如下:


public interface BeanPostProcessor {

/**
* Apply this BeanPostProcessor to the given new bean instance <i>before</i> any bean
* initialization callbacks (like InitializingBean's <code>afterPropertiesSet</code>
* or a custom init-method). The bean will already be populated with property values.
* The returned bean instance may be a wrapper around the original.
* @param bean the new bean instance
* @param beanName the name of the bean
* @return the bean instance to use, either the original or a wrapped one
* @throws org.springframework.beans.BeansException in case of errors
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
*/
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;

/**
* Apply this BeanPostProcessor to the given new bean instance <i>after</i> any bean
* initialization callbacks (like InitializingBean's <code>afterPropertiesSet</code>
* or a custom init-method). The bean will already be populated with property values.
* The returned bean instance may be a wrapper around the original.
* <p>In case of a FactoryBean, this callback will be invoked for both the FactoryBean
* instance and the objects created by the FactoryBean (as of Spring 2.0). The
* post-processor can decide whether to apply to either the FactoryBean or created
* objects or both through corresponding <code>bean instanceof FactoryBean</code> checks.
* <p>This callback will also be invoked after a short-circuiting triggered by a
* {@link InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation} method,
* in contrast to all other BeanPostProcessor callbacks.
* @param bean the new bean instance
* @param beanName the name of the bean
* @return the bean instance to use, either the original or a wrapped one
* @throws org.springframework.beans.BeansException in case of errors
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
* @see org.springframework.beans.factory.FactoryBean
*/
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;

}
BeanPostProcessor,可以在spring容器例項化bean之後,在執行bean的初始化方法前後,新增一些自己的處理邏輯。這裡說的初始化方法,指的是下面兩種:
1)bean實現了InitializingBean介面,對應的方法為afterPropertiesSet

2)在bean定義的時候,通過init-method設定的方法

注意:BeanPostProcessor是在spring容器載入了bean的定義檔案並且例項化bean之後執行的。BeanPostProcessor的執行順序是在BeanFactoryPostProcessor之後。

spring中,有內建的一些BeanPostProcessor實現類,例如:

org.springframework.context.annotation.CommonAnnotationBeanPostProcessor:支援@Resource註解的注入
org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor:支援@Required註解的注入
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor:支援@Autowired註解的注入
org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor:支援@PersistenceUnit和@PersistenceContext註解的注入
org.springframework.context.support.ApplicationContextAwareProcessor:用來為bean注入ApplicationContext等容器物件
這些註解類的BeanPostProcessor,在spring配置檔案中,可以通過這樣的配置 <context:component-scan base-package="*.*" /> ,自動進行註冊。(spring通過ComponentScanBeanDefinitionParser類來解析該標籤)

3、下面通過完整的一個例子,來加深理解

1)定義一個JavaBean

public class MyJavaBean implements InitializingBean {
private String desc;
private String remark;

public MyJavaBean() {
System.out.println("MyJavaBean的建構函式被執行啦");
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
System.out.println("呼叫setDesc方法");
this.desc = desc;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
System.out.println("呼叫setRemark方法");
this.remark = remark;
}
public void afterPropertiesSet() throws Exception {
System.out.println("呼叫afterPropertiesSet方法");
this.desc = "在初始化方法中修改之後的描述資訊";
}
public void initMethod() {
System.out.println("呼叫initMethod方法");
}
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[描述:").append(desc);
builder.append(", 備註:").append(remark).append("]");
return builder.toString();
}
}
2)定義一個BeanFactoryPostProcessor

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("呼叫MyBeanFactoryPostProcessor的postProcessBeanFactory");
BeanDefinition bd = beanFactory.getBeanDefinition("myJavaBean");
MutablePropertyValues pv = bd.getPropertyValues();
if (pv.contains("remark")) {
pv.addPropertyValue("remark", "在BeanFactoryPostProcessor中修改之後的備忘資訊");
}
}

}
3)定義一個BeanPostProcessor

public class MyBeanPostProcessor implements BeanPostProcessor {

public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor,物件" + beanName + "呼叫初始化方法之前的資料: " + bean.toString());
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor,物件" + beanName + "呼叫初始化方法之後的資料:" + bean.toString());
return bean;
}
}
4)spring的配置
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"
default-autowire="byName">

<bean id="myJavaBean" class="com.ali.caihj.postprocessor.MyJavaBean" init-method="initMethod">
<property name="desc" value="原始的描述資訊" />
<property name="remark" value="原始的備註資訊" />
</bean>

<bean id="myBeanPostProcessor" class="com.ali.caihj.postprocessor.MyBeanPostProcessor" />
<bean id="myBeanFactoryPostProcessor" class="com.ali.caihj.postprocessor.MyBeanFactoryPostProcessor" />
</beans>
5)測試類

public class PostProcessorMain {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("config/postprocessor.xml");
MyJavaBean bean = (MyJavaBean) context.getBean("myJavaBean");
System.out.println("===============下面輸出結果============");
System.out.println("描述:" + bean.getDesc());
System.out.println("備註:" + bean.getRemark());

}
}
6)執行結果如下:

7)分析

從上面的結果可以看出,BeanFactoryPostProcessor在bean例項化之前執行,之後例項化bean(呼叫建構函式,並呼叫set方法注入屬性值),然後在呼叫兩個初始化方法前後,執行了BeanPostProcessor。初始化方法的執行順序是,先執行afterPropertiesSet,再執行init-method。

4、進一步深入分析

在使用ApplicationContext啟動spring容器的時候,在AbstractApplicationContext.refresh()方法中,完成相關初始化工作:


1)BeanFactoryPostProcessor.postProcessBeanFactory,是在第5步執行的,invokeBeanFactoryPostProcessors方法實現如下:

/**
* Instantiate and invoke all registered BeanFactoryPostProcessor beans,
* respecting explicit order if given.
* <p>Must be called before singleton instantiation.
*/
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
// Invoke factory processors registered with the context instance.
for (Iterator it = getBeanFactoryPostProcessors().iterator(); it.hasNext();) {
BeanFactoryPostProcessor factoryProcessor = (BeanFactoryPostProcessor) it.next();
factoryProcessor.postProcessBeanFactory(beanFactory);
}

// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let the bean factory post-processors apply to them!
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);

// Separate between BeanFactoryPostProcessors that implement PriorityOrdered,
// Ordered, and the rest.
List priorityOrderedPostProcessors = new ArrayList();
List orderedPostProcessorNames = new ArrayList();
List nonOrderedPostProcessorNames = new ArrayList();
for (int i = 0; i < postProcessorNames.length; i++) {
if (isTypeMatch(postProcessorNames[i], PriorityOrdered.class)) {
priorityOrderedPostProcessors.add(beanFactory.getBean(postProcessorNames[i]));
}
else if (isTypeMatch(postProcessorNames[i], Ordered.class)) {
orderedPostProcessorNames.add(postProcessorNames[i]);
}
else {
nonOrderedPostProcessorNames.add(postProcessorNames[i]);
}
}

// First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered.
Collections.sort(priorityOrderedPostProcessors, new OrderComparator());
invokeBeanFactoryPostProcessors(beanFactory, priorityOrderedPostProcessors);

// Next, invoke the BeanFactoryPostProcessors that implement Ordered.
List orderedPostProcessors = new ArrayList();
for (Iterator it = orderedPostProcessorNames.iterator(); it.hasNext();) {
String postProcessorName = (String) it.next();
orderedPostProcessors.add(getBean(postProcessorName));
}
Collections.sort(orderedPostProcessors, new OrderComparator());
invokeBeanFactoryPostProcessors(beanFactory, orderedPostProcessors);

// Finally, invoke all other BeanFactoryPostProcessors.
List nonOrderedPostProcessors = new ArrayList();
for (Iterator it = nonOrderedPostProcessorNames.iterator(); it.hasNext();) {
String postProcessorName = (String) it.next();
nonOrderedPostProcessors.add(getBean(postProcessorName));
}
invokeBeanFactoryPostProcessors(beanFactory, nonOrderedPostProcessors);
}

/**
* Invoke the given BeanFactoryPostProcessor beans.
*/
private void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List postProcessors) {
for (Iterator it = postProcessors.iterator(); it.hasNext();) {
BeanFactoryPostProcessor postProcessor = (BeanFactoryPostProcessor) it.next();
postProcessor.postProcessBeanFactory(beanFactory);
}
}
通過beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false),獲取spring配置檔案中定義的所有實現BeanFactoryPostProcessor介面的bean,然後根據優先順序進行排序,之後對於每個BeanFactoryPostProcessor,呼叫postProcessBeanFactory方法。
2)而BeanPostProcessor的執行,取決於配置檔案中bean的定義,如果定義的bean是singleton並且不是抽象類,也不延遲初始化,則BeanPostProcessor是在第11步中執行;而對於prototype的bean,BeanPostProcessor是在程式getBean的時候執行的。在第6步中,呼叫registerBeanPostProcessors方法,註冊所有實現BeanPostProcessor介面的bean,該方法的實現如下:

protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);

// Register BeanPostProcessorChecker that logs an info message when
// a bean is created during BeanPostProcessor instantiation, i.e. when
// a bean is not eligible for getting processed by all BeanPostProcessors.
int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));

// Separate between BeanPostProcessors that implement PriorityOrdered,
// Ordered, and the rest.
List priorityOrderedPostProcessors = new ArrayList();
List orderedPostProcessorNames = new ArrayList();
List nonOrderedPostProcessorNames = new ArrayList();
for (int i = 0; i < postProcessorNames.length; i++) {
if (isTypeMatch(postProcessorNames[i], PriorityOrdered.class)) {
priorityOrderedPostProcessors.add(beanFactory.getBean(postProcessorNames[i]));
}
else if (isTypeMatch(postProcessorNames[i], Ordered.class)) {
orderedPostProcessorNames.add(postProcessorNames[i]);
}
else {
nonOrderedPostProcessorNames.add(postProcessorNames[i]);
}
}

// First, register the BeanPostProcessors that implement PriorityOrdered.
Collections.sort(priorityOrderedPostProcessors, new OrderComparator());
registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);

// Next, register the BeanPostProcessors that implement Ordered.
List orderedPostProcessors = new ArrayList();
for (Iterator it = orderedPostProcessorNames.iterator(); it.hasNext();) {
String postProcessorName = (String) it.next();
orderedPostProcessors.add(getBean(postProcessorName));
}
Collections.sort(orderedPostProcessors, new OrderComparator());
registerBeanPostProcessors(beanFactory, orderedPostProcessors);

// Finally, register all other BeanPostProcessors.
List nonOrderedPostProcessors = new ArrayList();
for (Iterator it = nonOrderedPostProcessorNames.iterator(); it.hasNext();) {
String postProcessorName = (String) it.next();
nonOrderedPostProcessors.add(getBean(postProcessorName));
}
registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);
}
在第11步中,呼叫finishBeanFactoryInitialization方法,該方法通過呼叫DefaultListableBeanFactory.preInstantiateSingletons(),進行相關初始化工作:

從上面的程式碼可以看出,對於非抽象類、非延遲初始化的單例bean,在spring容器啟動的時候呼叫getBean方法來例項化bean,並進行相關初始化工作,getBean方法最終呼叫AbstractAutowireCapableBeanFactory.doCreateBean方法,該方法的實現如下:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = (BeanWrapper) this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);

// Allow post-processors to modify the merged bean definition.
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
mbd.postProcessed = true;
}
}

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}

// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}

if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set actualDependentBeans = new LinkedHashSet(dependentBeans.length);
for (int i = 0; i < dependentBeans.length; i++) {
String dependentBean = dependentBeans[i];
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}

// Register bean as disposable.
registerDisposableBeanIfNecessary(beanName, bean, mbd);

return exposedObject;
}
在該方法中,首先呼叫createBeanInstance方法,建立bean例項物件(這個時候執行bean的構造方法),然後呼叫populateBean方法,對bean進行填充,注入相關依賴,之後再呼叫方法initializeBean,進行相關初始化工作,initializeBean方法的實現如下:
protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) {
if (bean instanceof BeanNameAware) {
((BeanNameAware) bean).setBeanName(beanName);
}

if (bean instanceof BeanClassLoaderAware) {
((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader());
}

if (bean instanceof BeanFactoryAware) {
((BeanFactoryAware) bean).setBeanFactory(this);
}

Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}

try {
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}

if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
從上面的實現可以看出,先呼叫applyBeanPostProcessorsBeforeInitialization方法,執行每個BeanPostProcessor的postProcessBeforeInitialization,然後呼叫invokeInitMethods方法,執行bean的初始化方法,最後呼叫applyBeanPostProcessorsAfterInitialization方法,執行每個BeanPostProcessor的postProcessAfterInitialization方法。這三個方法的實現如下:
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
throws BeansException {

Object result = existingBean;
for (Iterator it = getBeanPostProcessors().iterator(); it.hasNext();) {
BeanPostProcessor beanProcessor = (BeanPostProcessor) it.next();
result = beanProcessor.postProcessBeforeInitialization(result, beanName);
}
return result;
}

public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {

Object result = existingBean;
for (Iterator it = getBeanPostProcessors().iterator(); it.hasNext();) {
BeanPostProcessor beanProcessor = (BeanPostProcessor) it.next();
result = beanProcessor.postProcessAfterInitialization(result, beanName);
}
return result;
}
protected void invokeInitMethods(String beanName, Object bean, RootBeanDefinition mbd)
throws Throwable {

boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
if (logger.isDebugEnabled()) {
logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
((InitializingBean) bean).afterPropertiesSet();
}

String initMethodName = (mbd != null ? mbd.getInitMethodName() : null);
if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.isExternallyManagedInitMethod(initMethodName)) {
invokeCustomInitMethod(beanName, bean, initMethodName, mbd.isEnforceInitMethod());
}
}
從invokeInitMethods方法的實現可以看出,先執行afterPropertiesSet方法,然後再通過反射,執行init-method指定的方法。

原文連結:https://blog.csdn.net/caihaijiang/article/details/35552859