反射和注解

反射_注解

一.类加载器

1.概述:
    在jvm中,负责将本地上的class文件加载到内存的对象
2.分类:
      - BootstrapClassLoader 根类加载器-->C语言写的,我们获取不到
      ​	也被称为引导类加载器,负责Java核心类的加载
      ​	比如System,String等。
        jre/lib/rt.jar下的类都是核心类 
     ---------------------------------     

      - ExtClassLoader 扩展类加载器

      ​	负责JRE的扩展目录中jar包的加载。
      ​	在JDK中JRE的lib目录下ext目录

      - AppClassLoader 系统类加载器
      ​	负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包(第三方jar包)和类路径。或者自定义的类
       ----------------------------------------------------
        AppClassLoader的父加载器是ExtClassLoader
        ExtClassLoader的父加载器是BootstrapClassLoader
        但是他们不是子父类继承关系,他们有一个共同的爹-->ClassLoader
          
 3.获取加载器对象
    类名.class.getClassLoader
    
 4.双亲委派(全盘负责委托机制) 谁用谁加载
         a.Person中有一个String
            public class Person{
                String s = "涛哥"
            }

           Person本身是AppClassLoader加载
           String是BootstrapClassLoader

         b.加载顺序:
           Person本身是AppClassLoader加载,String本来按理来说是AppClassLoader
           但是AppClassLoader加载String的时候,会去问一问ExtClassLoader,,ext,你加载吗?
           ext说:偶no,我不负责加载核心类,我负责的是扩展类,所以你别急,我给你找人(BootstrapClassLoader)
           ext说:boot,你加载String吗?
           boot:,正好我加载核心类,行吧,我加载吧
-----------------------------------------------------------
           假如:ext和boot都不加载,app才会自己加载
               class Test{
                   new Person()
               }
               
a.AppClassLoader负责加载Test,然后按道理来讲Person也是由AppClassLoader加载到内存
  但是App先不加载,先去找ExtClassLoader,Ext不负责加载自定义类,Ext就去找Boot
  但是Boot只负责加载核心类,所以Boot也不加载
b.最后App看两个双亲不加载,自己就加载了Person
              
 5.双亲委派作用:(一个类在内存中加载几次呢?->1)
    能够让Class类加载一次
    创建一个Class对象
/*
	类名.class.getClassLorder()这个类的加载器
 	ClassLorder类的方法 getParent()返回父加载器
*/
public class ClassLoader {
	
	@Test
	public void classLorder(){
		//三个类加载器的关系
		//父子关系
		ClassLoader c1 = ClassLoader.class.getClassLoader();
		System.out.println(c1);
		
		ClassLoader c2 = c1.getParent();
		System.out.println(c2);
		
		ClassLoader c3 = c2.getParent();
		System.out.println(c3);
	}
	
    //获取AppClassLoader 系统类加载器
	@Test
	public void app(){
        //ClassLoader为本类类名
		ClassLoader c1 = ClassLoader.class.getClassLoader();
		System.out.println(c1);
	}
	
    //获取ExtClassLoader 扩展类加载器
	@Test
	public void ext(){
		ClassLoader c1 = DNSNameService.class.getClassLoader();
		System.out.println(c1);
	}
	 
    //BootstrapClassLoader 根类加载器
	@Test
	public void boot(){
		//引导类加载器,不是类,JVM内部,返回值null
		ClassLoader cl = String.class.getClassLoader();
		System.out.println(cl);
	}
}

二.反射

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YkwpSWdO-1610703651588)(image/02.反射的介绍.png)]

1.概述:根据Class对象操作Class对象中的成员

三.反射之获取Class对象

获取Class对象的方式:
      1.new对象 调用 getClass()->Object中的方法
      2.类名.class->class->每个数据类型,不管是基本的还是引用的,jvm都赋予了他们一个静态属性
                          名字就叫做class
      3.Class中方法->forName(String className)

      注意:
        class就加载一次,Class对象也就一个,3中方式获取出来的Class是同一个

      问题:
         3种获取Class对象的方式,哪个在开发中最常用:forName
             使用forName(Stirng className)->扩展性更好,灵活性更高

         因为:参数是一个字符串,将来我们可以将类的全限定名放在配置文件中,然后
             用io流读取,读取出来的字符串(全限定名)可以当做参数放在forName中
             这样,我们想获取不同的Class对象,直接改文件名就可以了,不用修改
             源代码的.-->可以代码实现一下
             
public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    private Person(String name){
        this.name = name;
        System.out.println("我是私有的构造");
    }
    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return name+"..."+age;
    }
}
//测试类
public class Test01 {
    public static void main(String[] args) throws ClassNotFoundException {
        //每个数据类型,不管基本还是引用数据类型,jvm都赋予了这个类型一个静态属性,名字叫class
        Class aClass = Person.class;
         
        Class aClass1 = Class.forName("cn.itcast.day15.class02.Person");

        //Object类中的getClass方法
        Person person = new Person();
        Class aClass2 = person.getClass();

        System.out.println(aClass==aClass1);
        System.out.println(aClass==aClass2);
    }
}

四.获取Class对象中的成员

一.获取所有public的构造方法

   反射通用的使用方式:
      1.获取要操作类的class对象
      2.利用Class类中的方法获取类中的成员(构造,变量,方法)
      3.运行获取出来的成员

   获取Class对象中的构造方法:
      Constructor<?>[] getConstructors()->获取所有的构造方法(public修饰的)
public class Demo01_Constructor {
    public static void main(String[] args) throws Exception{
        //获取Person类的Class对象
        Class pClass = Class.forName("cn.itcast.day20.fanshe02.Person");
        Constructor[] constructors = pClass.getConstructors();
        //遍历数组
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
    }
}

二.获取空参构造

       1.获取指定的构造方法:
            Constructor<T> getConstructor(Class<?>... parameterTypes)
                parameterTypes:需要传递参数类型的class对象
                如果获取的是无参构造,参数不写

       2.Constructor类中的方法
          T newInstance(Object...initargs)-->创建对象->new Person("柳岩",1)
            如果使用此方法创建的是无参构造,参数不用写
 private static void method01() throws Exception {
        //获取Person类的Class对象
        Class pClass = Class.forName("cn.itcast.day20.fanshe02.Person");

        //获取无参构造
        Constructor constructor = pClass.getConstructor();

        Object o = constructor.newInstance();//Person p = new Person()
        System.out.println(o);
    }

三.利用空参构造创建对象的快捷方式

       1.利用空参构造创建对象的快捷方式
          直接调用Class类中的newInstance方法

         前提:被反射的类,必须具有public权限的无参构造
    private static void method02()throws Exception {
        //获取Person类的Class对象
        Class pClass = Class.forName("cn.itcast.day20.fanshe02.Person");
        Object o = pClass.newInstance();
        System.out.println(o);
    }

四.利用反射获取有参构造并创建对象

1.获取指定的构造方法:
            Constructor<T> getConstructor(Class<?>... parameterTypes)
                parameterTypes:需要传递参数类型的class对象
                如果获取的是无参构造,参数不写

2.Constructor类中的方法
          T newInstance(Object...initargs)-->创建对象->new Person("柳岩",1)
            如果使用此方法创建的是无参构造,参数不用写
public class Demo03_Constructor {
    public static void main(String[] args) throws Exception{
        //获取Person类的Class对象
        Class pClass = Class.forName("cn.itcast.day20.fanshe02.Person");

        //获取有参构造
        Constructor constructor = pClass.getConstructor(String.class, int.class);
        System.out.println(constructor);

        //创建对象
        //Person p = new Person("柳岩", 36)
        Object o = constructor.newInstance("柳岩", 36);
        System.out.println(o);
    }

}

五.利用反射获取私有构造(暴力反射)

获取私有的构造(扩展):
        Constructor<?>[] getDeclaredConstructors()->获取所有的构造,包括私有的

        Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)  ->获取指定的构造

         AccessibleObject类中的方法-->暴力反射
            void setAccessible(boolean flag)
                              flag:false->代表不能访问私有的成员
                              flag:true->解除私有权限
public class Demo04_Constructor {
    public static void main(String[] args) throws Exception{
        //获取Person类的Class对象
        Class pClass = Class.forName("cn.itcast.day20.fanshe02.Person");

        //method01(pClass);

        method02(pClass);
    }

    private static void method02(Class pClass)throws Exception {
        //获取私有的构造方法
        Constructor dds = pClass.getDeclaredConstructor(String.class);

        //解除私有权限
        dds.setAccessible(true);

        //创建对象
        Object o = dds.newInstance("郭磊");
        System.out.println(o);
    }

    /*
       获取所有的构造->包括私有
     */
    public static void method01(Class pClass){
        Constructor[] dds = pClass.getDeclaredConstructors();
        //遍历数组
        for (Constructor dd : dds) {
            System.out.println(dd);
        }
    }

}

六.利用反射获取所有成员方法

利用反射获取类中的方法:
        Method[] getMethods()获取所有的方法,public
public class Demo05_Method {
    public static void main(String[] args)throws Exception {
        Class c = ClassUtils.getC();//抽出来的工具类可以不抽取
        Method[] methods = c.getMethods();
        //遍历数组
        for (Method method : methods) {
            System.out.println(method);
        }
    }
}

七.反射之获取方法(有参,无参)

   获取有参以及无参的方法
      Method getMethod(String name, Class<?>... parameterTypes)
                       name:获取的方法名
                       parameterTypes:该方法的参数类型
                       如果获取的方法没参数,parameterTypes不用写

      Method类中的一个方法
         Object invoke(Object obj, Object... args)  ->执行方法
                       obj:是调用newInstance方法生成的对象
                       args:运行方法时传递的实参

         如果执行的方法是void,调用invoke方法没必要用返回值接收了
         如果执行的方法没有参数,那么args不用写
public class Demo06_Method {
    public static void main(String[] args)throws Exception {
        Class c = ClassUtils.getC();
        System.out.println("---------获取setName方法-------------");
        //获取set方法
        Method setName = c.getMethod("setName", String.class);

        //利用空参构造创建Person对象
        Object o = c.newInstance();//Person o = new Person()

        //调用方法
        setName.invoke(o, "柳岩");
        System.out.println(o);

        System.out.println("---------获取getName方法-------------");

        Method getName = c.getMethod("getName");
        //执行getName方法,有返回值的
        Object invoke = getName.invoke(o);
        System.out.println(invoke);
    }
}

五.反射练习(编写一个小框架)

利用反射,解析配置文件中的信息
   文件中配置的信息是
      类的全限定名   className=cn.itcast.day20.fanshe03_test.Person
      类中的某一个方法名  methodName=eat


   步骤:
      1.创建配置文件->properties
        存的信息键值对的形式

        问题:配置文件放在哪里?放在src下->切记
        问题:项目开发完,交给用户使用,给用户的是编译后的class文件
            如果将配置文件放在src下面,idea生成的out目录中这个配置文件会显示在out的项目路径下
            我们给用户class文件的时候,也需要把配置文件给用户,不然项目无法读取配置文件中的信息,也就无法执行

            src 存放的是源代码 编译后产生的class文件,同步的,但是生成的out路径,下没有src这个目录,以为src存放源代码的

         问题:如何读取src目录下的文件?
              直接new FileInputStream(模块名\\src\\配置文件名)是不行的
              因为这样写,而我们给的用户是out下的资源,而out下存放的class文件路径是没有src
              所以这样写,给了用户,用户一使用,直接就读不到这个配置文件了

         解决:
           使用类的加载器
              ClassLoader类定义方法
                 InputStream getResourceAsStream("直接写文件名")返回一个字节输入流
                 此流会自动从类目录下扫描文件读取 ->直接扫描src下的配置文件

      2.利用IO流读取配置文件
        读到Properties集合中

      3.获取Properties中对应的值
         获取类的全限定名
         方法名
      4.利用反射获取类的Class对象
        利用反射去指定方法
//在src下创建一个pro.properties文件
className=cn.itcast.day15.class02.Person
methodName=eat
public class Test {
    public static void main(String[] args)throws Exception{
        //获取类加载器
        ClassLoader classLoader = Test.class.getClassLoader();
        //使用类加载器的输入流读取配置文件
        InputStream in = classLoader.getResourceAsStream("config.properties");
        //创建Properties集合
        Properties properties = new Properties();
        //调用load方法,将流中的配置文件读取到Properties集合中
        properties.load(in);
        //获取配置文件中的value
        String className = properties.getProperty("className");//cn.itcast.day20.fanshe03_test.Person
        String methodName = properties.getProperty("methodName");//eat

        //根据获取出来的className创建Person的Class对象
        Class aClass = Class.forName(className);

        //根据从配置文件中解析出来的方法名去获取Person中的方法
        Method method = aClass.getMethod(methodName);

        //创建对象
        Object o = aClass.newInstance();
        //执行方法
        method.invoke(o);
    }
}

六.注解

一.注解的介绍

 1.jdk1.5版本的新特性->一个引用数据类型
       和类,接口,枚举是同一个层次的
 2.作用:
        说明:对代码进行说明,生成doc文档(API文档)(不会用)
        检查:检查代码是否有错误   @Override(会用)
        分析:对代码进行分析,起到了代替配置文件的作用(会用)

 3.JDK中的注解:
        @Override  ->  检测此方法是否为重写方法
           jdk1.5版本,支持父类的方法重写
           jdk1.6版本,支持接口的方法重写
        @Deprecated -> 方法已经过时,不推荐使用
                       调用方法的时候,方法上会有横线,但是能用
        @SuppressWarnings->消除警告  @SuppressWarnings("all")

二.注解的定义以及属性的定义格式

   1.自定义注解格式:
      修饰符 @interface 注解名{
          属性
      }
   2.属性的定义格式:为了提高注解作用
     - 格式1:数据类型 属性名();-->没有默认值的-->需要后面赋值
     - 格式2:数据类型 属性名() default 默认值;-->可以改变的

   3.注解中能够定义什么样的属性
     - 八种基本数据类型(int,float,boolean,byte,double,char,long,short)- String类型,Class类型,枚举类型,注解类型。
     - 以上所有类型的一维数组。int[][] arr = {{1,2},{3,4}}     [0][0]    [1,0]->3
public @interface Book {
    //书名
    String bookName();
    //价格
    double price();
    //作者
    String[] author();

    //数量
    //Integer count();错误,不能使用包装类
    //int count() default 10;这个是可以的,定义属性的时候可以指定默认值
}

三.注解的使用

//注解的使用
/*
   使用注解,就是为注解的属性赋值
   格式:
      @注解名(属性名=值,属性名=值)
   String[] author():作者是字符串的数组
   数组的赋值-->属性名 = {"曹雪芹","高鹗"}
*/
@Book(booName = "红楼梦",price = 200.9,author = {"曹雪芹","高鹗"})
pubilc class BookShelf{
    
}
注解注意事项:
      1.空注解可以直接使用
      2.一个对象中不能连续使用同一个注解多次,但是一个对象中可以使用多个不同的注解
       (不同的位置可以使用一样的注解,但是同样的位置不能使用一样的注解)
      3.使用注解时,如果此注解中有属性,注解中的属性一定要赋值,如果有多个属性,,隔开
        如果注解中的属性有数组,那么如果数组只有一个元素值,那么{}不用写,反之用写
      4.如果注解中的属性值有默认值,那么我们不必要写,也不用重新赋值,反之必须写上
      5.如果注解中只有一个属性,并且属性名叫value,那么使用注解的时候,属性名不用写

四.注解解析的方法


注解解析:获取注解中的属性值

    接口:AnnotatedElement接口中的方法(用于解析注解的接口)
       boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
          判断当前class对象是否有指定的注解
          返回值:boolean  返回的是true,证明该class对象上有注解;返回的是false,证明该class对象没有注解

          Class<? extends Annotation> annotationClass
          传递的是class对象, 传递的其实就是该class对象上对应注解的class对象

       <T extends Annotation> getAnnotation(Class<T> annotationClass)->获得当前class对象上指定的注解对象。

       解释:
         参数传递的是注解的class对象
         传递哪个注解类型,返回的就是哪个注解对象


       AnnotatedElement毕竟是个接口,你有实现类
         实现类:Class类  Constructor构造方法 Field成员变量  Method成员方法
         结论:注解的解析,和哪个技术密切相关--> 反射

      需求:获取BookShelf1类上的注解Book的属性值
 =====================================================
    注解的解析思想:
       1.反射带有注解的类
       2.判断这个类上是否有注解
       3.获取这个注解
       4.获取注解中的属性值

     ------------------
     假如我们要是解析方法上的注解属性值
       1.反射带有注解的类
       2.反射方法->getMethod-->Method
       3.判断方法上是否有注解
       4.获取这个注解
       5.获取注解的属性值
@Book(booName = "红楼梦",price = 200.9,author = {"曹雪芹","高额"})
pubilc class BookShelf{
    
}


/*
 运行之后发现没有输出,而且c.isAnnotationPresent(Book.class)判断为false,
 明明类上有注解,但是却返回false,为啥呢?那么我们接下来学习一下元注解
*/
public class Test01 {
    public static void main(String[] args)throws Exception {
        //1.反射带有注解得类
        Class c = Class.forName("cn.itcast.day21.zhujie02.BookShelf01");
        //2.判断这个类上是否有注解
        boolean b = c.isAnnotationPresent(Book.class);
        System.out.println(b);
        if (b){
            //如果有注解,获取注解
            //Annotation annotation = c.getAnnotation(Book.class);
            Book book = (Book) c.getAnnotation(Book.class);
            //获取注解中的属性值
            System.out.println("书名:"+book.bookName());
            System.out.println("价格:"+book.price());
            System.out.println("作者:"+ Arrays.toString(book.author()));
        }

    }
}

十一.元注解

**
 * 自定义的注解 Book
 * 定义属性
 *
 * JDK的元注解,比喻注解的总管
 * 管理其他的注解
 *
 * 元注解对我们的注解进行控制
 * 1: 控制我们的注解,可以写在哪里,(,方法,变量上,...)
 * 2: 控制我们的注解的生命周期
 *
 * JDK的2个元注解
 *
 * @Target  指示其他注解,出现的位置->点进Target底层
 *    ElementType[] value(); 数组,可以赋值多个->点ElementType底层
 *    ElementType是数据类型,是枚举
 *    枚举的属性,都是静态修饰,直接类名调用
 *      TYPE, 其他注解可以写在类上
 *      FIELD,其他注解可以写在成员变量
 *      METHOD,其他注解可以写在方法上
 *      PARAMETER,其他注解可以写在方法参数上
 *      CONSTRUCTOR,其他注解可以写在构造方法上
 *
 *  @Retention 指示其他注解的生命周期->点到Retention底层
 *     RetentionPolicy value(); 不是数组,赋值一个->点到RetentionPolicy底层
 *     RetentionPolicy数据类型
 *     枚举的属性,都是静态修饰,直接类名调用
 *     SOURCE(默认级别) 注解仅存在于源码中java文件中(不在class文件中,也不在方法区中)->@Override只是检                        测方法是否为重写方法
 *     CLASS  注解存在于编译后的class文件中->Class文件中出现了,方法区中没有
 *     RUNTIME 运行时期的内存中-->方法区中出现了,一旦在方法区中出现了,我们才能利用反射获取到注解
     
       所以当我们在注解上写SOURCE 运行上面的Test案例判断类上有没有注解,才会返回false
 */
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
    //书名
    String bookName();
    //价格
    double price() ;
    //作者
    String[] author();
}

------------------------------------
@Book(booName = "红楼梦",price = 200.9,author = {"曹雪芹","高额"})
pubilc class BookShelf{
    @Book(booName = "红楼梦",price = 200.9,author = {"曹雪芹","高额"})
    public void lookBook(){
        System.out.println("看书");
    }
}

十二.注解再次解析

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
    String bookName();
    double price();
    String[] author();
}

@Book(booName = "红楼梦",price = 200.9,author = {"曹雪芹","高鹗"})
pubilc class BookShelf{
    
}
=================================================================
public class Test01 {
    public static void main(String[] args)throws Exception {
        //1.反射带有注解得类
        Class c = Class.forName("cn.itcast.day21.zhujie03.BookShelf");
        //2.判断这个类上是否有注解
        boolean b = c.isAnnotationPresent(Book.class);
        System.out.println(b);
        if (b){
            //如果有注解,获取注解
            //Annotation annotation = c.getAnnotation(Book.class);
            Book book = (Book) c.getAnnotation(Book.class);
            //获取注解中的属性值
            System.out.println("书名:"+book.bookName());
            System.out.println("价格:"+book.price());
            System.out.println("作者:"+ Arrays.toString(book.author()));
        }

    }
}

十三.模拟Junit

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
  /*
   使用@MyTest注解
   哪个方法上有@MyTest我们就让哪个方法执行
  */
public class Test01 {
    @MyTest
    public void method01(){
        System.out.println("我是method01");
    }

    public void method02(){
        System.out.println("我是method02");
    }

    @MyTest
    public void method03(){
        System.out.println("我是method03");
    }

/*
        //1.反射获取当前类的Class对象
        //2.利用反射获取方法->getMethods->Method
        //3.遍历数组,将每个Method对象获取出来,挨个判断,该方法上有没有注解
        //4.如果有,直接调用invoke去执行有注解的方法
 */
    public static void main(String[] args) throws Exception{
        //1.获取本类的Class对象
        Class aClass = Test01.class;
        //创建对象
        Object o = aClass.newInstance();

        //2.获取方法
        Method[] methods = aClass.getMethods();
//        3.判断方法上是否有注解
        for (Method method : methods) {
            boolean b = method.isAnnotationPresent(MyTest.class);
            if (b){
               //如果方法上有注解,直接invoke执行
               method.invoke(o);
            }
        }
    }
}

十三.Lombok

使用的注解有:
   @Data->包含get/set,toString,hashCode,equals,无参构造方法
   @Getter->生成get方法
   @Setter->生成set方法
   @NoArgsConstructor@AllArgsConstructor
   - @NoArgsConstructor:无参数构造方法。
   - @AllArgsConstructor:满参数构造方法。
   @@EqualsAndHashCode->生成hashCode和Equals
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值