框架的基础——反射&注解&动态代理

反射

加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。

  • 在一个类的外部,是不能通过类的实例化对象调用私有化的属性和方法。

  • 通过反射可以调用类的私有化属性和方法。

  • Q:通过直接new对象和反射的方式都可以调用公共结构,开发中用哪个?

    A:建议直接使用new的方式。在编译中无法确定需要new谁的对象,则使用反射的方式。

  • Q:反射和封装是否矛盾?

    A:不矛盾。反射的特征:动态性。可以将封装视为一种提示,提示你需要调用私有化属性或方法时,可以调用公共的属性和方法来代替直接调用它。反射表示的是一种能力,表示能不能调用属性和方法。

  • 类的加载过程:

    • 程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。
    • 接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为CLass的一个实例。
  • 获取Class的实例

    //方式一:调用运行时类的属性
    Class clazz1 = Person.class;
    //方式二:通过运行时类的对象
    Person p1 =new Person();
    Class clazz2 = p1.getClass;
    //方式三:调用Class的forname方法
    Class clazz3 = Class.forname("Person");//JDBC用的就是这种方法
    //方法四:类的加载器(了解)
        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
    Class clazz4= classLoader.loadClass("Person");
    //以上四种方法获取的地址值相同。他们获得的是唯一存在的运行时类。
    

获取Class对象后,可以通过newInstance()方法可以获取运行时类的对象

Class<User> userClass = User.class;
User user = userClass.newInstance();
//可以通过该对象调用属性和方法。
//实际上也是调用无参构造器来创建对象,所以如果存在有参构造器,则必须有无参构造器,不然会报错。
//空参构造器的访问权限至少得够(默认为public)

反射的动态性的体现

  • 编译完成后不能确定需要创建哪一个对象
  • 只有在运行时才知道创建哪一个类的对象

获取运行时类的属性及其内部结构

Class<User> userClass = User.class;
Field[] fields = userClass.getFields();
//该方法只能获取当前运行时类及其父类权限修饰为Public的属性

Field[] declaredFields = userClass.getDeclaredFields();
//该方法可以获得当前运行时类任意权限的属性。
//通过增强for循环可以获得属性的内部结构
for (Field f:declaredFields) {
    //获取权限修饰符
    int modifiers = f.getModifiers();
    String s = Modifier.toString(modifiers);
    //获取数据类型
    Class<?> type = f.getType();
    //获取变量名
    String name = f.getName();
}

获取运行时类的方法及其内部结构

Method[] methods = userClass.getMethods();
//该方法只能获取当前运行时类及其父类权限修饰为Public的方法

Method[] declaredMethods = userClass.getDeclaredMethods();
//该方法可以获得当前运行时类任意权限的方法。
//获取方法的方法名、返回值类型、修饰符、参数列表与属性的类似。

//通过getConstructor()可以获得运行时类public修饰的构造器
Class<User> userClass = User.class;
Constructor constructor = userClass.getConstructor();

//通过getDeclaredConstructors()可以获得运行时类的全部构造方法。
Constructor[] declaredConstructors = userClass.getDeclaredConstructors();

//也可以获得构造器数组
Constructor[] constructors = userClass.getConstructors();

通过运行时类获取指定的结构:属性或方法

  • 通过属性名获取属性
    • 通过getField()方法获取当前运行时类及其父类的public属性
    • 通过getDeclaredField()方法获取当前运行时类的所有属性

通过getField()方法获取当前运行时类及其父类的public属性

//获取Class实例
Class<User> userClass = User.class;
//通过Class实例获取当前运行时类的对象
User user = userClass.newInstance();
//通过Class实例获取指定运行时类的属性
Field userName = userClass.getField("userName");
//对某个对象的当前属性值进行赋值
userName.set(user,"Tom");
//获取某个对象的当前属性值
String uName = (String) userName.get(user);
System.out.println(uName);
//打印结果为Tom

我的理解:以往都是通过对象为属性值进行赋值,现在是通过指定的属性值为对象进行赋值,体现了反射的动态性,即编译时不知道应该创建哪一个类的对象,只有运行时才能知道。

通过getDeclaredField()方法获取当前运行时类的所有属性

//获取Class实例
Class<User> userClass = User.class;
//通过Class实例获取当前运行时类的对象
User user = userClass.newInstance();
//通过Class实例获取当前运行时类的属性
Field userName1 = userClass.getDeclaredField("userName");
//通过当前的属性值解除访问限制
userName1.setAccessible(true);
//对某个对象的当前属性值进行赋值
userName1.set(user,"Jerry");
//获取某个对象的当前属性值
String uName = (String) userName1.get(user);
System.out.println(uName);

通过setAccessible(true)可以解除访问限制


通过运行时类调用指定的方法

  • 非静态方法
//获取Class实例
Class<User> userClass = User.class;
//通过Class实例获取当前运行时类的对象
User user = userClass.newInstance();
//通过Class实例获取指定方法,如果存在方法的重载则在后续的参数中填写参数列表。
//String为String.class
Method add = userClass.getDeclaredMethod("add");
//通过指定方法解除访问限制
add.setAccessible(true);
//通过指定方法的invoke方法去选择用哪一个对象去调用
//如果需要传参数,则在后续的参数列表中填写参数
//如果是静态方法,则这个位置写啥都行
add.invoke(user);

注解(Annotation)

注解就是代码里的特殊标记

框架=反射+注解+设计模式

三大基本注解

  • @Override:限制重写父类的方法。
  • @Deprecated:表示所修饰的类或是方法已经过时。
  • @SuppressWarnings:抑制编译器警告

  • 元注解:修饰注解的注解成为元注解
  • 元数据:修饰数据的数据成为元数据。

元注解

  • @Retention:指明所修饰注解(Annotation)的生命周期
    • Resource:在编译时就会被抛弃。
    • Class:不会被加载到内存中,默认情况。
    • Runtime:会被保存在内存中。只有声明为RUNTIME的注解才能通过反射获取。
  • @Target:指明所修饰的注解能修饰哪些元素。
  • @Documented:被修饰的注解会被javadoc提取成文档。默认情况下javadoc是不包含注解的。
  • @Inherited:被修饰的注解具有继承性,某个类使用了该注解,则其子类默认继承该注解。

动态代理

要想实现动态代理,需要解决的问题。

  • 如何根据加载到内存中的被代理类,动态地创建一个代理类的及其对象。
  • 当通过代理类的对象调用方法时,如何动态地去调用被代理类的同名方法。
//接口
interface Human{
    String belief(String belief);
    void eat();
}
//接口的实现类(被代理类)
class SuperMan implements Human{
    @Override
    public String belief(String belief) {
        System.out.println("我信仰"+belief);
        return belief;
    }

    @Override
    public void eat() {
        System.out.println("我吃鱼");
    }
}

class ProxyFactory{
    //类似于工厂模式。
    public static Object getObject(Object obj){
        //创建Handler对象,用来解决:如何动态地去调用被代理类的同名方法。
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(obj);
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),myInvocationHandler);
    }
}
class MyInvocationHandler implements InvocationHandler{
    private Object obj;//需要使用被代理类的对象进行赋值。
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //method为代理类对象调用的方法,obj为被代理对象,args为参数列表。
        //通过反射的动态性,使用方法找对象。
        Object invoke = method.invoke(obj, args);
        return invoke;
    }

    public MyInvocationHandler(Object obj) {
        this.obj = obj;
    }
}
posted @ 2021-12-26 22:40  Boerk  阅读(122)  评论(0编辑  收藏  举报