zoukankan      html  css  js  c++  java
  • spring源码学习之容器的扩展(二)

    六 BeanFactory的后处理
    BeanFactory作为spring容器的基础,用于存放所有已经加载的bean,为了保证程序上的高扩展性,spring针对BeanFactory做了大量的扩展,比如我们熟知的PostProcessor.

    6.1 激活注册的BeanFactoryPostProcessor
    正式介绍之前,先了解一下BeanFactoryPostProcessor的用法
    BeanFactoryPostProcessor接口跟BeanPostProcessor类似,可以对bean的定义(配置元数据)进行处理,也就是说,Spring IoC容器允许BeanFactoryPostProcessor
    在容器实际实例化任何其他bean之前读取配置元数据,并有可能修改他。如果你愿意,你可以配置多个BeanFactoryPostProcessor。你还能通过“order”属性来控制
    BeanFactoryPostProcessor的执行次序(仅当BeanFactoryPostProcessor实现了Ordered接口才可以设置此属性因此在实现BeanFactoryPostProcessor就应当考虑实现Ordered)
    如果你想改变实际bean实例(假如从配置元数据创建的对象),那么你最好使用BeanPostProcessor,同样的,BeanFactoryPostProcessor的作用域范围是容器级的
    他之和你使用的容器有关,如果你在容器中定义一个BeanFactoryPostProcessor,他仅仅对此容器中的bean进行后置管理,即使这两个容器在同一层次上,在spring中
    存在于BeanFactoryPostProcessor的典型应用,比如PropertyPlaceholderConfigurer

    1、BeanFactoryPostProcessor的典型应用PropertyPlaceholderConfigurer
    有时候,阅读spring的bean的配置文件的时候,也许会遇到类似如下的一些配置:

    1 <bean id="message" class="distConfig.HelloMessage">
    2   <property name="mes">
    3     <value>${bean.message}</value>
    4   </property>
    5 </bean>

    其中出现了变量引用:${bean.message}。这就是spring的分散配置,可以在另外的配置中为bean.message指定值,如在bean.property配置如下定义:
    bean.message=Hi,can you find me?
    当访问名为message的bean时,mes属性就会被置为字符串"Hi,can you find me?",但是spring是怎么知道存在这样的配置文件呢?这就要靠PropertyPlaceholderConfigurer
    这个类了

    1 <bean id="mesHandler" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    2   <property name="locations">
    3     <list>
    4       <value>config/bean.properties</value>
    5     </list>
    6   </property>
    7 </bean>

    在这个bean中指定了配置文件为config/bean.properties,这里似乎找到答案了,但是其实还有一个问题,这个“mesHandler”,只不过是框架管理的一个bean,并没有
    被别的bean或者对象引用,spring的beanFactory是如何知道从这个bean中获取配置信息呢?
    查看PropertyPlaceholderConfigurer的层级结构,可以看出这个类间接实现了BeanFactoryPostProcessor接口,这是一个很特别的接口,当spring加载任何实现了
    该接口的bean的配置时,都会在bean工厂载入所有bean的配置之后执行postProcessBeanFactory方法,在PropertyResourceConfigurer类中实现了postProcessBeanFactory方法

    org.springframework.beans.factory.config包下的PropertyResourceConfigurer类中的:

     1 @Override
     2 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
     3     try {
     4         Properties mergedProps = mergeProperties();
     5 
     6         // Convert the merged properties, if necessary.
     7         convertProperties(mergedProps);
     8 
     9         // Let the subclass process the properties.
    10         processProperties(beanFactory, mergedProps);
    11     }
    12     catch (IOException ex) {
    13         throw new BeanInitializationException("Could not load properties", ex);
    14     }
    15 }

    这三个方法分别得到配置,将得到的配置装换为合适的类型,最后将配置告知BeanFactory
    正是实现了BeanFactoryPostProcessor接口,BeanFactory会在实例化任何bean之前获得配置信息,从而能够正确解析bean中描述文件中变量引用

    2、使用自定义BeanFactoryPostProcessor

     1 // 配置文件beanFactory.xml
     2 <beans>
     3     <bean id="bfpp" class="com.spring.ch04.ObscenityRemovingBeanFactoryProcessor">
     4         <property name="obscenities">
     5             <set>
     6                 <value>bollocks</value>
     7                 <value>winky</value>
     8                 <value>bum</value>
     9                 <value>microsoft</value>
    10             </set>
    11         </property>
    12     </bean>
    13     <bean id="simpleBean" class="com.spring.ch04.SimplePostProcessor">
    14         <property name="connectionString" value="bollocks"></property>
    15         <property name="password" value="imaginecup"></property>
    16         <property name="username" value="microsoft"></property>
    17     </bean>
    18 </beans>
    19 
    20 // ObscenityRemovingBeanFactoryProcessor.java文件
    21 public class ObscenityRemovingBeanFactoryProcessor implements BeanFactoryPostProcessor{
    22     private Set<String> obscenities;
    23     
    24     public ObscenityRemovingBeanFactoryProcessor(){
    25         this.obscenities = new HashSet<String>();
    26     }
    27     
    28     public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    29         String[] beanNames = beanFactory.getBeanDefinitionName();
    30         for(String beanName:beanNames){
    31             BeanDefinition bd = beanFactory.getBeanDefinition(beanName);
    32             StringValueResolver valueResolver = new StringValueResolver(){
    33                 public String resolverStringValue(String strVal){
    34                     if(isObscene(strVal)){
    35                         return "******";
    36                     }
    37                     return strVal;
    38                 }
    39             };
    40             
    41             BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
    42             visitor.visitBeanDefinition(bd);
    43         }
    44     }
    45     
    46     public boolean isObscene(Object value){
    47         String potentialObscene = value.toString().toUpperCase();
    48         return this.obscenities.contains(potentialObscene);
    49     }
    50     
    51     public void setObscenities(Set<String> obscenities){
    52         this.obscenities.clear();
    53         for(String obscenity:obscenities){
    54             this.obscenities.add(obscenity);
    55         }
    56     }
    57     
    58     // 执行类
    59     public class PropertyConfigurerDerno{
    60         public static void main(String[] args){
    61             ConfigurableListableBeanFactory bf = new XrnlBeanFactory(new ClassPathResource("beanFactory.xml"));
    62             BeanFactoryPostProcessor bfpp = (BeanFactoryPostProcessor) bf.getBean("simpleBean");
    63             bfpp.postProcessBeanFactory(bf);
    64             System.out.println(bf.getBean("simpleBean"));
    65         }
    66     }
    67 }

    通过ObscenityRemovingBeanFactoryProcessor spring很好的实现了屏蔽obscenities定义的不应该展示的属性

    3、激活BeanFactoryPostProcessor
    了解BeanFactoryPostProcessor的用法后便可以深入研究BeanFactoryPostProcessor的调用过程了

    org.springframework.context.support包下的AbstractApplicationContext类,中的refresh方法

     1 protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
     2     PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
     3 
     4     // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
     5     // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
     6     if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
     7         beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
     8         beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
     9     }
    10 }

    接下来继续执行PostProcessorRegistrationDelegate类中的invokeBeanFactoryPostProcessors方法
    org.springframework.context.support包下的PostProcessorRegistrationDelegate类中的invokeBeanFactoryPostProcessors方法
    看了一下spring5.0中的源码和作者的源码有很大的出入,做了许多优化,作者的spring源码绝对不是spring5.0的

      1 public static void invokeBeanFactoryPostProcessors(
      2         ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
      3 
      4     // Invoke BeanDefinitionRegistryPostProcessors first, if any.
      5     Set<String> processedBeans = new HashSet<>();
      6 
      7     // 对BeanDefinitionRegistry进行处理
      8     if (beanFactory instanceof BeanDefinitionRegistry) {
      9         BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
     10         List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
     11         List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
     12 
     13         // 硬编码注册的后处理
     14         for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
     15             if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
     16                 BeanDefinitionRegistryPostProcessor registryProcessor =
     17                         (BeanDefinitionRegistryPostProcessor) postProcessor;
     18                 // 对于BeanDefinitionRegistryPostProcessor类型,在BeanFactoryPostProcessors的基础上还有自己定义的方法,需要先调用
     19                 registryProcessor.postProcessBeanDefinitionRegistry(registry);
     20                 registryProcessors.add(registryProcessor);
     21             }
     22             else {
     23                 // 记录常规BeanFactoryPostProcessor
     24                 regularPostProcessors.add(postProcessor);
     25             }
     26         }
     27 
     28         // Do not initialize FactoryBeans here: We need to leave all regular beans
     29         // uninitialized to let the bean factory post-processors apply to them!
     30         // Separate between BeanDefinitionRegistryPostProcessors that implement
     31         // PriorityOrdered, Ordered, and the rest.
     32         List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
     33 
     34         // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
     35         // 配置注册的后处理器 ,实现执行实现了PriorityOrdered接口的BeanDefinitionRegistryPostProcessors
     36         String[] postProcessorNames =
     37                 beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
     38         for (String ppName : postProcessorNames) {
     39             if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
     40                 currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
     41                 processedBeans.add(ppName);
     42             }
     43         }
     44         sortPostProcessors(currentRegistryProcessors, beanFactory);
     45         registryProcessors.addAll(currentRegistryProcessors);
     46         
     47         // BeanDefinitionRegistryPostProcessor的特殊处理
     48         invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
     49         currentRegistryProcessors.clear();
     50 
     51         // Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered.
     52         // 接下来执行实现了Ordered接口的BeanDefinitionRegistryPostProcessors
     53         postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
     54         for (String ppName : postProcessorNames) {
     55             if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
     56                 currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
     57                 processedBeans.add(ppName);
     58             }
     59         }
     60         sortPostProcessors(currentRegistryProcessors, beanFactory);
     61         registryProcessors.addAll(currentRegistryProcessors);
     62         invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
     63         currentRegistryProcessors.clear();
     64 
     65         // Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.
     66         // 最后执行剩余其他的BeanDefinitionRegistryPostProcessors
     67         boolean reiterate = true;
     68         while (reiterate) {
     69             reiterate = false;
     70             postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
     71             for (String ppName : postProcessorNames) {
     72                 if (!processedBeans.contains(ppName)) {
     73                     currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
     74                     processedBeans.add(ppName);
     75                     reiterate = true;
     76                 }
     77             }
     78             sortPostProcessors(currentRegistryProcessors, beanFactory);
     79             registryProcessors.addAll(currentRegistryProcessors);
     80             invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
     81             currentRegistryProcessors.clear();
     82         }
     83 
     84         // Now, invoke the postProcessBeanFactory callback of all processors handled so far.
     85         // 调用到目前为止处理的所有处理器的postProcessBeanFactory回调
     86         invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
     87         invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
     88     }
     89 
     90     else {
     91         // Invoke factory processors registered with the context instance.
     92         // 调用在上下文实例中注册的工厂处理器。
     93         invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
     94     }
     95 
     96     // Do not initialize FactoryBeans here: We need to leave all regular beans
     97     // uninitialized to let the bean factory post-processors apply to them!
     98     // 不要在这里初始化FactoryBeans:我们需要保留所有常规bean未初始化让bean工厂的后处理器适用于他们!
     99     String[] postProcessorNames =
    100             beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);
    101 
    102     // Separate between BeanFactoryPostProcessors that implement PriorityOrdered,
    103     // Ordered, and the rest.
    104     // 实现PriorityOrdered,Ordered和其余的接口的BeanFactoryPostProcessors之间分开
    105     List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
    106     List<String> orderedPostProcessorNames = new ArrayList<>();
    107     List<String> nonOrderedPostProcessorNames = new ArrayList<>();
    108     for (String ppName : postProcessorNames) {
    109         if (processedBeans.contains(ppName)) {
    110             // skip - already processed in first phase above
    111         }
    112         else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
    113             priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
    114         }
    115         else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
    116             orderedPostProcessorNames.add(ppName);
    117         }
    118         else {
    119             nonOrderedPostProcessorNames.add(ppName);
    120         }
    121     }
    122 
    123     // First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered.
    124     sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
    125     invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
    126 
    127     // Next, invoke the BeanFactoryPostProcessors that implement Ordered.
    128     List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>();
    129     for (String postProcessorName : orderedPostProcessorNames) {
    130         orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
    131     }
    132     sortPostProcessors(orderedPostProcessors, beanFactory);
    133     invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);
    134 
    135     // Finally, invoke all other BeanFactoryPostProcessors.
    136     List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>();
    137     for (String postProcessorName : nonOrderedPostProcessorNames) {
    138         nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
    139     }
    140     invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);
    141 
    142     // Clear cached merged bean definitions since the post-processors might have
    143     // modified the original metadata, e.g. replacing placeholders in values...
    144     // 清除缓存的合并bean定义,因为后处理器可能已经修改了原始元数据,例如, 替换值中的占位符...
    145     beanFactory.clearMetadataCache();
    146 }

    从上面的代码可以看出,对于BeanFactoryPostProcessor的处理主要分为两种。一种是对于BeanDefinitionRegistry类处理,另一种是对于普通的BeanFactoryPostProcessor
    处理,面对这两种都需要考虑硬编码注入注册的后处理以及通过配置注入的后处理器

    6.2 注册BeanPostProcessor
    上面提到了BeanFactoryPostProcessor的调用,现在我们探索下BeanPostProcessor,但是这里并不是调用,而是注册。真正的调用实际上是在bean的实例化阶段进行的
    这是一个很重要的步骤,也是很多功能BeanFactory不支持的重要原因。spring中的大部分功能都是通过后处理器的方式进行扩展的,这是spring框架的一个特性,但是在
    BeanFactory中其实并没有实现后处理器的自动注册,所以在调用的时候如果没有进行手动注册其实是不能使用的,但是ApplicationContext中却添加了自动注册的功能
    如定义一个这样的后处理器:

    1 public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor{
    2     public Object postProcessorBeforeInitialization(Object bean, String beanName) throws BeansException {
    3         System.out.println("====");
    4         return null;
    5     }
    6     ......
    7 }

    在配置文件中添加配置:
    <bean class="processors.MyInstantiationAwareBeanPostProcessor" />
    那么使用BeanFactory方式进行spring的bean的加载时是不会有任何变化的,但是使用ApplicationContext方式获取bean的时候会在获取每个bean的打印"===",
    而这个特性就是在registerBeanPostProcessor方法中进行的,接着看下这个方法registerBeanPostProcessor
    org.springframework.context.support包下的AbstractApplicationContext类中refresh()方法中的该行代码:
    // 注册拦截bean创建的bean处理器,这里只是注册,真正的调用是在getBean的时候
    registerBeanPostProcessors(beanFactory);

     1 // 进入到方法中:同一个类中
     2 protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
     3     PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
     4 }
     5 
     6 // org.springframework.context.support包下的PostProcessorRegistrationDelegate类中的registerBeanPostProcessors方法:
     7 public static void registerBeanPostProcessors(
     8         ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
     9 
    10     String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
    11 
    12     // Register BeanPostProcessorChecker that logs an info message when
    13     // a bean is created during BeanPostProcessor instantiation, i.e. when
    14     // a bean is not eligible for getting processed by all BeanPostProcessors.
    15     /**
    16      * BeanPostProcessorChecker是一个普通的信息打印 可能会有些情况
    17      * 当spring的配置中的后处理器还没有被注册就已经开始了bean的初始化时
    18      * 便会打印BeanPostProcessorChecker中设定的信息
    19      */
    20     int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
    21     beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));
    22 
    23     // Separate between BeanPostProcessors that implement PriorityOrdered,
    24     // Ordered, and the rest.
    25     // 使用PriorityOrdered保证顺序
    26     List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
    27     List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
    28     // 使用Ordered保证顺序
    29     List<String> orderedPostProcessorNames = new ArrayList<>();
    30     // 无序BeanPostProcessor
    31     List<String> nonOrderedPostProcessorNames = new ArrayList<>();
    32     for (String ppName : postProcessorNames) {
    33         if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
    34             BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
    35             priorityOrderedPostProcessors.add(pp);
    36             if (pp instanceof MergedBeanDefinitionPostProcessor) {
    37                 internalPostProcessors.add(pp);
    38             }
    39         }
    40         else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
    41             orderedPostProcessorNames.add(ppName);
    42         }
    43         else {
    44             nonOrderedPostProcessorNames.add(ppName);
    45         }
    46     }
    47 
    48     // First, register the BeanPostProcessors that implement PriorityOrdered.
    49     // 首先,注册所有的实现了PriorityOrdered的BeanPostProcessors
    50     sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
    51     registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);
    52 
    53     // Next, register the BeanPostProcessors that implement Ordered.
    54     // 接下来。注册所有实现了Ordered接口的BeanPostProcessors
    55     List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>();
    56     for (String ppName : orderedPostProcessorNames) {
    57         BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
    58         orderedPostProcessors.add(pp);
    59         if (pp instanceof MergedBeanDefinitionPostProcessor) {
    60             internalPostProcessors.add(pp);
    61         }
    62     }
    63     sortPostProcessors(orderedPostProcessors, beanFactory);
    64     registerBeanPostProcessors(beanFactory, orderedPostProcessors);
    65 
    66     // Now, register all regular BeanPostProcessors.
    67     // 然后,注册所有无序的BeanPostProcessors
    68     List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>();
    69     for (String ppName : nonOrderedPostProcessorNames) {
    70         BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
    71         nonOrderedPostProcessors.add(pp);
    72         if (pp instanceof MergedBeanDefinitionPostProcessor) {
    73             internalPostProcessors.add(pp);
    74         }
    75     }
    76     registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);
    77 
    78     // Finally, re-register all internal BeanPostProcessors.
    79     // 最后,注册所有的MergedBeanDefinitionPostProcessor类型的BeanPostProcessor,并非重复注册
    80     // 在beanFactory.addBeanPostProcessor中会先移除已经存在的BeanPostProcessor
    81     sortPostProcessors(internalPostProcessors, beanFactory);
    82     registerBeanPostProcessors(beanFactory, internalPostProcessors);
    83 
    84     // Re-register post-processor for detecting inner beans as ApplicationListeners,
    85     // moving it to the end of the processor chain (for picking up proxies etc).
    86     // 添加ApplicationListeners注册器
    87     beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
    88 }

    6.3 初始化消息资源

    spring中定义了访问国际化信息的MessageSource接口,并提供了几个易用的实现类。MessageSource分别被HierarchicalMessageSource和ApplicationContext接口
    扩展,HierarchicalMessageSource接口最重要的两个实现类ReloadableResourceBundleMessageSource和ResourceBundleMessageSource,他们基于java的ResourceBundle
    基础类实现允许仅通过资源名加载国际化资源,ReloadableResourceBundleMessageSource提供了定时刷新功能,允许在不重启系统的情况下,更新资源信息,
    StaticMessageSource主要用于程序测试,他允许通过编程的方式提供国际化信息。DelegatingMessageSource是为方便操作父MessageSource而提供的代理类
    仅仅举例ResourceBundleMessageSource的实现方式:

     1 (1)定义资源文件
     2 message.properties(默认英文)
     3 test=test
     4 message_zh_CN.properties(简体中文)
     5 test=测试
     6 
     7 (2)定义配置文件
     8 <bean id="messageResource" class="org.springframework.context.support.ResourceBundleMessageSource">
     9     <property name="beanName">
    10         <list>
    11             <value>test/message</value>
    12         </list>
    13     </property>
    14 </bean>
    15 这个bean的id必须配置成messageResource,否则会报错
    16 
    17 (3)使用ApplicationContext访问国际化信息
    18 String[] configs = {"application.xml"};
    19 ApplicationContext ctx = new ClassPathXmlApplicationContext(configs);
    20 Object params = {"", new GregorianCalendar().getTime()};
    21 
    22 String str1 = ctx.getMessage("test",params,Locale.US);
    23 String str2 = ctx.getMessage("test",params,Locale.CHINA);
    24 
    25 System.out.println(str1);
    26 System.out.println(str2);

    org.springframework.context.support包下的AbstractApplicationContext类中refresh方法中的国际化处理的代码
    // 为上下文初始化Message源,即不同语言的消息体,即国际化处理
    initMessageSource();

    initMessageSource方法中,主要功能是提取配置中定义的MessageSource,并将其记录到spring容器中,也就是AbstractApplicationContext中,当然,如果用户
    未设置资源文件的话,spring中也提供了默认的配置DelegatingMessageSource
    在initMessageSource中获取自定义的资源文件的方式为beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);这里spring使用了硬编码方式硬性
    规定了资源文件必须为message,否则获取不到资源

    org.springframework.context.support包下的AbstractApplicationContext类中

     1 protected void initMessageSource() {
     2     ConfigurableListableBeanFactory beanFactory = getBeanFactory();
     3     if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
     4         
     5         // 获取用户自定义的资源文件
     6         this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
     7         // Make MessageSource aware of parent MessageSource.
     8         if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
     9             HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
    10             if (hms.getParentMessageSource() == null) {
    11                 // Only set parent context as parent MessageSource if no parent MessageSource
    12                 // registered already.
    13                 hms.setParentMessageSource(getInternalParentMessageSource());
    14             }
    15         }
    16         if (logger.isDebugEnabled()) {
    17             logger.debug("Using MessageSource [" + this.messageSource + "]");
    18         }
    19     }
    20     else {
    21         // Use empty MessageSource to be able to accept getMessage calls.
    22         // 用户未定义则用默认的DelegatingMessageSource
    23         DelegatingMessageSource dms = new DelegatingMessageSource();
    24         dms.setParentMessageSource(getInternalParentMessageSource());
    25         this.messageSource = dms;
    26         beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
    27         if (logger.isDebugEnabled()) {
    28             logger.debug("Unable to locate MessageSource with name '" + MESSAGE_SOURCE_BEAN_NAME +
    29                     "': using default [" + this.messageSource + "]");
    30         }
    31     }
    32 }
    33 
    34 org.springframework.context.support包下的AbstractApplicationContext类中实现了三种获取资源文件的方法
    35 @Override
    36 public String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale) {
    37     return getMessageSource().getMessage(code, args, defaultMessage, locale);
    38 }
    39 
    40 @Override
    41 public String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException {
    42     return getMessageSource().getMessage(code, args, locale);
    43 }
    44 
    45 @Override
    46 public String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException {
    47     return getMessageSource().getMessage(resolvable, locale);
    48 }
    49 
    50 getMessageSource方法是获取之前定义的自定义资源配置
    51 private MessageSource getMessageSource() throws IllegalStateException {
    52     if (this.messageSource == null) {
    53         throw new IllegalStateException("MessageSource not initialized - " +
    54                 "call 'refresh' before accessing messages via the context: " + this);
    55     }
    56     return this.messageSource;
    57 }

    6.4 初始化ApplicationEventMulticaster
    org.springframework.context.support包下的AbstractApplicationContext类中refresh方法中的初始化应用消息广播器的代码
    // 初始化应用消息广播器,并放入"applicationEventMulticaster"bean中
    initApplicationEventMulticaster();

    先看一下spring中事件监听的简单用法

     1 (1)定义监听事件
     2 public class TestEvent extends ApplicationEvent{
     3 
     4     public String message;
     5     
     6     public TestEvent(Object source){
     7         super(source);
     8     }
     9     
    10     public TestEvent(Object source, String msg){
    11         super(source);
    12         this.msg = msg;
    13     }
    14     
    15     public void print(){
    16         System.out.println(message);
    17     }
    18 }
    19 
    20 (2)定义监听器
    21 public TestListener implements ApplicationListeners{
    22     
    23     public void onApplicationEvent(ApplicationEvent event){
    24         if(event instanceof TestEvent){
    25             TestEvent testEvent = (TestEvent) event;
    26             testEvent.print();
    27         }
    28     }
    29 }
    30 
    31 (3)添加配置文件
    32 <bean id="testListener" class="com.test.event.TestListener" />
    33 
    34 (4)测试
    35 public class Test{
    36     public static void main(String[] args){
    37         ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    38         TestEvent event = new TestEvent("hello","msg");
    39         context.publishEvent(event);
    40     }
    41 }

    当程序运行时,spring会将发出的TestEvent事件转给我们自定义的TestListener进行进一步处理

    这个和设计模式中观察者模式有点类似,看源码中ApplicationEventMulticaster如何被初始化的,以确保功能的正常运行
    initApplicationEventMulticaster的方式比较简单,无非考虑两种情况:
    如果用户自定义了事件广播器,那么使用用户自定义的时间广播器
    如果用户没有自定义事件广播器,那么使用默认的ApplicationEventMulticaster

     1 protected void initApplicationEventMulticaster() {
     2     ConfigurableListableBeanFactory beanFactory = getBeanFactory();
     3     if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
     4         this.applicationEventMulticaster =
     5                 beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
     6         if (logger.isDebugEnabled()) {
     7             logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
     8         }
     9     }
    10     else {
    11         this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
    12         beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
    13         if (logger.isDebugEnabled()) {
    14             logger.debug("Unable to locate ApplicationEventMulticaster with name '" +
    15                     APPLICATION_EVENT_MULTICASTER_BEAN_NAME +
    16                     "': using default [" + this.applicationEventMulticaster + "]");
    17         }
    18     }
    19 }

    按照之前的介绍顺序及逻辑,我们推断,作为广播器,一定是存放监听器并在合适的时候调用监听器,看看默认的广播器实现SimpleApplicationEventMulticaster的源码
    org.springframework.context.support包下的AbstractApplicationContext类中refresh中的代码
    // 在所有注册的bean中查找Listener bean,注册到消息广播器中
    registerListeners();

    通过这个方法来看一下
    org.springframework.context.support包下的AbstractApplicationContext类中

     1 protected void registerListeners() {
     2     // Register statically specified listeners first.
     3     for (ApplicationListener<?> listener : getApplicationListeners()) {
     4         getApplicationEventMulticaster().addApplicationListener(listener);
     5     }
     6 
     7     // Do not initialize FactoryBeans here: We need to leave all regular beans
     8     // uninitialized to let post-processors apply to them!
     9     // 不要在这里初始化FactoryBeans:我们需要保留所有常规bean未初始化让后处理器适用于他们!
    10     String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
    11     for (String listenerBeanName : listenerBeanNames) {
    12         getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
    13     }
    14 
    15     // Publish early application events now that we finally have a multicaster...
    16     // 现在我们终于有了一个多播器,发布早期的应用程序事件...
    17     Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
    18     this.earlyApplicationEvents = null;
    19     if (earlyEventsToProcess != null) {
    20         for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
    21             getApplicationEventMulticaster().multicastEvent(earlyEvent);
    22         }
    23     }
    24 }

    当产生spring事件的时候默认使用SimpleApplicationEventMulticaster的multicastEvent来广播事件,遍历所有的监听器,并使用监听器中onApplicationEvent方法
    进行监听器的处理,而对于每个监听器来说其实都可以获取到产生的事件但是是否处理则由时间监听器决定

     1 @Override
     2 public void multicastEvent(ApplicationEvent event) {
     3     multicastEvent(event, resolveDefaultEventType(event));
     4 }
     5 
     6 @Override
     7 public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
     8     ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
     9     for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
    10         Executor executor = getTaskExecutor();
    11         if (executor != null) {
    12             executor.execute(() -> invokeListener(listener, event));
    13         }
    14         else {
    15             invokeListener(listener, event);
    16         }
    17     }
    18 }

    6.5 注册监听器
    看源码spring注册监听器的时候做了什么处理:
    org.springframework.context.support包下的AbstractApplicationContext类中refresh中的代码
    // 在所有注册的bean中查找Listener bean,注册到消息广播器中
    registerListeners();

     1 protected void registerListeners() {
     2     // Register statically specified listeners first.
     3     for (ApplicationListener<?> listener : getApplicationListeners()) {
     4         getApplicationEventMulticaster().addApplicationListener(listener);
     5     }
     6 
     7     // Do not initialize FactoryBeans here: We need to leave all regular beans
     8     // uninitialized to let post-processors apply to them!
     9     // 不要在这里初始化FactoryBeans:我们需要保留所有常规bean未初始化让后处理器适用于他们!
    10     String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
    11     for (String listenerBeanName : listenerBeanNames) {
    12         getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
    13     }
    14 
    15     // Publish early application events now that we finally have a multicaster...
    16     // 现在我们终于有了一个多播器,发布早期的应用程序事件...
    17     Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
    18     this.earlyApplicationEvents = null;
    19     if (earlyEventsToProcess != null) {
    20         for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
    21             getApplicationEventMulticaster().multicastEvent(earlyEvent);
    22         }
    23     }
    24 }

    上面那写到了multicastEvent方法的实现具体的代码,这里不详细贴出来了

    七 初始化非延迟加载单例
    完成BeanFactory的初始化工作,其中包括ConversionService的设置,配置冻结以及非延迟加载的bean的初始化工作

    org.springframework.context.support包下的AbstractApplicationContext类中refresh中的代码

     1 // 初始化剩下的单实例(非惰性的)
     2 finishBeanFactoryInitialization(beanFactory);
     3 
     4 protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
     5     // Initialize conversion service for this context.
     6     if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
     7             beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
     8         beanFactory.setConversionService(
     9                 beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
    10     }
    11 
    12     // Register a default embedded value resolver if no bean post-processor
    13     // (such as a PropertyPlaceholderConfigurer bean) registered any before:
    14     // at this point, primarily for resolution in annotation attribute values.
    15     // 如果没有bean后处理器,则注册默认的嵌入值解析器
    16     //(例如PropertyPlaceholderConfigurer bean)之前注册的任何一个:此时,主要用于注释属性值的解析
    17     if (!beanFactory.hasEmbeddedValueResolver()) {
    18         beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
    19     }
    20 
    21     // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
    22     // 尽早初始化LoadTimeWeaverAware bean以允许尽早注册其变换器。
    23     String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
    24     for (String weaverAwareName : weaverAwareNames) {
    25         getBean(weaverAwareName);
    26     }
    27 
    28     // Stop using the temporary ClassLoader for type matching.
    29     // 停止使用临时ClassLoader进行类型匹配。
    30     beanFactory.setTempClassLoader(null);
    31 
    32     // Allow for caching all bean definition metadata, not expecting further changes.
    33     // 冻结所有bean定义,说明注册的bean定义将不被修改或任何进一步的处理
    34     beanFactory.freezeConfiguration();
    35 
    36     // Instantiate all remaining (non-lazy-init) singletons.
    37     // 初始化剩下的单实例(非惰性的)
    38     beanFactory.preInstantiateSingletons();
    39 }

    1、ConversionService设置
    之前提到过使用自定义类型转换器从String转换到Date的方式,那么,在spring中,还提供了另一种转换方式:使用Converter,同样来看一下如何使用:

     1 (1)定义转换器
     2 public class String2DateConverter implements Converter<String, Date>{
     3     public Date convert(String args){
     4         try{
     5             return DateUtils.parseDate(arg0, new String[]{"yyyy-MM-dd HH:mm:ss"});
     6         }catch(ParseException e){
     7             return null;
     8         }
     9     }
    10 }
    11 (2)注册
    12 <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    13     <property name="converters">
    14         <list>
    15             <bean class="String2DateConverter" />
    16         </list>
    17     </property>
    18 </bean>
    19 (3)测试
    20 public void testStringToDateConvert(){
    21     DefaultConversionService conversionService = new DefaultConversionService();
    22     conversionservice.addConverter(new String2DateConverter());
    23     String dateStr = "2019-07-26 15:35:28";
    24     Date date = conversionService.convert(dateStr);
    25 }

    2、冻结配置
    冻结所有的bean定义,说明注册的bean定义将不被修改或进行任何进一步的处理
    org.springframework.beans.factory.support包下的AbstractApplicationContext类中

    1 @Override
    2 public void freezeConfiguration() {
    3     this.configurationFrozen = true;
    4     this.frozenBeanDefinitionNames = StringUtils.toStringArray(this.beanDefinitionNames);
    5 }

    3、初始化非延迟加载
    ApplicationContext实现默认的行为就是在启动时将所有单例bean提前进行实例化,提前实例化意味着作为初始化过程的一部分,ApplicationContext实例会创建并
    配置所有的单例bean。通常情况下这是一件好事,因为这样在配置中的任何错误就会即刻被发现,而这个实例化过程就是在preInstantiateSingletons中完成的
    org.springframework.beans.factory.support包下的DefaultListableBeanFactory类

     1 @Override
     2 public void preInstantiateSingletons() throws BeansException {
     3     if (logger.isDebugEnabled()) {
     4         logger.debug("Pre-instantiating singletons in " + this);
     5     }
     6 
     7     // Iterate over a copy to allow for init methods which in turn register new bean definitions.
     8     // While this may not be part of the regular factory bootstrap, it does otherwise work fine.
     9     // 迭代一个副本以允许init方法,这些方法又注册新的bean定义。
    10     // 虽然这可能不是常规工厂引导程序的一部分,但它确实可以正常工作。
    11     List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
    12 
    13     // Trigger initialization of all non-lazy singleton beans...
    14     // 触发所有非惰性单例bean的初始化...
    15     for (String beanName : beanNames) {
    16         RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
    17         if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
    18             if (isFactoryBean(beanName)) {
    19                 Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
    20                 if (bean instanceof FactoryBean) {
    21                     final FactoryBean<?> factory = (FactoryBean<?>) bean;
    22                     boolean isEagerInit;
    23                     if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
    24                         isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
    25                                         ((SmartFactoryBean<?>) factory)::isEagerInit,
    26                                 getAccessControlContext());
    27                     }
    28                     else {
    29                         isEagerInit = (factory instanceof SmartFactoryBean &&
    30                                 ((SmartFactoryBean<?>) factory).isEagerInit());
    31                     }
    32                     if (isEagerInit) {
    33                         getBean(beanName);
    34                     }
    35                 }
    36             }
    37             else {
    38                 getBean(beanName);
    39             }
    40         }
    41     }
    42 
    43     // Trigger post-initialization callback for all applicable beans...
    44     // 触发所有适用bean的后初始化回调...
    45     for (String beanName : beanNames) {
    46         Object singletonInstance = getSingleton(beanName);
    47         if (singletonInstance instanceof SmartInitializingSingleton) {
    48             final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
    49             if (System.getSecurityManager() != null) {
    50                 AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
    51                     smartSingleton.afterSingletonsInstantiated();
    52                     return null;
    53                 }, getAccessControlContext());
    54             }
    55             else {
    56                 smartSingleton.afterSingletonsInstantiated();
    57             }
    58         }
    59     }
    60 }

    八 finishRefresh
    org.springframework.context.support包下的AbstractApplicationContext类中refresh中的代码
    // 完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人
    finishRefresh();

     1 // 同一个类中:
     2 protected void finishRefresh() {
     3     // Clear context-level resource caches (such as ASM metadata from scanning).
     4     // 清除上下文级资源缓存(例如来自扫描的ASM元数据)。
     5     clearResourceCaches();
     6 
     7     // Initialize lifecycle processor for this context.
     8     // 为此上下文初始化生命周期处理器。
     9     initLifecycleProcessor();
    10 
    11     // Propagate refresh to lifecycle processor first.
    12     // 首先将刷新传播到生命周期处理器。
    13     getLifecycleProcessor().onRefresh();
    14 
    15     // Publish the final event.
    16     // 发布最终的event
    17     publishEvent(new ContextRefreshedEvent(this));
    18 
    19     // Participate in LiveBeansView MBean, if active.
    20     // 如果处于活动状态,请参考LiveBeansView MBean。
    21     LiveBeansView.registerApplicationContext(this);
    22 }

    1、initLifecycleProcessor
    当ApplicationContext启动或者停止的时候,他会通过LifecycleProcessor来与所有声明的bean的周期做状态更新,而在LifecycleProcessor的使用前需要初始化

     1 protected void initLifecycleProcessor() {
     2     ConfigurableListableBeanFactory beanFactory = getBeanFactory();
     3     if (beanFactory.containsLocalBean(LIFECYCLE_PROCESSOR_BEAN_NAME)) {
     4         this.lifecycleProcessor =
     5                 beanFactory.getBean(LIFECYCLE_PROCESSOR_BEAN_NAME, LifecycleProcessor.class);
     6         if (logger.isDebugEnabled()) {
     7             logger.debug("Using LifecycleProcessor [" + this.lifecycleProcessor + "]");
     8         }
     9     }
    10     else {
    11         DefaultLifecycleProcessor defaultProcessor = new DefaultLifecycleProcessor();
    12         defaultProcessor.setBeanFactory(beanFactory);
    13         this.lifecycleProcessor = defaultProcessor;
    14         beanFactory.registerSingleton(LIFECYCLE_PROCESSOR_BEAN_NAME, this.lifecycleProcessor);
    15         if (logger.isDebugEnabled()) {
    16             logger.debug("Unable to locate LifecycleProcessor with name '" +
    17                     LIFECYCLE_PROCESSOR_BEAN_NAME +
    18                     "': using default [" + this.lifecycleProcessor + "]");
    19         }
    20     }
    21 }

    2、onRefresh
    org.springframework.context.support包下的DefaultLifecycleProcessor类

     1 @Override
     2 public void onRefresh() {
     3     startBeans(true);
     4     this.running = true;
     5 }
     6 
     7 private void startBeans(boolean autoStartupOnly) {
     8     Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
     9     Map<Integer, LifecycleGroup> phases = new HashMap<>();
    10     lifecycleBeans.forEach((beanName, bean) -> {
    11         if (!autoStartupOnly || (bean instanceof SmartLifecycle && ((SmartLifecycle) bean).isAutoStartup())) {
    12             int phase = getPhase(bean);
    13             LifecycleGroup group = phases.get(phase);
    14             if (group == null) {
    15                 group = new LifecycleGroup(phase, this.timeoutPerShutdownPhase, lifecycleBeans, autoStartupOnly);
    16                 phases.put(phase, group);
    17             }
    18             group.add(beanName, bean);
    19         }
    20     });
    21     if (!phases.isEmpty()) {
    22         List<Integer> keys = new ArrayList<>(phases.keySet());
    23         Collections.sort(keys);
    24         for (Integer key : keys) {
    25             phases.get(key).start();
    26         }
    27     }
    28 }

    3、publishEvent
    当完成ApplicationContext初始化的时候,要通过spring中事件发布机制来发出ContextRefreshedEvent事件,以保证对应的监听器可以做进一步的逻辑吃力

     1 // 同一个类中:
     2 protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
     3     Assert.notNull(event, "Event must not be null");
     4     if (logger.isTraceEnabled()) {
     5         logger.trace("Publishing event in " + getDisplayName() + ": " + event);
     6     }
     7 
     8     // Decorate event as an ApplicationEvent if necessary
     9     // 如有必要,将事件装饰为ApplicationEvent
    10     ApplicationEvent applicationEvent;
    11     if (event instanceof ApplicationEvent) {
    12         applicationEvent = (ApplicationEvent) event;
    13     }
    14     else {
    15         applicationEvent = new PayloadApplicationEvent<>(this, event);
    16         if (eventType == null) {
    17             eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
    18         }
    19     }
    20 
    21     // Multicast right now if possible - or lazily once the multicaster is initialized
    22     // 如果可能的话现在就进行组播 - 或者在初始化多播器后懒洋洋地进行组播
    23     if (this.earlyApplicationEvents != null) {
    24         this.earlyApplicationEvents.add(applicationEvent);
    25     }
    26     else {
    27         getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
    28     }
    29 
    30     // Publish event via parent context as well...
    31     if (this.parent != null) {
    32         if (this.parent instanceof AbstractApplicationContext) {
    33             ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
    34         }
    35         else {
    36             this.parent.publishEvent(event);
    37         }
    38     }
    39 }
  • 相关阅读:
    1、PHP入门二维数组与循环
    Nginx 配置反向代理后,页面中取绝对URL地址的问题显示代理端口
    苹果手机上点击WEUI日期控件不容易点中
    ios 不支持-,-时间。
    Newtonsoft.Json添加项
    Baidu地图Map api直接加https不起作用
    腾讯云cos封装
    linux连接工具隧道模式
    微信调试工具测试时有时候复制URL没有corpid解决
    WEUI控件JS用法
  • 原文地址:https://www.cnblogs.com/ssh-html/p/11261075.html
Copyright © 2011-2022 走看看