内省与注解

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*/
        }
    }
posted @   carat9588  阅读(91)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示