JDK動態代理

在《springAOP之代理模式》中說了代理模式,包含靜態代理和動態代理,在動態代理模式中又分為JDK動態代理和CGlib動態代理,今天重點來看JDK動態代理。

一、概述

說到JDK動態代理就必須想到JDK動態代理要求有一個統一的接口,那為什麼要有接口,下面會說到,下面看我的接口類,

package cn.com.jdk.proxy;

public interface Subject {

    void sayHello(String a);
}

接口類很簡單就是一個簡單的方法定義。下面看實際的接口的實現類SubjectImpl,

package cn.com.jdk.proxy;

public class SubjectImpl implements Subject {

    @Override
    public void sayHello(String a) {
        // TODO Auto-generated method stub

        System.out.println("hello:"+a);
    }

}

實現類簡單的事項了Subject接口,進行了打印操作。下面看代理類

package cn.com.jdk.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class JDKProxy implements InvocationHandler {
    private SubjectImpl si;
    //此屬性不用管
    private String a;
/**
 * proxy  JDK動態生成的代理類的實例
 * method 目標方法的Method對象     Class.forName("cn.com.jdk.proxy.Subject").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
 * args   目標方法的參數                       new Object[] { paramString }
 */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("before");
        //使用反射的放式調用si(被代理類)目標方法
        Object o=method.invoke(si, args);
        System.out.println("after");
        return o;
    }
    public JDKProxy(SubjectImpl si,String a) {
        this.si=si;
        this.a=a;
    }

}

上面是代理類的實現,在代理類中含義被代理類的一個引用,且提供了響應的構造方法。下面具體的使用,

package cn.com.jdk.proxy;

import java.lang.reflect.Proxy;

public class ProxyTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //進行此項設置,可以在項目的com/sun/proxy目錄下找到JDK動態生成的代理類的字節碼文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        SubjectImpl si=new SubjectImpl();
        
        Subject subject=(Subject)Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(), new JDKProxy(si,"111"));
        subject.sayHello("tom");

    }

}

上面是使用的代碼,通過Proxy類的newProxyInstance方法獲得一個Subject的實例,調用sayHello方法,下面看執行結果

before
hello:tom
after

可以看到執行了sayHello方法,且打印了before和after,這不正是代理類中invoke方法的執行嗎,看下面

很神奇的一件事,我們不光調用了sayHello方法,實現了打印,而且在加入了自己的打印方法,這不正是AOP的增強功能嗎。這一切是怎麼發生的那,下面細細說來。

二、詳述

上面,我們又複習了JDK動態代理的內容,以及演示了如何使用JDK動態代理,下面我們要看這是怎麼實現的,先從測試的下面這段代碼說起,也是最重要的代碼,JDK動態代理的精華都在這句代碼里,

Subject subject=(Subject)Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(), new JDKProxy(si,"111"));

這句代碼是調用了Proxy類的newProxyInstance方法,此方法的入參如下,

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

 一共三個參數,一個是ClassLoader,這裏傳入的是被代理對象的類加載器;一個是Class,這裏傳入的是被代理對象所實現的接口;一個是InvocationHandler,這裏傳入的是代理類,代理類實現了InvocationHandler接口。

 1、newProxyInstance方法

下面看newProxyInstance方法的定義,

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
          //1、使用代理類的類加載器和其所實現的接口,動態生成代理類
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            //2、返回JDK生成的代理類的構造方法,該構造方法的參數為
            //  InvocationHandler
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
//3、返回該構造方法的一個實例,也就是使用InvocationHandler為參數的構造方法利用反射的機制返回一個實例。
return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }

該方法中有三步比較重要,上面的註釋已經標出。

1.1、getProxyClass0(loader, intfs)方法

該方法便是上面的第一步,這一步的作用是JDK返回一個代理類的實例,方法上的註釋如下,

/*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

註釋直譯過來是查找或者生成指定的代理類,這裡有兩層意思,一個是查找,第二個是生成,由此可以想到這個方法中應該有緩存,下面看方法的具體定義,

/**
     * Generate a proxy class.  Must call the checkProxyAccess method
     * to perform permission checks before calling this.
     */
    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

這個方法很簡單,判斷了接口的數量,大於65535便拋異常,接口的數量大於65535的可能性不大。最後調用了proxyClassCache的get方法,首先看proxyClassCache,從字面上理解是代理類的緩存,看其定義,

/**
     * a cache of proxy classes
     */
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

是一個WeakCache對象實例,看下該構造方法,

/**
     * Construct an instance of {@code WeakCache}
     *
     * @param subKeyFactory a function mapping a pair of
     *                      {@code (key, parameter) -> sub-key}
     * @param valueFactory  a function mapping a pair of
     *                      {@code (key, parameter) -> value}
     * @throws NullPointerException if {@code subKeyFactory} or
     *                              {@code valueFactory} is null.
     */
    public WeakCache(BiFunction<K, P, ?> subKeyFactory,
                     BiFunction<K, P, V> valueFactory) {
        this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
        this.valueFactory = Objects.requireNonNull(valueFactory);
    }

看了該類的構造方法后,回到proxyClassCache.get(loader, interfaces)方法的調用,我們已經知道proxyClassCache是WeakCache的一個實例,那麼get方法如下,

 /**
     * Look-up the value through the cache. This always evaluates the
     * {@code subKeyFactory} function and optionally evaluates
     * {@code valueFactory} function if there is no entry in the cache for given
     * pair of (key, subKey) or the entry has already been cleared.
     *
     * @param key       possibly null key
     * @param parameter parameter used together with key to create sub-key and
     *                  value (should not be null)
     * @return the cached value (never null)
     * @throws NullPointerException if {@code parameter} passed in or
     *                              {@code sub-key} calculated by
     *                              {@code subKeyFactory} or {@code value}
     *                              calculated by {@code valueFactory} is null.
     */
    public V get(K key, P parameter) {
        Objects.requireNonNull(parameter);

        expungeStaleEntries();

        Object cacheKey = CacheKey.valueOf(key, refQueue);

        // lazily install the 2nd level valuesMap for the particular cacheKey
        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
        if (valuesMap == null) {
            ConcurrentMap<Object, Supplier<V>> oldValuesMap
                = map.putIfAbsent(cacheKey,
                                  valuesMap = new ConcurrentHashMap<>());
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }

        // create subKey and retrieve the possible Supplier<V> stored by that
        // subKey from valuesMap
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;

        while (true) {
            if (supplier != null) {
                // supplier might be a Factory or a CacheValue<V> instance
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
            // else no supplier in cache
            // or a supplier that returned null (could be a cleared CacheValue
            // or a Factory that wasn't successful in installing the CacheValue)

            // lazily construct a Factory
            if (factory == null) {
                factory = new Factory(key, parameter, subKey, valuesMap);
            }

            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    // successfully installed Factory
                    supplier = factory;
                }
                // else retry with winning supplier
            } else {
                if (valuesMap.replace(subKey, supplier, factory)) {
                    // successfully replaced
                    // cleared CacheEntry / unsuccessful Factory
                    // with our Factory
                    supplier = factory;
                } else {
                    // retry with current supplier
                    supplier = valuesMap.get(subKey);
                }
            }
        }
    }

 上面是WeakCache的get方法,這個方法暫時不作說明,後面會詳細介紹WeakCache類,請參見《JDK動態代理之WeakCache 》。這裏只需記住該get方法會返回一個代理類的實例即可。那麼此代理類是如何定義的那?

1.1.1、$Proxy0.class代理類

這個代理類是JDK動態生成的,其命名規則為以“$”開頭+Proxy+“從0開始的序列”。上面在測試的時候,我們加入了下面這行代碼,

//進行此項設置,可以在項目的com/sun/proxy目錄下找到JDK動態生成的代理類的字節碼文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

註釋中寫到可以生成代理類的字節碼文件,下面是使用反編譯工具過來的java代碼,

package com.sun.proxy;

import cn.com.jdk.proxy.Subject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy
  implements Subject
{
  private static Method m1;
  private static Method m3;
  private static Method m2;
  private static Method m0;
 //參數為InvocationHandler的構造方法
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
   //調用父類Proxy的構造方法,在父類的構造方法中會初始化h屬性
    super(paramInvocationHandler);
  }

  public final boolean equals(Object paramObject)
    throws 
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }
//實現的Subject的sayHello方法
  public final void sayHello(String paramString)
    throws 
  {
    try
    {
      //調用h的invoke方法,這裏的h指的是實現了InvocationHandler的類
      //調用其中的invoke方法,在本例中是調用JDKProxy類中的invoke方
      //
      this.h.invoke(this, m3, new Object[] { paramString });
      return;
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

  public final String toString()
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("cn.com.jdk.proxy.Subject").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
    }
    throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
  }
}

上面是反編譯過來的JDK生成的代理類的代碼,包含了一個使用InvocationHandler作為參數的構造方法,以及實現了Subject接口的sayHello方法。上面註釋中寫到該構造方法調用了其父類Proxy的構造方法,下面看其父類Proxy的構造方法,

protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

把InvocationHandler的值賦給了h,h的定義如下,

protected InvocationHandler h;

那麼在生成的代理類中自然會繼承該屬性,所以在代理類中的sayHello中使用下面的方法調用,

public final void sayHello(String paramString)
    throws 
  {
    try
    {
      this.h.invoke(this, m3, new Object[] { paramString });
      return;
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

上面的this.h便是其父類的h屬性。在上面的this.h.invoke中的m3是怎麼來的那,看下面,

 static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("cn.com.jdk.proxy.Subject").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
    }
    throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
  }

在該類的靜態代碼塊中給出了4個屬性。

1.2、getConstructor(constructorParams)方法

在上面的getProxyClass0方法中我們知道該方法會返回一個JDK生成代理類的Class對象,此類的定義便是上面的$Proxy0.class類。其定義在上面已經分析過。getConstructor方法要返回一個以constructorParams為參數的構造方法,

@CallerSensitive
    public Constructor<T> getConstructor(Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        return getConstructor0(parameterTypes, Member.PUBLIC);
    }

調用了getConstuctor0方法返回一個public的構造方法,

private Constructor<T> getConstructor0(Class<?>[] parameterTypes,
                                        int which) throws NoSuchMethodException
    {
        Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
        for (Constructor<T> constructor : constructors) {
            if (arrayContentsEq(parameterTypes,
                                constructor.getParameterTypes())) {
                return getReflectionFactory().copyConstructor(constructor);
            }
        }
        throw new NoSuchMethodException(getName() + ".<init>" + argumentTypesToString(parameterTypes));
    }

上面的方法會返回一個public的構造方法。

回到最初的調用,我們看getConstructor方法的參數是constructorParams,此屬性定義如下,

/** parameter types of a proxy class constructor */
    private static final Class<?>[] constructorParams =
        { InvocationHandler.class };

是一個Class數組,其類型為InvocationHandler。這樣便可以知道是通過代理類的Class對象返回其構造方法cons。有了構造方法下面便是通過構造方法生成實例。

1.3、cons.newInstance(new Object[]{h})方法

此方法便是通過構造方法返回一個代理類的實例。

 

上面分析了Proxy的newProxyInstance方法,此方法最終會返回一個代理類的實例,會經過下面幾個步驟,

從上面的步驟,我們知道在獲得代理類的構造方法時,是獲得其參數為InvocationHandler的構造方法,所以肯定要實現InvocationHandler接口,在本例中便是JDKProxy類,這個類實現了這個接口。值開篇我們講到JDK動態代理必須要有統一的接口,從上面的步驟中我們知道在生成代理類的Class對象時使用了兩個參數,一個ClassLoader,另一個是接口,這裏就是為什麼要有統一的接口,因為在生成代理類的Class對象中需要接口,所以被代理類必須要有一個接口。

2、方法調用

這裏的方法調用,便是對應使用方法中的下面這行代碼,

subject.sayHello("tom");

在上面的分析中獲得了一個代理類的實例,即下面這行代碼,

Subject subject=(Subject)Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(), new JDKProxy(si,"111"));

通過使用被代理類的類加載器、被代理類所實現的接口、實現了InvocationHandler接口的類的實例三個參數,返回了一個代理類的實例。上面已經詳細分析過。此代理類的實例繼承了Proxy,實現了Subject接口。其sayHello方法如下,

public final void sayHello(String paramString)
    throws 
  {
    try
    {
      this.h.invoke(this, m3, new Object[] { paramString });
      return;
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

上面已經分析過,this.h是InvocationHandler的實例,這裏便是new JDKProxy(si,”111″),m3是m3 = Class.forName(“cn.com.jdk.proxy.Subject”).getMethod(“sayHello”, new Class[] { Class.forName(“java.lang.String”) });下面看JDKProxy中的invoke方法,

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("before");
        //使用反射的放式調用目標方法
        Object o=method.invoke(si, args);
        System.out.println("after");
        return o;
    }

此方法的三個參數分別為代理類的實例、Method對象(sayHello),調用sayHello時的參數,所以要調用被代理類的sayHello方法,需要這樣寫:method.invoke(si,args),即調用被代理類(SubjectImpl)的sayHello方法,參數為args(tom)。下面是一個簡單的方法調用過程,

三、總結

本文分析了JDK動態代理的簡單使用方法及背後的原理,有不當之處歡迎指正,感謝!

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

【其他文章推薦】

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

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

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

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

Probius:一個功能強大的自定義任務系統

斷更的這些日子,我又折騰了一個輪子,文末參考源碼

大約在一年半以前寫過一篇文章『探秘varian:優雅的發布部署程序』,裡邊有講到我們採用類似lego的模塊化方式來構建CICD的流程,雖能滿足我們的需求,但終究需要編寫代碼,使用成本有點高,不夠友好。近段時間終於下定決心將其重構,只為帶來更好的使用體驗,於是便有了這個項目Probius

Probius為遊戲星際爭霸里的角色,是一隻充滿好奇心的星靈探測機,取此名字的意思也是希望用戶能夠在這個系統中充分發揮想象,藉助此系統實現各種自定義的功能,覆蓋更多的運維場景

設計思路

Probius由三個關鍵詞構成:命令、模板、任務

命令:為系統中的最小粒度,可以是一個具體的linux命令,或者是一個腳本都可以

模板:模板為一組命令的集合

任務:模板為靜態的定義,而任務就是模板的執行,執行一個任務實際上就是去執行了一個模板內的所有命令

整體思想跟varian一樣,但不同的是可以僅僅通過web端的配置,就能實現各種各樣的功能,下邊具體介紹下如何配置的

頁面配置

新建命令,在這個頁面可以創建命令或者腳本

如果是單純的命令,直接在命令輸入框填寫即可,如果是需要執行腳本,則點擊腳本之後,會額外多出一個腳本輸入框,填寫要執行的腳本

理論上不限制腳本的類型,可以是shell、python或者go之類的,前提是系統上有腳本的運行環境,當命令或者腳本有參數的時候可以在參數列寫上參數名稱,然後在最終執行任務的時候需要傳遞具體參數的值過來

在命令執行完成后,會根據命令的返回狀態也就是$?的值來判斷命令是否執行成功,當$?為0是表示執行成功,否則表示執行失敗,如果是執行的腳本時,需要在腳本最後明確腳本返回狀態,shell腳本可以在腳本執行成功時通過exit指定退出狀態,例如

ls /ops-coffee.cn &&\
exit 0 ||\
exit 2

而對於python腳本則可以藉助sys.exit這樣寫

import sys

if 'www' in 'ops-coffee.cn':
  sys.exit(0)
else:
  sys.exit(3)

其他語言類似

模板的創建分為幾步,先創建一個模板

然後給模板添加任務

主要為選擇任務、確定執行順序、選擇執行主機以及執行用戶,添加完成后可以在模板詳情頁面看到關聯的命令

模板定義了一個完整的任務流程,定義完成后就可以執行任務了,執行任務界面寫的比較簡單

這界面主要給運維人員使用,定義任務名稱、所要執行的模板ID、以及參數,支持定時執行或者周期執行,只需要加上crontab參數即可,除了可以立即執行任務外,還可以將次任務保存為常用任務,後續在常用任務頁面可以直接執行

這個功能主要方便其他非運維人員使用本系統,同時也支持針對任務設置權限,可以將權限設置給某個用戶組,那麼則只有這個組內的成員可以看到並執行任務了

任務執行后可以通過任務歷史查看任務執行詳情,在這個頁面可以清晰的看到任務執行到了哪一步,是成功還是失敗

可以點擊每一步任務後邊的日誌查看實時日誌輸出

寫在最後

如果你用過我們開源的一站式DevOps平台CODO的話,會發現這個系統跟CODO的TASK模塊非常像,是的沒錯,這個設計與CODO的TASK如出一轍,但開源的CODO任務模塊要更加強大,例如支持分組執行、支持任務重做、支持人工干預等等

TASK的源碼在這裏:https://github.com/opendevops-cn/codo-task,感興趣可以自行閱讀部署,需要注意的是CODO為微服務架構,單獨安裝TASK是無法正常運行的,具體部署方法參考官方文檔

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

.Net Core基礎的健康檢查

前言

健康檢查能查看我們的應用程序當前是否是一個健康的運行狀態。微軟已經給我們提供了健康檢查輪子,只需要簡單的配置就能完成服務的狀態檢查。一起來實現一個最簡單的健康檢查吧。

開始

  • 新建一個空的webApi項目。 並引用Microsoft.Extensions.Diagnostics.HealthChecks 包。並在ConfigureServicesConfigure中加入相關配置
public void ConfigureServices(IServiceCollection services)
{
    //健康檢查服務
    services.AddHealthChecks();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    //加入中間件
    app.UseHealthChecks("/healthChecks");
}

最簡單的檢查就完成了,我們測試一下。

返Healthy,表示服務正常。

自定義拓展

HealthChecks提供了一個IHealthCheck接口,這個接口只有一個CheckHealthAsync方法,我們只需要實現這個接口就可以實現我們需要的各種自定義的檢查項目。CheckHealthAsync返回一個HealthCheckResult的枚舉代表健康檢查的幾種狀態,分別是異常,降級,健康。

public enum HealthStatus
{
    Unhealthy = 0,
    Degraded = 1,
    Healthy = 2,
}

實現接口,返回不健康狀態。

public class SqlHealthChecks : IHealthCheck
{
    public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = new CancellationToken())
    {
        if (1 == 1)
        {
            return Task.FromResult(HealthCheckResult.Unhealthy());
        }
    }
}

ConfigureServices中添加自定義的檢查,AddCheck可以添加你自定的健康檢查服務,

public void ConfigureServices(IServiceCollection services)
{
    //健康檢查服務
    services.AddHealthChecks().AddCheck<SqlHealthChecks>("key");
}

測試可以發現返回的為不健康的應用

自定義返回值

我們可以利用HealthCheckOptions來實現健康檢查的自定義返回內容.

private static Task WriteResponse(HttpContext context, HealthReport healthReport)
{
    context.Response.ContentType = "application/json";
    var result = JsonHelper.SerializeObject(new
    {
        code = context.Response.StatusCode,
        errors = healthReport.Entries.Select(e => new
        {
            key = e.Key,
            value = e.Value.Status.ToString()
        })
    });

    return context.Response.WriteAsync(result);
}


public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime appLifetime)
{

    app.UseHealthChecks("/healthChecks", new HealthCheckOptions{ResponseWriter = WriteResponse});

}

測試返回效果

引入Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore 可以為DbContext進行檢查


public void ConfigureServices(IServiceCollection services)
{
    services.AddHealthChecks().AddCheck<SqlHealthChecks>("key").AddDbContextCheck<DbContext>("DbContext");
}

健康檢查UI

引入AspNetCore.HealthChecks.UI並在ConfigureServicesConfigure中加入相應的配置

public void ConfigureServices(IServiceCollection services)  
{  
    services.AddHealthChecksUI();  
}  

public void Configure(IApplicationBuilder app, IHostingEnvironment env)  
{  
    app.UseHealthChecksUI();  
}  

appsetting,json文件中加入配置

{
  "HealthChecksUI": {
    "HealthChecks": [
      {
        "Name": "HealthCheck",
        "Uri": "https://localhost:5000/healthCheck"
      }
    ],
    "EvaluationTimeinSeconds": 10,
    "MinimumSecondsBetweenFailureNotifications": 60
  }
}

啟動項目並指向/healthchecks-ui。

擴展包

開源社區已經有很多現有的優秀的擴展包我們可以直接引用

AspNetCore.HealthChecks.Npgsql
AspNetCore.HealthChecks.Redis
AspNetCore.HealthChecks.AzureStorage
AspNetCore.HealthChecks.AzureServiceBus
AspNetCore.HealthChecks.MySql
AspNetCore.HealthChecks.DocumentDb
AspNetCore.HealthChecks.SqLite
AspNetCore.HealthChecks.Kafka
AspNetCore.HealthChecks.RabbitMQ
AspNetCore.HealthChecks.IdSvr
AspNetCore.HealthChecks.DynamoDB
AspNetCore.HealthChecks.Oracle
AspNetCore.HealthChecks.Uris
AspNetCore.HealthChecks.System
AspNetCore.HealthChecks.Network
AspNetCore.HealthChecks.SqlServer
AspNetCore.HealthChecks.MongoDb

參考文章

  • 微軟官方文檔
  • 社區
  • 源碼理解HealthCheck

總結

實現了一個最簡單的健康檢查功能,可以在這個基礎上進行自定義的擴展和開發。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

防海水高溫傷核反應爐 瑞典要求核電廠提計畫

摘錄自2018年8月21日中央社報導

瑞典核能監管機關瑞典輻射安全局局長培爾松20日表示,為防範海水高溫傷害核反應爐,他們已要求國內核電廠營運商近月內提出因應計畫。

今夏熱浪造成瑞典7月氣溫攀升至歷史新高,用來冷卻核反應爐的海水溫度也因此遠高於正常水準,並超過安全標準,導致瑞典數個核電廠反應爐必須關閉或減少發電量。

上回瑞典輻射安全局(Swedish Radiation Safety Authority, SSM)要求核電廠提出反應爐修改計畫,是在2011年日本發生福島核災之後。當時提出截至2020年的修改計畫,所需經費達數億歐元。

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

研究:為挽救蜜蜂的二代農藥「速殺氟」會扼殺熊蜂後代

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

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※台北網頁設計公司全省服務真心推薦

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

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

澳洲政府修能源政策 溫室氣體減排以後再談

摘錄自2018年8月21日中央廣播電台報導

路透社報導,在執政聯盟內部的反對下,澳洲總理滕博爾(Malcolm Turnbull)今天(20日)取消能源政策中要求減少排放溫室氣體的部分。但他表示,澳洲仍信守巴黎氣候協議(Paris Agreement)承諾。

滕博爾政府的能源政策「國家能源保障」(National Energy Guarantee),先前要求發電產業的溫室氣體排放量,到2030年以前,必須比2005年減少26%。

墨爾本大學(University of Melbourne)政治學教授艾克斯利(Robyn Eckersley)則說:「這完全是對澳洲自由黨(Liberal Party)
右翼成員投降,他們希望永久保留澳洲的煤礦經濟。」

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

【其他文章推薦】

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

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

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

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

※回頭車貨運收費標準

碳儲存技術突破 吸收空氣中二氧化碳的菱鎂礦 數天內就可合成

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

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

碳交易收益回饋弱勢 加州的能源正義之路

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

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

【其他文章推薦】

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

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

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

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

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

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

留住庇里牛斯原生熊最後血脈 法國將野放母熊

摘錄自2018年8月24日中央社報導

法國政府規劃在南部庇里牛斯山區野放兩頭母熊以促進繁衍。這片山區目前只有43頭熊,其中一頭保有當地原生熊的最後血脈,即使畜牧業者反對,政府對野放的態度也是勢在必行。

根據法國國家狩獵及野生動物局(ONCFS)於2017年調查,庇里牛斯山區還有43頭熊,但棲息地分布不平衡,主要集中在山區的中部和東部,西部只有兩頭公熊。

因此,生態部規劃今年秋天把兩頭生於斯洛維尼亞的母熊引入庇里牛斯山西北部的貝亞納(Bearn)地區,讓物種有機會繁衍。

庇里牛斯山-大西洋省(Pyrenees-Atlantiques)和奧克西塔尼大區(Occitanie)警署於今年6月到7月調查約6000名網路使用者的意願,結果顯示多達88.9%的受調者贊成引入兩頭熊到庇里牛斯山,只有8.9%不贊成。

在所有受調者中,約27%是庇里牛斯山區附近省份的居民,這些人有71.6%贊成野放,25.4%反對;若只看貝亞納地區的「當事人」意見,贊成比率降到58.6%,但仍超過半數。

法國政府今年5月公布2018年到2028年「庇里牛斯山區棕熊保育計畫」,未來10年內可能會執行不只一次的野放計畫,若牧羊人因熊攻擊而蒙受損失,也會予以賠償。

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

【其他文章推薦】

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

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

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

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

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

麥加朝聖減環境足跡 綠色朝覲漸扎根

摘錄自2018年8月23日中央社報導

200多萬名穆斯林到麥加朝聖接近尾聲,數以千計清潔工忙著在垃圾當中將塑膠分離出來。這是全世界最大型的年度盛會之一,對沙烏地阿拉伯帶來巨大環保挑戰。

聖城麥加附近的米納(Mina)馬穆尼亞營地(Mamuniya)放置幾個不同顏色的大桶子:黑色桶收集有機垃圾,藍色桶回收鋁鐵罐和塑膠,這些都是為了減少朝覲環境足跡的作法。

麥加市公共衛生部門主管薩阿迪(Mohammed al-Saati)指出,到伊斯蘭教第一聖地朝聖期間,製造出的垃圾量超過4萬2000公噸。

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

【其他文章推薦】

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

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

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

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

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