内省与注解
1.内省机制
-
在实际编程中,我们常常需要一些用来包装值对象的类,例如Student、Employee、Order,这些类中往往没有业务方法,只是为了把需要处理的实体对象进行封装,有这样的特征:
-
属性都是私有的;
-
有无参的public构造方法;
-
对私有属性根据需要提供 公有的getXxx方法以及setXxx方法;例如属性名称为name,则有getName方法返回属性name值,setName方法设置name值;注意方法的名称通常是get或set加上属性名称,并把属性名称的首字母大写;这些方法称为getters/setters;getters必须有返回值没有方法参数;setter值没有返回值,有方法参数;
-
-
符合这些特征的类,被称为JavaBean;
-
内省(Inspector)机制就是基于反射的基础,Java语言对Bean类属性、事件的一种缺省处理方法。
-
只要类中有getXXX方法,或者setXXX方法,或者同时有getXXX及setXXX方法,其中getXXX方法没有方法参数,有返回值;setXXX方法没有返回值,有一个方法参数;那么内省机制就认为XXX为一个属性;
-
与Java内省有关的主要类及接口有:
-
java.beans.Introspector类: 为获得JavaBean属性、事件、方法提供了标准方法;通常使用其中的getBeanInfo方法返回BeanInfo对象;
-
Java.beans.BeanInfo接口:不能直接实例化,通常通过Introspector类返回该类型对象,提供了返回属性描述符对象(PropertyDescriptor)、方法描述符对象(MethodDescriptor) 、bean描述符(BeanDescriptor)对象的方法;
-
Java.beans.PropertyDescriptor类:用来描述一个属性,该属性有getter及setter方法;
-
2.内省机制对于属性的操作
-
可以使用PropertyDescriptor类的方法获取属性相关的信息,例如getName方法返回属性的名字:
-
PropertyDescriptor类中定义了方法可以获取该属性的getter和setter方法:
-
案例
import com.tjetc.reflect.Person; /*Interface BeanInfo PropertyDescriptor[] getPropertyDescriptors()返回bean的所有属性的描述符。 */ import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class IntrospectorTest { public static void main(String[] args) throws IntrospectionException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { //static BeanInfo getBeanInfo(Class<?> beanClass) 内省Java Bean并了解其所有属性,暴露的方法和事件。 BeanInfo beanInfoPerson = Introspector.getBeanInfo(Person.class); //返回bean的所有属性的描述符。 PropertyDescriptor[] propertyDescriptors = beanInfoPerson.getPropertyDescriptors(); for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { System.out.print(propertyDescriptor.getName() + " ");/*age class name num*/ } System.out.println(); //static Class<?> forName(String className) Class<?> personClazz = Class.forName("com.tjetc.reflect.Person"); //Constructor<T> getConstructor(Class<?>... parameterTypes) Constructor<?> constructor = personClazz.getConstructor(null); Object p = constructor.newInstance(); for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { //synchronized Method getReadMethod() Method readMethod = propertyDescriptor.getReadMethod(); if(readMethod!=null){ if(readMethod.getName().equals("getName")){ Object getResult = readMethod.invoke(p, null); System.out.println(getResult);/*null*/ } } //synchronized Method getWriteMethod() Method writeMethod = propertyDescriptor.getWriteMethod(); if(writeMethod!=null){ if(writeMethod.getName().equals("setName")){ Object yi = writeMethod.invoke(p, "Yi"); System.out.println(p);/*Person{puEmail='null', name='Yi', age=0}*/ } } } } }
3.内省属性的注意事项
-
很多框架都使用了内省机制检索对象的属性,定义属性名字时,名字最好起码以两个小写字母开头,例如stuName,而不要使用sName,某些情况下,可能会导致检索属性失败;
-
再次强调,内省机制检索属性时,是根据getter和setter方法确认属性名字,而不是根据类里声明的属性名决定;
4.注解的功能
-
定义:注解(Annotation),也叫元数据,是一种代码级别的说明。
-
是Java 的JDK1.5版本开始引入的一个特性,与类、接口、枚举是在同一个层次。
-
它可以声明在包、类、属性、方法、局部变量、方法参数、注解、接口等的前面,用来对这些元素进行说明,注释。
-
-
作用分类:
-
①编写文档:通过代码里标识的元数据生成文档【生成文档doc文档】
-
② 代码分析:通过代码里标识的元数据对代码进行分析【使用反射】
-
③编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】
-
-
很多框架技术的新版本中,都可以使用注解替代XML配置文件;
5.注解声明
-
(自定义)注解的声明形式如下:
public @interface 注解名字 { 注解属性 }
-
声明注解类型MyAnnotation.java,如下所示:
import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(value = RetentionPolicy.RUNTIME) public @interface MyAnnotation { }
6.注解修饰目标
-
注解可以用于不同的目标,例如接口、类、构造方法、方法、属性、类型等;
-
声明注解时,可以使用JDK中已经定义的注解@Target声明注解修饰的目标;
-
@Target中使用枚举ElementType表示修饰目标,有如下几种修饰目标:
-
修改MyAnnotation类为MyAnnotation2,添加@Target,指定修饰目标为TYPE,即类、接口、枚举等声明可用;
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(value = RetentionPolicy.SOURCE) //@Target中使用枚举ElementType表示修饰目标 @Target(value = ElementType.TYPE) public @interface MyAnnotation2 { }
7.注解生命周期
-
JDK中已经定义了注解@Retention,用来定义注解的声明周期;
-
@Retention使用枚举RetentionPolicy定义生命周期,共有三种情况
-
修改MyAnnotation2为MyAnnotation3,指定其生命周期为RUNTIME;
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; //注解生命周期是源代码,注释将被编译器丢弃。 //@Retention(value = RetentionPolicy.SOURCE) //注释将由编译器记录在类文件中,但VM不需要在运行时保留。 @Retention(value = RetentionPolicy.CLASS) @Target(value = ElementType.TYPE) public @interface MyAnnotation3 { }
8.注解属性
-
注解的声明形式如下:
public @interface 注解名字 { 注解属性 }
-
注解属性看起来像个方法,其实是属性,属性类型包括所有基本类型、String、Class、enum、Annotation、以上类型的数组形式,注解元素声明形式如下:
public String urlpattern(); public boolean onload();
- 只有一个属性时,名称推荐定义成value
public @interface MyAnnotationOne { public String value(); }
-
修改注解类型MyAnnotation3.java为MyAnnotation4.java,添加2个属性
-
注解中的属性在使用的时候可以指定值,也可以在声明的时候赋默认值;对onload属性赋默认值为false;
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; //注释将由编译器记录在类文件中,并由VM在运行时保留,因此可以反射读取。 @Retention(value = RetentionPolicy.RUNTIME) @Target(value = ElementType.TYPE) public @interface MyAnnotation4 { //定义属性 public String urlpattern();/*public 可省略*/ public boolean onload() default false;/*default表示默认值*/ }
9.注解的使用
-
使用注解非常简单,不管是JDK中内置的已经定义好的注解还是自定义的注解,只要使用 @注解名称(属性值列表)的形式,均可以使用;
-
LoginWebComponent中使用MyAnnotaion4,指定了urlpattern及onload属性值:
//注解属性有默认值时,使用时不用设置值 @MyAnnotation4(urlpattern = "/aaa/bbb.html", onload = true) public class LoginWebComponent { public void doPost(String urlpattern, boolean onload) { System.out.println("访问URL: " + urlpattern + " ; onload属性为: " + onload); } }
-
LogOffWebComponent中使用MyAnnotaion4,指定了urlpattern属性值:
@MyAnnotation4(urlpattern = "/ccc/ddd.html") public class LogOffWebComponent { public void doPost(String urlpattern, boolean onload) { System.out.println("访问URL:" + urlpattern + " ; onload属性为:" + onload); } }
10.反射对注解的操作
-
Class类中定义了获取注解的方法:
-
Annotation对象可以直接像使用方法一样使用注解的属性;
import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class TestMyAnnotation { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { testAnnotation("com.tjetc.annotation.LoginWebComponent"); //testAnnotation("com.tjetc.annotation.LogOffWebComponent"); } private static void testAnnotation(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { //返回LoginWebComponent的类对象。 Class<?> LoginClazz = Class.forName(className); //Annotation[] getAnnotations()返回此元素上 存在的注释。 Annotation[] annotations = LoginClazz.getAnnotations(); /*LoginWebComponent用了4个注解 只有MyAnnotation4和MyAnnotation4的生命周期是运行时(RUNTIME) public static final RetentionPolicy RUNTIME 注释将由编译器记录在类文件中,并由VM在运行时保留,因此可以反射读取。 */ for (Annotation annotation : annotations) { //返回此 Object的运行时类。 Class<? extends Annotation> aClass = annotation.getClass(); System.out.println(aClass.getName()); //返回此注释的注释类型。 Class<? extends Annotation> annotationClazz = annotation.annotationType(); System.out.println(annotationClazz.getName()); } System.out.println(); //返回该元素的注释指定的注释类型,如果存在于此元素,否则为null MyAnnotation4 annotation4 = LoginClazz.getAnnotation(MyAnnotation4.class); String urlpattern = annotation4.urlpattern(); boolean onload = annotation4.onload(); //创建由此类对象表示的类的新实例。该类被实例化为一个具有空参数列表的new表达式。如果类尚未初始化,则初始化该类。 Object o = LoginClazz.newInstance(); //返回方法对象匹配指定的 name和 parameterTypes Method doPostLogin = LoginClazz.getMethod("doPost", String.class, boolean.class); /*Object invoke(Object obj, Object... args) 参数 obj - 从底层方法被调用的对象 args - 用于方法调用的参数 结果 由该对象表示的方法在 obj上调用 args */ Object invokeResult = doPostLogin.invoke(o, urlpattern, onload); System.out.println(invokeResult);/*invokeResult:null*/ } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话