分類: 3C資訊

  • Spring AOP學習筆記02:如何開啟AOP

    Spring AOP學習筆記02:如何開啟AOP

      上文簡要總結了一些AOP的基本概念,並在此基礎上敘述了Spring AOP的基本原理,並且輔以一個簡單例子幫助理解。從本文開始,我們要開始深入到源碼層面來一探Spring AOP魔法的原理了。

      要使用Spring AOP,第一步是要將這一功能開啟,一般有兩種方式:

    • 通過xml配置文件的方式;
    • 通過註解的方式;

     

    1. 配置文件開啟AOP功能

      我們先來看一下配置文件的方式,這個上文也提到過,在xml文件中加上對應的標籤,而且別忘了加上對應的名稱空間(即下面的xmlns:aop。。。):

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns="http://www.springframework.org/schema/beans"
           xmlns:aop = "http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
         http://www.springframework.org/schema/aop
         http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
         
         <aop:aspectj-autoproxy/>
    
    </beans>

      這裡是通過標籤<aop:aspectj-autoproxy/>來完成開啟AOP功能,這是一個自定義標籤,需要自定義其解析,而這些spring都已經實現好了,前面專門寫過一篇文章講述spring是如何解析自定義xml標籤的,我們這裏大致回顧一下解析流程:

    • 定義一個XML文件來描述你的自定義標籤元素;
    • 創建一個Handler,擴展自NamespaceHandlerSupport,用於註冊下面的parser;
    • 創建若干個BeanDefinitionParser的實現,用來解析XML文件中的定義;
    • 將上述文件註冊到Spring中,這裏其實是做一下配置;

      我們就不照着這個步驟來了,我們直接參考spring對這個自定義標籤的解析過程,上面的4個步驟只是作為參考,在整個解析過程中都會涉及到。

      前面講解析自定義xml標籤時候提到過,解析的流程大致如下:

    • 首先會去獲取自定義標籤對應的名稱空間;
    • 然後根據名稱空間找到對應的NamespaceHandler;
    • 調用自定義的NamespaceHandler進行解析;

    1.1 獲取名稱空間

      這裏<aop:aspectj-autoproxy/>對應的名稱空間是什麼呢?在上面的開啟aop的配置文件裏面名稱空間那裡給出了一些線索,其實就是下面這個:

    http://www.springframework.org/schema/aop

      至於名稱空間的獲取,也無甚好說的,其實就是直接調用org.w3c.dom.Node提供的相應方法來完成名稱空間的提取。

    1.2 獲取handler

      然後又是如何根據名稱空間找到對應的NamespaceHandler呢?之前也說到過,在找對應的NamespaceHandler時會去META-INF/spring.handlers這個目錄下加載資源文件,我們來找一下spring.handlers這個文件看看(需要去spring-aop對應的jar報下找):

    http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

      看到沒,這裡是以key-value的形式維護着名稱空間和對應handler的關係的,所以對應的handler就是這個AopNamespaceHandler。spring根據名稱空間找到這個handler之後,會通過反射的方式將這個類加載,並緩存起來。

    1.3 解析標籤

      上面的handler只有一個自定義的方法:

    public void init() {
        // In 2.0 XSD as well as in 2.1 XSD.
        registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
        registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
        registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
    
        // Only in 2.0 XSD: moved to context namespace as of 2.1
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    }

      這是一個初始化方法,在加載的時候會執行,主要作用就是註冊一些解析器,這裏我們主要關注AspectJAutoProxyBeanDefinitionParser,這就是我們要找的,它的作用就是解析<aop:aspectj-autoproxy/>標籤的。主要流程就是,spring會調用上一步拿到的AopNamespaceHandler的parse()方法,在這個方法裏面,會將解析的工作委託給AspectJAutoProxyBeanDefinitionParser來完成具體解析工作,我們就來看一下具體幹了啥吧。

      開始解析的工作從這裏開始:

    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));

      此時我們拿到的handler其實是我們自定義的AopNamespaceHandler了,但是它並沒有實現parse()方法,所以這裏這個應該是調用的父類(NamespaceHandlerSupport)中的parse()方法:

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        // 尋找解析器並進行解析操作
        return findParserForElement(element, parserContext).parse(element, parserContext);
    }
    
    private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        // 獲取元素名稱,也就是<aop:aspectj-autoproxy/>中的aspectj-autoproxy
        String localName = parserContext.getDelegate().getLocalName(element);
        // 根據aspectj-autoproxy找到對應的解析器,也就是在registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
        // 註冊的解析器
        BeanDefinitionParser parser = this.parsers.get(localName);
        if (parser == null) {
            parserContext.getReaderContext().fatal(
                "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
        }
        return parser;
    }

      首先是尋找元素對應的解析器,然後調用其parse()方法。結合我們前面的示例,其實就是首先獲取在AopNamespaceHandler類中的init()方法中註冊對應的AspectJAutoProxyBeanDefinitionParser實例,並調用其parse()方法進行進一步解析:

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
        extendBeanDefinition(element, parserContext);
        return null;
    }
    
    // 下面的代碼在AopConfigUtils中
    public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
            ParserContext parserContext, Element sourceElement) {
    
        BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
                parserContext.getRegistry(), parserContext.extractSource(sourceElement));
        useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
        registerComponentIfNecessary(beanDefinition, parserContext);
    }
    
    public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
        return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
    }
    
    private static BeanDefinition registerOrEscalateApcAsRequired(Class cls, BeanDefinitionRegistry registry, Object source) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
            BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
            if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
                int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
                int requiredPriority = findPriorityForClass(cls);
                if (currentPriority < requiredPriority) {
                    apcDefinition.setBeanClassName(cls.getName());
                }
            }
            return null;
        }
        RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
        beanDefinition.setSource(source);
        beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
        return beanDefinition;
    }

      上面這一堆代碼最核心的部分就在後兩個方法中,就是完成了對AnnotationAwareAspectJAutoProxyCreator類的註冊,到這裏對自定義標籤<aop:aspectj-autoproxy/>的解析也就完成了,可以看到其最核心的部分就是完成了對AnnotationAwareAspectJAutoProxyCreator類的註冊,那為什麼註冊了這個類就開啟了aop功能呢?這裏先賣個關子,後面詳細說。

      這裏再回過頭來看一下上面說到的spring對自定義標籤解析的4個步驟,其實第一步的schema對應的是在org.springframework.aop.config路徑下的spring-aop-3.0.xsd文件,其映射關係是維護在META-INF/spring.schemas文件中的,而spring-aop-3.0.xsd的主要作用就是描述自定義標籤。

      當通過META-INF/spring.handlers找到對應的AopNamespaceHandler,並通過在其加載后執行init()方法過程中完成了AspectJAutoProxyBeanDefinitionParser的註冊,有這個parser再來完成對自定義標籤的解析工作,這對應上面4個步驟中的第二步和第三部。至於第四步的配置工作,無非就是將spring.schemas和spring.handlers這兩個配置文件放在META-INF/目錄下罷了。

      關於這部分解析過程,寫得不是非常詳細,如果有不明白,可以參考之前一篇文章,講spring是如何解析自定義xml標籤。

     

    2. 註解方式開啟aop

      另一種開啟spring aop的方式是通過註解的方式,使用的註解是@EnableAspectJAutoProxy,可以通過配置類的方式完成註冊:

    @Configuration
    @EnableAspectJAutoProxy
    public class AppConfig {
    
    }

       也可以在啟動類上直接加上這個註解,這在springboot中比較常見,其實質也是上面的方式。通過這種方式配置之後,就開啟了aop功能,那具體又是如何實現的呢?我們看一下這個註解:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(AspectJAutoProxyRegistrar.class)
    public @interface EnableAspectJAutoProxy {
    
        /**
         * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
         * to standard Java interface-based proxies. The default is {@code false}.
         */
        boolean proxyTargetClass() default false;
    
        /**
         * Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
         * for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
         * Off by default, i.e. no guarantees that {@code AopContext} access will work.
         * @since 4.3.1
         */
        boolean exposeProxy() default false;
    
    }

      這裏我們的關注點是其通過@Import(AspectJAutoProxyRegistrar.class)引入了AspectJAutoProxyRegistrar,那這又是什麼?

    class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    
        /**
         * Register, escalate, and configure the AspectJ auto proxy creator based on the value
         * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
         * {@code @Configuration} class.
         */
        @Override
        public void registerBeanDefinitions(
                AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
            AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
    
            AnnotationAttributes enableAspectJAutoProxy =
                    AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
            if (enableAspectJAutoProxy != null) {
                if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                    AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                }
                if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                    AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
                }
            }
        }
    
    }

      看到這裏,是不是有點眼熟了呢?是的,其實它也是和上面說的xml配置使用的方式一樣,通過AopConfigUtils來完成AnnotationAwareAspectJAutoProxyCreator類的註冊。是不是比xml配置文件的方式方便許多呢。

     

    4. 開啟aop的魔法

      通過前面的學習我們了解了可以通過Spring自定義配置完成對AnnotationAwareAspectJAutoProxyCreator類型的自動註冊,而這個類到底是做了什麼工作來實現AOP的操作呢?這裏還是先來看一下AnnotationAwareAspectJAutoProxyCreator的類層次結構:

      這裡有一個很重要的點,就是AnnotationAwareAspectJAutoProxyCreator實現了BeanPostProcessor接口。在IOC部分的文章中有詳細說過,Spring在加載Bean的過程中會在實例化bean前後調用BeanPostProcessor的相關方法(相關邏輯是在initializeBean方法中,調用postProcessBeforeInitialization、postProcessAfterInitialization方法),而AOP的魔法就是從這裏開始的。

      每次看到這裏,我內心對spring的軟件架構設計都是湧現出無比的佩服,通過後處理器的方式來做擴展,對原有模塊是沒有任何改動,也不會產生耦合,spring親自踐行着對修改關閉,對擴展開放的原則。

     

    3. 總結

       本文我們學習了spring是如何開啟aop功能的,無論是通過xml配置文件方式,還是通過Java config這種註解的方式,其最終都是完成了將AnnotationAwareAspectJAutoProxyCreator這個類註冊到spring容器當中,那這個類又有什麼魔法,可以達到將其註冊到容器即達到開啟aop的功效,其實其繼承自BeanPostProcessor接口,通過後處理器的方式擴展出了開啟spring aop的功能。

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

    【其他文章推薦】

    台北網頁設計公司這麼多該如何選擇?

    ※智慧手機時代的來臨,RWD網頁設計為架站首選

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

    ※回頭車貨運收費標準

    網頁設計最專業,超強功能平台可客製化

    ※別再煩惱如何寫文案,掌握八大原則!

  • CSS3動畫基礎

    CSS3動畫基礎

    目錄

    編寫頁面
    transition元素過渡屬性
    貝塞爾曲線
    transform元素變換

    • translate平移
    • scale縮放
    • rotate旋轉
    • skew傾斜
    • matrix矩陣變換
    • perspective景深
    • transform-origin變換原點

    animation 和 keyframs(更精細的動畫)

    編寫頁面

    記事本或SublimeText或vscode編寫html:

    <html>
    
    <div id="box"></div>
    
    <style>
    #box {
        background-color: rgb(246, 96, 78); /*背景色*/
        width: 100px; /*寬度*/
        height: 100px; /*長度*/
        position: relative; /*位置*/
        border-radius: 15px; /*加點圓角*/
    }
    
    </style>
    
    </html>
    

    加上鼠標懸浮的效果:

    <html>
    
    <div id="box"></div>
    
    <style>
    #box {
        background-color: rgb(246, 96, 78); /*背景色*/
        width: 100px; /*寬度*/
        height: 100px; /*長度*/
        position: relative; /*位置*/
        border-radius: 15px; /*加點圓角*/
    }
    
    /*鼠標懸浮后的樣式*/
    #box:hover {
        /*向下偏移50px*/
        top: 50px;
    }
    </style>
    
    </html>
    

    效果圖如下:

    瀏覽器只渲染出“box”的初始狀態, 和鼠標懸浮后的狀態”top: 50px;”, 效果較為生硬,可以使用”transition”屬性豐富視覺效果。

    transition元素過渡屬性

      transition譯作“過渡”,在css3中,transition屬性用來設置元素過渡效果。
      transition包含4個子屬性,分別為:

    屬性 說明 默認值
    property 設置給元素的那個方面添加過渡效果,比如元素的”width”和”height”均發生改變時,可以指定該屬性為”width“,那麼元素的”width”的變動才有過渡效果。”all“表示所有變動都加上過渡效果。 all
    duration 設置過渡效果的持續時間,至少要給transition設置這個子屬性,否則transition屬性就沒意義了。 0s
    timing-function 過渡函數,該屬性決定元素的過渡效果與時間的關係。 ease
    delay delay即為“延遲”,表示該元素在加載后多久才開始過渡效果 0s

    這幾個元素的順序如下:

    transition: property duration timing-function delay;
    

    修改上面的“#box”樣式:

    #box {
        background-color: rgb(246, 96, 78); /*背景色*/
        width: 100px; /*寬度*/
        height: 100px; /*長度*/
        position: relative; /*位置*/
        border-radius: 15px; /*加點圓角*/
        /*設置過渡效果 持續1秒,延遲500毫秒才開始*/
        transition: 1s 500ms; /*等價於 transition: all 1s ease 500ms */
        /*兼容webkit內核*/
        -webkit-transition: 1s 500ms;
    }
    

    transition屬性加在”#box”元素上,表示該元素變換時按設置的效果進行變換。

    修改文件后可以發現過渡效果並沒有生效,這是因為”#box”沒有設置”top”,只是在鼠標懸浮后才出現”top”屬性,即解析器沒有找到“top”過渡的“初始狀態”,“過渡”就應該包含元素的初始狀態和最終狀態。

    給”#box”加上”top: 0;”:

    #box {
        background-color: rgb(246, 96, 78); /*背景色*/
        top: 0;
        width: 100px; /*寬度*/
        height: 100px; /*長度*/
        position: relative; /*位置*/
        border-radius: 15px; /*加點圓角*/
        /*設置過渡效果 持續1秒,延遲500毫秒才開始*/
        transition: 1s 500ms; /*等價於 transition: all 1s ease 500ms */
        /*兼容webkit內核*/
        -webkit-transition: 1s 500ms;
    }
    

    效果如下:

    關於timing-function,還可以選擇”linear”(線性效果)、”ease-in”(漸進)等,想實現更好玩的效果,可以藉助“貝塞爾曲線函數”。

    關於transition屬性——菜鳥教程傳送門

    貝塞爾曲線

    貝塞爾曲線百度百科
    關於貝塞爾曲線,有很多資料,不再贅述。

    貝塞爾曲線可視化
    這是一個貝塞爾曲線函數可視化的一個網站,用這個網站可以直觀地生成合適的動畫加速度函數。

      如上是網站的界面,函數的參數分別為坐標繫上紅球的x軸坐標、y軸坐標和藍綠球的x軸坐標和y軸坐標。坐標系橫軸為時間,縱軸為動畫的 progress, 直譯過來是進程、進展的意思,映射到平移上就是指移動的點到原點的偏移量。曲線的斜率,反映的是動畫的加速度。
      動圖中兩個方塊是自定義動畫與線性動畫的對比。自定義動畫後面具有彈跳的效果,在左上角坐標繫上表現為後段往下的凹陷。動畫的整體效果是元素離原點的距離越來越遠,到後段反而離近一點點,然後又遠離,直至到達終點。

    選擇合適的函數“cubic-bezier(.37,1.44,.57,.77)”設置到”#box”元素中:

    #box {
        background-color: rgb(246, 96, 78); /*背景色*/
        top: 0;
        width: 100px; /*寬度*/
        height: 100px; /*長度*/
        position: relative; /*位置*/
        border-radius: 15px; /*加點圓角*/
        transition: 1s cubic-bezier(.37,1.44,.57,.77) 500ms; 
        /*兼容webkit內核*/
        -webkit-transition: 1s cubic-bezier(.37,1.44,.57,.77) 500ms;
    }
    

    效果如圖:

    transform元素變換

      以上提及的動畫效果都是給元素設置初始狀態和最終狀態,然後讓瀏覽器自動渲染的,這種叫“補間動畫”,即定義初始和結束狀態,瀏覽器自動計算並補充“中間的狀態”最後渲染出來,“補間動畫”在flash,AE之類的軟件都可以看到。
      上面例子是已經知道了”box”的初始狀態”top: 0;”了,那萬一有的需求是一開始不知道“box”的位置呢,那該如何使得”box”向下移動?那就是”transform”屬性的功勞了。
      ”transform”就是“改變形態”的意思,就是“汽車人變形”里的“變形”,通過“transform”屬性可以改變元素的狀態。
      transform包含很多的變換效果,一一介紹。

    translate平移

    translate是“轉變,轉為”的意思,在css3中,translate是transform的子屬性,用來平移元素。
    translate包含如下幾種使用方法:

    名稱 描述 示例
    translateX(x) 表示水平移動,x為負是往左,為正則向右移動 transform: translateX(10px)
    transform: translateX(-15%)
    translateY(y) 豎直移動,y為負向上,為正向下 同上
    translateZ(z) 需配合“perspective()”使用,perspective()用來定義“景深”。z為負時是遠離用戶(屏幕),正是接近用戶 transform: perspective(500px) translateZ(200px)
    translate(x, y) 二維平面的移動,是最前面兩個的結合 簡單
    translate(x, y, z) 三維空間的移動,最前面三個的結合 同上

    把上面的html改成如下,效果一樣:

    #box {
        background-color: rgb(246, 96, 78); /*背景色*/
        width: 100px; /*寬度*/
        height: 100px; /*長度*/
        position: relative; /*位置*/
        border-radius: 15px; /*加點圓角*/
        transition: 1s cubic-bezier(.37,1.44,.57,.77) 500ms; 
        /*兼容webkit內核*/
        -webkit-transition: 1s cubic-bezier(.37,1.44,.57,.77) 500ms;
    }
    
    /*鼠標懸浮后的樣式*/
    #box:hover {
        /*向下偏移50px*/
        transform: translateY(50px);
        /*兼容webkit*/
        -webkit-transform: translateY(50px);
    }
    

    scale縮放

    scale就是縮放的意思,對元素進行縮放變換。包含:

    • scaleX(x)
    • scaleY(y)
    • scaleZ(z)
    • scale(x, y)
    • scale3d(x, y, z)

    用法與translate一致,只是參數是表示縮放的倍數,“1”表示原來的一倍(不放大不縮小),“0.5”縮小到原來一半,“2”變為原來兩倍。

    transform: scale(.5);
    

    rotate旋轉

    旋轉變換,包含:

    • rotate(angle): 最簡單的旋轉變換,angle為負逆時針,為正是順時針
    • rotateX(angle): 繞着X軸旋轉
    • rotateY(angle): 繞Y軸旋轉
    • rotaleZ(angle): 繞Z軸旋轉
    • rotate3d(x,y,z,angle): 這個複雜一點,是在空間直角坐標系(x,y,z)中選擇一個點,然後該點與原點(0,0,0)連成一條線,然後元素繞該線旋轉。
      1. rotate3d(1,0,0,180deg)等價於rotateX(180deg)
      2. rotate3d(0,1,0,180deg)等價於rotateY(180deg)
      3. rotate3d(0,0,1,180deg)等價於rotateZ(180deg)
    transform: rotate(180deg);
    

    skew傾斜

    傾斜變換,包含:

    • skewX(angle): 相對X軸傾斜,X軸方向上不變,Y軸旋轉angle度。
    • skewY(angle): 相對Y軸傾斜,同上。
    • skew(x-angle, y-angle): 結合起來。

    skew不太好理解,結合例子來看:
    一、

    transform: skewX(45deg);
    

    可以看到“測試字樣”在X軸上沒有變化,向著Y軸方向旋轉45度。

    二、

    transform: skewY(45deg);
    

    在Y軸方向上沒變,”box”的豎邊仍與Y軸平行,橫邊則向著X軸方向旋轉45度。

    三、

    transform: skew(45deg,45deg);
    

    skew不好理解,這裏貼出兩篇文章:

    • css3 2d skew()方法用法理解
    • css3中的skew(skewX,skewY)用法

    matrix矩陣變換

    矩陣變換,包含:

    • matrix(n,n,n,n,n,n)
    • matrix3d(n,n,n,n,n,n,n,n,n,n,n,n,n,n,n,n,n)

    其它的變換都可以由矩陣變換獲得,這是線性代數的知識,學的都還給老師了…….

    對CSS3中的transform:Matrix()矩陣的一些理解

    perspective景深

    用於定義景深,與上面提到的3d變換配合使用,景深就是元素離眼睛(屏幕)的距離,在電腦上,圖形通過變換來讓我們眼睛看到的圖形產生距離感,大概就是近大遠小之類的。

    transform: perspective(500px) rotate3d(1, 0, 0, 45deg);
    

    transform-origin

    transfor-origin屬性用來設置元素變換的基點。默認的,rotate繞元素中點旋轉,如果想讓元素繞左上角旋轉,可以把transform-origin設置為:

    transform-orgin: 0% 0%;
    

    示例:

    #box {
        background-color: rgb(246, 96, 78); /*背景色*/
        width: 100px; /*寬度*/
        height: 100px; /*長度*/
        position: relative; /*位置*/
        border-radius: 15px; /*加點圓角*/
        transition: 1s cubic-bezier(.37,1.44,.57,.77) 500ms; /*過渡效果*/
        transform-origin: 0% 0%;/*設置動畫的基點*/
        /*兼容webkit內核*/
        -webkit-transition: 1s cubic-bezier(.37,1.44,.57,.77) 500ms;
        -webkit-transform-origin: 0% 0%;
    
    }
    
    /*鼠標懸浮后的樣式*/
    #box:hover {
        transform: rotate(45deg);
        /*兼容webkit*/
        -webkit-transform: rotate(45deg);
    }
    

    注意,”transform-origin”屬性是放在”#box”上而不是”#box:hover”

    animation和keyframes(更精細的動畫)

      上面提到的動畫均為補間動畫,自定義初始和結束的狀態,由瀏覽器計算渲染中間狀態。這些初始和結束的關鍵狀態,可以稱為“關鍵幀”,即“keyframes”。如果我們想實現更為精細的動畫效果,想在元素變換的“過程中”也加上特定的“狀態”,即插入“關鍵幀”,可以通過 “keyframes” 和 “animation” 屬性實現。
      animation包含8個子屬性:

    名稱 描述
    name keyframe的名稱
    duration 持續時間
    timing-function 速度曲線
    delay 延遲多久才開始
    iteration-count 播放的次數,一整個動畫流程為一次
    direction 是否在播放完后再反向播放
    fill-mode 動畫不播放時的樣式
    play-state 動畫的狀態,正在運行還是暫停

    keyframe的定義如下:

    @keyframes name{
        percentage1 {state1}
        percentage2 {state2}
    }
    
    /*兼容webkit*/
    @-webkit-keyframes name{
        percentage1 {state1}
        percentage2 {state2}
    }
    

    name 是關鍵幀的名稱
    percentage 是動畫周期的時刻百分比,即整個動畫周期的第百分之幾的時刻,50%表示播放到一半,30%表示動畫播放到百分之30.
    state 是該時刻的元素狀態,如“top: 10px”,此刻元素距離上方的距離。

    修改html文件:

    <html>
    
    <div id="box" style="line-height: 100px; text-align: center;">測試</div>
    
    <style>
    
    /*關鍵幀*/
    @keyframes test{
        0%,20%,50%,80%,100%{transform: translateX(0)}
        40%{transform: translateX(30px)}
        60%{transform: translateX(15px)}
    }
    
    /*兼容*/
    @-webkit-keyframes test{
        0%,20%,50%,80%,100%{-webkit-transform: translateX(0)}
        40%{-webkit-transform: translateX(30px)}
        60%{-webkit-transform: translateX(15px)}
    }
    
    #box {
        background-color: rgb(246, 96, 78); /*背景色*/
        width: 100px; /*寬度*/
        height: 100px; /*長度*/
        position: relative; /*位置*/
        border-radius: 15px; /*加點圓角*/
        transition: 1s linear 500ms; /*過渡效果*/
        -webkit-transition: 1s linear 500ms; /*過渡效果,兼容webkit內核*/
    }
    
    /*鼠標懸浮后的樣式*/
    #box:hover {
        animation:test 1s 0s ease both; /*綁定關鍵幀*/
        -webkit-animation: test 1s 0s ease both; /*兼容*/
    }
    </style>
    
    </html>
    

    效果:

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

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

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

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

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

    ※回頭車貨運收費標準

  • Oracle數據遷移後由列的直方圖統計信息引起的執行計劃異常

    Oracle數據遷移後由列的直方圖統計信息引起的執行計劃異常

    (一)問題背景

    在使用impdp進行數據導入的時候,往往在導入表和索引的統計信息的時候,速度非常慢,因此我在使用impdp進行導入時,會使用exclude=table_statistics排除表的統計信息,從而加快導入速度,之後再手動收集統計信息。

                                                  圖.impdp導入數據的時導入統計信息速度非常慢

    導入語句如下:

    impdp user/password directory=DUMPDIR dumpfile=TEST01.dmp logfile=TEST01.log remap_schema=TEST_USER:TEST_USER123 exclude=table_statistics

    手動收集統計信息語句如下:

    EXEC dbms_stats.gather_table_stats(ownname => 'LIJIAMAN',tabname => 'TEST01');

    最近使用以上方法將數據還原到測試環境后,發現與生產環境執行計劃存在偏差,本來應該走全表掃描的,卻走了索引範圍掃描。經過確認,是由於列的直方圖統計信息未收集引發的執行計劃偏差。


    (二)列的直方圖統計信息

    什麼是列的直方圖統計信息呢?在Oracle數據庫中,Oracle默認列上的值是在最小值與最大值之間均分佈的,當在計算cardinatity時,會以均勻分佈的方式計算,但是在實際生活中某些場景下數據並非均勻分佈。舉個列子,某公司有員工10000人,表A的列COL1記錄員工的績效(分別是:A、B、C、D,A最好,D最差),那麼可能A佔了15%,B佔了60,C佔了20%,D佔了5%。很明顯在該場景下數據並非均勻分佈,假如以均勻分佈的方式去統計員工的績效,可能會導致執行計劃失准。

    當列的數據分佈不均勻的時候,就需要統計列上的數據分佈情況,從而走出正確的執行計劃,列的直方圖統計信息就是記錄列上的數據分佈情況的。


    (三)異常模擬

    STEP1:創建測試表test01

    create table test01
    (id number,
    name varchar2(10)
    );
    create index idx_test01_id on test01(id);

    向test01中插入測試數據

    begin
    insert into test01 values(1,'a');
    
    for i in 1..10 loop
    insert into test01 values(2,'b');
    end loop;
    
    for i in 1..100 loop
    insert into test01 values(3,'c');
    end loop;
    
    for i in 1..1000 loop
    insert into test01 values(4,'d');
    end loop;
    
    commit;
    end;

    查看數據分佈情況:

    SQL> SELECT ID,NAME,COUNT(*) FROM test01 GROUP BY ID,NAME ORDER BY COUNT(*);
    
    ID          NAME       COUNT(*)
    ---------- ---------- ----------
    1           a          1       
    2           b          10
    3           c          100
    4           d          1000


    STEP2:收集統計信息,因為上面查詢過id列,故在收集統計信息的時候,會收集直方圖的統計信息

    EXEC dbms_stats.gather_table_stats(ownname => 'LIJIAMAN',tabname => 'TEST01');

    查看是否已經收集了直方圖信息,發現id列上已經收集

    SQL> SELECT a.OWNER,a.TABLE_NAME,a.COLUMN_NAME,a.LOW_VALUE,a.HIGH_VALUE,a.NUM_BUCKETS,a.HISTOGRAM
    2 FROM dba_tab_columns a
    3 WHERE a.OWNER = 'LIJIAMAN' AND a.TABLE_NAME = 'TEST01';
    
    OWNER     TABLE_NAME   COLUMN_NAME   LOW_VALUE     HIGH_VALUE    NUM_BUCKETS   HISTOGRAM
    --------- -----------  ------------  ------------  ------------  -----------  ---------------
    LIJIAMAN  TEST01       ID            C102          C105          4             FREQUENCY
    LIJIAMAN  TEST01       NAME          61            64            1             NONE

    查看直方圖,已經將id列的4個值放入了4個bucket中:

    SQL> SELECT * FROM dba_tab_histograms a WHERE a.OWNER = 'LIJIAMAN' AND a.TABLE_NAME = 'TEST01';
    
    OWNER        TABLE_NAME    COLUMN_NAME    ENDPOINT_NUMBER    ENDPOINT_VALUE ENDPOINT_ACTUAL_VALUE
    -----------  ------------  -------------  ---------------    -------------- ----------------------
    LIJIAMAN     TEST01        ID                           1                 1 
    LIJIAMAN     TEST01        ID                          11                 2 
    LIJIAMAN     TEST01        ID                         111                 3 
    LIJIAMAN     TEST01        ID                        1111                 4 
    LIJIAMAN     TEST01        NAME                         0    5.036527952778 
    LIJIAMAN     TEST01        NAME                         1    5.192296858534


    STEP3:查看id=1和id=4的執行計劃,當id=1時,走索引範圍掃描,當id=4時,走全表掃描

    id列存在直方圖統計信息,當id=1時,走索引範圍掃描 id列存在直方圖統計信息,當id=4時,走全表掃描
    SELECT * FROM test01 WHERE ID=1
    
     Plan Hash Value  : 1151852672 
    
    ----------------------------------------------------------------------------------------
    | Id  | Operation                     | Name          | Rows | Bytes | Cost | Time     |
    ----------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT              |               |    1 |     5 |    2 | 00:00:01 |
    |   1 |   TABLE ACCESS BY INDEX ROWID | TEST01        |    1 |     5 |    2 | 00:00:01 |
    | * 2 |    INDEX RANGE SCAN           | IDX_TEST01_ID |    1 |       |    1 | 00:00:01 |
    ----------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ------------------------------------------
    * 2 - access("ID"=1)
    SELECT * FROM test01 WHERE ID=4
    
     Plan Hash Value  : 262542483 
    
    -----------------------------------------------------------------------
     | Id  | Operation           | Name   | Rows | Bytes | Cost | Time     |
    -----------------------------------------------------------------------
     |   0 | SELECT STATEMENT    |        | 1000 |  5000 |    3 | 00:00:01 |
     | * 1 |   TABLE ACCESS FULL | TEST01 | 1000 |  5000 |    3 | 00:00:01 |
    -----------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
     ------------------------------------------
     * 1 - filter("ID"=4)

    STEP4:接下來模擬數據遷移,排除統計信息

    導出表test01

    expdp lijiaman/lijiaman directory=DUMPDIR tables=LIJIAMAN.TEST01 dumpfile =test01.dmp

    刪除原來的表:

    SQL> drop table test01;
    Table dropped

    再次導入表,排除統計信息:

    impdp lijiaman/lijiaman directory=DUMPDIR dumpfile =test01.dmp exclude=table_statistics

    查看錶的統計信息,不存在統計信息:

    SQL> SELECT   a.OWNER,a.TABLE_NAME,a.COLUMN_NAME,a.LOW_VALUE,a.HIGH_VALUE,a.NUM_BUCKETS,a.HISTOGRAM
       2  FROM     dba_tab_columns a
       3  WHERE    a.OWNER = 'LIJIAMAN' AND a.TABLE_NAME = 'TEST01';
    
    OWNER          TABLE_NAME      COLUMN_NAME     LOW_VALUE    HIGH_VALUE   NUM_BUCKETS HISTOGRAM
     -------------- --------------- --------------- ------------ ------------ ----------- ---------------
     LIJIAMAN       TEST01          ID                                                    NONE
     LIJIAMAN       TEST01          NAME                                                  NONE

    STEP5:手動收集統計信息

    EXEC dbms_stats.gather_table_stats(ownname => 'LIJIAMAN',tabname => 'TEST01');

    發現統計信息已經收集,但是不存在直方圖的統計信息

    SQL> SELECT   a.OWNER,a.TABLE_NAME,a.COLUMN_NAME,a.LOW_VALUE,a.HIGH_VALUE,a.NUM_BUCKETS,a.HISTOGRAM
      2  FROM     dba_tab_columns a
      3  WHERE    a.OWNER = 'LIJIAMAN' AND a.TABLE_NAME = 'TEST01';
    
    OWNER     TABLE_NAME  COLUMN_NAME  LOW_VALUE   HIGH_VALUE  NUM_BUCKETS HISTOGRAM
    --------- ----------- -----------  ----------- ----------- ----------- ---------------
    LIJIAMAN  TEST01      ID           C102        C105                  1 NONE
    LIJIAMAN  TEST01      NAME         61          64                    1 NONE

    STEP6:再次查看id=1和id=4的執行計劃,當id=1或id=4時,都走索引範圍掃描

    id列未收集直方圖統計信息,當id=1時,走索引範圍掃描 id列未收集直方圖統計信息,當id=4時,走索引範圍掃描
    SELECT * FROM test01 WHERE ID=1
     Plan Hash Value  : 1151852672 
    
    ----------------------------------------------------------------------------------------
    | Id  | Operation                     | Name          | Rows | Bytes | Cost | Time     |
    ----------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT              |               |  278 |  1390 |    2 | 00:00:01 |
    |   1 |   TABLE ACCESS BY INDEX ROWID | TEST01        |  278 |  1390 |    2 | 00:00:01 |
    | * 2 |    INDEX RANGE SCAN           | IDX_TEST01_ID |  278 |       |    1 | 00:00:01 |
    ----------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ------------------------------------------
    * 2 - access("ID"=1)
    SELECT * FROM test01 WHERE ID=4
    
     Plan Hash Value  : 1151852672 
    
    ----------------------------------------------------------------------------------------
    | Id  | Operation                     | Name          | Rows | Bytes | Cost | Time     |
    ----------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT              |               |  278 |  1390 |    2 | 00:00:01 |
    |   1 |   TABLE ACCESS BY INDEX ROWID | TEST01        |  278 |  1390 |    2 | 00:00:01 |
    | * 2 |    INDEX RANGE SCAN           | IDX_TEST01_ID |  278 |       |    1 | 00:00:01 |
    ----------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ------------------------------------------
    * 2 - access("ID"=4)

    STEP7:再次收集統計信息,因為使用過了id列作為查詢條件,故再次收集統計信息時,會收集id列的直方圖信息:

    EXEC dbms_stats.gather_table_stats(ownname => 'LIJIAMAN',tabname => 'TEST01');

    可以看到,此時已經收集了id列的直方圖統計信息:

    SQL> SELECT   a.OWNER,a.TABLE_NAME,a.COLUMN_NAME,a.LOW_VALUE,a.HIGH_VALUE,a.NUM_BUCKETS,a.HISTOGRAM
      2  FROM     dba_tab_columns a
      3  WHERE    a.OWNER = 'LIJIAMAN' AND a.TABLE_NAME = 'TEST01';
    
    OWNER                          TABLE_NAME                     COLUMN_NAME                    LOW_VALUE     HIGH_VALUE    NUM_BUCKETS HISTOGRAM
    ------------------------------ ------------------------------ ------------------------------ ------------- ------------- ----------- ---------------
    LIJIAMAN                       TEST01                         ID                             C102          C105                    4 FREQUENCY
    LIJIAMAN                       TEST01                         NAME                           61            64                      1 NONE
    

    執行計劃已經按照我們想要的方式走:

    id列重新收集直方圖統計信息,當id=1時,走索引範圍掃描 id列重新收集直方圖統計信息,當id=4時,走全表掃描
    SELECT * FROM test01 WHERE ID=1
    
     Plan Hash Value  : 1151852672 
    
    ----------------------------------------------------------------------------------------
    | Id  | Operation                     | Name          | Rows | Bytes | Cost | Time     |
    ----------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT              |               |    1 |     5 |    2 | 00:00:01 |
    |   1 |   TABLE ACCESS BY INDEX ROWID | TEST01        |    1 |     5 |    2 | 00:00:01 |
    | * 2 |    INDEX RANGE SCAN           | IDX_TEST01_ID |    1 |       |    1 | 00:00:01 |
    ----------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ------------------------------------------
    * 2 - access("ID"=1)
    SELECT * FROM test01 WHERE ID=4
    
     Plan Hash Value  : 262542483 
    
    -----------------------------------------------------------------------
    | Id  | Operation           | Name   | Rows | Bytes | Cost | Time     |
    -----------------------------------------------------------------------
    |   0 | SELECT STATEMENT    |        | 1000 |  5000 |    3 | 00:00:01 |
    | * 1 |   TABLE ACCESS FULL | TEST01 | 1000 |  5000 |    3 | 00:00:01 |
    -----------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ------------------------------------------
    * 1 - filter("ID"=4)

    (四)總結

    在使用expdp/impdp進行導出/導入數據的時,統計信息是非常重要的,對於大部分統計信息,我們可以在導入結束之後收集獲得。但是對於列的直方圖統計信息,Oracle默認收集的方式是auto,即Oracle會根據用戶對列的使用情況進行判斷是否收集直方圖統計信息,然而數據剛遷移完成,在表還未使用的情況下收集統計信息,往往收集不到列的直方圖信息,這就造成了執行計劃異常,這種情況通常在下一次收集統計信息之後會有所改變。

    參考文檔:

    DBMS_STATS With METHOD_OPT =>’..SIZE auto’ May Not Collect Histograms (Doc ID 557594.1)

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

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

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

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計最專業,超強功能平台可客製化

    ※回頭車貨運收費標準

  • 02 . Zabbix配置監控項及聚合圖形

    02 . Zabbix配置監控項及聚合圖形

    安裝Zabbix Agent監控本機

    安裝agent軟件

    與server端不同,Agent只需安裝zabbix-agent包

    cat /etc/yum.repos.d/zabbix.repo 
    [zabbix]
    name=Zabbix Official Repository - $basearch
    baseurl=https://mirrors.aliyun.com/zabbix/zabbix/3.4/rhel/7/$basearch/
    enabled=1
    gpgcheck=1
    gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIX-A14FE591
     
    [zabbix-non-supported]
    name=Zabbix Official Repository non-supported - $basearch
    baseurl=https://mirrors.aliyun.com/zabbix/non-supported/rhel/7/$basearch/
    enabled=1
    gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIX
    gpgcheck=1
    
    
    curl https://mirrors.aliyun.com/zabbix/RPM-GPG-KEY-ZABBIX-A14FE591 -o /etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIX-A14FE591
    curl https://mirrors.aliyun.com/zabbix/RPM-GPG-KEY-ZABBIX -o /etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIX
    
    yum -y install zabbix-agent zabbix-get
    
    配置Agent並啟動
    vim /etc/zabbix/zabbix_agentd.conf
    Server=39.108.140.0                    # 被動模式 zabbix-server-ip
    ServerActive=39.108.140.0              # 主動模式 zabbix-server-ip
    Hostname=You-Men                       # Agent端主機名,最終显示在監控頁面上的名字
    UnsafeUserParameters=1                 # 是否限制用戶自定義keys使用特殊字符
    
    systemctl restart zabbix-agent
    netstat -antp|grep agent
    tcp        0      0 0.0.0.0:10050           0.0.0.0:*               LISTEN      3898/zabbix_agentd  
    tcp6       0      0 :::10050                :::*                    LISTEN      3898/zabbix_agentd
    
    配置snmp(可以不做)

    zabbix除了可以使用agent獲取數據之外,還可以通過snmp獲取數據,為了能夠讓zabbix監控更多的信息,將本機的snmp功能啟動起來.

    yum -y install net-snmp net-snmp-utils
    vim /etc/snmp/snmpd.conf
    com2sec notConfigUser 39.108.140.0 public
    access notConfigGroup "" any noauth exact all none none
    view all included .1 80
    systemctl restart snmpd && systemctl enabel snmpd
    ss -anup |grep snmp        # 161端口,udp協議
    # 測試snmp協議工作是否正常
    # snmpwalk -v 1 -c public 39.108.140.0   .1.3.6
    # 使用v1版本,共同體為public,來對192.168.0.1的.1.3.6分支進行walk。
    
    snmpwalk -v 2c -c public 39.108.140.0
    # 使用v2c版本,共同體為public,對39.108.140.0進行walk。
    # -v        显示當前SNMPWALK命令行版本.
    # -
    # 獲取cisco設備39.108.140.0的接口類型
    

    接下來我們到web界面上配置如何監控本地主機,我們看到接口上是127.0.0.1,但是我們配置文件寫的是39.108.140.0,我們讓這兩個IP一致.點擊3進去然後修改.

    更新完后,跳到下面頁面,稍等一會,重新載入一下頁面就是可用性為綠色了

    至此,監控本地主機就完成了,如果想看下監控本地主機的網卡流量就做下面圖2步驟.鼠標依次根據数字挨個點,如果想要監控項是中文的話,可以做Zabbix故障例一,但是4.4版本較以前版本有所改善,監控項不是亂碼,而是英文.

    如果想要將這種亂碼換成正常中文

    如果是windows在C盤搜索simkai.tff中文楷體,拷貝/上傳到服務器,然後cp到zabbix的字體目錄
    3.*版本:
    cp  /root/simkai.ttf   /usr/share/zabbix/fonts/
    
    # 不同的安裝方式,路徑會有所不同,所以可以直接find / -type d -type fonts找到類似的文件夾,那就是了
    # 注意字體權限問題
    vim /usr/share/zabbix/include/defines.inc.php
        define('ZBX_GRAPH_FONT_NAME',    'simkai');
        define('ZBX_FONT_NAME',            'simkai');
    

    Zabbix監控遠程主機

    如果遠程主機安裝不上zabbix-agent,可以通過裝的上的zabbix-agent的機器把包傳過去

    yum -y instlal yum-utils
    # 下載到指定目錄
    yum install zabbix-agent -y --downloadonly --downloaddir=/root
    1.安裝zabbix agent
        # 方法一(國外源zabbix好像下載不下來包了,用上面的源):
        # rpm -Uvh https://repo.zabbix.com/zabbix/4.4/rhel/7/x86_64/zabbix-release-4.4-1.el7.noarch.rpm
        yum clean all
        yum -y install zabbix-agent
        # 方法二:(使用別的機器傳過來的zabbix-agent包直接rpm安裝即可)
        rpm -ivh zabbix-agent-4.4.1-1.el7.x86_64.rpm
    
    # 修改zabbix-agent配置並啟動服務
    
        vim /etc/zabbix/zabbix_agentd.conf
        Server=192.168.244.144
        Server=192.168.244.144            //監控主機IP地址
        Hostname=agent1.zabbix.com        //被監控主機到監控主機的名字
        UnsafeUserParameters=1
    
        systemctl start zabbix-agent
        ss -antp |grep 10050
    # 接下來我們到web端進行操作
    # 為了服務方便管理和易於查看。
    # 監控系統中往往根據被監控的主機角色或其他屬性將同類主機劃分到同一個主機組中.
    

    如果等上一段時間,可用性哪裡沒有紅色警告,就說明這台主機被添加進來了,但是因為沒有掛載模板和創建監控項,所以我們接下來嘗試着掛載一下模板,然後再去創建監控項.

    我們到agent端裝一個nginx,然後去zabbix的web端找到此模板並掛載.

    yum -y install nginx
    systemctl start nginx
    

    測試監控主機

    接下來我們用瀏覽器或者elinks訪問一下nginx,產生一些數據,然後去zabbix上查看變化

    elinks --dump 116.196.83.113
    

    我們以後自定義Key監控項時,先看看最新數據有沒有數據過來,如果數據都不會過來,就別提圖形觸發器報警什麼了.

    至此,添加本地主機,遠程主機,創建主機組,掛載模板就已經完了

    Zabbix監控項

    監控項(Items)簡介

    監控項是Zabbix中獲得數據的基礎,沒有監控項,就沒有數據——因為一個主機只有監控項定義了單一的指標或者需要獲得的數據,監控項適用於採集數據的,多個同類的監控項可以定義成一個應用集,如,mysql增刪改查以及每秒鐘的讀表,寫錶速度可以寫成一個Mysql應用集.

    對於監控項的示例,需要輸入以下必要的信息

    名稱

    輸入CPU Load作為值,在列表中和其他地方,都會显示這個值作為監控項名稱.

    手動輸入system.cpu.load作為值,這是監控項的一個技術上的名稱,用於識別獲取信息的類型,這個特定值需要是Zabbix Agent預定義值的一種.
    https://www.zabbix.com/documentation/3.4/manual/config/items/itemtypes/zabbix_agent # 此網址就是zabbix官網的預定義值.

    信息類型

    在此處選擇Numeric(float),這個屬性定義了獲得數據的格式
    你也需要減少監控項歷史保留的天數,7或者14天,對於數據庫而言,最佳實踐是避免數據庫保留過多的歷史數據.
    我們選擇了數據類型后,暫時保持其他選項的默認值.
    1> 磁盤容量Units一般為B
    2> 網卡流量單位為bps
    3> Mysql每秒訪問量qps,例如MySQL每秒select,insert Mysql serlect

    點擊添加,新的監控項就出現在監控項列表中了

    查看數據

    當一個監控項定義完成后,你可能好奇他具體獲取了什麼值,前往監控首頁,點擊最新數據,選擇相應的主機.看數據能不能過來以及是不是自己想要的類型.

    圖表

    當監控項運行了一段時間后,可以查看可視化圖表,如果沒有可以自己創建一個,下面會有詳細介紹

    常用監控項

    1.服務器網絡接口進出流量和總流量
        net.if.in[if,<mode>]
        net.if.out[if,<mode>]
        net.if.total[if,<mode>]
    
    2.服務器啟動分區剩餘空間
        vfs.fs.size[fs,<mode>]
        vfs.fs.size[/boot,free]
    
    3.監控虛擬機內存
        vm.memory.size[<mode>]
        vm.memory.size[total
        vm.memory.size[free]
        vm.memory.size[wired]
    
    4.服務器服務狀態
        net.tcp.listen[port]
        net.tcp.port[<ip>,port]
        net.tcp.service[service,<ip>,<port>]
        net.tcp.service.perf[service,<ip>,<port>]
    
    5.服務器進程數量
            proc.num[<name>,<user>,<state>,<cmdine>]
            zabbix_get -s 39.108.140.0 -k proc.num
        121
        zabbix_get -s 39.108.140.0 -k proc.num[,,run]
        3
        zabbix_get -s 39.108.140.0 -k proc.num[,,sleep]
        118
    
    6.服務器CPU狀態(浮點型,無單位)    
        system.cpu.intr
        system.cpu.load[<cpu>,<mode>]
        system.cpu.num
        system.cpu.switches
        system.cpu.util[<cpu>,<type>,<mode>]
        zabbix_get -s 39.108.140.0 -k system.cpu.load[all,avg1]
        0.000000
        zabbix_get -s 39.108.140.0 -k system.cpu.load[,avg5]
        0.010000
    
    7.磁盤IO情況
        vfs.dev.read[device,<type>,<mode>]
        vfs.dev.write[device,<type>,<mode>]
    zabbix_get -s 39.108.140.0 -k vfs.dev.read[/dev/vda1]
    
    8.監控文件修改
        vfs.file.chsum[file]        # 如監控/etc/passwd ,/etc/group 文件從而知道是否有新用戶創建
        vfs.file.md5sum[file]
        vfs.file.size[file]        # 通常用來監控日誌
        vfs.fs.size[fs,<mode>]
    
    9.磁盤總和.
    
    監控網卡流量

    我們先創建一個應用集,這樣的話之後創建的網卡上傳,下載,總流量不會顯的很亂,都在一個Network應用集裏面,而且能導出成xml文件,放到其他的zabbxi主機上能直接用.

    我們此刻做的創建監控項是利用zabbix安裝好自帶的監控項,跟自定義Key差不多,都是寫一個監控腳本然後傳參,每一個鍵值相當於一個監控腳本

    接下來我們檢測---> 主機群組裡面去查看下最新數據,我們可以從下圖看到是有數據的

    下行寬帶和上行寬帶.

    下載就是in,下行寬帶,你發出去的就是out,作為一個服務器來說上行寬帶肯定要高,在家裡就是下行寬帶高,對服務器來說他需要接收很少的數據包,回復很多的數據包,而在家裡我們是發出去一個很小的數據包,返回來整個網頁.

    接下來我們再去創建一個網卡輸出流量,然後將他們做成一個圖標,以圖形化展示出來

    接下來我們再去監測裏面去查看最新數據,可以養成這個習慣,因為最新數據過來了才是說明當中數據流向沒有問題,如果數據都沒有過來你去創建圖形,圖表說沒有數據,你覺得得等一會,浪費時間影響效率

    可以看到,兩個監控項都是有數據的,接下來我們去創建圖形

    接下來我們去查看監測 ---> 圖形,選擇相應群組,相應的主機及創建的圖形

    這台主機可以裝一個nginx,然後上傳一張大一點圖片到網站根目錄,然後訪問,再查看網絡波動圖.

    或者我們直接上傳一個大點的rpm、tar包到其他主機.這樣看着明顯

    監控CPU

    跟剛才一樣,創建一個CPU應用集,方便管理歸納

    接下來我們創建應用集的監控項,cpuintr,cpu中斷數

    接下來我們創建一個cpu每隔一分鐘的負載監控項
    通過下圖,我們可以看到,每個監控項都是有數據過來的,接下來我們去創建圖形

    我們可以看到,數據是可以實時轉換成圖標的,接下來我們去做一個聚合圖形

    創建聚合圖形

    至此,我們第一個構造函數完成,另外一個構造函數同理,此處就不寫了,直接看結果圖.

    創建系統定義好的監控項,跟上面兩個都差不多,多做做自然就會了,如果不習慣使用官方定義好的key,我們可以根據公司環境自己寫腳本自定義key,此章完結.

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

    【其他文章推薦】

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

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

    ※教你寫出一流的銷售文案?

    ※回頭車貨運收費標準

    ※別再煩惱如何寫文案,掌握八大原則!

  • 秒懂系列,超詳細Java枚舉教程!!!

    秒懂系列,超詳細Java枚舉教程!!!

    所有知識體系文章,GitHub已收錄,歡迎Star!再次感謝,願你早日進入大廠!

    GitHub地址: https://github.com/Ziphtracks/JavaLearningmanual

    深入理解Java枚舉

    一、什麼是枚舉

    1.1 什麼是枚舉?

    至於枚舉,我們先拿生活中的枚舉來入手,然後再引申Java中的枚舉,其實它們的意義很相似。

    談到生活中的枚舉,假如我們在玩擲骰子的遊戲,在我們手中有兩個骰子,要求擲出兩個骰子的點數和必須大於6的概率,那麼在此情此景,我們就需要使用枚舉法一一列舉出骰子點數的所有可能,然後根據列舉出來的可能,求出概率。

    可能有的小夥伴發現,這就是數學啊?這就是數學中的概率學和統計學。對,我們的枚舉法就是常用於概率統計中的。

    1.2 Java中的枚舉類

    Java 5.0引入了枚舉,枚舉限制變量只能是預先設定好的值。使用枚舉可以減少代碼中的 bug,方便很多場景使用。

    二、Java枚舉的語法

    枚舉類中的聲明

    1訪問修辭符 enum 枚舉名 {
    2    枚舉成員,
    3    枚舉成員,
    4    ...
    5};

    class類中枚舉的聲明

    1訪問修飾符 class 類名 {
    2    enum 枚舉名 {
    3        枚舉成員,
    4        枚舉成員,
    5        ...
    6    }
    7}

    三、Java枚舉類的使用規則和應用場景

    3.1 Java枚舉類的使用規則

    至於枚舉你也有所了解了,Java中的枚舉也是一樣的。而Java中枚舉類的使用,也有特定的規則和場景。如果你看了以下的規則不明白的話,沒有關係,繼續向下學你就會明白,因為我在下面都會有講解到這些規則。如下幾個規則:

    • 類的對象是確定的有限個數。
    • 當需要定義一組常量時,建議使用枚舉。
    • 如果枚舉類中只有一個對象,則可以作為單例模式的實現方法。
    • 枚舉類不能被繼承
    • 枚舉類不能被單獨的new創建對象
    • 枚舉類中的枚舉成員是用`,`隔開的,多個枚舉成員之間用`_`隔開
    • 如果枚舉類中的只有一個或多個枚舉成員,其他什麼都沒有,我們在用`,`隔開的同時。最後可以省略`;`結束符。

    注意: 如果關於枚舉單例設計模式不太了解的小夥伴可以參考深度學習單例設計模式一文,你肯定會有意想不到收穫,請相信我!

    3.2 Java枚舉類的應用場景

    根據Java中使用枚舉類的規則,有以下幾種場景適合來使用枚舉類,如下:

    • 星期: Monday(星期一)、Tuesday(星期二)、Wednesday(星期三)、Thursday(星期四)、Firday(星期五)、Saturday(星期六)、Sunday(星期日)
    • 性別: Man(男)、Woman(女)
    • 季節: Spring(春天)、Summer(夏天)、Autumn(秋天)、Winter(冬天)
    • 支付方式: Cash(現金)、WeChatPay(微信)、Alipay(支付寶)、BankCard(銀行卡)、CreditCard(信用卡)
    • 訂單狀態: Nonpayment(未付款)、Paid(已付款)、Fulfilled(已配貨)、Delivered(已發貨)、Return(退貨)、Checked(已確認)
    • 線程狀態: Establish(創建)、Ready(就緒)、Run(運行)、Obstruct(阻塞)、Die(死亡)
    • 等等……

    四、枚舉類的基本使用步驟解析

    那我們就解釋以下這兩個規則,我們在上述中已經了解了枚舉的作用。Java中枚舉也不例外,也是一一列舉出來方便我們拿出來一個或多個使用。這有點像我們的多選框,我們把需要用到的所有選項內容放在各個多選框後面,當我們在使用的時候只需要勾選自己需要的勾選框即可,這就代表了我們需要被選中多選框後面的內容。

    那麼,Java中的枚舉類是如何使用呢?

    這裏我們簡單的模擬一個場景,假設你的女朋友十分的喜歡喝點冷飲或熱奶茶之類的飲品,在生活中也有很多像蜜雪冰城等等這種類型的飲品店。當你為女朋友買她愛喝的珍珠奶茶時,服務員會問你,要大杯、中杯還是小杯的。當然,為了滿足女朋友,你通常會選擇大杯。這就意味着店內不允許顧客點規則外的飲品。

    注意: 如果你是初學者或是不了解枚舉類的使用,此基本使用不懂沒有關係,請繼續往下看即可!

    於是,我用Java代碼來實現一下,上述場景。

    首先,創建枚舉類。分別為珍珠奶茶添加大、中、小杯杯型。

     1package com.mylifes1110.java;
    2
    3/**
    4 * @ClassName PearlMilkTea
    5 * @Description 為珍珠奶茶添加三個杯型:大、中、小
    6 * @Author Ziph
    7 * @Date 2020/6/8
    8 * @Since 1.8
    9 */

    10public enum PearlMilkTea {
    11    //注意:這裏枚舉類中只有枚舉成員,我在此省略了;結束符
    12    SMALL, MEDIUM, LARGE
    13}

    其次,創建珍珠奶茶對象,再有方法來判斷枚舉類中的大、中、小杯。最後打印女朋友喝哪個杯型的珍珠奶茶!

     1package com.mylifes1110.test;
    2
    3import com.mylifes1110.java.PearlMilkTea;
    4
    5/**
    6 * @ClassName PearlMilkTeaTest
    7 * @Description 為女朋友買哪個杯型的珍珠奶茶(默認大杯)
    8 * @Author Ziph
    9 * @Date 2020/6/8
    10 * @Since 1.8
    11 */

    12public class PearlMilkTeaTest {
    13    public static void main(String[] args) {
    14        //創建大杯的珍珠奶茶對象
    15        PearlMilkTea pearlMilkTea = PearlMilkTea.LARGE;
    16        PearlMilkTeaTest.drinkSize(pearlMilkTea);
    17    }
    18
    19    //判斷為女朋友買哪個杯型的珍珠奶茶
    20    public static void drinkSize(PearlMilkTea pearlMilkTea) {
    21        if (pearlMilkTea == PearlMilkTea.LARGE) {
    22            System.out.println("我為女朋友買了一大杯珍珠奶茶!");
    23        } else if (pearlMilkTea == PearlMilkTea.MEDIUM) {
    24            System.out.println("我為女朋友買了一中杯珍珠奶茶!");
    25        } else {
    26            System.out.println("我為女朋友買了一小杯珍珠奶茶!");
    27        }
    28    }
    29}

    image-20200608151052517

    雖然,我們了解了枚舉類中的基本使用,但是我們在語法中還介紹了一種在類中定義的枚舉。正好,在此也演示一下。如下:

    1public class PearlMilkTea {
    2    enum DrinkSize {
    3        SMALL,
    4        MEDIUM, 
    5        LARGE
    6    }
    7}

    如果這樣創建就可以在class類中去創建enum枚舉類了。想想前面例子中的代碼其實並不合理,這是為什麼呢?因為我們寫代碼要遵循單一職責原則和見命知意的命名規範。所以,我寫的代碼是在珍珠奶茶的枚舉類中列舉的大、中、小的三種杯型枚舉成員。所以根據規範來講,我們珍珠奶茶中不能擁有杯型相關的枚舉,畢竟我們在生活中的這類飲品店中喝的所有飲品種類都有這三種杯型,因此我們的所有飲品種類中都需要寫一個枚舉類,顯然這是很不合理的。

    如果讓它變的更加合理化,我們就細分飲品種類來創建飲品枚舉類和杯型的枚舉類並分別兩兩適用即可。也許有小夥伴會問我為什麼我要說這些合理不合理呢?因為自我感覺這是對枚舉類應用的思想鋪墊,所以你品、你細品!

    五、自定義枚舉類

    5.1 自定義枚舉類步驟

    關於第四章枚舉類的基本使用,也許小夥伴們對枚舉的陌生,而並不知道為什麼這樣去創建枚舉對象。接下來,我來帶你使用常量來自定義枚舉類,試試是不是那個效果。

    既然,上述第三章我舉出了這麼多枚舉類的應用場景,那我們挑選一個比較經典的春夏秋冬來實現自定義枚舉類。

    首先,我們先創建一個季節類,分別提供屬性、私有構造器、春夏秋冬常量、Getter方法和toString方法,步驟如下:

     1package com.mylifes1110.java;
    2
    3/**
    4 * 自定義季節的枚舉類
    5 */

    6public class Season {
    7    //聲明Season對象的屬性,為private final修飾
    8    private final String seasonName;
    9
    10    //私有化構造器,併為對象賦值
    11    private Season(String seasonName) {
    12        this.seasonName = seasonName;
    13    }
    14
    15    //提供當前枚舉的多個對象,為public static final修飾
    16    public static final Season SPRING = new Season("春天");
    17    public static final Season SUMMER = new Season("夏天");
    18    public static final Season AUTUMN = new Season("秋天");
    19    public static final Season WINTER = new Season("冬天");
    20
    21    //提供外界通過getter方法來獲取枚舉對象的屬性
    22    public String getSeasonName() {
    23        return seasonName;
    24    }
    25
    26    //重寫toString方法,以便打印出枚舉結果
    27    @Override
    28    public String toString() {
    29        return "Season{" +
    30                "seasonName='" + seasonName + '\'' +
    31                '}';
    32    }
    33}

    其次,我們去創建一個測試類,來使用該自定義枚舉類創建對象。由此看來,我們就可以根據類名來句點出常量對象了!

     1package com.mylifes1110.test;
    2
    3import com.mylifes1110.java.Season;
    4
    5/**
    6 * 測試類
    7 */

    8public class SeasonTest {
    9    public static void main(String[] args) {
    10        Season spring = Season.SPRING;
    11        System.out.println(spring);
    12    }
    13}

    最後打印結果是春天的對象,由於我們覆蓋了toString方法,即可見對象內的內容。

    image-20200608160220000

    5.2 使用帶有參枚舉類

    如果你在第三章時Java枚舉類的基本使用不明白,估計看完自定義枚舉類也了解的大差不差了。但是你有沒有發現我們自定義枚舉類是使用的有參數的對象呢?那我們怎樣使用真正的枚舉類來實現有參數的枚舉類呢?繼續看吧那就!

    在這裏我將自定義枚舉類改裝了一下,改裝成了enum枚舉類實現的使用有參對象。如下:

     1package com.mylifes1110.java;
    2
    3public enum Season {
    4    SPRING("春天"),
    5    SUMMER("夏天"),
    6    AUTUMN("秋天"),
    7    WINTER("冬天");
    8
    9    private final String seasonName;
    10
    11    Season1(String seasonName) {
    12        this.seasonName = seasonName;
    13    }
    14
    15    public String getSeasonName() {
    16        return seasonName;
    17    }
    18}

    不知道你有沒有發現少了點什麼,少的部分其實就是我們創建常量對象的部分,而且在這個枚舉類中我也沒有去重寫toString方法,至於為什麼,下面就告訴你。

    注意: 枚舉對象之間用,隔開!

    其次,去創建了該枚舉類的測試類,我們測試以下,並看一下沒有重寫toString方法打印出來的結果。

     1package com.mylifes1110.test;
    2
    3import com.mylifes1110.java.Season;
    4
    5public class Seaso1Test {
    6    public static void main(String[] args) {
    7        Season1 spring = Season.SPRING;
    8        System.out.println(spring);                     //SPRING
    9        System.out.println(spring.getSeasonName());     //春天
    10    }
    11}

    這裏我將打印的結果放在了打印語句後面的註釋中。我們發現沒有重寫toString方法竟然打印出來的是SPRING,這是為什麼呢?這應該從我們的繼承關係中分析,如果繼承的是基類Object的話,沒有重寫toString方法會打印對象地址。那麼我們就可以斷定,enum枚舉類的父類不是Object。那它的父類是誰呢?我們可以藉助來對象來獲取其父類,如下:

    1System.out.println(Season.class.getSuperclass());        //class java.lang.Enum

    同樣,答案放在了代碼後面的註釋中。我們發現它默認繼承的是Enum類。那麼,我們稍後就來就看看這個類中到底寫了些什麼方法。

    六、Enum常用方法的使用

    6.1 Enum中的所有方法

    關於Enum類中的所有方法我以表格的方式列舉出來!

    返回值 方法 描述
    String name() 獲取枚舉成員的名稱
    static T valueOf(Class<T> enumType, String name) 獲取指定枚舉成員名稱和類型的枚舉成員
    String[] values() 獲取枚舉成員的所有值
    int compareTo(E o) 比較此枚舉與指定對象的順序
    int hashCode() 獲取枚舉成員的哈希值
    int ordinal() 獲取枚舉成員的序數(第一個枚舉成員位置為0)
    String toString() 返回枚舉成員名稱
    Class<E> getDeclaringClass() 獲取枚舉成員的類對象

    6.2 name和toString

    關於name方法和toString方法,其實很簡單。name()就是根據枚舉成員來獲取該枚舉成員的字符串名稱。而同String方法也是用來獲取枚舉成員的字符串名稱。雖然作用都是相同的,但是name方法是用final修飾的不能被重寫,而toString是可以被重寫的。這裏我們還使用季節的案例來演示,打印結果並放在了代碼後面的註釋中,如下:

    1System.out.println(Season.SUMMER.name());            //SUMMER
    2System.out.println(Season.SUMMER.toString());        //SUMMER

    6.3 valueOf

    此方法的作用是傳入一個字符串,然後將它轉換成對應的枚舉成員。這裏傳入的字符串必須與定義的枚舉成員的名稱一致,嚴格區分大小寫。如果傳入的字符串並沒有找到其對應的枚舉成員對象,就會拋出異常。如下:

    1System.out.println(Season.valueOf("WINTER"));            //WINTER
    2System.out.println(Season.valueOf("WIN"));                //java.lang.IllegalArgumentException

    image-20200608173858862

    6.4 values

    values方法的名字中就帶有一個s,再加上它的返回值是一個字符串數組。所以我們就可以得出它的作用是獲取枚舉成員的所有值,這些值並以數組的形式存儲。

    1Season[] seasons = Season.values();
    2for (Season season : seasons) {
    3    System.out.print(season + " ");
    4}

    結果為:

    1SPRING SUMMER AUTUMN WINTER 

    6.5 ordinal

    該方法是獲取枚舉成員的序數,其第一個枚舉成員位置為0。其實,為了好理解的話,可以把它看作數組中的索引。數組中的第一個元素位置同樣也是從0開始。那我們打印一下,看看結果如何,如下:

    1//獲取指定枚舉成員的次序
    2System.out.println(Season.SUMMER.ordinal());
    3
    4//獲取所有成員的次序
    5Season[] seasons = Season.values();
    6for (Season s : seasons) {
    7    System.out.println(s + " -> " + s.ordinal());
    8}

    結果為:

    image-20200608175529079

    其源碼就是返回了一個從0開始int類型的值,從源碼中也可以看出最大值是int取值範圍的最大值。如下:

    image-20200608180839568

    6.6 compareTo

    compareTo方法相信我們已經是很熟悉了。其作用就是用來比較的。但是在枚舉類中它比較的是什麼呢?實際上compareTo方法比較的是兩個枚舉成員的次序數,並返回次序相減后的結果。

    首先,我們要知道SUMMER的次序數為1,WINTER的次序數為3。當使用前者比較後者,打印的結果是前者與後者相減后的差值,即1-3=-2

    1System.out.println(Season.SUMMER.compareTo(Season.WINTER));            //-2

    它的源碼是怎麼做的呢?那我們進入查看一下。

    其中,前面的操作都是在判斷比較的雙方是否是一個枚舉類,如果不是的話就拋出異常。如果為枚舉類的話,就直接將次序數做了相減操作並返回。

    image-20200608180532795

    七、Java枚舉的高級特性

    7.1 常量

    我們知道,常量是用public static final修飾的。1.5之後有了枚舉,我們就可以把相關的常量放在一個枚舉容器中,而且使用枚舉的好處還在於枚舉為我們提供了很多便捷的的方法。

    示例:

    1public enum Season {
    2    SPRING, SUMMER, AUTUMN, WINTER
    3}

    7.2 switch語句

    你了解的switch語句都支持哪種類型呢?我這裏說一下,switch語句支持的類型有如下幾種:

    • 基本數據類型: byte、short、char、int
    • 包裝數據類型: Byte、Short、Character、Integer
    • 枚舉類型: Enum
    • 字符串類型: String(jdk7+ 開始支持)

    具體枚舉類與switch語句的使用是如何實現呢?枚舉又是如何為switch語句提供便利的呢?來看一下吧。

     1package com.mylifes1110.java;
    2
    3public class WeekTest {
    4    public static void main(String[] args) {
    5        Week week = Week.MONDAY;
    6        switch (week) {
    7            case MONDAY:
    8                System.out.println("星期一");
    9                break;
    10            case TUESDAY:
    11                System.out.println("星期二");
    12                break;
    13            case WEDNESDAY:
    14                System.out.println("星期三");
    15                break;
    16            case THURSDAY:
    17                System.out.println("星期四");
    18                break;
    19            case FRIDAY:
    20                System.out.println("星期五");
    21                break;
    22            case SATURDAY:
    23                System.out.println("星期六");
    24                break;
    25            case SUNDAY:
    26                System.out.println("星期日");
    27                break;
    28            default:
    29                System.out.println("null");
    30        }
    31    }
    32}
    33
    34enum Week {
    35    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
    36}

    7.3 枚舉中定義多個參數與方法

    有參枚舉在5.2中我已經做了詳細說明,我們在定義枚舉時不只是可以定義多個參數,還可以定義其他的普通方法來使用,而關於普通方法的使用是根據場景的,這裏我就不再做過多的贅述了。

     1package com.mylifes1110.java;
    2
    3public enum Season {
    4    SPRING("春天"),
    5    SUMMER("夏天"),
    6    AUTUMN("秋天"),
    7    WINTER("冬天");
    8
    9    private final String seasonName;
    10
    11    public static String getName(int index) {  
    12        for (Season s : Season.values()) {  
    13            if (c.getIndex() == index) {  
    14                return c.name;  
    15            }  
    16        }  
    17        return null;  
    18    }
    19
    20    Season1(String seasonName) {
    21        this.seasonName = seasonName;
    22    }
    23
    24    public String getSeasonName() {
    25        return seasonName;
    26    }
    27}

    7.4 枚舉類實現接口

    雖然枚舉類不能繼承,但是可以實現接口。以下是一個實現過程。

    首先,創建一個接口。

    1package com.mylifes1110.inter;
    2
    3public interface Show {
    4    void show();
    5}

    其次,讓我們的四季枚舉類實現該接口並重寫方法。

     1package com.mylifes1110.java;
    2
    3import com.mylifes1110.inter.Show;
    4
    5public enum Season implements Show {
    6    SPRING("春天"),
    7    SUMMER("夏天"),
    8    AUTUMN("秋天"),
    9    WINTER("冬天");
    10
    11    private final String seasonName;
    12
    13    Season1(String seasonName) {
    14        this.seasonName = seasonName;
    15    }
    16
    17    public String getSeasonName() {
    18        return seasonName;
    19    }
    20
    21    @Override
    22    public void show() {
    23        System.out.println("嚮往四季如春");
    24    }
    25}

    最後,當我們使用每一個枚舉類都可以調用show方法,而打印的結果也都是“嚮往四季如春”

    1Season.WINTER.show();                //嚮往四季如春

    聰明的你我相信發現了這個缺點,我們不管使用哪一個枚舉成員時,調用的show方法都是同一個。所以,我們在實現接口后,可以這樣重寫方法,如下:

     1package com.mylifes1110.java;
    2
    3import com.mylifes1110.inter.Show;
    4
    5public enum Season1 implements Show {
    6    SPRING("春天") {
    7        @Override
    8        public void show() {
    9            System.out.println("春天是個踏青的季節");
    10        }
    11    },
    12    SUMMER("夏天") {
    13        @Override
    14        public void show() {
    15            System.out.println("夏天是個炎熱的季節,我要吃冰棍");
    16        }
    17    },
    18    AUTUMN("秋天") {
    19        @Override
    20        public void show() {
    21            System.out.println("秋天還算是涼爽");
    22        }
    23    },
    24    WINTER("冬天") {
    25        @Override
    26        public void show() {
    27            System.out.println("冬天的雪還不錯,就是有點冷");
    28        }
    29    };
    30
    31    private final String seasonName;
    32
    33    Season1(String seasonName) {
    34        this.seasonName = seasonName;
    35    }
    36
    37    public String getSeasonName() {
    38        return seasonName;
    39    }
    40}

    我們在枚舉成員的後面加了{},而重寫的方法可以寫在各個枚舉成員中,這樣就接觸了上述所有的那個限制。這下,我們使用哪個枚舉成員對象調用show方法都是不同的。是不是非常NICE?

    7.5 使用接口對枚舉分類

    使用接口對枚舉分類,我們需要創建一個接口容器,裏面存放着此接口容器所存放的多個枚舉類,然後將各個枚舉類實現此接口,以這樣的方式可實現對枚舉分類。代碼如下,打印結果放在了代碼後面的註釋中:

     1package com.mylifes1110.inter;
    2
    3public interface Weeks {
    4    //工作日
    5    enum WorkingDay implements Weeks {
    6        MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY
    7    }
    8
    9    //雙休日
    10    enum Weekend implements Weeks {
    11        SATURDAY, SUNDAY
    12    }
    13}
    14
    15class WeeksTest {
    16    public static void main(String[] args) {
    17        System.out.print("雙休日:");
    18        for (Weeks.Weekend weekend : Weeks.Weekend.values()) {
    19            System.out.print(weekend + " ");        //雙休日:SATURDAY SUNDAY
    20        }
    21
    22        //換行
    23        System.out.println();
    24
    25        System.out.print("工作日:");
    26        for (Weeks.WorkingDay workingDay : Weeks.WorkingDay.values()) {
    27            System.out.print(workingDay + " ");     //工作日:MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY
    28        }
    29
    30        //換行
    31        System.out.println();
    32
    33        Weeks.WorkingDay friday = Weeks.WorkingDay.FRIDAY;
    34        System.out.println("星期五:" + friday);      //星期五:FRIDAY
    35    }
    36}

    image-20200608194649335

    八 枚舉類集合

    8.1 EnumSet集合

    關於Set集合,我們知道其集合中的元素是不重複的。其中的方法有以下幾種:

    返回值 方法 描述
    static EnumSet<E> allOf(Class<E> elementType) 創建一個包含指定元素類型的所有元素的枚舉 set。
    EnumSet<E> clone() 返回一個set集合。
    static EnumSet<E> complementOf(EnumSet<E> s) 創建一個其元素類型與指定枚舉set相同的set集合(新集合中包含原集合所不包含的枚舉成員)
    static EnumSet<E> copyOf(EnumSet<E> s) 創建一個其元素類型與指定枚舉 set 相同的枚舉 set集合(新集合中包含與原集合相同的枚舉成員)
    static EnumSet<E> copyOf(Collection<E> s) 創建一個從指定 collection 初始化的枚舉 set
    static EnumSet<E> noneOf(Class<E> elementType) 創建一個具有指定元素類型的空枚舉 set
    static EnumSet<E> range(E from, E to) 創建一個最初包含由兩個指定端點所定義範圍內的所有元素的枚舉 set。
    static EnumSet<E> of 創建一個最初包含指定元素的枚舉 set。注意:可以指定多個元素,所以在這裏我沒有列舉參數
    8.1.1 allOf

    allOf方法需要我們傳入一個枚舉的類對象,它會根據傳入的枚舉類對象生成一個具有該類對象枚舉成員的Set集合。

    1//創建一個包含Week所有枚舉元素的Set集合
    2EnumSet<Week> weeks = EnumSet.allOf(Week.class);
    3System.out.println(weeks);              //[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
    4
    5//打印Set集合中的元素
    6for (Week week1 : weeks) {
    7    System.out.print(week1 + " ");      //MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY SATURDAY SUNDAY
    8}
    8.1.2 clone

    clone方法與直接打印枚舉的Set集合結果相同!

    1//返回一個Set集合
    2System.out.println(weeks.clone());      //[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
    8.1.3 range

    上面詳細講過枚舉是有序數的,而且枚舉類中的枚舉成員是秉承着從左向右的順序。所以我們可以使用range方法來創建指定枚舉成員端點的Set集合,也就是說我們需要傳入枚舉成員的起始與結束去創建一個該擁有該範圍枚舉成員的Set集合。如下:

    1//創建一個最初包含由兩個指定端點所定義範圍內的所有元素的枚舉 set。
    2System.out.println(EnumSet.range(Week.MONDAY, Week.FRIDAY));        //[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY]
    8.1.4 complementOf

    該方法有點特殊,它根據EnumSet去創建一個新Set集合。而新Set集合中枚舉成員相當於舊Set集合中枚舉成員的取反。

    我們用場景來模擬一下,當前Week枚舉類中有星期一到星期日7個枚舉成員。我們使用range方法創建一個從星期一到星期五的Set集合(s1),隨後我在將使用complementOf方法根據s1生成新的Set集合(s2),最後打印s2查看集合中的元素就只有星期六和星期日。

    注意: 如果我們的舊Set集合佔據了枚舉類中的所有枚舉成員,在使用complementOf方法生成的新Set集合,新集合中的元素打印後為空Set,即[]

    1//創建一個其元素類型與指定枚舉set相同的set集合(新集合中包含原集合所不包含的枚舉成員)
    2EnumSet<Week> weeks1 = EnumSet.complementOf(weeks);
    3System.out.println(weeks1);             //[]
    4
    5EnumSet<Week> range = EnumSet.range(Week.MONDAY, Week.FRIDAY);
    6EnumSet<Week> weeks3 = EnumSet.complementOf(range);
    7System.out.println(weeks3);                //[SATURDAY, SUNDAY]
    8.1.5 copyOf

    copyOf方法與complementOf相反,它創建一個新Set集合。而新Set集合中的枚舉成員與舊Set集合中的枚舉成員相同,這相當於就是Copy(複製功能)。如果你理解了complementOf方法,這個方法對你來說也是沒有挑戰。以下我使用copyOf方法複製了一份weeks,其枚舉成員一個不少。

    注意: copyOf方法還有一個可以複製connection集合來創建Set集合,其connection集合中必須存儲的是枚舉成員。

    1//創建一個其元素類型與指定枚舉 set 相同的枚舉 set集合(新集合中包含與原集合相同的枚舉成員)
    2EnumSet<Week> weeks2 = EnumSet.copyOf(weeks);
    3System.out.println(weeks2);             //[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
    1//複製存儲枚舉成員的HashSet集合
    2Set set = new HashSet();
    3set.add(Week.MONDAY);
    4EnumSet set1 = EnumSet.copyOf(set);
    5System.out.println(set1);        //[MONDAY]
    8.1.6 of

    of方法為我們提供了選擇性的便利,我們可以挑選任意枚舉成員成為Set集合的元素。

    1//創建一個最初包含指定元素的枚舉 set。
    2System.out.println(EnumSet.of(Week.MONDAY,Week.FRIDAY));            //[MONDAY, FRIDAY]
    8.1.7 noneOf

    傳入一個枚舉的類對象去創建一個空Set集合

    1EnumSet<Week> noneOf = EnumSet.noneOf(Week.class);
    2System.out.println(noneOf);                     //[]

    8.2 EnumMap集合

    8.2.1 EnumMap集合的方法列表

    關於Map集合,我們知道它是由鍵和值組成。EnumMap集合與HashMap集合的效率比較來說,EnumMap的效率高些。

    關於EnumMap集合的使用與HashMap是一致的,沒有什麼特殊的。至於EnumMap集合的方法,我這裏列舉一下。

    返回值 方法 描述
    void clear() 移除所有映射關係。
    EnumMap clone() 返回EnumMap集合。
    boolean containsKey(Object key) 包含此鍵,則返回true
    boolean containsValue(Object value) 包含一個或多個鍵映射到的該指定值,則返回true
    Set > entrySet() 返回映射鍵值關係的Set集合
    boolean equals(Object o) 比較對象與映射的相等關係
    V get(Object key) 獲取指定鍵映射的值,如果沒有,返回null
    Set<K> keySet() 返回所有鍵的Set集合
    V put(K key, V value) 將指定鍵值存儲在EnumMap集合中
    void putAll(Map m) 將所有鍵值對存儲在集合中
    V remove(Object key) 如果存在映射關係,則移除該映射關係
    int size() 返回存在映射關係的數量
    Collection<V> values() 返回此映射中所包含值的 Collection集合
    8.2.2 EnumMap集合的基本使用

    由於EnumMap集合與HashMap集合基本相似,這裏我就演示一下基本使用與HashMap不同的地方。

    EnumMap集合是我們new出來的對象,創建出來的對象需要傳入一個枚舉的類對象,才返回一個Map集合。Map集合是鍵值對形式存儲,所以我們在寫EnumMap集合的泛型時,根據需求來寫,如果需要鍵是某枚舉類型,我們泛型就寫它。如果有枚舉類是值的要求,那就泛型中的值寫枚舉類。鍵值對都要求是枚舉那也是OK的,我們寫泛型時都寫需求的枚舉類即可。除了創建對象和存儲對象需要指定枚舉類外,其他的與HashMap基本相同。

    如下,我在創建EnumMap集合時執行的Week枚舉類的類對象,泛型的鍵寫的是Week枚舉類,值寫的Integer,這就意味着我們在put(存儲鍵值對)的時候,鍵需要存儲Week枚舉類中的枚舉成員,值需要存儲Integer數值。

    1EnumMap<Week, Integer> map = new EnumMap<>(Week.class);
    2map.put(Week.MONDAY, 1);
    3map.put(Week.THURSDAY, 4);
    4System.out.println(map);            //{MONDAY=1, THURSDAY=4}

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

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※別再煩惱如何寫文案,掌握八大原則!

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※回頭車貨運收費標準

    台中搬家公司費用怎麼算?

  • 德國啟用新燃煤電廠 環保人士火大

    摘錄自2020年5月28日中央社報導

    德國將於30日讓一座最新的燃煤電廠投入營運,激怒環保人士。環保人士認為,這會打亂德國削減二氧化碳排放的努力。

    德國能源大廠Uniper發言人透過電郵證實,Datteln-4燃煤電廠將於30日根據商用條款為電網供電。造價13億美元的這座電廠延遲九年商轉且超出預算,原因在於諸多缺失而遲無法與電網連結。

    這座電廠也引發對德國燃煤發電退場的激烈爭辯。德國目前仍有近半供電靠燃煤,總理梅克爾去年達成一項協議,要讓德國在2038年以前完全汰除燃煤,但允許Datteln-4電廠啟用。

    德國「商務日報」(Handelsblatt)報導,聯邦政府與北萊茵-西發利亞邦(North Rhine-Westphalia)均強調會以關閉老舊電廠來換取Datteln-4電廠營運,讓整體碳排維持不變。

    能源議題
    能源轉型
    國際新聞
    燃煤電廠

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

    【其他文章推薦】

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※回頭車貨運收費標準

  • 致G20領袖 全球醫療工作者呼籲採納綠色振興

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

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

    【其他文章推薦】

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※超省錢租車方案

    ※教你寫出一流的銷售文案?

    網頁設計最專業,超強功能平台可客製化

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

  • 連年旱災牧草不生 澳洲羊隻大減創下空前新低

    摘錄自2020年5月28日中央社報導

    今(28日)公布的農業數據顯示,由於澳洲東部連年旱災,全國羊隻數量減少到100多年前有紀錄以來的歷史新低。

    農人去年低價拋售或丟棄總價值210億澳幣(約台幣4166億元)的牲畜,全國羊隻數量減至6600萬,至少是1905年以來最低。

    澳洲統計局(Australian Bureau of Statistics)表示:「東部數州旱災惡化且牧草不足,許多養牛人和養羊人不得不減少牲口數量。」澳洲統計局並指出,2018-19財政年度,牲口數量減少了7%。

    過去150年來,羊隻、特別是羊毛,都是澳洲經濟的最大支柱。今天公布的最新數字,並不涵蓋大片森林和農地發生毀滅性大火的2019年底和2020年初。

    農林漁牧業
    環境經濟
    土地利用
    循環經濟
    國際新聞
    澳洲
    旱災
    牧草
    牧羊

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

    【其他文章推薦】

    ※教你寫出一流的銷售文案?

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※回頭車貨運收費標準

    ※別再煩惱如何寫文案,掌握八大原則!

    ※超省錢租車方案

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※推薦台中搬家公司優質服務,可到府估價

  • 印尼展開人工造雨 避免往年森林大火重演

    摘錄自2020年5月28日中央社報導

    印尼年年發生嚴重森林大火,產生的有毒霧霾瀰漫東南亞,迫使學校關閉及引發擔憂。印尼政府今年已展開人造雨作業,試圖避免往年的災難重演。

    法新社報導,印尼去年因為乾旱天候遭逢2015年以來的最嚴重森林大火,約160萬公頃土地遭失控野火焚毀,大多發生在蘇門答臘島(Sumatra)及婆羅洲島(Borneo)。印尼森林大火往往是人為引發,以清理土地供農業使用,包括生產棕櫚油及紙漿的種植園。

    過去兩週以來,印尼開始在蘇門答臘島火災熱點廖內省(Riau Province)進行人工造雨,且計畫在蘇門答臘島及婆羅洲島其他地區展開人造雨計畫。印尼乾燥季節預計9月左右結束,人造雨作業將在整個乾季持續進行。

    生態保育
    生物多樣性
    國際新聞
    印尼
    人工增雨
    森林大火
    森林

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

    【其他文章推薦】

    ※超省錢租車方案

    ※別再煩惱如何寫文案,掌握八大原則!

    ※回頭車貨運收費標準

    ※教你寫出一流的銷售文案?

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    網頁設計最專業,超強功能平台可客製化

  • 印度猴群闖醫學院「毆打研究員」 下秒洗劫待驗的武肺檢體

    印度猴群闖醫學院「毆打研究員」 下秒洗劫待驗的武肺檢體

    摘錄自2020年05月30日三立新聞網印度報導

    武漢肺炎(COVID-19)仍持續在印度延燒,然29日卻發生一起「搶劫事件」,一群猴子突闖入北方省勒克瑙(Lucknow)一處醫學院、出手攻擊醫事人員,並將多個待檢驗新冠病毒的血液檢體劫走;消息一出,附近居民紛表擔憂,害怕病毒會因檢體洩漏而進一步擴散。

    醫學院領導人賈格(S. K. Garg)指出,現在沒有證據顯示人會傳染病毒給猴子,但不知道若猴子接觸有病毒的血液之後,是否會遭受感染。有關單位也表示,目前還沒掌握到猴子是否有把血液灑出來,但附近居民得知消息後,都很擔心若猴子將檢體帶到住宅區,恐會造成病毒擴散。

    猴子闖入人類居住區、造成騷亂甚至攻擊人類的事件近來在印度頻傳,環保人士對此表示,主要原因是猴子的棲息地遭破壞,而牠們為了尋找食物才會闖入人類的居住空間。

    ※ 本文與 行政院農業委員會 林務局   合作刊登

    國際新聞
    印度
    武漢肺炎
    猴子
    公共衛生
    處變不驚──與野生動物相遇
    人與動物衝突事件簿

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

    【其他文章推薦】

    網頁設計最專業,超強功能平台可客製化

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※回頭車貨運收費標準

    ※推薦評價好的iphone維修中心

    ※教你寫出一流的銷售文案?

    台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

    台中搬家公司費用怎麼算?