反射、注解、动态代理

本文详细介绍了Java中的反射机制,包括获取Class对象的三种方式、类加载器的工作原理、反射操作如构造方法、成员方法和字段的访问。此外,还讲解了注解的使用和元注解的作用。最后,探讨了Java动态代理的概念,如何通过Proxy类创建代理对象以及InvocationHandler的运用。
摘要由CSDN通过智能技术生成

反射


  • Class类是用来表述字节码文件 .Class 的类

获取字节码对象与包名_

  • 获取Class的三种方式
    - 类的非静态方法:对象名.getClass()
    - 类的静态方法:Class.forName(“类的全路径”);
    - 类的静态属性:类名.Class

		// 通过 类名.Class
        Class cls1 = Demo.class;

        // 通过对象名.Class
        Demo d = new Demo();
        Class cls2 = d.getClass();

        // 通过Class.forName() 短号里写类的全路径 (常用)
        Class cls3 = Class.forName("类的全路径");

获取类名的方法:
getName()
getSimpleName()


	Demo d = new Demo();
	Class cls = d.getClass();

	// 得到完整的包名 + 类名
    cls.getName();
    // 得到类名
    cls.getSimpleName();

类加载器_

Java中有三种类加载器,它们分别用于加载不同种类的class

  • 类加载器的作用:
    - 把Class文件加载到JVM中
  • 三种类加载器
    • BootStrapClassLoader (启动类加载器)
      • 加载JVM运行的初始化的一些类
    • ExtClassLoader (扩展类加载器)
      • 加载jdk目录下,有一个文件夹ext里面的jar包
    • AppClassLoader (应用程序类加载器)
      • 加载我们项目里面写的类和导入的第三方的jar包的类

	// 得到类的加载器
    System.out.println(cls1.getClassLoader()); //输出 sun.misc.Launcher$AppClassLoader@18b4aac2
    System.out.println(String.class.getClassLoader()); // 输出 null (如果输出为null就表示用的BootStrapClassLoader类加载器)

如果类加载器无法加载指定的类,就会报错

反射

构造方法的反射_

  • Constructor是描述字节码文件中构造方法的类,通过Constructor对象可以实例化这个类,得到对象

常用方法:

  • getConstructor(Class... parameterTypes)
    - 根据参数类型获得对应的Constructor对象
    - 只能获得public修饰的构造方法
  • getDeclaredConstructor(Class... parameterTypes)
    - 根据参数类型获得对应的Constructor对象
    - 可以是public、protected、(默认)、private修饰符的构造方法
  • getConstructors(Class... parameterTypes)
    - 获得类中的所有构造方法对象,只能获得public的
  • getDeclaredConstructors(Class... parameterTypes)
    - 获得类中的所有构造方法对象
    - 可以是public、protected、(默认)、private修饰符的构造方法

	import org.junit.Test;
	import java.lang.reflect.Constructor;
	
	public class Text {
	
	    @Test
	    public void test() throws Exception {
	        Class cls1 = Demo.class;
	
	
	        // 得到一个无参的构造方法 (只能得到public修饰的构造方法)
	        Constructor constructor = cls1.getConstructor();
	
	        // 得到一个带有一个参数的构造方法
	        Constructor constructor1 = cls1.getConstructor(String.class);
	
	        // 得到一个无参的构造方法 (可以得到任意修饰的构造方法)
	        Constructor declaredConstructor = cls1.getDeclaredConstructor();
	    }
	
	    @Test
	    public void test2() throws Exception {
	        Class cls1 = Demo.class;
	
	        // 得到所有的构造方法 (只能是public修饰的构造方法)
	        Constructor[] constructors = cls1.getConstructors();
	        
	        // 得到所有的构造方法 (可以得到任意修饰的构造方法)
	        Constructor[] declaredConstructors = cls1.getDeclaredConstructors();
	        // 遍历所有的构造方法
	        for (Constructor dc : declaredConstructors) {
	            System.out.println(dc);
	        }
	    }

}

通过反射执行构造方法_

Constructor类常用方法

  • T newInstance(Object... initargs)
    - 根据指定的参数创建对象
    - 如果调用的构造方法是有参的,那么就需要提供实际参数
  • void setAccessible(true)
    - 是否取消权限检查,true取消权限检查,false表示不取消 (设置 暴力反射)

	@Test
    public void test3() throws Exception {

        // 得到Demo类的Class文件
        Class<Demo> cls1 = Demo.class;

        // 获得无参构造方法
        Constructor<Demo> constructor = cls1.getConstructor();

        // 执行构造方法 (无参构造方法不需要传任何参数)
        Demo dm = constructor.newInstance();
        
        // 执行Demo中的show方法
        dm.show();

        // 获取有参构造方法
        Constructor<Demo> constructor1 = cls1.getConstructor(String.class);
        // 向有参构造方法传值 
        Demo dm2 = constructor1.newInstance("张三");
        // 调用Demo中的方法
        dm2.getName();

		/*
		// 如果被调用的构造方法是私有的需要设置暴力访问 (getDeclaredConstructor可以获取但	newInstance无法执行)
        Constructor<Demo> constructor1 = cls1.getDeclaredConstructor(String.class);

        // 设置方法可以访问
        constructor1.setAccessible(true);
        Demo dm2 = constructor1.newInstance("张三");
        */
    }

方法的反射_

常用方法:

  • Method getMethod(String name,Class... args)
    - 根据方法名和参数类型获得对应的成员方法对象,只能获得public的
  • Method getDeclaredMethod(String name,Class... args)
    - 根据方法名和参数类型获得对应的成员方法对象,包括public、protected、(默认)、private的
    - 参数1:要获取的方法的方法名
    - 参数2:要获取的方法的形参类型的Class对象
  • Method[] getMethods()
    - 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
  • Method[] getDeclaredMethods()
    - 获得类中的所有成员方法对象,返回数组,只获得本类的,包括public、protected、(默认)、private的


	  @Test
    public void method() throws Exception {
        Class cls1 = Demo.class;

        // 获取所有包含父类的方法 (所有公开的方法)
        Method[] methods = cls1.getMethods();

        // 获取当前类的所有方法
        Method[] declaredMethods = cls1.getDeclaredMethods();

        // 获取指定的方法
        Method show = cls1.getMethod("show");

        // 如果方法是非静态的,那么就需要传递类的实例对象
        show.invoke(cls1.getConstructor().newInstance());

        // getMethod 参数1是方法名,后面的参数是参数类型.Class
        Method MethodgetName = cls1.getDeclaredMethod("show", String.class);
        
        // invoke执行方法需要有类的实例对象 参娄1是实例对象,后面的参数是传递的值
        String s = (String) MethodgetName.invoke(cls1.getConstructor().newInstance(),"张三");
        
        // 有参数有返回值的方法
        System.out.println(s);

    }

通过反射执行成员方法_

Method对象常用方法:

  • Object invoke(Object obj, Object... args)
    - 参数1:调用该方法的对象
    - 参数2:调用该法时传递的实际参数
    - 返回值:该方法执行完毕后的返回值
  • void setAccessible(true)
    - true取消权限检查,false表示不取消

// 获取本类的一个方法 (只能获取被public修饰的)
cls1.getMethod(“show”);

// 获取所有包含父类的方法 (所有公开的方法,被public修饰的)
cls1.getMethods();

// 获取本类的方法 (任意修饰的方法)
cls1.getDeclaredMethod(“show”);

// 获取本类的所有方法 (任意修饰的方法)
cls1.getDeclaredMethods();

字段的反射

  • 每一个成员变量都是一个Field类的对象

通过反射获取类的成员变量_

  • Field getField(String name)
    - 根据成员变量名获得对应Field对象,只能获得public修饰
    - 参数:属性名
  • Field getDeclaredField(String name)
    - 根据成员变量名获得对应Field对象,包括public、protected、(默认)、private的
    - 参数:属性名
  • Field[] getFields()
    - 获得所有的成员变量对应的Field对象,只能获得public的
  • Field[] getDeclaredFields()
    - 获得所有的成员变量对应的Field对象,包括public、protected、(默认)、private的


	 @Test
    public void field() throws Exception {

        // 获取字节码对象
        Class cls = Demo.class;
        
        // 得到所有公开的public修饰的属性 (单个字段)
        Field name = cls.getField("name");
        
        // 得到所有不管修饰权限的单个字段
        Field age = cls.getDeclaredField("age");

        // 得到所有的字段 (只能获取public修饰的字段)
        Field[] fields = cls.getFields();
        
        // 得到所有不管修饰权限的所有字段
        Field[] declaredFields = cls.getDeclaredFields();

    }

通过反射访问成员变量_

Field对象常用方法:

  • void set(Object obj, Object value)
    - 参数1: 给哪个对象的属性赋值—该类的对象
    - 参数2: 给属性赋的值
  • Object get(Object obj)
  • void setAccessible(true)
    - 暴力反射 设置为可以直接访问私有类型的属性
  • Class getType()
    - 获取属性的类型,返回Class对象

	 @Test
    public void field2() throws Exception {

        // 获取字节码对象
        Class cls = Demo.class;

        // 获取一个字段
        Field fields = cls.getField("name");

        // 创建实例对象
        Demo d = (Demo) cls.getConstructor().newInstance();

        // 给字段赋值
        fields.set(d,"张三");

        // 获取name中的值
        String s = (String) fields.get(d);
        
        // 打印输出
        System.out.println(s);
    }

注解

  • 注解:(annotation)是一种代码级别的说明,和类 接口平级关系

注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,以后,javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无标记,看你的程序有什么标记,就去干相应的事

注解的作用:

  1. 生成帮助文档:@author和@version
  2. 执行编译期的检查:例如@Override
  3. 框架的配置

自定义注解_

格式:


	public @interface 注解名{
    注解属性
}
  • 注解属性
    • 格式:数据类型 属性名();
    • 属性类型
      • 基本类型
      • String
      • Class类型
      • 注解类型
      • 枚举类型
      • 以上类型的一维数组类型

使用注解并给注解属性赋值_

  • 如果一个注解没有属性,那么就不需要给注解属性赋值,直接使用即可 // @注解名
  • 如果一个注解中有属性,那么使用注解的时候一定要给注解所有属性赋值 // @注解名(属性名=值,属性名2=值2,…)

	// 自定义注解
	public @interface MyAnnotation {
    
    // 可以在里面赋默认值 用default关键字
    String s() default "Hello";
    String name();
    int age();
	}
	
	// 在类上添加注解并赋值
	@MyAnnotation(name = "张三",age = 20)
	public class Text {

	}

使用注解的注意事项:
一旦注解有属性了,使用注解的时候,属性必须有值
若属性类型是一维数组的时候,当数组的值只有一个的时候可以省略{}
注解属性可以有默认值 格式:属性类型 属性名() defaul t 默认值
如果注解中只有一个属性,并且属性名为value,那么使用注解给注解属性赋值的时候,注解属性名value可以省略

元注解_

  • 定义在注解上的注解(修饰注解的注解)

常见的元注解:

  • @Target:表示该注解作用在什么上面(位置),默认注解可以在任何位置. 值为:ElementType的枚举值
    • METHOD:方法
    • TYPE:类 接口
    • FIELD:字段
    • CONSTRUCTOR:构造方法声明
    • LOCAL_VARIABLE:局部变量

	@Target(ElementType.TYPE)// 只能在类上使用
	public @interface MyAnnotation1 {
	}
	
	//@MyAnnotation1 
	public class Test1 {
	    @MyAnnotation1 // 编译报错
	    int num;
	
	    //@MyAnnotation1 // 编译报错
	    public void show() {
	    }
	}
  • @Retention:定义该注解保留到那个代码阶段, 值为:RetentionPolicy类型,默认只在Class字节码阶段保留
    • SOURCE:只在源码上保留
    • CLASS:在源码和字节码上保留(默认)
    • RUNTIME:在所有的阶段都保留

	//@Retention(RetentionPolicy.SOURCE)  // MyAnnotation2可以保留到源码阶段
	//@Retention(RetentionPolicy.CLASS)   // MyAnnotation2可以保留到字节码阶段(源码有)
	@Retention(RetentionPolicy.RUNTIME)   // MyAnnotation2可以保留到运行阶段(源码,字节码阶段都有)
	public @interface MyAnnotation2 {
	
	}

注解解析_

  • java.lang.reflect.AnnotatedElement接口: Class、Method、Field、Constructor等实现了AnnotatedElement

常用方法:

T getAnnotation(Class annotationType) 得到指定类型的注解对象,没有返回null
boolean isAnnotationPresent(Class<?extends Annotation> annotationType): 判断指定的注解有没有

 
	@Target({ElementType.TYPE,ElementType.METHOD})
	@Retention(RetentionPolicy.RUNTIME)
	public @interface MyAnnotation {
	    String value();
	}
	
-----------------------------------------------------------------------------------------------

	@MyAnnotation("类上注解赋的值")
	public class Text {
	    
	    @MyAnnotation("方法上注解赋的值")
	   public void show(){
	
	   }
	}

-----------------------------------------------------------------------------------------------

	public class Test1 {

    @Test
    public void show() throws Exception {

        Class tx = Text.class;

        // 得到类上的注解
        // 判断这个类上面有没有MyAnnotation这个注解
        System.out.println(tx.isAnnotationPresent(MyAnnotation.class)); // true

        // 得到方法上的注解
        Method show = tx.getDeclaredMethod("show");
        // 判断这个方法上面有没有MyAnnotation这个注解
        System.out.println(show.isAnnotationPresent(MyAnnotation.class)); //true

        // isAnnotation 判断类本身是不是注解
        Class ma = MyAnnotation.class;
        System.out.println(ma.isAnnotation()); //true

		// 得到指定类型的注解对象
        MyAnnotation annotation = (MyAnnotation) tx.getAnnotation(MyAnnotation.class);

        // 非空判断
        if(annotation != null){
            // 读取value的值
            System.out.println(annotation.value());
        }

    }
}

代理

动态代理_

  • 动态代理就是在程序运行期间,直接通过反射生成一个代理对象,代理对象所属的类是不需要存在的

动态代理的获取:

  • jdk提供一个Proxy类可以直接给实现接口类的对象直接生成代理对象
  • Java.lang.reflect.Proxy类可以直接生成一个代理对象

动态代理Api

  • Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)生成一个代理对象
    • 参数1:ClassLoader loader 被代理对象的类加载器
    • 参数2:Class<?>[] interfaces 被代理对象的要实现的接口
    • 参数3:InvocationHandler h (接口)执行处理类
    • 返回值: 代理对象
    • 前2个参数是为了帮助在jvm内部生成被代理对象的代理对象,第3个参数,用来监听代理对象调用方法,帮助我们调用方法
  • nvocationHandler中的Object invoke(Object proxy, Method method, Object[] args)方法:调用代理类的任何方法,此方法都会执行
    • 参数1:代理对象(慎用)
    • 参数2:当前执行的方法
    • 参数3:当前执行的方法运行时传递过来的参数
    • 返回值:当前方法执行的返回值

	import java.lang.reflect.InvocationHandler;
	import java.lang.reflect.Method;
	import java.lang.reflect.Proxy;
	
	public class Testagent {
	    public static void main(String[] args) {
	
	        // 创建真实类对象
	        Sing ss = new SuperStart();
	
	        /**
	         * Proxy.newProxyInstance创建代理对象
	         *      参数一:给一个类加载器,真实类用的是什么类加载器,这里就写什么类加载器
	         *      参数二:要实现的接口,真实类实现什么接口,代理类就实现什么接口
	         *      参数三:调用处理器,用来让代理对象和真实对象沟通的地方
	         */
	        Sing proxyObj = (Sing) Proxy.newProxyInstance(ss.getClass().getClassLoader(), ss.getClass().getInterfaces(), new InvocationHandler() {
	            /**
	             * 1.invoke方法的使用时机
	             *         只要外面使用代理对象做任何事情(调用方法),这个invoke方法都会执行
	             * 2.invoke方法的作用
	             *          让我们在里面调用真实对象的方法,顺便也可以给这个真实对象的方法进行包装|升级|扩展
	             *
	             * @param proxy     代理对象
	             * @param method    在外面代理对象调用的方法 method对象
	             *                  如果外面执行的是这行代码:proxyObj.sing(),那么这个method即表示sing方法的反射method对象
	             * @param args      表示参数,在外面使用代理对象调用的方法传递的参数
	             * @return          可以把invoke方法执行的真实对象的方法返回值返回出去,那么外面的代理对象调用完方法,也就得到了结果
	             * @throws Throwable
	             */
	            @Override
	            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	
	                Object invoke = method.invoke(ss, args);
	                // 提供真实对象执行完后的返回值
	                return invoke;
	            }
	        });
	        proxyObj.sing();
	        
	    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值