Spring源碼解析之@Configuration

@Configuration簡介

用於標識一個類為配置類,與xml配置效果類似

用法簡介

public class TestApplication {

    public static void main(String args[]) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    }
}

@Configuration
public class AppConfig {

    @Bean
    public A a(){
        return new A();
    }
    @Bean
    public B b(){
        return new B();
    }
} 




public class A {

    public A(){
        System.out.println("Call A constructor");
    }
}



public class B {

    public B(){
        System.out.println("Call B constructor");
    }
}

上面的例子應該是@Configuration最普遍一種使用場景了,在@Configuration class下面配置@Bean method,用於想Spring Ioc容器注入bean.但其實我們把AppConfig的@Configuration註解去掉,對應的Bean也是可以被注入到容器中去的。

那麼問題來了@Configuration到底有什麼作用?

我們給AppConfig改寫一下,如下:

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

   @Bean
   public A a(){
      b();
      return new A();
   }
   @Bean
   public B b(){
      return new B();
   }

再去執行TestApplication#main,那麼執行結果會是什麼樣呢?

Call A constructor
Call B constructor

嗯哼?按照Java的語法,B的構造函數應該是被調用了兩次啊?為什麼只有輸出一句Call B constructor?

這其實就是@Configuration再發揮作用啦,不信你去掉@Configuration,再去運行一下,就會發現B的構造函數被執行了兩次。

官方給出了這樣一段解釋對於被@Configuration註解的類

In common scenarios,@Beanmethods are to be declared within@Configurationclasses, ensuring that “full” mode is always used and that cross-method references therefore get redirected to the container’s lifecycle management.

在一般情況下,@Bean method 是被聲明在@Configuration類中的,以確保 full mode總是被使用,並且跨方法的引用會被重定向到容器生命周期管理。

怎麼理解呢?

原來Spring將被@Configuration註解的配置類定義為full configuration, 而將沒有被@Configuration註解的配置類定義為lite configuration。full configuration能重定向從跨方法的引用,從而保證上述代碼中的b bean是一個單例.

源碼中是如何實現@Configuration語意

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
   /**
    * 調用無參構造函數,實例化AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner
    * 同時會調用父類GenericApplicationContext無參構造函數實例化一個關鍵的工廠DefaultListableBeanFactory
    * 同時還會註冊一些開天闢地的後置處理器到beanDefinitionMap,這些後置處理器有bean工廠後置處理器;有bean後置處理器
    */
   this();
   //將componentClasses註冊到beanDefinitionMap集合中去
   register(componentClasses);
  
   refresh();
}

跟蹤refresh()

@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      prepareBeanFactory(beanFactory);

      try {
         // Allows post-processing of the bean factory in context subclasses.
         //供上下文(Context)子類繼承,允許在這裏後置處理bean factory
         postProcessBeanFactory(beanFactory);

         // Invoke factory processors registered as beans in the context.
         //按順序調用BeanFactoryPostProcessor,這裏的按順序僅實現了PriorityOrdered和Ordered的語意,未實現@Order註解的語意
         //通過調用ConfigurationConfigPostProcessor#postProcessBeanDefinitionRegistry
         //解析@Configuration配置類,將自定義的BeanFactoryPostProcessor、BeanPostProcessor註冊到beanDefinitionMap
         //接着實例化所有(包括開天闢地)的BeanFactoryPostProcessor,然後再調用BeanFactoryPostProcessor#postProcessBeanFactory
         invokeBeanFactoryPostProcessors(beanFactory);

         // Register bean processors that intercept bean creation.
         //按順序將BeanPostProcessor實例化成bean並註冊到beanFactory的beanPostProcessors,
         //這裏的按順序僅實現了PriorityOrdered和Ordered的語意,未實現@Order註解的語意
         //因為BeanPostProcessor要在普通bean初始化()前後被調用,所以需要提前完成實例化並註冊到beanFactory的beanPostProcessors
         registerBeanPostProcessors(beanFactory);

         // Initialize message source for this context.
         //註冊國際化相關的Bean
         initMessageSource();

         // Initialize event multicaster for this context.
         //為上下文註冊應用事件廣播器(用於ApplicationEvent的廣播),如果有自定義則使用自定義的,如果沒有則內部實例化一個
         initApplicationEventMulticaster();

         // Initialize other special beans in specific context subclasses.
         onRefresh();

         // Check for listener beans and register them.
         //註冊所有(靜態、動態)的listener,並廣播earlyApplicationEvents
         registerListeners();

         // Instantiate all remaining (non-lazy-init) singletons.
         //實例化用戶自定義的普通單例Bean(非開天闢地的、非後置處理器)
         finishBeanFactoryInitialization(beanFactory);

         // Last step: publish corresponding event.
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         // Destroy already created singletons to avoid dangling resources.
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         // Propagate exception to caller.
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

跟蹤invokeBeanFactoryPostProcessors(beanFactory)

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
   PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

   // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
   // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
   if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
      beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
      beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
   }
}

跟蹤invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

public static void invokeBeanFactoryPostProcessors(
            ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {

        // Invoke BeanDefinitionRegistryPostProcessors first, if any.
        Set<String> processedBeans = new HashSet<>();

        if (beanFactory instanceof BeanDefinitionRegistry) {
            BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
            List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
            List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();

            for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
                if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
                    BeanDefinitionRegistryPostProcessor registryProcessor =
                            (BeanDefinitionRegistryPostProcessor) postProcessor;
                    registryProcessor.postProcessBeanDefinitionRegistry(registry);
                    registryProcessors.add(registryProcessor);
                }
                else {
                    regularPostProcessors.add(postProcessor);
                }
            }

            // Do not initialize FactoryBeans here: We need to leave all regular beans
            // uninitialized to let the bean factory post-processors apply to them!
            // Separate between BeanDefinitionRegistryPostProcessors that implement
            // PriorityOrdered, Ordered, and the rest.
            List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();

            // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
            String[] postProcessorNames =
                    beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
            for (String ppName : postProcessorNames) {
                if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                    currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                    processedBeans.add(ppName);
                }
            }
            sortPostProcessors(currentRegistryProcessors, beanFactory);
            registryProcessors.addAll(currentRegistryProcessors);
            //此處調用ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry,
            //解析配置類,為配置中的bean定義生成對應beanDefinition,並注入到registry的beanDefinitionMap
            invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
            currentRegistryProcessors.clear();

            // Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered.
            postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
            for (String ppName : postProcessorNames) {
                if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
                    currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                    processedBeans.add(ppName);
                }
            }
            sortPostProcessors(currentRegistryProcessors, beanFactory);
            registryProcessors.addAll(currentRegistryProcessors);
            invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
            currentRegistryProcessors.clear();

            // Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.
            boolean reiterate = true;
            while (reiterate) {
                reiterate = false;
                postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
                for (String ppName : postProcessorNames) {
                    if (!processedBeans.contains(ppName)) {
                        currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                        processedBeans.add(ppName);
                        reiterate = true;
                    }
                }
                sortPostProcessors(currentRegistryProcessors, beanFactory);
                registryProcessors.addAll(currentRegistryProcessors);
                invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
                currentRegistryProcessors.clear();
            }

            // Now, invoke the postProcessBeanFactory callback of all processors handled so far.
            //調用ConfigurationClassPostProcessor#postProcessBeanFactory增強配置類(通過cglib生成增強類,load到jvm內存,
            //設置beanDefinition的beanClass為增強類)
            //為什麼要增強配置類?主要是為了讓@Bean生成的bean是單例,
            invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
            invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
        }

        else {
            // Invoke factory processors registered with the context instance.
            invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, 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<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
        List<String> orderedPostProcessorNames = new ArrayList<>();
        List<String> nonOrderedPostProcessorNames = new ArrayList<>();
        for (String ppName : postProcessorNames) {
            if (processedBeans.contains(ppName)) {
                // skip - already processed in first phase above
            }
            else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
            }
            else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
                orderedPostProcessorNames.add(ppName);
            }
            else {
                nonOrderedPostProcessorNames.add(ppName);
            }
        }

        // First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered.
        sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
        invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);

        // Next, invoke the BeanFactoryPostProcessors that implement Ordered.
        List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
        for (String postProcessorName : orderedPostProcessorNames) {
            orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
        }
        sortPostProcessors(orderedPostProcessors, beanFactory);
        invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);

        // Finally, invoke all other BeanFactoryPostProcessors.
        List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
        for (String postProcessorName : nonOrderedPostProcessorNames) {
            nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class));
        }
        invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);

        // Clear cached merged bean definitions since the post-processors might have
        // modified the original metadata, e.g. replacing placeholders in values...
        beanFactory.clearMetadataCache();
    }

跟蹤invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);

private static void invokeBeanFactoryPostProcessors(
      Collection<? extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) {

   for (BeanFactoryPostProcessor postProcessor : postProcessors) {
      postProcessor.postProcessBeanFactory(beanFactory);
   }
}

跟蹤ConfigurationClassPostProcessor#postProcessBeanFactory

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
   int factoryId = System.identityHashCode(beanFactory);
   if (this.factoriesPostProcessed.contains(factoryId)) {
      throw new IllegalStateException(
            "postProcessBeanFactory already called on this post-processor against " + beanFactory);
   }
   this.factoriesPostProcessed.add(factoryId);
   if (!this.registriesPostProcessed.contains(factoryId)) {
      // BeanDefinitionRegistryPostProcessor hook apparently not supported...
      // Simply call processConfigurationClasses lazily at this point then.
      processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
   }
   //為@Configuration註解的類生成增強類(如果有必要),並替換bd中的beanClass屬性,
   enhanceConfigurationClasses(beanFactory);
   beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

到了這一步謎底幾乎已經揭曉了,@Configuration class是通過增強來實現它的語義的。通過增強把跨方法的引用調用重定向到Spring生命周期管理.我們近一步探索下這個enhanceConfigurationClasses方法

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
   Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
   for (String beanName : beanFactory.getBeanDefinitionNames()) {
      BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
      Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
      MethodMetadata methodMetadata = null;
      if (beanDef instanceof AnnotatedBeanDefinition) {
         methodMetadata = ((AnnotatedBeanDefinition) beanDef).getFactoryMethodMetadata();
      }
      if ((configClassAttr != null || methodMetadata != null) && beanDef instanceof AbstractBeanDefinition) {
         // Configuration class (full or lite) or a configuration-derived @Bean method
         // -> resolve bean class at this point...
         AbstractBeanDefinition abd = (AbstractBeanDefinition) beanDef;
         if (!abd.hasBeanClass()) {
            try {
               abd.resolveBeanClass(this.beanClassLoader);
            }
            catch (Throwable ex) {
               throw new IllegalStateException(
                     "Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
            }
         }
      }
      //在ConfigurationClassUtils.checkConfigurationClassCandidate方法中會標記Configuration is full or lite
      if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
         if (!(beanDef instanceof AbstractBeanDefinition)) {
            throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
                  beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
         }
         else if (logger.isInfoEnabled() && beanFactory.containsSingleton(beanName)) {
            logger.info("Cannot enhance @Configuration bean definition '" + beanName +
                  "' since its singleton instance has been created too early. The typical cause " +
                  "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
                  "return type: Consider declaring such methods as 'static'.");
         }
         configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
      }
   }
   if (configBeanDefs.isEmpty()) {
      // nothing to enhance -> return immediately
      return;
   }

   ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
   for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
      AbstractBeanDefinition beanDef = entry.getValue();
      // If a @Configuration class gets proxied, always proxy the target class
      beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
      // Set enhanced subclass of the user-specified bean class
      Class<?> configClass = beanDef.getBeanClass();
      //為@Configuration註解的類生成增強類
      Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
      if (configClass != enhancedClass) {
         if (logger.isTraceEnabled()) {
            logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
                  "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
         }
         beanDef.setBeanClass(enhancedClass);
      }
   }
}

看到有那麼一句話Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);

很明顯了,使用cglib技術為config class生成一個enhancedClass,再通過beanDef.setBeanClass(enhancedClass);修改beanDefinition的BeanClass屬性,在bean實例化階段,會利用反射技術將beanClass屬性對應的類實例化出來,所以最終實例化出來的@Configuration bean是一個代理類的實例。這裏稍微提一下為什麼要使用cglib,而不是jdk動態代理,主要是因為jdk動態代理是基於接口的,而這裏AppConfig並沒有實現任何接口,所以必須用cglib技術。

總結

被@Configuration 註解的類,是 full configuration class,該類會被增強(通過cglib),從而實現跨方法引用調用被重定向到Spring 生命周期管理,最終保證@Bean method生成的bean是一個單例。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

做程序員的一些偏見

“人類的心靈有一種神奇的能力,它能夠感知到別人的虛情假意。如果銷售人員的暗示是一種虛情假意的產物,那麼幾乎刻意肯定的是,這種暗示在客戶心中產生的結果也不會是一種真實的情感。”

                                                                ——諾瓦爾·霍金斯

前天,銷售同事老吳還說,做銷售一定要講人品,而且做人很重要。我很贊同他的觀點,但我還是對他的觀點做了補充:不單單是銷售要學會做人,我們任何一個崗位、任何一個人都要學會如何做人。崗位是做事層面,做人是做事的根基,無論你是銷售還是程序員、都離不開做人。我在部門裡經常對同事們說,甚至在面試的時候也跟應聘者強調,我們都是全民銷售。因為我們扁平化的開放管理制度,我們任何一個員工都有可能直接面對客戶。作為服務行業的我們,任何一個同事的印象以及專業態度都是我們的招牌。面對客戶就是面對市場,面對市場的員工就是覆蓋了銷售的角色。我們每個人都代表着一個企業,更代表了自己。有些客戶可能因為我們需求做得好,所以給了我更多的機會;也有可能有些客戶因為我們的美工同事洞悉到了客戶的需求而用真誠的設計打動了客戶,贏下了訂單;甚至可能是我們善良、踏實、勤奮的程序員同事感動了客戶,非要跟我們合作不可。這裏都包含着公司的文化和個人的修養。

 

我過去一直深受着各種流言的毒害,當然,我不會把問題丟給環境,真正的問題在於自己沒有足夠的獨立思考和判斷能力。別人說程序員普遍悶騷,別人說美工都很藝術,還有人說需求就是要有足夠的健談和洞察能力。其實,話有時候並沒有毛病,但我們缺少一個宏觀與高度的視野能力會很容易讓自己“入戲”,也就是會以偏概全。就像同事老吳說銷售考驗做人,如果我弱一點,我真會以為作為程序員的自己就沒那麼需要關注做人層面了,反正自己不用天天面對客戶。這就是我們常說的短見。

 

我現在的職位寫着是部門經理,不止戰略,在戰術上如架構、設計、開發我都一直在做,還帶着近80號人。我目前的狀態不是因為我踩正了職業規劃路線,或者我有管理的能力。而是我看不過眼和忍受不了之前團隊的低效和散漫,想自己做得有成就一點,有效率一點而已。沒人敢這麼做,而領導敢給我這樣一個機會,那就只有我自己扛起大旗向前沖了。有時候,當自己真覺得環境待不下去了,可能就是自己的機會。我不知道自己有沒有這個能力,但我有一個看清問題本質的視野和敢說敢做勇氣。個人發展的本質並不是職位驅動的,任何一個有能力的人都能有機會上升,不管你是美工、測試、需求還是程序員,就看自己願不願意放開自己思維和勇氣。我很早就已經說過,職位在做事面前都是一文不值。千萬不要被各種所謂的管理晉陞路線和技術大咖晉陞路線給誤導。人生都是問題驅動的,每個人的起點都不一樣,問題也都不一樣。多把視野放在當前問題身上,而不是帶着別人給出的規劃建議去逃避自己當前的問題,這個同事不好,那個老闆不行,這都不是自己想要的,這都是借口。能讓自己過得好,能讓家人過得好才是王道。所以,從這個角度看,無論什麼職位,跑得越高,越是辛苦。認識不了這個本質的,永遠做不了高層。

 

我們一出生就已經內置了銷售這個職位,我們需要與人打交道,我們需要結交朋友,我們需要面試找工作等等。其實,我們無時無刻在銷售着自己。而真誠就是我們每一個都需要具備的銷售品質。做銷售的門檻很低,可是考驗很大,但前途也無可限量。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

海洋熱浪讓更多鯨魚受困漁網 專家將研發預測工具

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

法國通過反浪費法 禁丟棄未售出商品

2020年2月3日苦勞網報導;陳韋綸編譯

繼禁止超商丟棄未售出食物後,,禁止服飾與精品公司摧毀未售出的商品,不過卻被批評缺乏罰則而難有成效。

共130項條文的《反浪費與經濟流通法案》,要求製造、進口與批發商,其中也包括亞馬遜(Amazon)等線上零售商,捐贈未出售的貨物,商品範圍涵蓋電子產品、衛生用品與化妝品。此外,該法案也希望2025年前回收所有塑膠製品,並將一次性塑膠瓶的使用減少一半。速食餐廳則須在2023年前停止使用塑膠容器。

總理菲力普(Édouard Philippe)表示,在法國,每年未使用卻被丟棄的消費產品高達6億5千萬歐元(約新台幣217億元)。根據「污染者付費」原則,企業也須為自己製造的廢棄物付費,例如菸草製造商必須在隔年支付處理煙蒂的費用。

在法國,。當初推動立法要求超商捐贈過期食物的德朗巴斯(Arash Derambarsh)表示,新法雖然立意良善,但是缺乏誠意,「如果沒有罰則,人們只會忽略法律」,導致新法難有成效。

※轉載自()

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※評比南投搬家公司費用收費行情懶人包大公開

孟買減少噪音試驗計畫 司機響按喇叭交通燈自動延遲轉綠燈

摘錄自2020年2月6日星島日報報導

印度孟買警方提出一項具有創意的計畫,藉以減少該市的噪音污染。如果駕駛人士響按造成大量噪音,交通燈會自動作出調整,延遲由紅燈轉成綠燈,令司機等得更久才可開車。

孟買警方於去年11月和12月推行這項試驗計畫,在交通燈燈柱上安裝量度聲音分貝的儀器。如果儀器錄得汽車響按製造出來的噪音達85分貝或以上,交通燈會延遲由紅燈轉成綠燈。

警方發言人說,在市內幾個繁忙的道路交匯處安裝這套裝置,每天試驗15分鐘。當局會於下月起擴大這項計畫,在市內10處地點進行試驗。如果試驗成功,會在整個交通管理系統內實行。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

世界最大廢水養殖系統在印度 濕地淨化勝過污水處理廠 仍不敵政府開發

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

Redis持久化的幾種方式——深入解析RDB

Redis 的讀寫都是在內存中,所以它的性能較高,但在內存中的數據會隨着服務器的重啟而丟失,為了保證數據不丟失,我們需要將內存中的數據存儲到磁盤,以便 Redis 重啟時能夠從磁盤中恢復原有的數據,而整個過程就叫做 Redis 持久化。

Redis 持久化也是 Redis 和 Memcached 的主要區別之一,因為 Memcached 是不具備持久化功能的。

1.持久化的幾種方式

Redis 持久化擁有以下三種方式:

  • 快照方式(RDB, Redis DataBase)將某一個時刻的內存數據,以二進制的方式寫入磁盤;
  • 文件追加方式(AOF, Append Only File),記錄所有的操作命令,並以文本的形式追加到文件中;
  • 混合持久化方式,Redis 4.0 之後新增的方式,混合持久化是結合了 RDB 和 AOF 的優點,在寫入的時候,先把當前的數據以 RDB 的形式寫入文件的開頭,再將後續的操作命令以 AOF 的格式存入文件,這樣既能保證 Redis 重啟時的速度,又能簡單數據丟失的風險。

因為每種持久化方案,都有特定的使用場景,讓我們先從 RDB 持久化說起吧。

2.RDB簡介

RDB(Redis DataBase)是將某一個時刻的內存快照(Snapshot),以二進制的方式寫入磁盤的過程。

3.持久化觸發

RDB 的持久化觸發方式有兩類:一類是手動觸發,另一類是自動觸發。

1)手動觸發

手動觸發持久化的操作有兩個: save 和 bgsave ,它們主要區別體現在:是否阻塞 Redis 主線程的執行。

① save 命令

在客戶端中執行 save 命令,就會觸發 Redis 的持久化,但同時也是使 Redis 處於阻塞狀態,直到 RDB 持久化完成,才會響應其他客戶端發來的命令,所以在生產環境一定要慎用

save 命令使用如下:

從圖片可以看出,當執行完 save 命令之後,持久化文件 dump.rdb 的修改時間就變了,這就表示 save 成功的觸發了 RDB 持久化。
save 命令執行流程,如下圖所示:

② bgsave 命令

bgsave(background save)既後台保存的意思, 它和 save 命令最大的區別就是 bgsave 會 fork() 一個子進程來執行持久化,整個過程中只有在 fork() 子進程時有短暫的阻塞,當子進程被創建之後,Redis 的主進程就可以響應其他客戶端的請求了,相對於整個流程都阻塞的 save 命令來說,顯然 bgsave 命令更適合我們使用。
bgsave 命令使用,如下圖所示:

bgsave 執行流程,如下圖所示:

2)自動觸發

說完了 RDB 的手動觸發方式,下面來看如何自動觸發 RDB 持久化?
RDB 自動持久化主要來源於以下幾種情況。

① save m n

save m n 是指在 m 秒內,如果有 n 個鍵發生改變,則自動觸發持久化。
參數 m 和 n 可以在 Redis 的配置文件中找到,例如,save 60 1 則表明在 60 秒內,至少有一個鍵發生改變,就會觸發 RDB 持久化。
自動觸發持久化,本質是 Redis 通過判斷,如果滿足設置的觸發條件,自動執行一次 bgsave 命令。
注意:當設置多個 save m n 命令時,滿足任意一個條件都會觸發持久化。
例如,我們設置了以下兩個 save m n 命令:

  • save 60 10
  • save 600 1

當 60s 內如果有 10 次 Redis 鍵值發生改變,就會觸發持久化;如果 60s 內 Redis 的鍵值改變次數少於 10 次,那麼 Redis 就會判斷 600s 內,Redis 的鍵值是否至少被修改了一次,如果滿足則會觸發持久化。

② flushall

flushall 命令用於清空 Redis 數據庫,在生產環境下一定慎用,當 Redis 執行了 flushall 命令之後,則會觸發自動持久化,把 RDB 文件清空。
執行結果如下圖所示:

③ 主從同步觸發

在 Redis 主從複製中,當從節點執行全量複製操作時,主節點會執行 bgsave 命令,並將 RDB 文件發送給從節點,該過程會自動觸發 Redis 持久化。

4.配置說明

合理的設置 RDB 的配置,可以保障 Redis 高效且穩定的運行,下面一起來看 RDB 的配置項都有哪些?

RDB 配置參數可以在  Redis 的配置文件中找見,具體內容如下:

# RDB 保存的條件
save 900 1
save 300 10
save 60 10000

# bgsave 失敗之後,是否停止持久化數據到磁盤,yes 表示停止持久化,no 表示忽略錯誤繼續寫文件。
stop-writes-on-bgsave-error yes

# RDB 文件壓縮
rdbcompression yes

# 寫入文件和讀取文件時是否開啟 RDB 文件檢查,檢查是否有無損壞,如果在啟動是檢查發現損壞,則停止啟動。
rdbchecksum yes

# RDB 文件名
dbfilename dump.rdb

# RDB 文件目錄
dir ./

其中比較重要的參數如下列表:
① save 參數
它是用來配置觸發 RDB 持久化條件的參數,滿足保存條件時將會把數據持久化到硬盤。
默認配置說明如下:

  • save 900 1:表示 900 秒內如果至少有 1 個 key 值變化,則把數據持久化到硬盤;
  • save 300 10:表示 300 秒內如果至少有 10 個 key 值變化,則把數據持久化到硬盤;
  • save 60 10000:表示 60 秒內如果至少有 10000 個 key 值變化,則把數據持久化到硬盤。

② rdbcompression 參數
它的默認值是 yes 表示開啟 RDB 文件壓縮,Redis 會採用 LZF 算法進行壓縮。如果不想消耗 CPU 性能來進行文件壓縮的話,可以設置為關閉此功能,這樣的缺點是需要更多的磁盤空間來保存文件。
③ rdbchecksum 參數
它的默認值為 yes 表示寫入文件和讀取文件時是否開啟 RDB 文件檢查,檢查是否有無損壞,如果在啟動是檢查發現損壞,則停止啟動。

5.配置查詢

Redis 中可以使用命令查詢當前配置參數。查詢命令的格式為:config get xxx ,例如,想要獲取 RDB 文件的存儲名稱設置,可以使用 config get dbfilename ,執行效果如下圖所示:

查詢 RDB 的文件目錄,可使用命令 config get dir ,執行效果如下圖所示:

6.配置設置

設置 RDB 的配置,可以通過以下兩種方式:

  • 手動修改 Redis 配置文件;
  • 使用命令行設置,例如,使用 config set dir "/usr/data" 就是用於修改 RDB 的存儲目錄。

注意:手動修改 Redis 配置文件的方式是全局生效的,即重啟 Redis 服務器設置參數也不會丟失,而使用命令修改的方式,在 Redis 重啟之後就會丟失。但手動修改 Redis 配置文件,想要立即生效需要重啟 Redis 服務器,而命令的方式則不需要重啟 Redis 服務器。

小貼士:Redis 的配置文件位於 Redis 安裝目錄的根路徑下,默認名稱為 redis.conf。

7.RDB 文件恢復

當 Redis 服務器啟動時,如果 Redis 根目錄存在 RDB 文件 dump.rdb,Redis 就會自動加載 RDB 文件恢復持久化數據。
如果根目錄沒有 dump.rdb 文件,請先將 dump.rdb 文件移動到 Redis 的根目錄。
驗證 RDB 文件是否被加載
Redis 在啟動時有日誌信息,會显示是否加載了 RDB 文件,我們執行 Redis 啟動命令:src/redis-server redis.conf ,如下圖所示:

從日誌上可以看出, Redis 服務在啟動時已經正常加載了 RDB 文件。

小貼士:Redis 服務器在載入 RDB 文件期間,會一直處於阻塞狀態,直到載入工作完成為止。

8.RDB 優缺點

1)RDB 優點

  • RDB 的內容為二進制的數據,佔用內存更小,更緊湊,更適合做為備份文件;
  • RDB 對災難恢復非常有用,它是一個緊湊的文件,可以更快的傳輸到遠程服務器進行 Redis 服務恢復;
  • RDB 可以更大程度的提高 Redis 的運行速度,因為每次持久化時 Redis 主進程都會 fork() 一個子進程,進行數據持久化到磁盤,Redis 主進程並不會執行磁盤 I/O 等操作;
  • 與 AOF 格式的文件相比,RDB 文件可以更快的重啟。

    2)RDB 缺點

  • 因為 RDB 只能保存某個時間間隔的數據,如果中途 Redis 服務被意外終止了,則會丟失一段時間內的 Redis 數據;
  • RDB 需要經常 fork() 才能使用子進程將其持久化在磁盤上。如果數據集很大,fork() 可能很耗時,並且如果數據集很大且 CPU 性能不佳,則可能導致 Redis 停止為客戶端服務幾毫秒甚至一秒鐘。

    9.禁用持久化

    禁用持久化可以提高 Redis 的執行效率,如果對數據丟失不敏感的情況下,可以在連接客戶端的情況下,執行 config set save "" 命令即可禁用 Redis 的持久化,如下圖所示:

    10.小結

    通過本文我們可以得知,RDB 持久化分為手動觸發和自動觸發兩種方式,它的優點是存儲文件小,Redis 啟動時恢複數據比較快,缺點是有丟失數據的風險。RDB 文件的恢復也很簡單,只需要把 RDB 文件放到 Redis 的根目錄,在 Redis 啟動時就會自動加載並恢複數據。

    11.思考題

    如果 Redis 服務器 CPU 佔用過高,可能是什麼原因導致的?歡迎各位在評論區,寫下你們的答案。

    12.參考&鳴謝

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

Python字典 你必須知道的用法系列

本文Python版本為3.7.X,閱讀本文之前需了解python字典的基本用法。

介紹

字典(dict)是Python中內置的一個數據結構,由多個鍵值對組成,鍵(key)和值(value)用冒號分隔,每個鍵值對之間用逗號(,)分隔,整個字典包括在大括號中({}),鍵必須是唯一的,值可以取任何類型,但是鍵必須是不可變類型,如字符串,数字或元組。

底層使用了hash表來關聯key和value,dict是無序的。特點包括:

  1. 查找和插入的速度極快,不會隨着key的增加而變慢;
  2. 需要佔用的內存較多

所以,dict是一種以空間換取時間的數據結構,應用於需要快速查找的場景。

操作

常用方法

get()

返回指定鍵的值,如果key不存在,則返回默認值(默認為None),而不會報錯,語法為dict.get(key)。

dict_1['age'] = 24

In [7]: print(dict_1.get('age'))
24

In [11]: print(dict_1.get('nama'))
None

In [12]: print(dict_1['nama'])
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-12-ef61a380920e> in <module>
----> 1 print(dict_1['nama'])

KeyError: 'nama'

key in dict

使用in操作符來判斷鍵是否存在於字典中,存在則返回True,否則返回False,語法為:key in dict。

In [15]: dict_1
Out[15]: {'name': None, 'age': 24, 'sex': None}

In [16]: print('name' in dict_1)
True

In [17]: print('nama' in dict_1)
False

在python 2中該功能使用has_key()方法實現。

items()

以列表形式返回可遍歷的(鍵, 值)元組數組,語法為dict.items()。

In [18]: dict_1
Out[18]: {'name': None, 'age': 24, 'sex': None}

In [19]: print(dict_1.items())
dict_items([('name', None), ('age', 24), ('sex', None)])

In [20]: for key, value in dict_1.items():
    ...:     print(key, value)
    ...:
name None
age 24
sex None

keys()

以列表返回一個字典的所有鍵:dict.keys()

In [21]: dict_1
Out[21]: {'name': None, 'age': 24, 'sex': None}

In [22]: print(dict_1.keys())
dict_keys(['name', 'age', 'sex'])

values()

以列表形式返回字典中的所有值:dict.values()

In [27]: dict_1
Out[27]: {'name': None, 'age': 24, 'sex': None, 'sub_name': 'Tony'}

In [28]: print(dict_1.values())
dict_values([None, 24, None, 'Tony'])

setdefault()

和get()類似,用戶獲得與給頂尖相關聯的值,不同的是,該方法如果鍵不存在時會添加鍵並將值設為默認值,語法為:dict.setdefault(key, default=None)。

In [23]: dict_1
Out[23]: {'name': None, 'age': 24, 'sex': None}

In [24]: print(dict_1.setdefault('name'))
None

In [25]: print(dict_1.setdefault('name', 'Tony'))
None

In [26]: print(dict_1.setdefault('sub_name', 'Tony'))
Tony

In [27]: dict_1
Out[27]: {'name': None, 'age': 24, 'sex': None, 'sub_name': 'Tony'}

update()

語法為:dict_1.update(dict_2),用於把dict_2的鍵值對更新到dict_1中,如果有相同的鍵會被覆蓋。

In [31]: dict_1
Out[31]: {'name': None, 'age': 24, 'sex': None, 'sub_name': 'Tony'}

In [32]: dict_2
Out[32]: {'name': 'Mary', 'age': 18, 'sex': None, 'sub_name': ''}

In [33]: dict_1.update(dict_2)

In [34]: dict_1
Out[34]: {'name': 'Mary', 'age': 18, 'sex': None, 'sub_name': ''}

clear()

刪除字典中的所有項,dict.clear(),舉個例子:

In [1]: dict_1 = dict(name="Tony", age=24)

In [2]: dict_2 = dict_1

In [3]: print(dict_2)
{'name': 'Tony', 'age': 24}

In [4]: dict_2.clear()

In [5]: dict_2
Out[5]: {}

In [6]: dict_1
Out[6]: {}

copy()

淺拷貝原始字典,返回一個具有相同鍵值對的新字典,dict.copy(),舉個例子:

In [1]: dict_1 = dict(name='Tony', info=['boy', 24])

In [2]: dict_3 = dict_1.copy()

In [3]: dict_3['name'] = "Ring"

In [4]: dict_3['info'].remove('boy')

In [5]: dict_3
Out[5]: {'name': 'Ring', 'info': [24]}

In [6]: dict_1
Out[6]: {'name': 'Tony', 'info': [24]}

fromkeys()

創建一個新字典,dict.fromkeys(seq[, value]),以序列seq中的元素做字典的鍵,value為字典所有鍵對應的初始值,其中value為可選參數, 默認為None。適用於數據初始化,舉個例子:

In [1]: info = ['name', 'age', 'sex']

In [2]: dict_1 = dict.fromkeys(info)

In [3]: dict_1
Out[3]: {'name': None, 'age': None, 'sex': None}

常見操作

合併字典

有四種方式:

  1. 常規處理
In [15]: dict_1
Out[15]: {'Tony': 24}

In [16]: dict_2
Out[16]: {'ben': 18}

In [17]: dict3 = dict()

In [18]: for key, value in dict_1.items():
    ...:     dict_3[key] = value
    ...:

In [19]: for key, value in dict_2.items():
    ...:     dict_3[key] = value
    ...:

In [20]: dict_3
Out[20]: {'Tony': 24, 'ben': 18}
  1. update()
In [9]: dict_1
Out[9]: {'Tony': 24}

In [10]: dict_2
Out[10]: {'ben': 18}

In [12]: dict_3 = dict_1.copy()

In [13]: dict_3.update(dict_2)

In [14]: dict_3
Out[14]: {'Tony': 24, 'ben': 18}
  1. 藉助字典的dict(d1, **d2)方法
In [33]: dict_1
Out[33]: {'Tony': 24}

In [34]: dict_2
Out[34]: {'ben': 18}

In [35]: dict_3 = dict(dict_1, **dict_2)

In [36]: dict_3
Out[36]: {'Tony': 24, 'ben': 18}

進階

字典推導式

和列表推導式類似,優點是底層用C實現,會快很多,推薦使用。

對換字典的鍵值

使用字典推導式可以輕鬆對換一個字典的鍵值:

In [42]: dict_4
Out[42]: {24: 'Tony', 18: 'ben'}

In [43]: dict_3
Out[43]: {'Tony': 24, 'ben': 18}

In [44]: dict_4 = {k:v for v, k in dict_3.items()}

In [45]: dict_4
Out[45]: {24: 'Tony', 18: 'ben'}

從字典中提取子集

想創建一個字典,其本身是另一個字典的子集。

舉個例子:

In [88]: a = {'Ben': 18, 'Jack': 12, 'Ring': 23, 'Tony': 24}

In [89]: b = {k:v for k, v in a.items() if v > 18}

In [90]: b
Out[90]: {'Ring': 23, 'Tony': 24}

生成有序字典

在Python3.6之前的字典是無序的,但是有時候我們需要保持字典的有序性,orderDict可以在dict的基礎上實現字典的有序性,這裏的有序指的是按照字典key插入的順序來排列,這樣就實現了一個先進先出的dict,當容量超出限制時,先刪除最早添加的key。
舉例:

In [49]: from collections import OrderedDict

In [50]: ordered_dict = OrderedDict([('a', 2), ('b', 4), ('c', 5)])

In [51]: for key, value in ordered_dict.items():
    ...:     print(key, value)
    ...:
a 2
b 4
c 5

可以看到OrderedDict是按照字典創建時的插入順序來排序。

原理:OrderedDict內部維護了一個雙向鏈表,它會根據元素加入的順序來排列鍵的位置,這也就導致OrderedDict的大小是普通字典的2倍多。

合併列表中key相同的字典

也就是生成所謂的一鍵多值字典,需要將對應的多個值保存在其它容器比如列表或集合,取決於多值是否需要保證唯一性。

舉個例子:

In [64]: from collections import defaultdict

In [65]: a = [{'a': 1}, {'b': 3}, {'c': 4}, {'a':5}, {'b':2}, {'b': 4}]

In [66]: b = defaultdict(list)

In [67]: [b[k].append(v) for item in a for k, v in item.items()]
Out[67]: [None, None, None, None, None, None]

In [68]: b
Out[68]: defaultdict(list, {'a': [1, 5], 'b': [3, 2, 4], 'c': [4]})

In [69]: b['a']
Out[69]: [1, 5]

尋找兩個字典的異同

場景:尋找兩個字典中的異同,包括相同的鍵或者相同的值。

分析:字典是一系列鍵值之間的映射集合,有以下特點:

  1. keys()會返回字典中的所有鍵,並且字典的鍵是支持集合操作的,所以利用集合的交叉並補即可對字典的鍵進行處理;
  2. items()返回(key, value)組成的對象,支持集合操作;
  3. values()並不支持集合操作,因為並不能保證所有的值是唯一的,但是如果必須要判斷操作,可以先將值轉化為集合來實現。

舉例:

In [78]: a = {'a':1, 'b':2, 'c':3}

In [79]: b = {'b':3, 'c':3, 'd':4}

In [80]: a.keys() & b.keys()
Out[80]: {'b', 'c'}

In [81]: a.keys() - b.keys()
Out[81]: {'a'}

In [82]: a.items() & b.items()
Out[82]: {('c', 3)}

再舉一個例子,在創建一個字典時,期望可以去除某些鍵:

In [85]: a
Out[85]: {'a': 1, 'b': 2, 'c': 3}

In [86]: c = {k: a[key] for k in a.keys() - {'b'}}

In [87]: c
Out[87]: {'a': 3, 'c': 3}

以上。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

暴風雨侵襲歐洲 打亂多國陸海空交通

摘錄自2020年2月10日公視報導

席捲歐洲中西部的暴風雨造成水災,強風也完全打亂陸海空的交通。不過還是有民航機勉強降落,場面驚險萬狀。這場席捲歐洲中西部的暴風雨,英國氣象局命名為「綺拉」,而德國則命名為「沙賓」,部分地區的風力達到13甚至14級。

德國公共廣播電視德國之聲,整理各國氣象資訊的報導指出,英國北部在週末的24小時內,累積雨量超過150毫米,造成一些河川河水潰堤,民眾穿著雨衣涉水而過。除了要小心不要失足跌進水溝,還有注意強風、以及路上的車輛。

除了交通受阻與生活不便,歐洲人相當重視的運動比賽也受到暴風雨牽連。德國科隆的職業足球甲組賽程因天候取消,威林根的世界盃跳台滑雪大賽,也把週末的賽程延後。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

F-貿聯今年營收估續揚兩位數;規劃再增利基產能

車用急單與高階IT應用需求優預期,F-貿聯(3665)去年第四季營收創下新高,法人估計,上季毛利率將優於前季,去年每股稅後盈餘將超過8元,締造新猷,而今年則賴車用、儲能系統與高階IT的動能延續,拉升全年營收成長兩位數。公司長期也計畫,將在南歐北非地區設立新抽線廠,並於印度廠啟動擴產,以支應車用、醫療及太陽能應用的訂單成長需求。

  F-貿聯是專業線組廠商,但相較於台廠同業,上市之初對IT產業的依賴度已不高,且在IT產業供應鏈,也多聚焦不同訊號轉換的中高階產品為主,避開紅海競爭。而在非IT領域,該公司配合如特斯拉、Polaris等車廠前期開發已久,隨該等客戶車種熱銷,F-貿聯不僅坐穩主要、甚至獨家供應商角色,去年來自車用的營收占比也已達31%,被外界歸類於具代表性的車用電子供應鏈。   而因特斯拉新款電動車上市,帶動上季的急單需求,加上因應PC薄型化或新款I/O介面所需的轉換裝置,如Dongle、Docking等需求仍強,挹注F-貿聯上季營收創高,法人估計,因產品組合與稼動率正向,毛利率可望高於去年第三季,去年每股稅後盈餘將超過8元,登上顛峰。   法人認為,F-貿聯今年營收的動能,仍來自高階IT、車用及儲能系統。在高階IT上,該公司已是微軟新款平板電腦Dongle轉換線材的主要供應商,且在可一對多轉換的Cable Docking Station方面,也是商用機如Dell、HP的供應商。因整體體市場新款商用機採用新版薄型Docking的比例,還僅一成上下,還有不小成長空間,是今年高階IT應用可以期待的正面因素。   至於車用市場,該公司主要客戶中,特斯拉動向最受關注。根據該客戶公布,去年車種總銷量達50,508台,達公司預期水準,今年喊出銷售將達88,400台,增幅75%,供應鏈訂單也將同步受惠。   比較值得留意的是,特斯拉去年發表的家用及企業用儲能系統,去年底前首批拉貨後,並無進一步通知今年訂單展望,一度引發外界憂慮。不過,就供應鏈評估,特斯拉今年之所以還不敢對儲能系統,抱持過度樂觀期待,主要是卡在電池產能,因此該客戶已釋出可能增加除Panasonic外的其他供應商,且新廠產能也會逐步開出,挹注供應鏈。   F-貿聯長期也規劃,將於北非南歐設立新的抽線廠,以支應車用及醫療的市場所需。另亦計畫在印度擴產,看好的則是當地太陽能電廠的基礎設備成長。法人估計,F-貿聯今年營收可望有兩位數成長,本業獲利增幅不亞營收,以目前股本計算,每股稅後盈餘應有挑戰9元實力。   (本文內容由授權使用;首圖來源:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!