欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Java如何創(chuàng)建Annotation

 更新時(shí)間:2019年05月23日 14:41:01   作者:銀河1號(hào)  
在本文中,我們將介紹注解的基礎(chǔ)知識(shí),包括注解是什么,它們?nèi)绾卧谑纠惺褂?,以及如何處理它們。下面和小編一起?lái)學(xué)習(xí)吧

前言

注解是Java很強(qiáng)大的部分,但大多數(shù)時(shí)候我們傾向于使用而不是去創(chuàng)建注解。例如,在Java源代碼里不難找到Java編譯器處理的@Override注解,Spring框架的@Autowired注解, 或Hibernate框架使用的@Entity 注解,但我們很少看到自定義注解。雖然自定義注解是Java語(yǔ)言中經(jīng)常被忽視的一個(gè)方面,但在開(kāi)發(fā)可讀性代碼時(shí)它可能是非常有用的資產(chǎn),同樣有助于理解常見(jiàn)框架(如Spring或Hibernate)如何簡(jiǎn)潔地實(shí)現(xiàn)其目標(biāo)。

在本文中,我們將介紹注解的基礎(chǔ)知識(shí),包括注解是什么,它們?nèi)绾卧谑纠惺褂?,以及如何處理它們。為了演示注解在?shí)踐中的工作原理,我們將創(chuàng)建一個(gè)Javascript Object Notation(JSON)序列化程序,用于處理帶注解的對(duì)象并生成表示每個(gè)對(duì)象的JSON字符串。在此過(guò)程中,我們將介紹許多常見(jiàn)的注解塊,包括Java反射框架和注解可見(jiàn)性問(wèn)題。感興趣的讀者可以在GitHub上找到已完成的JSON序列化程序的源代碼。

什么是注解?

注解是應(yīng)用于Java結(jié)構(gòu)的裝飾器,例如將元數(shù)據(jù)與類(lèi),方法或字段相關(guān)聯(lián)。這些裝飾器是良性的,不會(huì)自行執(zhí)行任何代碼,但運(yùn)行時(shí),框架或編譯器可以使用它們來(lái)執(zhí)行某些操作。更正式地說(shuō),Java語(yǔ)言規(guī)范(JLS)第9.7節(jié)提供了以下定義:

注解是信息與程序結(jié)構(gòu)相關(guān)聯(lián)的標(biāo)記,但在運(yùn)行時(shí)沒(méi)有任何影響。

請(qǐng)務(wù)必注意此定義中的最后一句:注解在運(yùn)行時(shí)對(duì)程序沒(méi)有影響。這并不是說(shuō)框架不會(huì)基于注解的存在而改變其運(yùn)行時(shí)行為,而是包含注解本身的程序不會(huì)改變其運(yùn)行時(shí)行為。雖然這可能看起來(lái)是細(xì)微差別,但為了掌握注解的實(shí)用性,理解這一點(diǎn)非常重要。

例如,某個(gè)實(shí)例的字段添加了@Autowired注解,其本身不會(huì)改變程序的運(yùn)行時(shí)行為:編譯器只是在運(yùn)行時(shí)包含注解,但注解不執(zhí)行任何代碼或注入任何邏輯來(lái)改變程序的正常行為(忽略注解時(shí)的預(yù)期行為)。一旦我們?cè)谶\(yùn)行時(shí)引入Spring框架,我們就可以在解析程序時(shí)獲得強(qiáng)大的依賴(lài)注入(DI)功能。通過(guò)引入注解,我們已經(jīng)指示Spring框架向我們的字段注入適當(dāng)?shù)囊蕾?lài)項(xiàng)。我們將很快看到(當(dāng)我們創(chuàng)建JSON序列化程序時(shí))注解本身并沒(méi)有完成此操作,而是充當(dāng)標(biāo)記,通知Spring框架我們希望將依賴(lài)項(xiàng)注入到帶注解的字段中。

Retention和Target

創(chuàng)建注解需要兩條信息:(1)retention策略和(2)target。保留策略(retention)指定了在程序的生命周期注解應(yīng)該被保留多長(zhǎng)時(shí)間。例如,注解可以在編譯時(shí)或運(yùn)行時(shí)期間保留,具體取決于與注解關(guān)聯(lián)的保留策略。從Java 9開(kāi)始,有三種標(biāo)準(zhǔn)保留策略,總結(jié)如下:

正如我們稍后將看到的,注解保留的運(yùn)行時(shí)選項(xiàng)是最常見(jiàn)的選項(xiàng)之一,因?yàn)樗试SJava程序反射訪問(wèn)注解并基于存在的注解執(zhí)行代碼,以及訪問(wèn)與注解相關(guān)聯(lián)的數(shù)據(jù)。請(qǐng)注意,注解只有一個(gè)關(guān)聯(lián)的保留策略。

注解的目標(biāo)(target)指定注解可以應(yīng)用于哪個(gè)Java結(jié)構(gòu)。例如,某些注解可能僅對(duì)方法有效,而其他注解可能對(duì)類(lèi)和字段都有效。從Java 9開(kāi)始,有11個(gè)標(biāo)準(zhǔn)注解目標(biāo),如下表所示:

有關(guān)這些目標(biāo)的更多信息,請(qǐng)參見(jiàn)JLS的第9.7.4節(jié)。要注意,注解可以關(guān)聯(lián)一個(gè)或多個(gè)目標(biāo)。例如,如果字段和構(gòu)造函數(shù)目標(biāo)與注解相關(guān)聯(lián),則可以在字段或構(gòu)造函數(shù)上使用注解。另一方面,如果注解僅關(guān)聯(lián)方法目標(biāo),則將注解應(yīng)用于除方法之外的任何構(gòu)造都會(huì)在編譯期間導(dǎo)致錯(cuò)誤。

注解參數(shù)

注解也可以具有參數(shù)。這些參數(shù)可以是基本類(lèi)型(例如int或double),String,類(lèi),枚舉,注解或前五種類(lèi)型中任何一種的數(shù)組(參見(jiàn)JLS的第9.6.1節(jié))。將參數(shù)與注解相關(guān)聯(lián)允許注解提供上下文信息或者可以參數(shù)化注解的處理器。例如,在我們的JSON序列化程序?qū)崿F(xiàn)中,我們將允許一個(gè)可選的注解參數(shù),該參數(shù)在序列化時(shí)指定字段的名稱(chēng)(如果沒(méi)有指定名稱(chēng),則默認(rèn)使用字段的變量名稱(chēng))。

如何創(chuàng)建注解?

對(duì)于我們的JSON序列化程序,我們將創(chuàng)建一個(gè)字段注解,允許開(kāi)發(fā)人員在序列化對(duì)象時(shí)標(biāo)記要轉(zhuǎn)換的字段名。例如,如果我們創(chuàng)建汽車(chē)類(lèi),我們可以使用我們的注解來(lái)注解汽車(chē)的字段(例如品牌和型號(hào))。當(dāng)我們序列化汽車(chē)對(duì)象時(shí),生成的JSON將包括make和model鍵,其中值分別代表make和model字段的值。為簡(jiǎn)單起見(jiàn),我們假設(shè)此注解僅用于String類(lèi)型的字段,確保字段的值可以直接序列化為字符串。

要?jiǎng)?chuàng)建這樣的字段注解,我們使用@interface 關(guān)鍵字聲明一個(gè)新的注解:

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)public @interface JsonField {
	public String value() default "";
}

我們聲明的核心是public @interface JsonField,聲明帶有public修飾符的注解——允許我們的注解在任何包中使用(假設(shè)在另一個(gè)模塊中正確導(dǎo)入包)。注解聲明一個(gè)String類(lèi)型value的參數(shù),默認(rèn)值為空字符串。

請(qǐng)注意,變量名稱(chēng)value具有特殊含義:它定義單元素注解(JLS的第9.7.3節(jié)),并允許我們的注解用戶向注解提供單個(gè)參數(shù),而無(wú)需指定參數(shù)的名稱(chēng)。例如,用戶可以使用@JsonField("someFieldName")并且不需要將注解聲明為注解@JsonField(value = "someFieldName"),盡管后者仍然可以使用(但不是必需的)。包含默認(rèn)值空字符串允許省略該值,value如果沒(méi)有顯式指定值,則導(dǎo)致值為空字符串。例如,如果用戶使用表單聲明上述注解@JsonField,則該value參數(shù)設(shè)置為空字符串。

注解聲明的保留策略和目標(biāo)分別使用@Retention和@Target注解指定。保留策略使用java.lang.annotation.RetentionPolicy枚舉指定,并包含三個(gè)標(biāo)準(zhǔn)保留策略的常量。同樣,指定目標(biāo)為java.lang.annotation.ElementType枚舉,包括11種標(biāo)準(zhǔn)目標(biāo)類(lèi)型中每種類(lèi)型的常量。

總之,我們創(chuàng)建了一個(gè)名為JsonField的public單元素注解,它在運(yùn)行時(shí)由JVM保留,并且只能應(yīng)用于字段。此注解只有單個(gè)參數(shù),類(lèi)型String的value,默認(rèn)值為空字符串。通過(guò)創(chuàng)建注解,我們現(xiàn)在可以注解要序列化的字段。

如何使用注解?

使用注解僅需要將注解放在適當(dāng)?shù)慕Y(jié)構(gòu)(注解的任何有效目標(biāo))之前。例如,我們可以創(chuàng)建一個(gè)Car類(lèi):

public class Car {
	@JsonField("manufacturer") private final String make;
	@JsonField private final String model;
	private final String year;
	public Car(String make, String model, String year) {
		this.make = make;
		this.model = model;
		this.year = year;
	}
	public String getMake() {
		return make;
	}
	public String getModel() {
		return model;
	}
	public String getYear() {
		return year;
	}
	@Override public String toString() {
		return year + " " + make + " " + model;
	}
}

該類(lèi)使用@JsonField注解的兩個(gè)主要用途:(1)具有顯式值,(2)具有默認(rèn)值。我們也可以使用@JsonField(value = "someName")注解一個(gè)字段,但這種樣式過(guò)于冗長(zhǎng),并沒(méi)有助于代碼的可讀性。因此,除非在單元素注解中包含注解參數(shù)名稱(chēng)可以增加代碼的可讀性,否則應(yīng)該省略它。對(duì)于具有多個(gè)參數(shù)的注解,需要顯式指定每個(gè)參數(shù)的名稱(chēng)來(lái)區(qū)分參數(shù)(除非僅提供一個(gè)參數(shù),在這種情況下,如果未顯式提供名稱(chēng),則參數(shù)將映射到value參數(shù))。

鑒于@JsonField注解的上述用法,我們希望將Car序列化為JSON字符串{"manufacturer":"someMake", "model":"someModel"} (注意,我們稍后將會(huì)看到,我們將忽略鍵manufacturer 和model在此JSON字符串的順序)。在這之前,重要的是要注意添加@JsonField注解不會(huì)改變類(lèi)Car的運(yùn)行時(shí)行為。如果編譯這個(gè)類(lèi),包含@JsonField注解不會(huì)比省略注解時(shí)增強(qiáng)類(lèi)的行為。類(lèi)的類(lèi)文件中只是簡(jiǎn)單地記錄這些注解以及參數(shù)的值。改變系統(tǒng)的運(yùn)行時(shí)行為需要我們處理這些注解。

如何處理注解?

處理注解是通過(guò)Java反射應(yīng)用程序編程接口(API)完成的。反射API允許我們編寫(xiě)代碼來(lái)訪問(wèn)對(duì)象的類(lèi)、方法、字段等。例如,如果我們創(chuàng)建一個(gè)接受Car對(duì)象的方法,我們可以檢查該對(duì)象的類(lèi)(即Car),并發(fā)現(xiàn)該類(lèi)有三個(gè)字段:(1)make,(2)model和(3)year。此外,我們可以檢查這些字段以發(fā)現(xiàn)每個(gè)字段是否都使用特定注解進(jìn)行注解。

這樣,我們可以遍歷傳遞給方法的參數(shù)對(duì)象關(guān)聯(lián)類(lèi)的每個(gè)字段,并發(fā)現(xiàn)哪些字段使用@JsonField注解。如果該字段使用了@JsonField注解,我們將記錄該字段的名稱(chēng)及其值。處理完所有字段后,我們就可以使用這些字段名稱(chēng)和值創(chuàng)建JSON字符串。

確定字段的名稱(chēng)需要比確定值更復(fù)雜的邏輯。如果@JsonField包含value參數(shù)的提供值(例如"manufacturer"之前使用的@JsonField("manufacturer")),我們將使用提供的字段名稱(chēng)。如果value參數(shù)的值是空字符串,我們知道沒(méi)有顯式提供字段名稱(chēng)(因?yàn)檫@是value參數(shù)的默認(rèn)值),否則,顯式提供了一個(gè)空字符串。后面這幾種情況下,我們都將使用字段的變量名作為字段名稱(chēng)(例如,在private final String model聲明中)。

將此邏輯組合到一個(gè)JsonSerializer類(lèi)中:

public class JsonSerializer {
	public String serialize(Object object) throws JsonSerializeException {
		try {
			Class<?> objectClass = requireNonNull(object).getClass();
			Map<String, String> jsonElements = new HashMap<>();
			for (Field field: objectClass.getDeclaredFields()) {
				field.setAccessible(true);
				if (field.isAnnotationPresent(JsonField.class)) {
					jsonElements.put(getSerializedKey(field), (String) field.get(object));
				}
			}
			System.out.println(toJsonString(jsonElements));
			return toJsonString(jsonElements);
		}
		catch (IllegalAccessException e) {
			throw new JsonSerializeException(e.getMessage());
		}
	}
	private String toJsonString(Map<String, String> jsonMap) {
		String elementsString = jsonMap.entrySet() .stream() .map(entry -> """ + entry.getKey() + "":"" + entry.getValue() + """) .collect(Collectors.joining(",")); return "{
			" + elementsString + "
		}
		"; } private static String getSerializedKey(Field field) { String annotationValue = field.getAnnotation(JsonField.class).value(); if (annotationValue.isEmpty()) { return field.getName(); } else { return annotationValue; } }}

請(qǐng)注意,為簡(jiǎn)潔起見(jiàn),已將多個(gè)功能合并到該類(lèi)中。有關(guān)此序列化程序類(lèi)的重構(gòu)版本,請(qǐng)參閱codebase存儲(chǔ)庫(kù)中的此分支。我們還創(chuàng)建了一個(gè)異常,用于表示在serialize方法處理對(duì)象時(shí)是否發(fā)生了錯(cuò)誤:

public class JsonSerializeException extends Exception {
	private static final long serialVersionUID = -8845242379503538623L;
	public JsonSerializeException(String message) {
		super(message);
	}
}

盡管JsonSerializer該類(lèi)看起來(lái)很復(fù)雜,但它包含三個(gè)主要任務(wù):(1)查找使用@JsonField注解的所有字段,(2)記錄包含@JsonField注解的所有字段的名稱(chēng)(或顯式提供的字段名稱(chēng))和值,以及(3)將所記錄的字段名稱(chēng)和值的鍵值對(duì)轉(zhuǎn)換成JSON字符串。

requireNonNull(object).getClass()檢查提供的對(duì)象不是null (如果是,則拋出一個(gè)NullPointerException)并獲得與提供的對(duì)象關(guān)聯(lián)的Class對(duì)象。并使用此對(duì)象關(guān)聯(lián)的類(lèi)來(lái)獲取關(guān)聯(lián)的字段。接下來(lái),我們創(chuàng)建String到String的Map,存儲(chǔ)字段名和值的鍵值對(duì)。

隨著數(shù)據(jù)結(jié)構(gòu)的建立,接下來(lái)遍歷類(lèi)中聲明的每個(gè)字段。對(duì)于每個(gè)字段,我們配置為在訪問(wèn)字段時(shí)禁止Java語(yǔ)言訪問(wèn)檢查。這是非常重要的一步,因?yàn)槲覀冏⒔獾淖侄问撬接械?。在?biāo)準(zhǔn)情況下,我們將無(wú)法訪問(wèn)這些字段,并且嘗試獲取私有字段的值將導(dǎo)致IllegalAccessException拋出。為了訪問(wèn)這些私有字段,我們必須禁止對(duì)該字段的標(biāo)準(zhǔn)Java訪問(wèn)檢查。setAccessible(boolean) 定義如下:

返回值true 表示反射對(duì)象應(yīng)禁止Java語(yǔ)言訪問(wèn)檢查。false 表示反射對(duì)象應(yīng)強(qiáng)制執(zhí)行Java語(yǔ)言訪問(wèn)檢查。

請(qǐng)注意,隨著Java 9中模塊的引入,使用setAccessible 方法要求將包含訪問(wèn)其私有字段的類(lèi)的包在其模塊定義中聲明為open。有關(guān)更多信息,請(qǐng)參閱 this explanation by Michał Szewczyk和Accessing Private State of Java 9 Modules by Gunnar Morling。

在獲得對(duì)該字段的訪問(wèn)權(quán)限之后,我們檢查該字段是否使用了注解@JsonField。如果是,我們確定字段的名稱(chēng)(通過(guò)@JsonField注解中提供的顯式名稱(chēng)或默認(rèn)名稱(chēng)),并在我們先前構(gòu)造的map中記錄名稱(chēng)和字段值。處理完所有字段后,我們將字段名稱(chēng)映射轉(zhuǎn)換為JSON字符串。

處理完所有記錄后,我們將所有這些字符串與逗號(hào)組合在一起。這會(huì)產(chǎn)生一個(gè)字符串"<fieldName1>":"<fieldValue1>","<fieldName2>":"<fieldValue2>",...。一旦這個(gè)字符串被連接起來(lái),我們用花括號(hào)括起來(lái),創(chuàng)建一個(gè)有效的JSON字符串。

為了測(cè)試這個(gè)序列化器,我們可以執(zhí)行以下代碼:

Car car = new Car("Ford", "F150", "2018");
JsonSerializer serializer = new JsonSerializer();
serializer.serialize(car);

輸出:

{"model":"F150","manufacturer":"Ford"}

正如預(yù)期的那樣,Car對(duì)象的maker和model字段已經(jīng)被序列化,使用字段的名稱(chēng)作為鍵,字段的值作為值。請(qǐng)注意,JSON元素的順序可能與上面看到的輸出相反。發(fā)生這種情況是因?yàn)閷?duì)于類(lèi)的聲明字段數(shù)組沒(méi)有明確的排序,如getDeclaredFields文檔中所述:

返回?cái)?shù)組中的元素未排序,并且不按任何特定順序排列。

由于此限制,JSON字符串中元素的順序可能會(huì)有所不同。為了使元素的順序具有確定性,我們必須自己強(qiáng)加排序。由于JSON對(duì)象被定義為一組無(wú)序的鍵值對(duì),因此根據(jù)JSON標(biāo)準(zhǔn),不需要強(qiáng)制排序。但請(qǐng)注意,序列化方法的測(cè)試用例應(yīng)該輸出{"model":"F150","manufacturer":"Ford"} 或者{"manufacturer":"Ford","model":"F150"}。

結(jié)論

Java注解是Java語(yǔ)言中非常強(qiáng)大的功能,但大多數(shù)情況下,我們使用標(biāo)準(zhǔn)注解(例如@Override)或通用框架注解(例如@Autowired),而不是開(kāi)發(fā)人員。雖然不應(yīng)使用注解來(lái)代替以面向?qū)ο蟮姆绞?,但它們可以極大地簡(jiǎn)化重復(fù)邏輯。例如,我們可以注解每個(gè)可序列化字段而不是在接口中的方法創(chuàng)建一個(gè)toJsonString以及所有可以序列化的類(lèi)實(shí)現(xiàn)此接口。它還將序列化邏輯與域邏輯分離,從域邏輯的簡(jiǎn)潔性中消除了手動(dòng)序列化的混亂。

雖然在大多數(shù)Java應(yīng)用程序中不經(jīng)常使用自定義注解,但是對(duì)于Java語(yǔ)言的任何中級(jí)或高級(jí)用戶來(lái)說(shuō),需要了解此功能。這個(gè)特性的知識(shí)不僅增強(qiáng)了開(kāi)發(fā)人員的知識(shí)儲(chǔ)備,同樣也有助于理解最流行的Java框架中的常見(jiàn)注解。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • SpringBoot3集成Redis的方法詳解

    SpringBoot3集成Redis的方法詳解

    緩存在項(xiàng)目開(kāi)發(fā)中,基本上是必選組件之一,Redis作為一個(gè)key-value存儲(chǔ)系統(tǒng),具備極高的數(shù)據(jù)讀寫(xiě)效率,并且支持的數(shù)據(jù)類(lèi)型比較豐富,下面我們就來(lái)看看SpringBoot3是如何集成Redis的吧
    2023-08-08
  • java Thumbnails 圖片處理的使用

    java Thumbnails 圖片處理的使用

    這篇文章主要介紹了java Thumbnails 圖片處理的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • java 請(qǐng)求跨域問(wèn)題解決方法實(shí)例詳解

    java 請(qǐng)求跨域問(wèn)題解決方法實(shí)例詳解

    這篇文章主要介紹了java 請(qǐng)求跨域問(wèn)題解決方法實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下
    2017-04-04
  • 使用Java自帶的mail?API實(shí)現(xiàn)郵件發(fā)送功能全過(guò)程

    使用Java自帶的mail?API實(shí)現(xiàn)郵件發(fā)送功能全過(guò)程

    電子郵件的應(yīng)用非常廣泛,例如在某網(wǎng)站注冊(cè)了一個(gè)賬戶,自動(dòng)發(fā)送一封歡迎郵件,通過(guò)郵件找回密碼,自動(dòng)批量發(fā)送活動(dòng)信息等,下面這篇文章主要給大家介紹了關(guān)于如何使用Java自帶的mail?API實(shí)現(xiàn)郵件發(fā)送功能的相關(guān)資料,需要的朋友可以參考下
    2023-04-04
  • SpringBoot2 Jpa 批量刪除功能的實(shí)現(xiàn)

    SpringBoot2 Jpa 批量刪除功能的實(shí)現(xiàn)

    這篇文章主要介紹了SpringBoot2 Jpa 批量刪除功能的實(shí)現(xiàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-01-01
  • springboot項(xiàng)目如何防止XSS攻擊

    springboot項(xiàng)目如何防止XSS攻擊

    XSS攻擊全稱(chēng)跨站腳本攻擊,是一種在web應(yīng)用中的計(jì)算機(jī)安全漏洞,允許惡意web用戶將代碼植入到提供給其它用戶使用的頁(yè)面中。本文介紹防止XSS攻擊的方法
    2021-06-06
  • Java中使用正則表達(dá)式獲取網(wǎng)頁(yè)中所有圖片的路徑

    Java中使用正則表達(dá)式獲取網(wǎng)頁(yè)中所有圖片的路徑

    這篇文章主要介紹了Java中使用正則表達(dá)式獲取網(wǎng)頁(yè)中所有圖片的路徑,本文直接給出實(shí)例代碼,需要的朋友可以參考下
    2015-06-06
  • Java使用PDFBox實(shí)現(xiàn)調(diào)整PDF每頁(yè)格式

    Java使用PDFBox實(shí)現(xiàn)調(diào)整PDF每頁(yè)格式

    這篇文章主要為大家詳細(xì)介紹了Java如何使用PDFBox實(shí)現(xiàn)調(diào)整PDF每頁(yè)格式,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考下
    2024-03-03
  • java md5工具類(lèi)分享

    java md5工具類(lèi)分享

    這篇文章主要介紹了java的md5工具類(lèi),需要的朋友可以參考下
    2014-02-02
  • Spring中bean集合注入的方法詳解

    Spring中bean集合注入的方法詳解

    Spring作為項(xiàng)目中不可缺少的底層框架,提供的最基礎(chǔ)的功能就是bean的管理了。bean的注入相信大家都比較熟悉了,但是有幾種不太常用到的集合注入方式,可能有的同學(xué)會(huì)不太了解,今天我們就通過(guò)實(shí)例看看它的使用
    2022-07-07

最新評(píng)論