一文带你深入理解【Java基础】· Java反射机制(下)

写在前面


        Hello大家好, 我是【麟-小白】,一位软件工程专业的学生,喜好计算机知识。希望大家能够一起学习进步呀!本人是一名在读大学生,专业水平有限,如发现错误不足之处,请多多指正!谢谢大家!!!

        如果小哥哥小姐姐们对我的文章感兴趣,请不要吝啬你们的小手,多多点赞加关注呀!❤❤❤ 爱你们!!!


目录

写在前面

1. 获取运行时类的完整结构

1.1 通过反射获取运行时类的完整结构

1.2 代码演示

 2. 调用运行时类的指定结构

2.1 调用运行时类的指定结构

2.2 关于setAccessible方法的使用

2.3 代码演示

3. 反射的应用:动态代理

3.1 代理设计模式的原理

3.2 Java动态代理相关API

3.3 动态代理步骤

3.4 动态代理与AOP(Aspect Orient Programming)

结语


【往期回顾】

一文带你深入理解【Java基础】· Java反射机制(上)

一文带你深入理解【Java基础】· 网络编程(下)

一文带你深入理解【Java基础】· 网络编程(上)

一文带你深入理解【Java基础】· IO流(下)

一文带你深入理解【Java基础】· IO流(中)

一文带你深入理解【Java基础】· IO流(上)

一文带你深入理解【Java基础】· 泛型


1. 获取运行时类的完整结构


1.1 通过反射获取运行时类的完整结构

Field Method Constructor Superclass Interface Annotation
  • 实现的全部接口
  • 所继承的父类
  • 全部的构造器
  • 全部的方法
  • 全部的Field

使用反射可以取得:
  • 实现的全部接口
    • public Class<?>[] getInterfaces()
    • 确定此对象所表示的类或接口实现的接口。
  • 所继承的父类
    • public Class<? Super T> getSuperclass()
    • 返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的Class。

  • 全部的构造器
    • public Constructor<T>[] getConstructors()
    • 返回此 Class 对象所表示的类的所有public构造方法。
    • public Constructor<T>[] getDeclaredConstructors()
    • 返回此 Class 对象表示的类声明的所有构造方法。
    • Constructor类中:
      • 取得修饰符: public int getModifiers();
      • 取得方法名称: public String getName();
      • 取得参数的类型:public Class<?>[] getParameterTypes();
  • 全部的方法
    • public Method[] getDeclaredMethods()
    • 返回此Class对象所表示的类或接口的全部方法
    • public Method[] getMethods()
    • 返回此Class对象所表示的类或接口的public的方法
    • Method类中:
      • public Class<?> getReturnType()取得全部的返回值
      • public Class<?>[] getParameterTypes()取得全部的参数
      • public int getModifiers()取得修饰符
      • public Class<?>[] getExceptionTypes()取得异常信息
  • 全部的Field
    • public Field[] getFields()
    • 返回此Class对象所表示的类或接口的publicField
    • public Field[] getDeclaredFields()
    • 返回此Class对象所表示的类或接口的全部Field
    • Field方法中:
      • public int getModifiers() 以整数形式返回此Field的修饰符
      • public Class<?> getType() 得到Field的属性类型
      • public String getName() 返回Field的名称。
  • Annotation相关
    • get Annotation(Class<T> annotationClass)
    • getDeclaredAnnotations()
  • 泛型相关
    • 获取父类泛型类型:Type getGenericSuperclass()
    • 泛型类型:ParameterizedType
    • 获取实际的泛型类型参数数组:getActualTypeArguments()
  • 类所在的包
    • Package getPackage()
小 结:
1. 在实际的操作中,取得类的信息的操作代码,并不会经常开发。
2. 一定要熟悉 java.lang.reflect 包的作用,反射机制。
3. 如何取得属性、方法、构造器的名称,修饰符等。

1.2 代码演示

public class FieldTest {
    @Test
    public void test1() {
        Class clazz = Person.class;
        //获取属性结构
        //getFields():获取当前运行时类及其父类中声明为public访问权限的属性
        Field[] fields = clazz.getFields();
        for (Field f : fields) {
            System.out.println(f);
        }
        System.out.println();
        //getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field f : declaredFields) {
            System.out.println(f);
        }
    }

    /** 权限修饰符  数据类型 变量名 */
    @Test
    public void test2() {
        Class clazz = Person.class;
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field f : declaredFields) {
            //1.权限修饰符
            int modifier = f.getModifiers();
            System.out.print(Modifier.toString(modifier) + "\t");
            //2.数据类型
            Class type = f.getType();
            System.out.print(type.getName() + "\t");
            //3.变量名
            String fName = f.getName();
            System.out.print(fName);
            System.out.println();
        }
    }
}


 2. 调用运行时类的指定结构


2.1 调用运行时类的指定结构

  • 调用指定方法
  • 通过反射,调用类中的方法,通过Method类完成。步骤:
    • 通过Class类的getMethod(String name,Class…parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。
    • 之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。

Object invoke(Object obj, Object … args)
说明:
  • Object 对应原方法的返回值,若原方法无返回值,此时返回null
  • 若原方法若为静态方法,此时形参Object obj可为null
  • 若原方法形参列表为空,则Object[] argsnull
  • 若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问private的方法。
  • 调用指定属性
  • 在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和get()方法就可以完成设置和取得属性内容的操作。
    • public Field getField(String name) 返回此Class对象表示的类或接口的指定的public的Field
    • public Field getDeclaredField(String name)返回此Class对象表示的类或接口的指定的Field
  • Field中:
    • public Object get(Object obj) 取得指定对象obj上此Field的属性内容
    • public void set(Object obj,Object value) 设置指定对象obj上此Field的属性内容

2.2 关于setAccessible方法的使用

  • MethodFieldConstructor对象都有setAccessible()方法。
  • setAccessible启动和禁用访问安全检查的开关。
  • 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
    • 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true
    • 使得原本无法访问的私有成员也可以访问
  • 参数值为false则指示反射的对象应该实施Java语言访问检查。

2.3 代码演示

public class MethodTest {
    @Test
    public void test1() {
        Class clazz = Person.class;
        //getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
        Method[] methods = clazz.getMethods();
        for (Method m : methods) {
            System.out.println(m);
        }
        System.out.println();
        //getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method m : declaredMethods) {
            System.out.println(m);
        }
    }

    /**
    @Xxxx
    权限修饰符  返回值类型  方法名(参数类型1 形参名1,...) throws XxxException{}
     */
    @Test
    public void test2() {
        Class clazz = Person.class;
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method m : declaredMethods) {
            //1.获取方法声明的注解
            Annotation[] annos = m.getAnnotations();
            for (Annotation a : annos) {
                System.out.println(a);
            }
            //2.权限修饰符
            System.out.print(Modifier.toString(m.getModifiers()) + "\t");
            //3.返回值类型
            System.out.print(m.getReturnType().getName() + "\t");
            //4.方法名
            System.out.print(m.getName());
            System.out.print("(");
            //5.形参列表
            Class[] parameterTypes = m.getParameterTypes();
            if (!(parameterTypes == null && parameterTypes.length == 0)) {
                for (int i = 0; i < parameterTypes.length; i++) {
                    if (i == parameterTypes.length - 1) {
                        System.out.print(parameterTypes[i].getName() + " args_" + i);
                        break;
                    }
                    System.out.print(parameterTypes[i].getName() + " args_" + i + ",");
                }
            }
            System.out.print(")");
            //6.抛出的异常
            Class[] exceptionTypes = m.getExceptionTypes();
            if (exceptionTypes.length > 0) {
                System.out.print("throws ");
                for (int i = 0; i < exceptionTypes.length; i++) {
                    if (i == exceptionTypes.length - 1) {
                        System.out.print(exceptionTypes[i].getName());
                        break;
                    }
                    System.out.print(exceptionTypes[i].getName() + ",");
                }
            }
            System.out.println();
        }
    }
}

3. 反射的应用:动态代理


3.1 代理设计模式的原理

  • 使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
  • 之前为大家讲解过代理机制的操作,属于静态代理,特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。好可以通过一个代理类完成全部的代理功能。
  • 动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。
  • 动态代理使用场合:
    • 调试
    • 远程方法调用

动态代理相比于静态代理的优点:

  • 抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。

3.2 Java动态代理相关API

  • Proxy :专门完成代理的操作类,是所有动态代理类的父类。通过此类为一个或多个接口动态地生成实现类。
  • 提供用于创建动态代理类和动态代理对象的静态方法


3.3 动态代理步骤

1. 创建一个实现接口 InvocationHandler 的类,它必须实现 invoke 方法,以完成代理的具体操作。

2.创建被代理的类以及接口 

 3.通过Proxy的静态方法

4.通过 Subject代理调用RealSubject实现类的方法 


3.4 动态代理与AOPAspect Orient Programming)

前面介绍的 Proxy InvocationHandler ,很难看出这种动态代理的优势,下面介绍一种更实用的动态代理机制

改进后的说明:
  • 代码段1、代码段2、代码段3和深色代码段分离开了,但代码段123又和一个特定的方法A耦合了!最理想的效果是:代码块123既可以执行方法A,又无须在程序中以硬编码的方式直接调用深色代码的方法
public interface Dog{
    void info();
    void run();
}
public class HuntingDog implements Dog{
    public void info(){
        System.out.println("我是一只猎狗");
    }
    public void run(){
        System.out.println("我奔跑迅速");
    }
}
public class DogUtil{
    public void method1(){
        System.out.println("=====模拟通用方法一=====");
    }
    public void method2(){
        System.out.println("=====模拟通用方法二=====");
    }
}
public class MyInvocationHandler implements InvocationHandler{
    // 需要被代理的对象
    private Object target;
    public void setTarget(Object target){
        this.target = target;
    }
    // 执行动态代理对象的所有方法时,都会被替换成执行如下的invoke方法
    public Object invoke(Object proxy, Method method, Object[] args) throws Exception{
        DogUtil du = new DogUtil();
        // 执行DogUtil对象中的method1。
        du.method1();
        // 以target作为主调来执行method方法
        Object result = method.invoke(target , args);
        // 执行DogUtil对象中的method2。
        du.method2();
        return result;
    }
}
public class MyProxyFactory{
    // 为指定target生成动态代理对象
    public static Object getProxy(Object target) throws Exception{
        // 创建一个MyInvokationHandler对象
        MyInvokationHandler handler = new MyInvokationHandler();
        // 为MyInvokationHandler设置target对象
        handler.setTarget(target);
        // 创建、并返回一个动态代理对象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces() , handler);
    }
}
public class Test{
    public static void main(String[] args) throws Exception{
        // 创建一个原始的HuntingDog对象,作为target
        Dog target = new HuntingDog();
        // 以指定的target来创建动态代理
        Dog dog = (Dog)MyProxyFactory.getProxy(target);
        dog.info();
        dog.run();
    }
}
  • 使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有太大的意义。通常都是为指定的目标对象生成动态代理
  • 这种动态代理在AOP中被称为AOP代理,AOP代理可代替目标对象,AOP代理包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异:
    • AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理

 

结语


本人会持续更新文章的哦!希望大家一键三连,你们的鼓励就是作者不断更新的动力

猜你喜欢

转载自blog.csdn.net/qq_34025246/article/details/129467364