作者:小傅哥 博客:https://
? 沉淀、分享、成長(zhǎng),讓自己和他人都能有所收獲!??
? 目錄 一、前言
二、目標(biāo)
三、方案
四、實(shí)現(xiàn)
五、測(cè)試
1. 事先準(zhǔn)備
2. 屬性配置文件
3. 單元測(cè)試
六、總結(jié)
七、系列推薦
一、前言 寫(xiě)代碼,就是從能用到好用的不斷折騰!
你聽(tīng)過(guò)擾動(dòng)函數(shù)嗎?你寫(xiě)過(guò)斐波那契(Fibonacci)散列嗎?你實(shí)現(xiàn)過(guò)梅森旋轉(zhuǎn)算法嗎?怎么 沒(méi)聽(tīng)過(guò)這些寫(xiě)不了代碼嗎!不會(huì)的,即使沒(méi)聽(tīng)過(guò)你一樣可以寫(xiě)的了代碼,比如你實(shí)現(xiàn)的數(shù)據(jù)庫(kù)路由數(shù)據(jù)總是落在1庫(kù)1表它不散列分布、你實(shí)現(xiàn)的抽獎(jiǎng)系統(tǒng)總是把運(yùn)營(yíng)配置的最大紅包發(fā)出去提高了運(yùn)營(yíng)成本、你開(kāi)發(fā)的秒殺系統(tǒng)總是在開(kāi)始后的1秒就掛了貨品根本給不出去。
除了一部分僅把編碼當(dāng)成搬磚應(yīng)付工作外的程序員,還有一部分總是在追求極致的碼農(nóng)。寫(xiě)代碼還能賺錢(qián),真開(kāi)心! 這樣的碼農(nóng)總是會(huì)考慮??還有沒(méi)有更好的實(shí)現(xiàn)邏輯能讓代碼不僅是能用,還要好用呢?其實(shí)這一點(diǎn)的追求到完成,需要大量擴(kuò)展性學(xué)習(xí)和深度挖掘,這樣你設(shè)計(jì)出來(lái)的系統(tǒng)才更你考慮的更加全面,也能應(yīng)對(duì)各種復(fù)雜的場(chǎng)景。
二、目標(biāo) 在目前 IOC、AOP 兩大核心功能模塊的支撐下,完全可以管理 Bean 對(duì)象的注冊(cè)和獲取,不過(guò)這樣的使用方式總感覺(jué)像是刀耕火種有點(diǎn)難用。因此在上一章節(jié)我們解決需要手動(dòng)配置 Bean 對(duì)象到 spring.xml 文件中,改為可以自動(dòng)掃描帶有注解 @Component 的對(duì)象完成自動(dòng)裝配和注冊(cè)到 Spring 容器的操作。
那么在自動(dòng)掃描包注冊(cè) Bean 對(duì)象之后,就需要把原來(lái)在配置文件中通過(guò) property name="token" 配置屬性和Bean的操作,也改為可以自動(dòng)注入。這就像我們使用 Spring 框架中 @Autowired、@Value 注解一樣,完成我們對(duì)屬性和對(duì)象的注入操作。
三、方案 其實(shí)從我們?cè)谕瓿?Bean 對(duì)象的基礎(chǔ)功能后,后續(xù)陸續(xù)添加的功能都是圍繞著 Bean 的生命周期進(jìn)行的,比如修改 Bean 的定義 BeanFactoryPostProcessor,處理 Bean 的屬性要用到 BeanPostProcessor,完成個(gè)性的屬性操作則專門(mén)繼承 BeanPostProcessor 提供新的接口,因?yàn)檫@樣才能通過(guò) instanceof 判斷出具有標(biāo)記性的接口。所以關(guān)于 Bean 等等的操作,以及監(jiān)聽(tīng) Aware、獲取 BeanFactory,都需要在 Bean 的生命周期中完成。那么我們?cè)谠O(shè)計(jì)屬性和 Bean 對(duì)象的注入時(shí)候,也會(huì)用到 BeanPostProcessor 來(lái)完成在設(shè)置 Bean 屬性之前,允許 BeanPostProcessor 修改屬性值。整體設(shè)計(jì)結(jié)構(gòu)如下圖:
要處理自動(dòng)掃描注入,包括屬性注入、對(duì)象注入,則需要在對(duì)象屬性 applyPropertyValues 填充之前 ,把屬性信息寫(xiě)入到 PropertyValues 的集合中去。這一步的操作相當(dāng)于是解決了以前在 spring.xml 配置屬性的過(guò)程。 而在屬性的讀取中,需要依賴于對(duì) Bean 對(duì)象的類(lèi)中屬性的配置了注解的掃描,field.getAnnotation(Value.class); 依次拿出符合的屬性并填充上相應(yīng)的配置信息。這里有一點(diǎn) ,屬性的配置信息需要依賴于 BeanFactoryPostProcessor 的實(shí)現(xiàn)類(lèi) PropertyPlaceholderConfigurer,把值寫(xiě)入到 AbstractBeanFactory的embeddedValueResolvers集合中,這樣才能在屬性填充中利用 beanFactory 獲取相應(yīng)的屬性值 還有一個(gè)是關(guān)于 @Autowired 對(duì)于對(duì)象的注入,其實(shí)這一個(gè)和屬性注入的唯一區(qū)別是對(duì)于對(duì)象的獲取 beanFactory.getBean(fieldType),其他就沒(méi)有什么差一點(diǎn)了。 當(dāng)所有的屬性被設(shè)置到 PropertyValues 完成以后,接下來(lái)就到了創(chuàng)建對(duì)象的下一步,屬性填充,而此時(shí)就會(huì)把我們一一獲取到的配置和對(duì)象填充到屬性上,也就實(shí)現(xiàn)了自動(dòng)注入的功能。 四、實(shí)現(xiàn) 1. 工程結(jié)構(gòu)small-spring-step-14 └── src ├── main │ └── java │ └── cn.bugstack.springframework │ ├── aop │ │ ├── aspectj │ │ │ └── AspectJExpressionPointcut.java │ │ │ └── AspectJExpressionPointcutAdvisor.java │ │ ├── framework │ │ │ ├── adapter │ │ │ │ └── MethodBeforeAdviceInterceptor.java │ │ │ ├── autoproxy │ │ │ │ └── MethodBeforeAdviceInterceptor.java │ │ │ ├── AopProxy.java │ │ │ ├── Cglib2AopProxy.java │ │ │ ├── JdkDynamicAopProxy.java │ │ │ ├── ProxyFactory.java │ │ │ └── ReflectiveMethodInvocation.java │ │ ├── AdvisedSupport.java │ │ ├── Advisor.java │ │ ├── BeforeAdvice.java │ │ ├── ClassFilter.java │ │ ├── MethodBeforeAdvice.java │ │ ├── MethodMatcher.java │ │ ├── Pointcut.java │ │ ├── PointcutAdvisor.java │ │ └── TargetSource.java │ ├── beans │ │ ├── factory │ │ │ ├── annotation │ │ │ │ ├── Autowired.java │ │ │ │ ├── AutowiredAnnotationBeanPostProcessor.java │ │ │ │ ├── Qualifier.java │ │ │ │ └── Value.java │ │ │ ├── config │ │ │ │ ├── AutowireCapableBeanFactory.java │ │ │ │ ├── BeanDefinition.java │ │ │ │ ├── BeanFactoryPostProcessor.java │ │ │ │ ├── BeanPostProcessor.java │ │ │ │ ├── BeanReference.java │ │ │ │ ├── ConfigurableBeanFactory.java │ │ │ │ ├── InstantiationAwareBeanPostProcessor.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 │ │ │ └── PropertyPlaceholderConfigurer.java │ │ ├── BeansException.java │ │ ├── PropertyValue.java │ │ └── PropertyValues.java │ ├── context │ │ ├── annotation │ │ │ ├── ClassPathBeanDefinitionScanner.java │ │ │ ├── ClassPathScanningCandidateComponentProvider.java │ │ │ └── Scope.java │ │ ├── event │ │ │ ├── AbstractApplicationEventMulticaster.java │ │ │ ├── ApplicationContextEvent.java │ │ │ ├── ApplicationEventMulticaster.java │ │ │ ├── ContextClosedEvent.java │ │ │ ├── ContextRefreshedEvent.java │ │ │ └── SimpleApplicationEventMulticaster.java │ │ ├── support │ │ │ ├── AbstractApplicationContext.java │ │ │ ├── AbstractRefreshableApplicationContext.java │ │ │ ├── AbstractXmlApplicationContext.java │ │ │ ├── ApplicationContextAwareProcessor.java │ │ │ └── ClassPathXmlApplicationContext.java │ │ ├── ApplicationContext.java │ │ ├── ApplicationContextAware.java │ │ ├── ApplicationEvent.java │ │ ├── ApplicationEventPublisher.java │ │ ├── ApplicationListener.java │ │ └── ConfigurableApplicationContext.java │ ├── core.io │ │ ├── ClassPathResource.java │ │ ├── DefaultResourceLoader.java │ │ ├── FileSystemResource.java │ │ ├── Resource.java │ │ ├── ResourceLoader.java │ │ └── UrlResource.java │ ├── stereotype │ │ └── Component.java │ └── utils │ ├── ClassUtils.java │ └── StringValueResolver.java └── test └── java └── cn.bugstack.springframework.test ├── bean │ ├── IUserService.java │ └── UserService.java └── ApiTest.java工程源碼 :公眾號(hào)「bugstack蟲(chóng)洞?!?,回復(fù):Spring 專欄,獲取完整源碼
自動(dòng)掃描注入占位符配置和對(duì)象的類(lèi)關(guān)系,如圖 15-2
圖 15-2 在整個(gè)類(lèi)圖中以圍繞實(shí)現(xiàn)接口 InstantiationAwareBeanPostProcessor 的類(lèi) AutowiredAnnotationBeanPostProcessor 作為入口點(diǎn),被 AbstractAutowireCapableBeanFactory創(chuàng)建 Bean 對(duì)象過(guò)程中調(diào)用掃描整個(gè)類(lèi)的屬性配置中含有自定義注解 Value、Autowired、Qualifier,的屬性值。 這里稍有變動(dòng)的是關(guān)于屬性值信息的獲取,在注解配置的屬性字段掃描到信息注入時(shí),包括了占位符從配置文件獲取信息也包括 Bean 對(duì)象,Bean 對(duì)象可以直接獲取,但配置信息需要在 AbstractBeanFactory 中添加新的屬性集合 embeddedValueResolvers,由 PropertyPlaceholderConfigurer#postProcessBeanFactory 進(jìn)行操作填充到屬性集合中。 2. 把讀取到屬性填充到容器定義解析字符串接口
cn.bugstack.springframework.util.StringValueResolver
public interface StringValueResolver { String resolveStringValue (String strVal) ; }接口 StringValueResolver 是一個(gè)解析字符串操作的接口 填充字符串
public class PropertyPlaceholderConfigurer implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory (ConfigurableListableBeanFactory beanFactory) throws BeansException { try { // 加載屬性文件 DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); Resource resource = resourceLoader.getResource(location); // ... 占位符替換屬性值、設(shè)置屬性值 // 向容器中添加字符串解析器,供解析@Value注解使用 StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(properties); beanFactory.addEmbeddedValueResolver(valueResolver); } catch (IOException e) { throw new BeansException("Could not load properties" , e); } } private class PlaceholderResolvingStringValueResolver implements StringValueResolver { private final Properties properties; public PlaceholderResolvingStringValueResolver (Properties properties) { this .properties = properties; } @Override public String resolveStringValue (String strVal) { return PropertyPlaceholderConfigurer.this .resolvePlaceholder(strVal, properties); } } }在解析屬性配置的類(lèi) PropertyPlaceholderConfigurer 中,最主要的其實(shí)就是這行代碼的操作 beanFactory.addEmbeddedValueResolver(valueResolver) 這是把屬性值寫(xiě)入到了 AbstractBeanFactory 的 embeddedValueResolvers 中。 這里說(shuō)明下,embeddedValueResolvers 是 AbstractBeanFactory 類(lèi)新增加的集合 List<StringValueResolver> embeddedValueResolvers String resolvers to apply e.g. to annotation attribute values 3. 自定義屬性注入注解自定義注解,Autowired、Qualifier、Value
@Retention (RetentionPolicy.RUNTIME)@Target ({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD})public @interface Autowired { }@Target ({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})@Retention (RetentionPolicy.RUNTIME)@Inherited @Documented public @interface Qualifier { String value () default "" ; } @Target ({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})@Retention (RetentionPolicy.RUNTIME)@Documented public @interface Value { /** * The actual value expression: e.g. "#{systemProperties.myProp}". */ String value () ; }3個(gè)注解在我們?nèi)粘J褂?Spring 也是非常常見(jiàn)的,注入對(duì)象、注入屬性,而 Qualifier 一般與 Autowired 配合使用。 4. 掃描自定義注解cn.bugstack.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
public class AutowiredAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor , BeanFactoryAware { private ConfigurableListableBeanFactory beanFactory; @Override public void setBeanFactory (BeanFactory beanFactory) throws BeansException { this .beanFactory = (ConfigurableListableBeanFactory) beanFactory; } @Override public PropertyValues postProcessPropertyValues (PropertyValues pvs, Object bean, String beanName) throws BeansException { // 1. 處理注解 @Value Class<?> clazz = bean.getClass(); clazz = ClassUtils.isCglibProxyClass(clazz) ? clazz.getSuperclass() : clazz; Field[] declaredFields = clazz.getDeclaredFields(); for (Field field : declaredFields) { Value valueAnnotation = field.getAnnotation(Value.class ) ; if (null != valueAnnotation) { String value = valueAnnotation.value(); value = beanFactory.resolveEmbeddedValue(value); BeanUtil.setFieldValue(bean, field.getName(), value); } } // 2. 處理注解 @Autowired for (Field field : declaredFields) { Autowired autowiredAnnotation = field.getAnnotation(Autowired.class ) ; if (null != autowiredAnnotation) { Class<?> fieldType = field.getType(); String dependentBeanName = null ; Qualifier qualifierAnnotation = field.getAnnotation(Qualifier.class ) ; Object dependentBean = null ; if (null != qualifierAnnotation) { dependentBeanName = qualifierAnnotation.value(); dependentBean = beanFactory.getBean(dependentBeanName, fieldType); } else { dependentBean = beanFactory.getBean(fieldType); } BeanUtil.setFieldValue(bean, field.getName(), dependentBean); } } return pvs; } }AutowiredAnnotationBeanPostProcessor 是實(shí)現(xiàn)接口 InstantiationAwareBeanPostProcessor 的一個(gè)用于在 Bean 對(duì)象實(shí)例化完成后,設(shè)置屬性操作前的處理屬性信息的類(lèi)和操作方法。只有實(shí)現(xiàn)了 BeanPostProcessor 接口才有機(jī)會(huì)在 Bean 的生命周期中處理初始化信息 核心方法 postProcessPropertyValues,主要用于處理類(lèi)含有 @Value、@Autowired 注解的屬性,進(jìn)行屬性信息的提取和設(shè)置。 這里需要注意一點(diǎn)因?yàn)槲覀冊(cè)?AbstractAutowireCapableBeanFactory 類(lèi)中使用的是 CglibSubclassingInstantiationStrategy 進(jìn)行類(lèi)的創(chuàng)建,所以在 AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues 中需要判斷是否為 CGlib 創(chuàng)建對(duì)象,否則是不能正確拿到類(lèi)信息的。ClassUtils.isCglibProxyClass(clazz) ? clazz.getSuperclass() : clazz; 5. 在Bean的生命周期中調(diào)用屬性注入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 對(duì)象 bean = resolveBeforeInstantiation(beanName, beanDefinition); if (null != bean) { return bean; } // 實(shí)例化 Bean bean = createBeanInstance(beanDefinition, beanName, args); // 在設(shè)置 Bean 屬性之前,允許 BeanPostProcessor 修改屬性值 applyBeanPostProcessorsBeforeApplyingPropertyValues(beanName, bean, beanDefinition); // 給 Bean 填充屬性 applyPropertyValues(beanName, bean, beanDefinition); // 執(zhí)行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置處理方法 bean = initializeBean(beanName, bean, beanDefinition); } catch (Exception e) { throw new BeansException("Instantiation of bean failed" , e); } // 注冊(cè)實(shí)現(xiàn)了 DisposableBean 接口的 Bean 對(duì)象 registerDisposableBeanIfNecessary(beanName, bean, beanDefinition); // 判斷 SCOPE_SINGLETON、SCOPE_PROTOTYPE if (beanDefinition.isSingleton()) { registerSingleton(beanName, bean); } return bean; } /** * 在設(shè)置 Bean 屬性之前,允許 BeanPostProcessor 修改屬性值 * * @param beanName * @param bean * @param beanDefinition */ protected void applyBeanPostProcessorsBeforeApplyingPropertyValues (String beanName, Object bean, BeanDefinition beanDefinition) { for (BeanPostProcessor beanPostProcessor : getBeanPostProcessors()) { if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor){ PropertyValues pvs = ((InstantiationAwareBeanPostProcessor) beanPostProcessor).postProcessPropertyValues(beanDefinition.getPropertyValues(), bean, beanName); if (null != pvs) { for (PropertyValue propertyValue : pvs.getPropertyValues()) { beanDefinition.getPropertyValues().addPropertyValue(propertyValue); } } } } } // ... }AbstractAutowireCapableBeanFactory#createBean 方法中有這一條新增加的方法調(diào)用,就是在設(shè)置 Bean 屬性之前,允許 BeanPostProcessor 修改屬性值 的操作 applyBeanPostProcessorsBeforeApplyingPropertyValues 那么這個(gè) applyBeanPostProcessorsBeforeApplyingPropertyValues 方法中,首先就是獲取已經(jīng)注入的 BeanPostProcessor 集合并從中篩選出繼承接口 InstantiationAwareBeanPostProcessor 的實(shí)現(xiàn)類(lèi)。 最后就是調(diào)用相應(yīng)的 postProcessPropertyValues 方法以及循環(huán)設(shè)置屬性值信息,beanDefinition.getPropertyValues().addPropertyValue(propertyValue); 五、測(cè)試 1. 事先準(zhǔn)備配置 Dao
@Component public class UserDao { private static Map<String, String> hashMap = new HashMap<>(); static { hashMap.put("10001" , "小傅哥,北京,亦莊" ); hashMap.put("10002" , "八杯水,上海,尖沙咀" ); hashMap.put("10003" , "阿毛,香港,銅鑼灣" ); } public String queryUserName (String uId) { return hashMap.get(uId); } }給類(lèi)配置上一個(gè)自動(dòng)掃描注冊(cè) Bean 對(duì)象的注解 @Component,接下來(lái)會(huì)把這個(gè)類(lèi)注入到 UserService 中。 注解注入到 UserService
@Component ("userService" )public class UserService implements IUserService { @Value ("${token}" ) private String token; @Autowired private UserDao userDao; public String queryUserInfo () { try { Thread.sleep(new Random(1 ).nextInt(100 )); } catch (InterruptedException e) { e.printStackTrace(); } return userDao.queryUserName("10001" ) + "," + token; } // ... }這里包括了兩種類(lèi)型的注入,一個(gè)是占位符注入屬性信息 @Value("${token}"),另外一個(gè)是注入對(duì)象信息 @Autowired 2. 屬性配置文件token.properties
token=RejDlI78hu223Opo983Dsspring.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www./schema/beans" xmlns:xsi ="http://www./2001/XMLSchema-instance" xmlns:context ="http://www./schema/context" xsi:schemaLocation ="http://www./schema/beans http://www./schema/beans/spring-beans.xsd http://www./schema/context" > <bean class ="cn.bugstack.springframework.beans.factory.PropertyPlaceholderConfigurer" > <property name ="location" value ="classpath:token.properties" /> </bean > <context:component-scan base-package ="cn.bugstack.springframework.test.bean" /> </beans > 在 spring.xml 中配置了掃描屬性信息和自動(dòng)掃描包路徑范圍。 3. 單元測(cè)試@Test public void test_scan () { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml" ); IUserService userService = applicationContext.getBean("userService" , IUserService.class ) ; System.out.println("測(cè)試結(jié)果:" + userService.queryUserInfo()); }單元測(cè)試時(shí)候就可以完整的測(cè)試一個(gè)類(lèi)注入到 Spring 容器,同時(shí)這個(gè)屬性信息也可以被自動(dòng)掃描填充上。 測(cè)試結(jié)果
測(cè)試結(jié)果:小傅哥,北京,亦莊,RejDlI78hu223Opo983Ds Process finished with exit code 0 從測(cè)試結(jié)果可以看到現(xiàn)在我們的使用方式已經(jīng)通過(guò)了,有自動(dòng)掃描類(lèi),有注解注入屬性。這與使用 Spring 框架越來(lái)越像了。 六、總結(jié) 從整個(gè)注解信息掃描注入的實(shí)現(xiàn)內(nèi)容來(lái)看,我們一直是圍繞著在 Bean 的生命周期中進(jìn)行處理,就像 BeanPostProcessor 用于修改新實(shí)例化 Bean 對(duì)象的擴(kuò)展點(diǎn),提供的接口方法可以用于處理 Bean 對(duì)象實(shí)例化前后進(jìn)行處理操作。而有時(shí)候需要做一些差異化的控制,所以還需要繼承 BeanPostProcessor 接口,定義新的接口 InstantiationAwareBeanPostProcessor 這樣就可以區(qū)分出不同擴(kuò)展點(diǎn)的操作了。 像是接口用 instanceof 判斷,注解用 Field.getAnnotation(Value.class); 獲取,都是相當(dāng)于在類(lèi)上做的一些標(biāo)識(shí)性信息,便于可以用一些方法找到這些功能點(diǎn),以便進(jìn)行處理。所以在我們?nèi)粘i_(kāi)發(fā)設(shè)計(jì)的組件中,也可以運(yùn)用上這些特點(diǎn)。 當(dāng)你思考把你的實(shí)現(xiàn)融入到一個(gè)已經(jīng)細(xì)分好的 Bean 生命周期中,你會(huì)發(fā)現(xiàn)它的設(shè)計(jì)是如此的好,可以讓你在任何初始化的時(shí)間點(diǎn)上,任何面上,都能做你需要的擴(kuò)展或者改變,這也是我們做程序設(shè)計(jì)時(shí)追求的靈活性。 七、系列推薦