




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、Java 注解(Annotation)(1) Annotation( 注释)是JDK5.0及以后版本引入的。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。注释是以注释名在代码中存在的,根据注释参数的个数,我们可以将注释分为:标记注释、单值注释、完整注释三类。它们都不会直接影响到程序的语义,只是作为注释(标识)存在,我们可以通过反射机制编程实现对这些元数据的访问。另外,你可以在编译时选择代码里的注释是否只存在于源代码级,或者它也能在class文件中出现。元数据的作用如果要对于元数据的作用进行分类,目前还没有明确的定义,不过我们可以根据它所起的作用,大致可分为三类:编写文档:通过
2、代码里标识的元数据生成文档。代码分析:通过代码里标识的元数据对代码进行分析。编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查。基本内置注释Java代码1. package com.iwtxokhtd.annotation;2. /*3. *测试Override注解4. * author Administrator5. *6. */7. public class OverrideDemoTest 8.9. /Override10. public String tostring()11. return "测试注释"12. 13. package com.iwtxok
3、htd.annotation;/*测试Override注解 * author Administrator*/public class OverrideDemoTest /Overridepublic String tostring()return "测试注释"Deprecated的作用是对不应该在使用的方法添加注释,当编程人员使用这些方法时,将会在编译时显示提示信息,它与 javadoc里的deprecated标记有相同的功能,准确的说,它还不如javadoc deprecated ,因为它不支持参数,使用Deprecated 的示例代码示例如下:Java代码1. pack
4、age com.iwtxokhtd.annotation;2. /*3.*测试Deprecated 注解4.* author Administrator5.6.*/7. public class DeprecatedDemoTest 8.public static void main(String口 args) 9./使用DeprecatedClass里声明被过时的方法10.DeprecatedClass.DeprecatedMethod();11.12. 13. class DeprecatedClass14.Deprecated15.public static void Deprecate
5、dMethod() 16.17.package com.iwtxokhtd.annotation;/*测试Deprecated 注解* author Administrator*/public class DeprecatedDemoTest public static void main(String args) /使用DeprecatedClass里声明被过时的方法DeprecatedClass.DeprecatedMethod();class DeprecatedClassDeprecatedpublic static void DeprecatedMethod() SuppressWa
6、rnings, 其参数有:deprecation ,使用了过时的类或方法时的警告unchecked ,执行了未检查的转换时的警告Break时的警告fallthrough ,当Switch程序块直接通往下一种情况而没有path ,在类路径、源文件路径等中有不存在的路径时的警告serial ,当在可序列化的类上缺少serialVersionUID 定义时的警告finally ,任何finally 子句不能正常完成时的警告all ,关于以上所有情况的警告Java代码1. package com.iwtxokhtd.annotation;2.3. import java.util.ArrayList;
7、4. import java.util.List;5.6. public class SuppressWarningsDemoTest 7.9.8. public static List list=new ArrayList();SuppressWarnings("unchecked")10.public void add(String data)11. list.add(data);12. 13. package com.iwtxokhtd.annotation;import java.util.ArrayList;import java.util.List;public
8、 class SuppressWarningsDemoTest public static List list=new ArrayList();SuppressWarnings("unchecked")public void add(String data)list.add(data);(2)自定义注释interface,如下例:它类似于新创建一个接口类文件,但为了区分,我们需要将它声明为Java代码1. public interface NewAnnotation 2. )public interface NewAnnotation )使用自定义的注释类型Java代码1.
9、 public class AnnotationT est 2. NewAnnotation3.public static void main(String口 args) 4.5. )public class AnnotationT est NewAnnotationpublic static void main(String args) )为自定义注释添加变量Java代码1. public interface NewAnnotation 2. String value();public interface NewAnnotation String value();)Java代码1. publ
10、ic class AnnotationT est 2. NewAnnotation("main method")3. public static void main(String口 args) 4. saying();5. )6. NewAnnotation(value = "say method")7. public static void saying() 8. )9. ) public class AnnotationT est NewAnnotation("main method") public static void ma
11、in(String args) saying();NewAnnotation(value = "say method")public static void saying() 定义一个枚举类型,然后将参数设置为该枚举类型,并赋予默认值Java代码1. public interface Greeting 2. public enum FontColor3.BLUE,RED,GREEN4. );5. String name();6. FontColor fontColor() default FontColor.RED;7. )public interface Greeting
12、 public enum FontColorBLUE,RED,GREEN;String name();FontColor fontColor() default FontColor.RED;这里有两种选择,其实变数也就是在赋予默认值的参数上,我们可以选择使用该默认值,也可以重新设置一个值来替换默认值Java代码1. NewAnnonation("main method")2. public static void main(String args) 3. saying();4. sayHelloWithDefaultFontC010r();5. sayHelloWithRe
13、dFontC010r();6.7. 8. NewAnnonation("say method")9. public static void saying()10.11. 12. /此时的fontC010r 为默认的RED13. Greeting(name="defaultfontcolor")14. public static void sayHelloWithDefaultFontC010r()15.16. 17. 现在将 fontColor 改为 BLUE18. Greeting(name="notdefault",fontCol
14、or=Greeting.FontColor.BLUE)19. public static void sayHelloWithRedFontC010r() 20. NewAnnonation("main method")public static void main(String args) saying();sayHelloWithDefaultFontC010r();sayHelloW让hRedFontC010r();NewAnnonation("say method") public static void saying()此时的fontColor
15、为默认的RED Greeting(name="defaultfontcolor")public static void sayHelloWithDefaultFontC010r() /现在将fontColor 改为BLUEGreeting(name="notdefault",fontColor=Greeting.FontColor.BLUE) public static void sayHelloWithRedFontC010r() (3)注释的高级应用限制注释的使用范围用丁2931指定ElementType 属性Java代码1. package jav
16、a.lang.annotation;2. public enum ElementType 3. TYPE,4. /用于类,接口,枚举但不能是注释5. FIELD,6. /字段上,包括枚举值7. METHOD,9. PARAMETER,10. /方法的参数11. CONSTRUCTOR,12. /构造方法13. LOCAL_VARIABLE,14. /本地变量或 catch语句15. ANNOTATION_TYPE,16. /注释类型(无数据)17. PACKAGE18. / Java 包19. package java.lang.annotation;public enum ElementTy
17、pe TYPE,/用于类,接口,枚举但不能是注释FIELD,/字段上,包括枚举值METHOD,/方法,不包括构造方法PARAMETER,/方法的参数CONSTRUCTOR,/构造方法LOCAL_VARIABLE,/本地变量或 catch语句ANNOTATION_TYPE,/注释类型(无数据)PACKAGE/ Java 包注解保持性策略Java代码1. /限制注解使用范围2. T arget(ElementType.METHOD,ElementType.CONSTRUCTOR)3. public interface Greeting 4.5. /使用枚举类型6. public enum Font
18、Color7. BLUE,RED,GREEN8. ;9. String name();10. FontColor fontC010ro default FontColor.RED;11. /限制注解使用范围Target(ElementType.METHOD,ElementType.CONSTRUCTOR)public interface Greeting /使用枚举类型public enum FontColorBLUE,RED,GREEN;String name();FontColor fontC010ro default FontColor.RED;在Java编译器编译时,它会识别在源代码里
19、添加的注释是否还会保留,这就是RetentionPolicy 。下面是 Java 定义的 RetentionPolicy 枚举:编译器的处理有三种策略:将注释保留在编译后的类文件中,并在第一次加载类时读取它将注释保留在编译后的类文件中,但是在运行时忽略它按照规定使用注释,但是并不将它保留到编译后的类文件中Java代码1. package java.lang.annotation;2. public enum RetentionPolicy 3. SOURCE,4. /此类型会被编译器丢弃5. CLASS,6. /此类型注释会保留在 class文件中,但JVM会忽略它7. RUNTIME8. /
20、此类型注释会保留在 class文件中,JVM会读取它9. )package java.lang.annotation;public enum RetentionPolicy SOURCE,/此类型会被编译器丢弃CLASS,/此类型注释会保留在 class文件中,但JVM会忽略它RUNTIME/此类型注释会保留在 class文件中,JVM会读取它)Java代码1. /让保持性策略为运行时态,即将注解编码到class文件中,让虚拟机读取2. Retention(RetentionPolicy.RUNTIME)3. public interface Greeting 4.5./使用枚举类型6. pu
21、blicenum FontColor7. BLUE,RED,GREEN8. ;9. Stringname();10. FontColor fontC010ro default FontColor.RED;11. 让保持性策略为运行时态,即将注解编码到class文件中,让虚拟机读取Retention(RetentionPolicy.RUNTIME)public interface Greeting /使用枚举类型public enum FontColorBLUE,RED,GREEN;String name();FontColor fontC010ro default FontColor.RED;
22、Java提供的Documented元注释跟Javadoc的作用是差不多的,其实它存在的好处是开发人员可以定制Javadoc不支持的文档属性,并在开发中应用。它的使用跟前两个也是一样的, 简单代码示例如下:Java代码1. /让它定制文档化功能2. /使用此注解时必须设置RetentionPolicy 为RUNTIME3. Documented4. public interface Greeting 5.6. /使用枚举类型7. public enum FontColor8. BLUE,RED,GREEN9. ;10. String name();11. FontColor fontC010ro
23、 default FontColor.RED;12. /使用此注解时必须设置RetentionPolicy 为RUNTIMEDocumentedpublic interface Greeting /使用枚举类型public enum FontColorBLUE,RED,GREEN;String name();FontColor fontC010ro default FontColor.RED;标注继承Java代码1. /让它允许继承,可作用到子类2. Inherited3. public interface Greeting 4.5. /使用枚举类型6. publicenum FontColo
24、r7. BLUE,RED,GREEN8. ;9. Stringname();10. FontColor fontC010ro default FontColor.RED;11. 让它允许继承,可作用到子类Inheritedpublic interface Greeting /使用枚举类型public enum FontColorBLUE,RED,GREEN;String name();FontColor fontC010ro default FontColor.RED;(4)读取注解信息属于重点,在系统中用到注解权限时非常有用,可以精确控制权限的粒度Java代码1. package com.i
25、wtxokhtd.annotation;2. import java.lang.annotation.Annotation;3. import java.lang.reflect.Method;4.5. 读取注解信息6. public class ReadAnnotationInfoTest 7. public static void main(String args)throws Exception est");8. /测试AnnotationTest 类,得到此类的类对象9. Class c=Class.forName("com.iwtxokhtd.annotation
26、.AnnotationT10. /获取该类所有声明的方法11. Method 口methods=c.getDeclaredMethods();12. /声明注解集合13. Annotation annotations;14. /遍历所有的方法得到各方法上面的注解信息15. for(Method method:methods)16. /获取每个方法上面所声明的所有注解信息17. annotations=method.getDeclaredAnnotations();18. 再遍历所有的注解,打印其基本信息19. for(Annotation an:annotations)20. System.o
27、ut.println(" 方法名为:"+method.getName()+" 其上面的注解为:"+an.annotationType().getSimpleName();21. Method 口meths=an.annotationType().getDeclaredMethods();22. /遍历每个注解的所有变量23. for(Method meth:meths)24. System.out.println(" 注解的变量名为:"+meth.getName();25. 26.27. 28. 29.30. 31.32. packa
28、ge com.iwtxokhtd.annotation;import java.lang.annotation.Annotation;import java.lang.reflect.Method;/读取注解信息public class ReadAnnotationInfoTest public static void main(String口 args)throws Exception /测试AnnotationTest 类,得到此类的类对象Class c=Class.forName("com.iwtxokhtd.annotation.AnnotationTest");/
29、获取该类所有声明的方法Method 口methods=c.getDeclaredMethods();/声明注解集合Annotation口 annotations;/遍历所有的方法得到各方法上面的注解信息for(Method method:methods)/获取每个方法上面所声明的所有注解信息annotations=method.getDeclaredAnnotations();/再遍历所有的注解,打印其基本信息for(Annotation an:annotations)System.out.println(" 方法名为:"+method.getName()+" 其
30、上面 的注解为: "+an.annotationType().getSimpleName();Method 口meths=an.annotationType().getDeclaredMethods();/遍历每个注解的所有变量for(Method meth:meths)System.out.println(" 注解的变量名为:"+meth.getName();)利用自定义Java注解实现资源注入这里是想介绍一下如何通过Java的注解机制,实现对 bean资源的注入。主要介绍实现的方法,至于例子的实用性不必讨论。需求:一个应用有两个数据库,分别为 DB-A , D
31、B-B。假设持久层木g架使用iBatis来实现,那么SqlMapClient对象在创建时,对于两个不同的DB连接要有两个不同的 SqlMapClient 对象,假设我们有一个 Service类为MyService.java ,该类中有两个 SqlMapClient 对象sqlMapA、sqlMapB 分别对应着 DB-A、DB-B 。先看看我们的 SqlMapClient.java 类:(自定义 SqlMapClient 类,用来演示。)import java.util.Map;import mons.lang.builder.ToStringBuilder;import mons.lang.
32、builder.ToStringStyle;SuppressWarnings("unchecked")public class SqlMapClient 百 public SqlMapClient(String s, String t) sqlMap = s;type = t;卜部 public SqlMapClient。卜private String type = null ;private String sqlMap = null ;/ get、set方法略/用于演示查询后返回一个String的返回结果public String selectForObject(Stri
33、ng sql, Map in) return this .toString();Override部 public String toString() return new ToStringBuilder( this , ToStringStyle.SHORT_PREFIX_STYLE).append("sqlMap", sqlMap).append("type", type).toString();卜MyService.java 类实现:import java.util.Map;SuppressWarnings("unchecked"
34、)二 ± public class MyService I DataSource(type="B", sqlMap="com/annotation/sql-map-config-B.xml")I private SqlMapClient sqlMapB = null ;II DataSource(type="A", sqlMap="com/annotation/sql-map-config-A.xml")null ;I private SqlMapClient sqlMapA =/ get、set方法略I
35、 /模拟在DB-B数据库取得数据部 public String selectForObjectFromB(String sql, Map in) return sqlMapB.selectForObject("", null );卜I/模拟在DB-A数据库取得数据部 public String selectForObjectFromA(String sql, Map in) return sqlMapA.selectForObject("", null );卜接下来就是我们的注解类:DataSource.javaimport java.lang.anno
36、tation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;Target(ElementType.FIELD)Retention(RetentionPolicy.RUNTIME)public interface DataSource /* Dao 的类型* return*/String type() default "A" / 连接的数据库类型 A or BIStr
37、ing sqlMap() default "" / Sql-Map-Config 文件的路径, 用于加载 iBatis 的 SqlMapClient对象尸定义资源注入的接口 IFieldWiring.java 。之所以这里要定义这个接口,是为了以后扩展用,我们很方便的定义更多的自定义注解。IFieldWiring.javaimport java.lang.annotation.Annotation;import java.lang.reflect.Field;public interface IFieldWiring Class<? extends Annotatio
38、n> annotationClass();void wiring(Object object, Field field);尸IFieldWiring.java 的实现类-DataSourceWiring.java 。(该类实现只为演示用,有很多 地方是可以改进的)import java.lang.annotation.Annotation;import java.lang.reflect.Field;public class DataSourceWiring implements IFieldWiringOverride电电 public void wiring(Object objec
39、t, Field field) Object fieldObj = ReflectUtils.getFieldValue(object, field.getName();/ 获彳导 field对应的对象甑 if (fieldObj != null ) return ;卜DataSource annotation = field.getAnnotation(DataSource.class );String type = annotation.type();String sqlMap = annotation.sqlMap();/这里可以用缓存来实现,不用每次都去创建新的SqlMapClient
40、 对象SqlMapClient sqlMapImpl = new SqlMapClient(sqlMap, type);/将生成SqlMapClient 注入到bean对象的字段上ReflectUtils.setFieldValue(object, field.getName(), SqlMapClient.class , sqlMapIm pl);Override部 public Class<? extends Annotation> annotationClass() return DataSource. class;提供的:这里的ReflectUtils.java 也是我们自
41、定义的,并非有 Springimport java.lang.reflect.Field;import java.lang.reflect.Method;import mons.lang.StringUtils;public class ReflectUtils /* 取得字段值* param obj* param fieldName* return* /public static Object getFieldValue(Object obj, String fieldName) if (obj = null | fieldName = null | "".equals(
42、fieldName) return null ;卜Class<?> clazz = obj.getClass();韩 try String methodname = "get" + StringUtils.capitalize(fieldName);Method method = clazz.getDeclaredMethod(methodname);method.setAccessible( true );return method.invoke(obj);部 catch (Exception e) 百申 try Field field = clazz.get
43、DeclaredField(fieldName);field.setAccessible( true );return field.get(obj); catch (Exception e1) e1.printStackTrace();卜卜return null ;I卜public static void setFieldValue(Object target, String fname, Class<?> fieldClass,中申 Object fieldObj) if (!fieldClass.isAssignableFrom(fieldObj.getClass() retu
44、rn ;Class<?> clazz = target.getClass();try Method method = clazz.getDeclaredMethod("set" + Character.toUpperCase(fname.charAt(0)+ fname.substring(1), fieldClass);method.setAccessible(true );method.invoke(target, fieldObj);中申 catch (Exception e) try Field field = clazz.getDeclaredFiel
45、d(fname);field.setAccessible(true );field.set(target, fieldObj); catch (Exception e1) e1.printStackTrace();已经基本大功告成了,只要将我们的DataSourceWiring.java 类使用起来即可。MyAnnotationBeanProcessor.java,这个类主要用于为 bean对象注入资源。import java.lang.reflect.Field;public class MyAnnotationBeanProcessor /*注入资源* param serviceObjec
46、t* param fieldAutoWirings /所有实现IFieldWiring的接口的对象,我们可以在此扩* throws ExceptionfieldAutoWirings)*/public void wire(Object serviceObject, IFieldWiringthrows Exception Class<?> cls = serviceObject.getClass();for (Field field : cls.getDeclaredFields() for (IFieldWiring fieldAutoWiring : fieldAutoWiri
47、ngs) if (field.isAnnotationPresent(fieldAutoWiring.annotationClass() fieldAutoWiring.wiring(serviceObject, field);好了,开始我们的测试类:FieldWiringTest.javathrows Exception public class FieldWiringTest public static void main(String args)MyAnnotationBeanProcessor processor =new MyAnnotationBeanProcessor();MyS
48、ervice b = new MyService();processor.wire(b, new DataSourceWiring(); / 注入 DataSource 资源System.out.println(b.selectForObjectFromB("",null );System.out.println(b.selectForObjectFromA("",null );)执行结果:SqlMapClientsqlMap=com/annotation/sql-map-config-B.xml,type=BSqlMapClientsqlMap=com
49、/annotation/sql-map-config-A.xml,type=A由执行结果可以说明DataSource 资源已经被我们正确的注入了。如果想扩展的话,只需要新建一个类实现IFieldWiring 接口即可。假设叫InParamWiring.java ,实现了接口定义的两个方法后,在使用的时候,只要用以下代码便可将资源注入了:MyAnnotationBeanProcessor processor =new MyAnnotationBeanProcessor。;MyService b = new MyService();processor.wire(b, new DataSourceW
50、iring(), new InParamWiring(); / 注入 DataSource 、InParam 资源注:以上代码重在演示,其实这个需求可以在Spring中管理两个不同的SqlMapClient 对象,然后通过Spring的自动注入实现。下一篇将介绍怎么通过Spring实现这样的自定义资源注入。出处 1、Entity(name="EntityName")必须,name为可选,对应数据库中一的个表2、 Table(name="",catalog="",schema="")可选,通常和Entity配合使用,
51、只能标注在实体的 class定义处,表示实体对应的数据库表的信息 name:可选,表示表的名称.默认地,表名和实体名称一致,只有在不一致的情况下才需要指定表名 catalog:可选,表示 Catalog 名称,默认为 Catalog("").schema:可选,表示Schema 名称,默认为 Schema("").3、id必须id定义了映射到数据库表的主键的属性,一个实体只能有一个属性被映射为主键.置于getXxxx()前.4、GeneratedValue(strategy=GenerationType,generator="")可选
52、和TABLE 4种,分别表示让strategy:表示主键生成策略 ,有 AUTO,INDENTITY,SEQUENCEORM框架自动选择 根据数据库的Identity字段生成根据数据库表的 Sequence字段生成,以有根据一个额外的表生成主键,默认为AUTOgenerator:表示主键生成器的名称,这个属性通常和 ORM框架相关,例如,Hibernate 可以指定 uuid等主键生成方式.示例:IdGeneratedValues(strategy=StrategyType.SEQUENCE)public int getPk() return pk;5、Basic(fetch=FetchTyp
53、e,optional=true)可选Basic表示一个简单的属性到数据库表的字段的映射,对于没有任彳5标注的getXxxx()方法,默认即为Basicfetch:表示该属性的读取策略,有EAGER和LAZY两种,分别表示主支抓取和延迟加载,默认为EAGER.optional:表示该属性是否允许为null,默认为true示例:Basic(optional=false)public String getAddress() return address;6、Column可选Column 描述了数据库表中该字段的详细定义,这对于根据JPA注解生成数据库表结构的工具非常有作用.name:表示数据库表中该
54、字段的名称,默认情形属性名称一致nullable:表示该字段是否允许为null,默认为trueunique:表示该字段是否是唯一标识,默认为falselength:表示该字段的大小,仅又t String类型的字段有效insertable:表示在ORM框架执行插入操作时,该字段是否应出现INSETRT语句中,默认为trueupdateable:表示在ORM框架执行更新操作时,该字段是否应该出现在UPDATE语句中,默认为true.对于一经创建就不可以更改的字段,该属性非常有用,如对于birthday 字段.columnDefinition:表示该字段在数据库中的实际类型.通常ORM框架可 以根据属性类型自动判断数据库中字段的类型,但是又t于Date类型仍无法确定数据库中字段类型究竟是DATE,TIME还是TIMESTAMP.此外,String的默认映射类型为 VARCHAR,如果要将String类型映射到特定 数据库的BLOB或TEXT字段类型,该属性非常有用.示例:Column(name="BIRTH",nullable="false",columnDefinition="DATE")public String getBithday(
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年庆祝妇女节活动方案
- 高三上学期诚实考试主题班会课件
- 2025年电子式热过载继电器项目可行性研究报告
- 2025年电化铝包装材料项目可行性研究报告
- 商丘师范学院《卫生微生物学》2023-2024学年第二学期期末试卷
- 上海民航职业技术学院《新媒体产品设计与项目管理》2023-2024学年第一学期期末试卷
- 洛阳文化旅游职业学院《中医体质学》2023-2024学年第二学期期末试卷
- 山东省济宁市济宁一中2025届高考生物试题模拟(三诊)试题含解析
- 湖南省新化县2025届初三下学期3月练习卷化学试题试卷含解析
- 湖北中医药大学《经济学原理》2023-2024学年第二学期期末试卷
- 新课标(水平三)体育与健康《篮球》大单元教学计划及配套教案(18课时)
- 《我和我的父辈》电影的艺术特色分析2600字
- 医疗保障基金使用监督管理条例解读
- 【MOOC】儿科学-滨州医学院 中国大学慕课MOOC答案
- 风机及塔筒吊装工程吊装方案
- 八年级信息技术上学期 第三课wps的辅助功能 说课稿
- 隧洞施工安全教育培训
- 2024年人大题库考试中国特色社会主义理论题库答案
- 爱国卫生专业培训
- 抗菌药物科学化管理
- 双碳背景下的我国物流行业优化发展研究
评论
0/150
提交评论