持續虧損 美國電動車商Fisker聲請破產保護

美國插電式混合動力車製造商Fisker Automotive在經歷幾年的慘澹經營后,終於在上週五(22日)聲請第11章破產保護,而香港首富李嘉誠之子李澤楷參與的集團Hybrid Technology LLC已同意出手併購。

據彭博社、Thomson Reuters報導,剛創立不久的Hybrid Technology同意向美國能源部支付2,500萬美元,買下Fisker遲遲無法償還的債務(總額原本多達1.68億美元)。

整體來算,美國能源部對Fisker挹注的1.92億美元投資案僅收回約5,300萬美元;換句話說,納稅人總計損失了1.39億美元。

Hybrid Technology透過聲明稿表示,該公司計劃重新生產、販售Fisker已在18個月前停產的插電式混合動力車「Karma」,並會再度展開其他混合動力電動車的研發計畫。

Karma一台要價103,000美元,包括Justin Bieber等名人都曾購入這款汽車。Karma的設計雖然獲得市場極力讚賞,但諸多品管問題卻讓Fisker的形象嚴重受損、虧損連連。

Fisker已在今年4月辭退大多數職員以便保留現金,而資金周轉困難也令該公司無法如期支付帳單,這促使能源部在10月中旬展開Fisker債務的拍賣行動。

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

【其他文章推薦】

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

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

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

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

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

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

談談spring-boot-starter-data-redis序列化

在上一篇中springboot 2.X 集成redis中提到了在spring-boot-starter-data-redis中使用JdkSerializationRedisSerializerl來實現序列化,
這裏看下具體是如何實現的。
1.RedisSerializer接口
在spring-data-redis包下,有一個RedisSerializer接口,提供了序列化和反序列化的基本接口。

public interface RedisSerializer<T> {

	/**
	 * Serialize the given object to binary data.
	 *
	 * @param t object to serialize. Can be {@literal null}.
	 * @return the equivalent binary data. Can be {@literal null}.
	 */
	@Nullable
	byte[] serialize(@Nullable T t) throws SerializationException;

	/**
	 * Deserialize an object from the given binary data.
	 *
	 * @param bytes object binary representation. Can be {@literal null}.
	 * @return the equivalent object instance. Can be {@literal null}.
	 */
	@Nullable
	T deserialize(@Nullable byte[] bytes) throws SerializationException;

	/**
	 * Obtain a {@link RedisSerializer} using java serialization.<br />
	 * <strong>Note:</strong> Ensure that your domain objects are actually {@link java.io.Serializable serializable}.
	 *
	 * @return never {@literal null}.
	 * @since 2.1
	 */
	static RedisSerializer<Object> java() {
		return java(null);
	}

	/**
	 * Obtain a {@link RedisSerializer} using java serialization with the given {@link ClassLoader}.<br />
	 * <strong>Note:</strong> Ensure that your domain objects are actually {@link java.io.Serializable serializable}.
	 *
	 * @param classLoader the {@link ClassLoader} to use for deserialization. Can be {@literal null}.
	 * @return new instance of {@link RedisSerializer}. Never {@literal null}.
	 * @since 2.1
	 */
	static RedisSerializer<Object> java(@Nullable ClassLoader classLoader) {
		return new JdkSerializationRedisSerializer(classLoader);
	}

	/**
	 * Obtain a {@link RedisSerializer} that can read and write JSON using
	 * <a href="https://github.com/FasterXML/jackson-core">Jackson</a>.
	 *
	 * @return never {@literal null}.
	 * @since 2.1
	 */
	static RedisSerializer<Object> json() {
		return new GenericJackson2JsonRedisSerializer();
	}

	/**
	 * Obtain a simple {@link java.lang.String} to {@literal byte[]} (and back) serializer using
	 * {@link java.nio.charset.StandardCharsets#UTF_8 UTF-8} as the default {@link java.nio.charset.Charset}.
	 *
	 * @return never {@literal null}.
	 * @since 2.1
	 */
	static RedisSerializer<String> string() {
		return StringRedisSerializer.UTF_8;
	}
}

可以看到byte[] serialize(@Nullable T t)和T deserialize(@Nullable byte[] bytes)就是序列化和反序列化接口,並且下面還定義了java的JdkSerializationRedisSerializer序列化、json的GenericJackson2JsonRedisSerializer和string的StringRedisSerializer.UTF_8.
2.1 JdkSerializationRedisSerializer序列化

public class JdkSerializationRedisSerializer implements RedisSerializer<Object> {

	private final Converter<Object, byte[]> serializer;
	private final Converter<byte[], Object> deserializer;

	/**
	 * Creates a new {@link JdkSerializationRedisSerializer} using the default class loader.
	 */
	public JdkSerializationRedisSerializer() {
		this(new SerializingConverter(), new DeserializingConverter());
	}

	/**
	 * Creates a new {@link JdkSerializationRedisSerializer} using a {@link ClassLoader}.
	 *
	 * @param classLoader the {@link ClassLoader} to use for deserialization. Can be {@literal null}.
	 * @since 1.7
	 */
	public JdkSerializationRedisSerializer(@Nullable ClassLoader classLoader) {
		this(new SerializingConverter(), new DeserializingConverter(classLoader));
	}

	/**
	 * Creates a new {@link JdkSerializationRedisSerializer} using a {@link Converter converters} to serialize and
	 * deserialize objects.
	 *
	 * @param serializer must not be {@literal null}
	 * @param deserializer must not be {@literal null}
	 * @since 1.7
	 */
	public JdkSerializationRedisSerializer(Converter<Object, byte[]> serializer, Converter<byte[], Object> deserializer) {

		Assert.notNull(serializer, "Serializer must not be null!");
		Assert.notNull(deserializer, "Deserializer must not be null!");

		this.serializer = serializer;
		this.deserializer = deserializer;
	}

	public Object deserialize(@Nullable byte[] bytes) {

		if (SerializationUtils.isEmpty(bytes)) {
			return null;
		}

		try {
			return deserializer.convert(bytes);
		} catch (Exception ex) {
			throw new SerializationException("Cannot deserialize", ex);
		}
	}

	@Override
	public byte[] serialize(@Nullable Object object) {
		if (object == null) {
			return SerializationUtils.EMPTY_ARRAY;
		}
		try {
			return serializer.convert(object);
		} catch (Exception ex) {
			throw new SerializationException("Cannot serialize", ex);
		}
	}
}

在JdkSerializationRedisSerializer構造方法中,傳入了Converter的兩個對象,serialize的序列化就使用SerializingConverter的convert方法

public byte[] convert(Object source) {
	try  {
		return this.serializer.serializeToByteArray(source);
	}
	catch (Throwable ex) {
		throw new SerializationFailedException("Failed to serialize object using " +
				this.serializer.getClass().getSimpleName(), ex);
	}
}

Serializer 接口

void serialize(T object, OutputStream outputStream) throws IOException;

default byte[] serializeToByteArray(T object) throws IOException {
	ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
	serialize(object, out);
	return out.toByteArray();
}

在這裏JdkSerializationRedisSerializer中,使用的是DefaultSerializer,它實現了serialize方法:

public class DefaultSerializer implements Serializer<Object> {

	/**
	 * Writes the source object to an output stream using Java serialization.
	 * The source object must implement {@link Serializable}.
	 * @see ObjectOutputStream#writeObject(Object)
	 */
	@Override
	public void serialize(Object object, OutputStream outputStream) throws IOException {
		if (!(object instanceof Serializable)) {
			throw new IllegalArgumentException(getClass().getSimpleName() + " requires a Serializable payload " +
					"but received an object of type [" + object.getClass().getName() + "]");
		}
		ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
		objectOutputStream.writeObject(object);
		objectOutputStream.flush();
	}

}

可以看到使用了ObjectOutputStream的writeObject方法來實現的,下面會繼續調用writeObject0方法,相關可以查看ObjectOutputStream的序列化和反序列化。
JdkSerializationRedisSerializer的反序列化方式轉化類型有區別,這裏就不詳細介紹了。

2.2 GenericJackson2JsonRedisSerializer序列化
GenericJackson2JsonRedisSerializer主要使用ObjectMapper來實現。

@Override
public byte[] serialize(@Nullable Object source) throws SerializationException {

	if (source == null) {
		return SerializationUtils.EMPTY_ARRAY;
	}

	try {
		return mapper.writeValueAsBytes(source);
	} catch (JsonProcessingException e) {
		throw new SerializationException("Could not write JSON: " + e.getMessage(), e);
	}
}

@Override
public Object deserialize(@Nullable byte[] source) throws SerializationException {
	return deserialize(source, Object.class);
}
public <T> T deserialize(@Nullable byte[] source, Class<T> type) throws SerializationException {

	Assert.notNull(type,
			"Deserialization type must not be null! Please provide Object.class to make use of Jackson2 default typing.");

	if (SerializationUtils.isEmpty(source)) {
		return null;
	}

	try {
		return mapper.readValue(source, type);
	} catch (Exception ex) {
		throw new SerializationException("Could not read JSON: " + ex.getMessage(), ex);
	}
}

查看writeValueAsBytes方法,並且繼續向下,可以看到使用了jackson相關包進行json化數據。

private final void _serialize(JsonGenerator gen, Object value,
		JsonSerializer<Object> ser, PropertyName rootName)
	throws IOException
{
	try {
		gen.writeStartObject();
		gen.writeFieldName(rootName.simpleAsEncoded(_config));
		ser.serialize(value, gen, this);
		gen.writeEndObject();
	} catch (Exception e) {
		throw _wrapAsIOE(gen, e);
	}
}

2.3 StringRedisSerializer
StringRedisTemplate中使用了UTF_8的編碼格式。

public class StringRedisSerializer implements RedisSerializer<String> {

	private final Charset charset;

	/**
	 * {@link StringRedisSerializer} to use 7 bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode
	 * character set.
	 *
	 * @see StandardCharsets#US_ASCII
	 * @since 2.1
	 */
	public static final StringRedisSerializer US_ASCII = new StringRedisSerializer(StandardCharsets.US_ASCII);

	/**
	 * {@link StringRedisSerializer} to use ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.
	 *
	 * @see StandardCharsets#ISO_8859_1
	 * @since 2.1
	 */
	public static final StringRedisSerializer ISO_8859_1 = new StringRedisSerializer(StandardCharsets.ISO_8859_1);

	/**
	 * {@link StringRedisSerializer} to use 8 bit UCS Transformation Format.
	 *
	 * @see StandardCharsets#UTF_8
	 * @since 2.1
	 */
	public static final StringRedisSerializer UTF_8 = new StringRedisSerializer(StandardCharsets.UTF_8);

	/**
	 * Creates a new {@link StringRedisSerializer} using {@link StandardCharsets#UTF_8 UTF-8}.
	 */
	public StringRedisSerializer() {
		this(StandardCharsets.UTF_8);
	}

	/**
	 * Creates a new {@link StringRedisSerializer} using the given {@link Charset} to encode and decode strings.
	 *
	 * @param charset must not be {@literal null}.
	 */
	public StringRedisSerializer(Charset charset) {

		Assert.notNull(charset, "Charset must not be null!");
		this.charset = charset;
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.redis.serializer.RedisSerializer#deserialize(byte[])
	 */
	@Override
	public String deserialize(@Nullable byte[] bytes) {
		return (bytes == null ? null : new String(bytes, charset));
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.redis.serializer.RedisSerializer#serialize(java.lang.Object)
	 */
	@Override
	public byte[] serialize(@Nullable String string) {
		return (string == null ? null : string.getBytes(charset));
	}

	@Override
	public Class<?> getTargetType() {
		return String.class;
	}
}

當你的redis數據庫裏面本來存的是字符串數據或者你要存取的數據就是字符串類型數據的時候,可以使用這種方式,非常簡便。

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

【其他文章推薦】

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

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

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

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

【Java思考】Java 中的實參与形參之間的傳遞到底是值傳遞還是引用傳遞呢?

科普:

  • 值傳遞(pass by value)是指在調用函數時將實際參數複製一份傳遞到函數中,這樣在函數中如果對參數進行修改,將不會影響到實際參數。
  • 引用傳遞(pass by reference)是指在調用函數時將實際參數的地址直接傳遞到函數中,那麼在函數中對參數所進行的修改,將影響到實際參數。
值傳遞 引用傳遞
根本區別 會創建副本(Copy) 不創建副本,直接引用
效果 函數中無法改變原始對象 函數中可以改變原始對象

Java 中的實參与形參之間的傳遞到底是值傳遞還是引用傳遞呢?

其實之前我和大多數人一樣認為:傳遞的參數如果是“基本數據類型”,那就是“值傳遞”,如果是“引用類型”(即 對象),那就是“引用傳遞”。

但是昨天我突然覺得:好像。。。不一定!
誒,別急着懟我說:Nemo!你傳遞過對象沒啊,把對象傳過去,修改對象的屬性值,屬性值就是的的確確的修改了啊!

誒,你說的沒錯,確實是修改了,但是你也說了是修改對象的屬性值,傳過去的是對象地址,而你的實際操作並沒有對你傳入的地址進行修改,只是修改了對象地址下面的屬性值。

如果只是修改對象地址下面的屬性值的話,那麼值傳遞和引用傳遞有差嗎?
值傳遞:複製對象地址給函數,函數修改對象地址下面的屬性值。
引用傳遞:引用對象地址給函數,函數修改對象地址下面的屬性值。
這兩者有差嗎,無論是複製還是引用,傳入的對象地址都沒有改變,改變的只是對象地址下面的屬性值。

類比:我們可以類比一下,你家的地址是“北京市海淀區清華園1號”
引用傳遞:你給我引用你的地址,我過去你的地址那,打開你家的門,偷你家電動車的電瓶。
值傳遞:你不給我你的地址,我從網上找到你的地址,複製一份,過去你的地址那,打開你家的門,偷你家電動車的電瓶。
你瞧瞧,這兩者有差嗎?無論是怎樣拿到你家的地址,你家的電瓶我要定了啊,你家的電瓶都會被修改啊。

舉例代碼:

package temp;

/**
 * @author Nemo
 * @date 2020/6/22
 */
public class ValueTransfer {
    public static void main(String[] args) {
        Home yourHome = new Home("你的家");
        Nemo nemo = new Nemo();
        nemo.steal(yourHome);
        yourHome.show();
    }
}

class Home {
    public String name;
    public boolean battery = true;

    public boolean isBattery() {
        return battery;
    }

    public void setBattery(boolean battery) {
        this.battery = battery;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Home(String name) {
        this.name = name;
    }

    public void show() {
        if (this.isBattery()) {
            System.out.println(name + "的電瓶還在喲~");
        } else {
            System.out.println(name + "的電瓶被偷了!");
        }
    }

}

class Nemo {
    public void steal(Home home) {
        //如果是引用傳遞的話,那麼我把你的家整個都變為了別人的家,那麼你的家對象上現在應該存放的是別人的家
        //如果是值傳遞的話,那麼我只是把你的家對象複製了一個新的,這個新的家是別人的家,我偷一個跟你家一模一樣的別人家的電瓶,你家的電瓶應該不會變
        home = new Home("別人的家");
        home.battery = false;
        home.show();
    }

}

在 Nemo 類的 steal 方法中,我們可以看到註釋:

  1. 如果是引用傳遞,那麼我把你的家整個都變為了別人的家,那麼你的家對象上現在應該存放的是別人的家,並且你家(即 別人家)的電瓶也應該被我偷了。
  2. 如果是值傳遞,那麼我只是把你的家對象參數複製了一個新的,這個新的家我設為了別人的家,我偷一個跟你家一模一樣的別人家的電瓶,你家的電瓶應該不會變。

運行結果:

別人的家的電瓶被偷了!
你的家的電瓶還在喲~

根據運行結果來看,很顯然,是第二種情況,也就是值傳遞,我偷的是一個跟你家一模一樣的別人家的電瓶,而你家的電瓶還在。

結論

Java 中只有值傳遞

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

Linux下如何尋找相同文件?

大家好,我是良許。

隨着電腦的使用,系統里將產生很多垃圾,最典型的就是同一份文件被保存到了不同的位置,這樣導致的結果就是磁盤空間被大量佔用,系統運行越來越慢。

所以如果你的電腦空間告急的話,可以試着去刪除這樣的文件,釋放一些空間。在 Linux 下,我們可以通過識別文件的 inode 值來找出系統中的相同文件。

inode 是一個數據結構,記錄了文件所有信息,除了文件名和文件內容。如果兩個或多個文件具有相同的 inode 值,即使它們的文件名不一樣,位置不一樣,它們的內容、所有者、權限其實都是一樣的,我們可以將其視有相同文件。

這類型的文件其實就是所謂的「硬鏈接」。硬鏈接具有相同的 inode 值,但文件名不一樣。而軟鏈接其實就是快捷方式,它指向目標文件,但有着自己的 inode 值。

$ ls -l my*
-rw-r--r-- 4 liangxu liangxu   228 Apr 12 19:37 myfile
lrwxrwxrwx 1 liangxu liangxu     6 Apr 15 11:18 myref -> myfile
-rw-r--r-- 4 liangxu liangxu   228 Apr 12 19:37 mytwin

我們無法直接知道同一目錄下有哪些文件是有相同的 inode 值,但要識別起來也不難。其實我們只要使用 ls -i 命令,再以 inode 值進行排序,就可以直接找到這些文件。

$ ls -i | sort -n | more
 ...
 788000 myfile	<==
 788000 mytwin	<==
 801865 Name_Labels.pdf
 786692 never leave home angry
 920242 NFCU_Docs
 800247 nmap-notes

在這個結果的第一列里,就是對應的 inode 值。所以從這個結果里我們一眼就可以看出來,哪些文件具有相同 inode 值。

如果你只是想找到一個文件的對應硬鏈接文件,我們可以使用 find 命令,再加個 -samefile 選項即可快速找到。

$ find . -samefile myfile
./myfile
./save/mycopy
./mytwin

這些文件都是有相同的 inode 值,不信的話可以再使用 ls 命令來查看更多信息:

$ find . -samefile myfile -ls
 788000    4 -rw-r--r--   4 liangxu    liangxu      228 Apr 12 19:37 ./myfile
 788000    4 -rw-r--r--   4 liangxu    liangxu      228 Apr 12 19:37 ./save/mycopy
 788000    4 -rw-r--r--   4 liangxu    liangxu      228 Apr 12 19:37 ./mytwin

我們可以看到,除了文件名之外,這幾個文件名的信息完全一樣。細心的朋友可能會注意到,在第2列(硬連接數)是4,而實際上我們找出來的文件只有3個,這說明還有一個文件與他們共享 inode 值,只是我們通過這條命令沒有找出來而已。

作為一個懶人,每次敲命令多麻煩,直接上腳本找出目錄下的相同文件!

#!/bin/bash

# seaches for files sharing inodes

prev=""

# list files by inode
ls -i | sort -n > /tmp/$0

# search through file for duplicate inode #s
while read line
do
    inode=`echo $line | awk '{print $1}'`
    if [ "$inode" == "$prev" ]; then
        grep $inode /tmp/$0
    fi
    prev=$inode
done < /tmp/$0

# clean up
rm /tmp/$0

運行結果:

$ ./findHardLinks
 788000 myfile
 788000 mytwin

當然了,你還可以使用 find 命令,根據 inode 值,找到系統里所有相同文件。

$ find / -inum 788000 -ls 2> /dev/null
 788000   4 -rw-r--r--   4 liangxu   liangxu    228 Apr 12 19:37 /tmp/mycopy
 788000   4 -rw-r--r--   4 liangxu   liangxu    228 Apr 12 19:37 /home/liangxu/myfile
 788000   4 -rw-r--r--   4 liangxu   liangxu    228 Apr 12 19:37 /home/liangxu/save/mycopy
 788000   4 -rw-r--r--   4 liangxu   liangxu    228 Apr 12 19:37 /home/liangxu/mytwin

在這條命令里,我們將錯誤提示重定向到 /dev/null 這個特殊文件里,這樣在搜索一些我們沒有權限訪問的路徑時,不會滿屏的 permission denied

公眾號:良許Linux

有收穫?希望老鐵們來個三連擊,給更多的人看到這篇文章

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

【其他文章推薦】

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

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

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

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

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

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

Azure Monitor(一)Application Insights

一,引言

  Azure Monitor 是 Azure 中的一項完整堆棧監視服務,是一種收集和分析遙測數據的服務。它提供了一組完整的功能來監視 Azure 資源以及其他雲中和本地的資源。Azure Monitor  該服務有助於實現雲應用程序以及本地資源和應用程序的最大性能和可用性。 它显示了應用程序的執行方式,並可識別應用程序存在的任何問題。

       Azure Monitor 會收集兩種基本類型的數據 – 指標和日誌。 指標表明資源的執行方式,以及使用的其他資源。 日誌包含显示資源創建/修改時間的記錄。

 

 Azure Monitor 從一系列組件中自動收集數據。 例如:

  1,應用程序數據:與自定義應用程序代碼相關的數據。
  2,操作系統數據:來自託管應用程序的 Windows 或 Linux 虛擬機的數據。
  3,Azure 資源數據:與 Azure 資源(如 Web 應用或負載均衡器)的操作相關的數據。
  4,Azure 訂閱數據:與訂閱相關的數據。 它包括有關 Azure 運行狀況和可用性的數據。
  5,Azure 租戶數據:有關 Azure 組織級別服務的數據,例如 Azure Active Directory。
由於 Azure Monitor 是自動系統,因此在創建 Azure 資源(如虛擬機和 Web 應用)后,它會立即從這些源中收集數據。 可通過以下方式擴展 Azure Monitor 收集的數據:
  1,啟用診斷:對於某些資源(如 Azure SQL 數據庫),僅在啟用診斷日誌記錄后才會收到有關資源的完整信息。 可使用 Azure 門戶、Azure CLI 或 PowerShell 來啟用診斷。
  2,添加代理:對於虛擬機,可安裝 Log Analytics 代理,並將其配置為將數據發送到 Log Analytics 工作區。 此代理會增加發送到 Azure Monitor 的信息量。
開發人員可能還想要從自定義代碼(例如 Web 應用、Azure 函數或移動應用)將數據發送到 Azure Monitor。 他們通過調用數據收集器 API 來發送數據。 你可通過 HTTP 與此 REST 接口通信。 此接口與各種開發框架(如 .NET Framework、Node.js 和 Python)兼容。 開發人員可選擇自己最喜歡的語言和框架在 Azure Monitor 中記錄數據。

日誌

日誌包含對資源所做更改的相關時間戳信息。 記錄的信息類型因日誌源而異。 日誌數據會整理成記錄,每種記錄類型具有不同的屬性集。 日誌可以包含数字值(如 Azure Monitor 指標),但大多數日誌包含文本數據,而不是数字值。
最常見的日誌項目類型會記錄事件。 事件可能偶爾發生,而不是按固定的間隔或根據某種計劃發生。 事件由應用程序和服務創建,這些應用程序和服務為事件提供上下文。 可將指標數據存儲在日誌中,以便將其與其他監視數據合併起來用於分析。
在 Log Analytics 工作區中記錄來自 Azure Monitor 的數據。 Azure 提供分析引擎和豐富的查詢語言。 日誌显示了上下文的任何問題,有助於確定根本原因。

指標

指標是数字值,用於描述系統某些方面在某個時間點的情況。 Azure Monitor 可以近乎實時地捕獲指標。 這些指標按固定時間間隔收集,在因其頻繁採樣而發出警報時很有用。 可使用多種算法,將指標與其他指標進行比較,並觀察隨時間變化的趨勢。
指標存儲在時序數據庫中。 分析時間戳數據時,使用此數據存儲最為有效。 指標適用於警報和快速檢測問題。 可通過指標了解有關係統性能的信息。 如果需要,可以將它們與日誌進行合併,確定問題的根本原因。

   Azure Monitor 現在包括 Log Analytics 和 Application Insights,其提供的高級工具適用於收集和分析遙測數據,以便最大程度地提高雲和本地的資源和應用程序的性能和可用性。 它可以幫助你了解應用程序的性能,並主動識別影響應用程序及其所依賴資源的問題。那麼今天就先了解 Application Insights,通過它可以監控網站的可用性、性能和使用情況。快速診斷確定並診斷應用程序中的錯誤,而無需等待用戶報告這些錯誤以及提供用戶數據的分析,用戶,會話,事件等,

二,正文

 1,什麼是 Application Insights?

  Application Insights 是 Azure Monitor 的一項功能。 使用它可以監視實時應用程序。 它將自動檢測性能異常,並且包含了強大的分析工具來幫助診斷問題,了解用戶在應用中實際執行了哪些操作。 它旨在幫助持續提高性能與可用性。 它適用於本地雲、混合雲或任何公有雲中託管的各種平台(包括 .NET、Node.js、Java 和 Python)上的應用。 它與 DevOps 進程集成,並且具有與不同開發工具的連接點。 可以通過與 Visual Studio App Center 集成來監視和分析移動應用的遙測數據。

 2,為NET.Core Web項目添加Application Insights

新增 NET Core Web 項目

 管理 NuGet 包=》Microsoft.ApplicationInsights.AspNetCore

 註冊Application Insights 遙測收集服務

services.AddApplicationInsightsTelemetry();

azure portal 新建 Applaction Insights 服務

點擊 “Create” 按鈕

 

選擇已有的資源組/創建新的資源組,填寫 Application Insights 的服務名稱 “Azure.Monitor.Application_Insights” (我這裡是之前已經創建服務名稱為 “Azure.Monitor.Application_Insights” ,這裏忽略圖中名稱後面沒有 s)

 

 複製圖中圈起來的檢測密鑰:Instrumentation Key

 配置 appsetting 配置文件中的 InstrumentationKey 的值

{
      "ApplicationInsights": {
        "InstrumentationKey": "putinstrumentationkeyhere"
      },
      "Logging": {
        "LogLevel": {
          "Default": "Warning"
        }
      }
    }

3,運行 Web 應用程序,查看遙測數據

選擇 Monitoring=》Logs

 

 

 消息實時上報差不多需要3-5分鐘,差不多3分鐘后,我們再次點擊 “Run”,我們只看到 “Warning”,“Error”,“Critical”,我們沒有得到 “Information” 和 “Debug” (後面會講到)

 同時,如下圖所示,我們還可以寫一些查詢語句,比如根據時間戳降序排列

 

 我們還可以編寫where 條件,例如 查詢 message==”Warning 1″的警告信息

 

 Monitoring Logs的這個功能還是很強大的,它可以瀏覽我們的日誌信息,同時展開當前日誌,可以展示更多的信息,比如 “operation_ParentId”,可以用來關聯來自同一個Http請求的所有的消息的ID

 圈起來的兩組數據,是我相隔2分鐘后的請求日誌結果,我們可以看到它們對ID都有相同的操作。因為是對於我們在一分鐘內看到的是同一個Http請求。

 查看手動拋的異常 Exception

 我們可以看出異常的時間,異常信息,異常發生的位置,異常的類型,操作等等

 記錄的異常行號為37行,可以對比一下手動拋出異常的行數

 同時,application insights還提供了一個可視化的地方,Investigate=》Failures,從這裏可以看到

  1,正常,異常的請求。

  2,請求對應的響應碼。

  3,各個接口/頁面的異常情況。

  4,異常類型的分佈。

  5,依賴性信息

 其實,我們可以從代碼中可以看到,我們自己手動拋了一個異常,異常雖然用try catch 進行包裹,但是對於應用程序來說,這個異常還沒有進行正確的處理掉,比如返回信息,返回狀態碼等等。

 切換到 Exceptions,可以看到這個異常的信息了

 同時,我們可以得到一些額外的堆棧信息,甚至可以看到異常的代碼行,控制器方法,類等信息

 

 回到上一個話題,Application Insights 默認情況下只監控 “Warnning”,“Error”,“Critical” 類型的信息,我們可以通過appsetting 配置文件設置Application Insights的監視級別

"ApplicationInsights": {
      "LogLevel": {
        "Default": "Debug",
        "Miccrosoft": "Error"
      }
    },

全部代碼 牽扯隱私的部分,這裏使用 “0”進行替代

{
  "Logging": {
    "ApplicationInsights": {
      "LogLevel": {
        "Default": "Debug",
        "Miccrosoft": "Error"
      }
    },
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ApplicationInsights": {
    "InstrumentationKey": "000000-0000-0000-0000-00000000000000"
  }
}

 繼續在Application Insights的logs查看監測數據

 bingo,修改監測默認配置成功!

三,總結

  Application Insights 可以用來監控網站的可用性、性能和使用情況。快速診斷確定並診斷應用程序中的錯誤,而無需等待用戶報告這些錯誤。提供用戶數據的分析,用戶,會話,事件等Application Insights 提供服務器端監視和客戶端/瀏覽器監視功能,它默認數據保留90天,同時還有支持實時流數據上報(延時低至1秒,不保留數據),增加自定義埋點(自定義的指標)等

  Application Insights 服務處理數據並將數據聚合到一個表單中,方便查詢和可視化。

————–我是分割線—————–

github:https://github.com/yunqian44/Azure.Monitor.git

作者:Allen 

版權:轉載請在文章明顯位置註明作者及出處。如發現錯誤,歡迎批評指正。

 

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

【其他文章推薦】

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

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

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

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

Spring AOP學習筆記04:AOP核心實現之創建代理

  上文中,我們分析了對所有增強器的獲取以及獲取匹配的增強器,在本文中我們就來分析一下Spring AOP中另一部分核心邏輯–代理的創建。這部分邏輯的入口是在wrapIfNecessary()方法中緊接着增強器的獲取之後的createProxy():

protected Object createProxy(
        Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {

    ProxyFactory proxyFactory = new ProxyFactory();
    // 獲取當前類中相關屬性
    proxyFactory.copyFrom(this);
    // 決定對於給定的bean是否應該使用targetClass而不是它的接口進行代理
    if (!shouldProxyTargetClass(beanClass, beanName)) {
        // Must allow for introductions; can't just set interfaces to
        // the target's interfaces only.
        Class<?>[] targetInterfaces = ClassUtils.getAllInterfacesForClass(beanClass, this.proxyClassLoader);
        for (Class<?> targetInterface : targetInterfaces) {
            // 添加代理接口
            proxyFactory.addInterface(targetInterface);
        }
    }

    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    for (Advisor advisor : advisors) {
        // 加入增強器
        proxyFactory.addAdvisor(advisor);
    }
    // 設置要代理的類
    proxyFactory.setTargetSource(targetSource);
    // 定製代理
    customizeProxyFactory(proxyFactory);
    // 用來控制代理工廠被配置之後,是否還允許修改通知
    // 默認值為false(即在代理被配置之後,不允許修改代理的配置)
    proxyFactory.setFrozen(this.freezeProxy);
    if (advisorsPreFiltered()) {
        proxyFactory.setPreFiltered(true);
    }

    return proxyFactory.getProxy(this.proxyClassLoader);
}

  對於代理類的創建及處理,Spring委託給了ProxyFactory進行處理,而在上面的函數中主要是對ProxyFactory的初始化操作,為真正的代理創建做準備,初始化包括如下內容:

  • 獲取當前中的屬性;
  • 添加代理接口;
  • 封裝Advisor並加入到ProxyFactory中;
  • 設置要代理的類;
  • 對代理工廠進行定製化處理,供子類實現;
  • 進行獲取代理操作;

  其中封裝Advisor並加入到ProxyFactory中以及創建代理是兩個相對繁瑣的過程,可以通過ProxyFactory提供的addAdvisor方法直接將增強器置入代理創建工廠中,但是將攔截器封裝為增強器還是需要一定的邏輯的。

protected Advisor[] buildAdvisors(String beanName, Object[] specificInterceptors) {
    // 解析註冊的所有interceptorName
    Advisor[] commonInterceptors = resolveInterceptorNames();

    List<Object> allInterceptors = new ArrayList<Object>();
    if (specificInterceptors != null) {
        // 加入攔截器
        allInterceptors.addAll(Arrays.asList(specificInterceptors));
        if (commonInterceptors != null) {
            if (this.applyCommonInterceptorsFirst) {
                allInterceptors.addAll(0, Arrays.asList(commonInterceptors));
            }
            else {
                allInterceptors.addAll(Arrays.asList(commonInterceptors));
            }
        }
    }
    if (logger.isDebugEnabled()) {
        int nrOfCommonInterceptors = (commonInterceptors != null ? commonInterceptors.length : 0);
        int nrOfSpecificInterceptors = (specificInterceptors != null ? specificInterceptors.length : 0);
        logger.debug("Creating implicit proxy for bean '" + beanName + "' with " + nrOfCommonInterceptors +
                " common interceptors and " + nrOfSpecificInterceptors + " specific interceptors");
    }

    Advisor[] advisors = new Advisor[allInterceptors.size()];
    for (int i = 0; i < allInterceptors.size(); i++) {
        // 將攔截器進行包裝轉化為Advisor
        advisors[i] = this.advisorAdapterRegistry.wrap(allInterceptors.get(i));
    }
    return advisors;
}

public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
    // 如果要封裝的對象本身就是Advisor類型的那麼無需再做過多處理
    if (adviceObject instanceof Advisor) {
        return (Advisor) adviceObject;
    }
    // 如果不是Advisor與Advice兩種類型,則拋出異常
    if (!(adviceObject instanceof Advice)) {
        throw new UnknownAdviceTypeException(adviceObject);
    }
    Advice advice = (Advice) adviceObject;
    if (advice instanceof MethodInterceptor) {
        // 如果是MethodInterceptor類型則使用DefaultPointcutAdvisor封裝
        return new DefaultPointcutAdvisor(advice);
    }
    // 如果存在Advisor的適配器那麼也同樣需要進行封裝
    for (AdvisorAdapter adapter : this.adapters) {
        // Check that it is supported.
        if (adapter.supportsAdvice(advice)) {
            return new DefaultPointcutAdvisor(advice);
        }
    }
    throw new UnknownAdviceTypeException(advice);
}

  因為Spring中涉及過多的攔截器、增強器、增強方法等方式來對邏輯進行增強,所以非常有必要將增強器封裝成Advisor來進行代理的創建,完成了增強的封裝過程,那麼接下來就是最重要的一步–代理的創建與獲取。

public Object getProxy(ClassLoader classLoader) {
    return createAopProxy().getProxy(classLoader);
}

1. 創建代理

protected final synchronized AopProxy createAopProxy() {
    if (!this.active) {
        activate();
    }
    // 創建代理
    return getAopProxyFactory().createAopProxy(this);
}

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                    "Either an interface or a target is required for proxy creation.");
        }
        if (targetClass.isInterface()) {
            return new JdkDynamicAopProxy(config);
        }
        return CglibProxyFactory.createCglibProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}

  到這裏已經完成了代理的創建了,不管我們之前是否有閱讀過Spring的源碼,但是應該或多或少都聽說過對於Spring的代理中JDKProxy的實現CglibProxy的實現。Spring是如果選取的呢?現在我們就從源碼的角度分析,看看到底Spring是如何選擇代理方式的。

  從上面代碼中的判斷條件可以看到3個方面影響着Spring的判斷:

  • optimize:用來控制通過CGLIB創建的代理是否使用激進的優化策略。除非完全了解AOP代理如何處理優化,否則不推薦用戶使用這個設置。目前這個屬性僅用於CGLIB代理,對於JDK動態代理(默認代理)無效。
  • proxyTargetClass:這個屬性為true時,目標類本身被代理而不是目標類的接口,並且使用CGLIB方式創建代理,xml文件配置方式為:<aop:aspectj-autoproxy proxy-target-class=”true”/>。
  • hasNoUserSuppliedProxyInterfaces:是否存在代理接口。

  下面是對JDK與Cglib方式的總結:

  • 如果目標對象實現了接口,默認情況下會採用JDK的動態代理實現AOP;
  • 如果目標對象實現了接口,可以強制使用CGLIB實現AOP;
  • 如果目標對象沒有實現接口,則必須採用CGLIB方式實現AOP,Spring會自動切換;

如何強制使用CGLIB實現AOP?

  • 添加CGLIB庫,Spring_HOME/cglib/*.jar
  • 在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class=”true”/>

JDK動態代理和CGLIB字節碼生成的區別?

  • JDK動態代理只能對實現了接口的類生成代理,而不能針對類。
  • CGLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法,因為是繼承,所以該類或方法最好不要聲明成final。

 

2. 獲取代理

  確定了使用哪種代理方式之後便可以進行代理的創建了,Spring中主要使用了兩種方式來實現代理的創建:JDK動態代理、cglib,我們一一來解析。

2.1 JDK動態代理方式

  這裏直接定位到JdkDynamicAopProxy中的getProxy():

public Object getProxy(ClassLoader classLoader) {
    if (logger.isDebugEnabled()) {
        logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
    }
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

  JDK動態代理的使用關鍵是創建自定義的InvocationHandler,而InvocationHandler中包含了需要覆蓋的函數getProxy,這裏其實JdkDynamicAopProxy就是繼承了InvocationHandler的,所以上面的方法正是完成了這個操作,並且我們還可以推斷出,在JdkDynamicAopProxy中一定會有一個invoke函數,並且JdkDynamicAopProxy會把AOP的核心邏輯寫在其中,找一下,一定會有這樣一個函數的:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    MethodInvocation invocation;
    Object oldProxy = null;
    boolean setProxyContext = false;

    TargetSource targetSource = this.advised.targetSource;
    Class<?> targetClass = null;
    Object target = null;

    try {
        // 處理equals方法
        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
            return equals(args[0]);
        }
        // 處理hash方法
        if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
            return hashCode();
        }
        if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
                method.getDeclaringClass().isAssignableFrom(Advised.class)) {
            // Service invocations on ProxyConfig with the proxy config...
            return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
        }

        Object retVal;
        // 有時候目標對象內部的自我調用將無法實施切面中的增強,則需要通過此屬性暴露代理
        if (this.advised.exposeProxy) {
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }

        target = targetSource.getTarget();
        if (target != null) {
            targetClass = target.getClass();
        }

        // 獲取當前方法的攔截器鏈
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

        if (chain.isEmpty()) {
            // 如果沒有任何攔截器鏈則直接調用切點方法
            retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
        }
        else {
            // 將攔截器封裝在ReflectiveMethodInvocation,以便於使用其proceed進行鏈式調用攔截器
            invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            // 執行攔截器鏈
            retVal = invocation.proceed();
        }

        // 返回結果
        Class<?> returnType = method.getReturnType();
        if (retVal != null && retVal == target && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
            retVal = proxy;
        }
        else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
            throw new AopInvocationException(
                    "Null return value from advice does not match primitive return type for: " + method);
        }
        return retVal;
    }
    finally {
        if (target != null && !targetSource.isStatic()) {
            // Must have come from TargetSource.
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

  上面的invoke()函數最主要的工作就是創建了一個攔截器鏈,並使用ReflectiveMethodInvocation類進行了鏈的封裝,而在ReflectiveMethodInvocation類的proceed方法中實現了攔截器的逐一調用,那麼我們就繼續來探究,在proceed方法中是怎麼實現諸如前置增強在目標方法前調用以及後置增強在目標方法后調用的邏輯的。

public Object proceed() throws Throwable {
    //    執行完所有增強后執行切點方法
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return invokeJoinpoint();
    }
    // 獲取下一個要執行的攔截器
    Object interceptorOrInterceptionAdvice =
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
    if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
        // 動態匹配
        InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
        if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
            return dm.interceptor.invoke(this);
        }
        else {
            // 若未匹配則不執行攔截器,調用攔截器鏈中下一個
            return proceed();
        }
    }
    else {
        // 普通攔截器,直接調用。將this作為參數傳入以保證當前實例中調用鏈的執行
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}

  ReflectiveMethodInvocation的主要職責是維護一個鏈式調用的計數器,記錄著當前調用鏈的位置,以便鏈可以有序地進行下去。其實在這個方法中並沒有我們設想的維護各種增強的順序,但是細心的讀者可能會發現,這部分工作其實是委託給了各個增強器來實現,前面有說到。

2.2 Cglib方式

  完成CGLIB代理的類是委託給CglibAopProxy類去實現的,我們來一探究竟。根據前面的分析,我們容易判斷出來,CglibAopProxy的入口應該也是在getProxy():

public Object getProxy(ClassLoader classLoader) {
    if (logger.isDebugEnabled()) {
        logger.debug("Creating CGLIB proxy: target source is " + this.advised.getTargetSource());
    }

    try {
        Class<?> rootClass = this.advised.getTargetClass();
        Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

        Class<?> proxySuperClass = rootClass;
        if (ClassUtils.isCglibProxyClass(rootClass)) {
            proxySuperClass = rootClass.getSuperclass();
            Class<?>[] additionalInterfaces = rootClass.getInterfaces();
            for (Class<?> additionalInterface : additionalInterfaces) {
                this.advised.addInterface(additionalInterface);
            }
        }

        // 驗證Class
        validateClassIfNecessary(proxySuperClass);

        // 創建及配置CGLIB Enhancer
        Enhancer enhancer = createEnhancer();
        if (classLoader != null) {
            enhancer.setClassLoader(classLoader);
            if (classLoader instanceof SmartClassLoader &&
                    ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
                enhancer.setUseCache(false);
            }
        }
        enhancer.setSuperclass(proxySuperClass);
        enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
        enhancer.setStrategy(new MemorySafeUndeclaredThrowableStrategy(UndeclaredThrowableException.class));
        enhancer.setInterceptDuringConstruction(false);
        // 設置攔截器
        Callback[] callbacks = getCallbacks(rootClass);
        Class<?>[] types = new Class<?>[callbacks.length];
        for (int x = 0; x < types.length; x++) {
            types[x] = callbacks[x].getClass();
        }
        enhancer.setCallbackFilter(new ProxyCallbackFilter(
                this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
        enhancer.setCallbackTypes(types);
        enhancer.setCallbacks(callbacks);

        // 生成代理類及創建代理對象
        Object proxy;
        if (this.constructorArgs != null) {
            proxy = enhancer.create(this.constructorArgTypes, this.constructorArgs);
        }
        else {
            proxy = enhancer.create();
        }

        return proxy;
    }
    catch (CodeGenerationException ex) {
        catch若干異常。。。
    }
}

  上面的函數中就是一個完整創建Enhancer的過程,詳細可以參考Enhancer的文檔,這裏最重要的是通過getCallbacks()方法設置攔截器鏈。

private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
    // 對於expose-proxy屬性的處理
    boolean exposeProxy = this.advised.isExposeProxy();
    boolean isFrozen = this.advised.isFrozen();
    boolean isStatic = this.advised.getTargetSource().isStatic();

    // 將攔截器封裝在DynamicAdvisedInterceptor中
    Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);

    // Choose a "straight to target" interceptor. (used for calls that are
    // unadvised but can return this). May be required to expose the proxy.
    Callback targetInterceptor;
    if (exposeProxy) {
        targetInterceptor = isStatic ?
                new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) :
                new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource());
    }
    else {
        targetInterceptor = isStatic ?
                new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) :
                new DynamicUnadvisedInterceptor(this.advised.getTargetSource());
    }

    // 將攔截器加入到callback中
    Callback targetDispatcher = isStatic ?
            new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp();

    Callback[] mainCallbacks = new Callback[] {
            aopInterceptor,  // for normal advice
            targetInterceptor,  // invoke target without considering advice, if optimized
            new SerializableNoOp(),  // no override for methods mapped to this
            targetDispatcher, this.advisedDispatcher,
            new EqualsInterceptor(this.advised),
            new HashCodeInterceptor(this.advised)
    };

    Callback[] callbacks;

    // If the target is a static one and the advice chain is frozen,
    // then we can make some optimisations by sending the AOP calls
    // direct to the target using the fixed chain for that method.
    if (isStatic && isFrozen) {
        Method[] methods = rootClass.getMethods();
        Callback[] fixedCallbacks = new Callback[methods.length];
        this.fixedInterceptorMap = new HashMap<String, Integer>(methods.length);

        // TODO: small memory optimisation here (can skip creation for methods with no advice)
        for (int x = 0; x < methods.length; x++) {
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(methods[x], rootClass);
            fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
                    chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
            this.fixedInterceptorMap.put(methods[x].toString(), x);
        }

        // Now copy both the callbacks from mainCallbacks
        // and fixedCallbacks into the callbacks array.
        callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];
        System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
        System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
        this.fixedInterceptorOffset = mainCallbacks.length;
    }
    else {
        callbacks = mainCallbacks;
    }
    return callbacks;
}

  在getCallback()中Spring考慮了很多情況,有很多的細節,但是我們閱讀源碼是沒有必要也沒有那麼多精力把每一個細節都弄明白的,重點是抓住主幹即可。這裏只需要理解最常用的,比如將advised屬性封裝在DynamicAdvisedInterceptor並加入在callbacks中,這麼做的目的是什麼呢?在CGLIB中對於方法的攔截是通過將自定義的攔截器(實現了MethodInterceptor接口的類)加入Callback中並在調用代理時直接激活攔截器中的intercept()方法來實現的,而在getCallback()方法中正好有這一部分功能的實現,DynamicAdvisedInterceptor繼承自MethodInterceptor,加入Callback中后,在再次調用代理時會直接調用其intercept()方法,由此推斷,對於CGLIB方式實現的代理,其核心邏輯應該是在DynamicAdvisedInterceptor中的intercept()方法中的:

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    Class<?> targetClass = null;
    Object target = null;
    try {
        if (this.advised.exposeProxy) {
            // Make invocation available if necessary.
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }
        // May be null. Get as late as possible to minimize the time we
        // "own" the target, in case it comes from a pool...
        target = getTarget();
        if (target != null) {
            targetClass = target.getClass();
        }
        // 獲取攔截器鏈
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            // 如果攔截器鏈為空則直接激活原方法
            retVal = methodProxy.invoke(target, args);
        }
        else {
            // 鏈式調用
            retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
        }
        retVal = processReturnType(proxy, target, method, retVal);
        return retVal;
    }
    finally {
        if (target != null) {
            releaseTarget(target);
        }
        if (setProxyContext) {
            // Restore old proxy.
            AopContext.setCurrentProxy(oldProxy);
        }
    }
}

  這裏的實現與JDK動態代理方式實現代理中的invoke方法大同小異,都是首先構造攔截器鏈,然後封裝此鏈進行串聯調用,不同的是在JDK動態代理的方式中是直接構造ReflectiveMethodInvocation,而在cglib中則是使用CglibMethodInvocation,其是繼承自ReflectiveMethodInvocation,但是proceed()方法並沒有重寫。

 

3. 總結

  本文着重分析了Spring AOP實現原理中代理對象的創建過程,在bean的初始化過程中會執行Spring的後置處理器,這裡會去判斷這個bean是否需要增強,如果需要則會根據Aspect中定義的增強信息,對指定bean進行增強,也就是創建一個代理對象。對代理對象的創建有兩種方式,一種是通過JDK動態代理的方式,另一種是通過cglib的方式。

 

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

【其他文章推薦】

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

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

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

※幫你省時又省力,新北清潔一流服務好口碑

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

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

一文入門:XGBoost與手推二階導

作者前言

在2020年還在整理XGB的算法,其實已經有點過時了。。不過,主要是為了學習算法嘛。現在的大數據競賽,XGB基本上已經全面被LGB模型取代了,這裏主要是學習一下Boost算法。之前已經在其他博文中介紹了Adaboost算法和Gradient-boost算法,這篇文章講解一下XGBoost。

Adaboost和XGBoost無關,但是Gradient-boost與XGBoost有一定關係。
一文搞懂:Adaboost及手推算法案例
一文讀懂:GBDT梯度提升

樹模型概述

XGB就是Extreme Gradient Boosting極限梯度提升模型。XGB簡單的說是一組分類和回歸樹(CART)的組合。跟GBDT和Adaboost都有異曲同工之處。
【CART=classification adn regression trees】

這裏對於一個決策樹,如何分裂,如何選擇最優的分割點,其實就是一個搜索的過程。搜索怎麼分裂,才能讓目標函數最小。目標函數如下:
\(Obj = Loss + \Omega\)
\(Obj\)就是我們要最小化的優化函數,\(Loss\)就是這個CART模型的預測結果和真實值得損失。\(\Omega\)就是這個CART模型的複雜度,類似神經網絡中的正則項。
【上面的公式就是一個抽象的概念。我們要知道的是:CART樹模型即要求預測盡可能準確,又要求樹模型不能過於複雜。】

對於回歸問題,我們可以用均方差來作為Loss:
\(Loss=\sum_i{(y_i-\hat{y_i})^2}\)

對於分類問題,用交叉熵是非常常見的,這裏用二值交叉熵作為例子:
\(Loss = \sum_i{(y_ilog(\hat{y_i})+(1-y_i)log(\hat{y_i}))}\)

總之,這個Loss就是衡量模型預測準確度的損失。

下面看一下如何計算這個模型複雜度\(\Omega\)吧。
\(\Omega = \gamma T+\frac{1}{2} \lambda \sum^T_j{w_j}^2\)

\(T\)表示恭弘=叶 恭弘子節點的數量,\(w_j\)表示每個恭弘=叶 恭弘子節點上的權重(與恭弘=叶 恭弘子節點的樣本數量成正比)。

【這裡有點麻煩的在於,\(w_j\)是與每個恭弘=叶 恭弘子節點的樣本數量成正比,但是並非是樣本數量。這個\(w_j\)的求取,要依靠與對整個目標函數求導數,然後找到每個恭弘=叶 恭弘子節點的權重值\(w_j\)。】

XGB vs GBDT

其實說了這麼多,感覺XGB和GDBT好像區別不大啊?下面整理一下網上有的說法,再加上自己的理解。有錯誤請指出評論,謝謝!

區別1:自帶正則項

GDBT中,只是讓新的弱分類器來擬合負梯度,那擬合多少棵樹才算好呢?不知道。XGB的優化函數中,有一個\(\Omega\)複雜度。這個複雜度不是某一課CART的複雜度,而是XGB中所有CART的總複雜度。可想而知,每多一顆CART,這個複雜度就會增加他的懲罰力度,當損失下降小於複雜度上升的時候,XGB就停止了。

區別2:有二階導數信息

GBDT中新的CART擬合的是負梯度,也就是一階導數。而在XGB會考慮二階導數的信息。

這裏簡單推導一下XGB如何用上二階導數的信息的:

  1. 之前我們得到了XGB的優化函數:
    \(Obj = Loss + \Omega\)

  2. 然後我們把Loss和Omega寫的更具體一點:
    \(Obj = \sum_i^n{Loss(y_i,\hat{y}_i^t)}+\sum_j^t{\Omega(cart_j)}\)

    • \(\hat{y_i^t}\)表示總共有t個CART弱分類器,然後t個弱分類器給出樣本i的估計值就。
    • \(y_i\)第i個樣本的真實值;
    • \(\Omega(cart_j)\)第j個CART模型的複雜度。
  3. 我們現在要求取第t個CART模型的優化函數,所以目前我們只是知道前面t-1的模型。所以我們得到:
    \(\hat{y}_i^t = \hat{y}_i^{t-1}+f_t(x_i)\)
    t個CART模型的預測,等於前面t-1個CART模型的預測加上第t個模型的預測。

  4. 所以可以得到:
    \(\sum_i^n{Loss(y_i,\hat{y}_i^t)}=\sum_i^n{Loss(y_i,\hat{y}_i^{t-1}+f_t(x_i))}\)
    這裏考慮一下特勒展開:
    \(f(x+\Delta x)\approx f(x)+f'(x)\Delta x + \frac{1}{2} f”(x)\Delta x^2\)

  5. 如何把泰勒公式帶入呢?
    \({Loss(y_i,\hat{y}_i^t)}\)中的\(y_i\)其實就是常數,不是變量
    所以其實這個是可以看成\(Loss(\hat{y}_i^t)\),也就是:
    \(Loss(\hat{y}_i^{t-1}+f_t(x_i))\)

  6. 帶入泰勒公式,把\(f_t(x_i)\)看成\(\Delta x\)
    \(Loss(\hat{y}_i^{t-1}+f_t(x_i))=Loss(\hat{y}_i^{t-1})+Loss'(\hat{y}_i^{t-1})f_t(x_i)+\frac{1}{2}Loss”(\hat{y}_i^{t-1})(f_t(x_i))^2\)

    • 在很多的文章中,會用\(g_i=Loss'(\hat{y}_i^{t-1})\),以及\(h_i=Loss”(\hat{y}_i^{t-1})\)來表示函數的一階導數和二階導數。
  7. 把泰勒展開的東西帶回到最開始的優化函數中,刪除掉常數項\(Loss(\hat{y}_i^{t-1})\)(這個與第t個CART模型無關呀)以及前面t-1個模型的複雜度,可以得到第t個CART的優化函數:
    \(Obj^t \approx \sum_i^n{[g_i f_t(x_i)+\frac{1}{2}h_i(f_t(x_i))^2}]+{\Omega(cart_t)}\)

【所以XGB用到了二階導數的信息,而GBDT只用了一階的梯度】

區別3:列抽樣

XGB借鑒了隨機森林的做法,不僅僅支持樣本抽樣,還支持特徵抽樣(列抽樣),不僅可以降低過擬合,還可以減少計算。

區別4:缺失值

XGB可以自適應的處理樣本中的缺失值。如何處理的這裏就不再講述。

喜歡的話請關注我們的微信公眾號~【你好世界煉丹師】。

  • 公眾號主要講統計學,數據科學,機器學習,深度學習,以及一些參加Kaggle競賽的經驗。
  • 公眾號內容建議作為課後的一些相關知識的補充,飯後甜點。
  • 此外,為了不過多打擾,公眾號每周推送一次,每次4~6篇精選文章。

微信搜索公眾號:你好世界煉丹師。期待您的關注。

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

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

※超省錢租車方案

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

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

寮國水壩潰堤傷亡 增至31死130失蹤

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

寮國官員表示,南部水壩潰堤造成的死亡人數攀升至31人,這場史無前例災難席捲十幾個村莊和農田後數週,仍有130人失蹤。

不習慣組織大規模救援行動的寮國政府,自7月23日大壩崩塌以來,死亡與失蹤人數陸續從災區傳出,但數字卻劇烈波動,官員和國營媒體提供相互矛盾訊息。

阿速坡省(Attapeu)副省長翁拉(Ounla Xayasith)昨天告訴記者:「過去一天的行動,搜救團隊發現幾具屍體,使死亡人數攀升至31人,失蹤人數為130人。」

潰堤的大壩耗資12億美元,由南韓、寮國和泰國公司合資興建,在豪雨後潰決,淹沒了寮國最貧困省分的十幾個村莊。

寮國能源和礦產部長坎瑪尼(Khammany Inthirath)表示,施工不良可能導致事故發生,政府將對此展開正式調查。

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

【其他文章推薦】

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

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

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

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

德出現罕見高溫旱象 綠黨籲成立氣候基金

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

德國今年出現罕見的旱象,高溫也一再打破紀錄,德國綠黨建議成立氣候調適基金,以補償氣候變遷帶來的衝擊。

根據德國氣象局(DWD)的數據,今年4月到7月的均溫,比歷史平均氣溫高攝氏3.6度,創下1881年有氣象紀錄以來的最高溫。

德國今年也出現少見的旱象,今年4月到7月的降雨量,比平均降雨量少110毫米,同樣創下有紀錄以來的新低,各地農人紛紛抱怨收成大幅減少。

鑑於氣候變遷對產業和生活都帶來衝擊,綠黨4日建議政府成立20億歐元(約新台幣708億元)的氣候調適基金,用來支付農業補償、森林大火、水患和醫療的費用。

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

人類已在8月首日 耗盡了地球一年的資源

摘錄自2018年8月2日台灣英文新聞報導

8月1日,人類已消耗今年大自然給予我們一年份的資源。

綜合法媒《法新社》與英媒《衛報》的報導,與全球足跡網絡合作的世界自然基金會(World Wildlife Fund,WWF)的Valérie Gramond表示,今年到8月1日,人類已用掉地球供人類一年份的樹、水、土地、漁貨等資源量,而釋放的碳排放,也已超出海洋與森林所能負荷且吸收的量。

WWF指出,人類需要1.7個地球才能維持目前對於生態資源需求的水平。

首次紀錄「地球超載日」(Earth Overshoot Day, EOD)亦稱作「生態負債日」,在1970年為12月29日,去(2017)年則落在8月3日,今年更提早2天來到8月1日,創下有史以來最早的記錄。

雖然這段期間有稍稍減緩了一些,但在二年前,資源的消耗又再度恢復其速度。據此趨勢估計,明(2019)年的「生態負債日」可能會提前到7月份。

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

【其他文章推薦】

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

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

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

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

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

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