随记——java基础——反射——200622

10 篇文章 0 订阅

尚硅谷_Java零基础教程-java入门必备-初学者基从入门到精通全套完整版(宋红康主讲) P636-665

(*)表示仅需了解

1. java反射机制概述

  • 反射被视为动态语言的关键,反射机制允许程序在执行期借助于ReflectionAPI取得任何类内的内部信息,并能直接操作任意对象的内部属性及方法。
  • 加载完类之后,在堆内存的方法区就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象地称之为:反射。
    • 正常方式:
      1. 引入需要的“包类”名称
      2. 通过new实例化
      3. 取得实例化对象
    • 反射方式:
      1. 实例化对象
      2. getClass()方法
      3. 取得完整的“包类”名称

* 动态语言 vs 静态语言

  • 动态语言:是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
    • 例如:Object-C、C#、JavaScript、PHP、Python、Erlang。
  • 运行时结构不可变的语言就是静态语言。
    • 例如:java、C、C++
    • 但Java有一定的动态性,可以利用反射机制、字节码操作获得类似动态语言的特性。

代码体现反射的动态性:

@Test
    public void test2() {
        for (int i=0;i<10;i++) {
            int num = new Random().nextInt(3);//0,1,2
            String classPath = "";
            switch (num){
                case 0:
                    classPath = "java.util.Date";
                    break;
                case 1:
    //                classPath = "java.sql.Date"; // 无空参构造器
                    classPath = "java.util.HashMap";
                    break;
                case 2:
                    classPath = "learn.others.reflect.Person";
                    break;
            }
            try {
                System.out.println(getInstance(classPath));
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    }

    public Object getInstance(String classPath) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class clazz = Class.forName(classPath);
        return clazz.newInstance();
    }

java反射机制研究与应用

  • java反射机制提供的功能:
    • 在运行时判断任意一个对象所属的类
    • 在运行时构造任意一个类的对象
    • 在运行时判断任意一个类所具有的成员变量和方法
    • 在运行时获取泛型信息
    • 在运行时调用任意一个对象的成员变量和方法
    • 在运行时处理注解
    • 生成动态代理

反射相关的主要API

  • java.lang.Class
  • java.lang.reflect.Method
  • java.lang.reflect.Field
  • java.lang.reflect.Constructor

反射测试1:构造对象,调用方法、属性(非私有)

try {
            //1. 通过反射创建对象
            Class clase = Person.class;
            Constructor cons = clase.getConstructor(String.class, int.class);

            Person tom = (Person) cons.newInstance("Tom",23);
            System.out.println(tom.toString());

            //通过反射调用属性、方法
//            Field age = clase.getDeclaredField("age");//报错,因为是私有的
            Field name = clase.getDeclaredField("name");
            name.set(tom,"refa");
            System.out.println(tom.toString());//Person{name='refa', age=23}

             Method show = clase.getDeclaredMethod("show");
             show.invoke(tom);//show a person

            final Method showEmotion = clase.getDeclaredMethod("showEmotion", String.class);
            showEmotion.invoke(tom,"sad");//I'm sad now
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException | NoSuchFieldException e) {
            e.printStackTrace();
        } 

反射测试2:构造对象,调用方法、属性(私有)

通过setAccessible(true)

//通过反射调用 私有 的属性、方法
            //私有构造器
            Constructor pcons = clase.getDeclaredConstructor(String.class);
            pcons.setAccessible(true);
            Person privat = (Person) pcons.newInstance("privat");
            System.out.println(privat.toString());//Person{name='privat', age=0}
            //私有属性
            Field age = clase.getDeclaredField("age");
            age.setAccessible(true);
            age.set(privat,20);
            System.out.println(privat.toString());//Person{name='privat', age=20}
            //私有方法
            final Method showEmotion = clase.getDeclaredMethod("showEmotion", String.class);
            showEmotion.setAccessible(true);
            showEmotion.invoke(privat,"nervous");//I'm nervous now

?什么时候用反射

  • 编译的时候无法确定new哪个类的对象。
  • 例如:在web应用中,程序先启动,根据用户的选择是登录还是别的,去创建对象,也就是在编译之前无法确定new的是哪个类的对象。这种情况下就可以用反射。

?反射与封装性

封装性无法“强硬的阻止”反射调用私有的属性/方法/构造器,但是标识为私有,即是建议不要调用,或者说慎用。

2. 理解Class类并获取Class实例

对Class类的理解

  1. 类的加载过程:
      程序经过javac.exe命令后,会生成一个或多个字节码文件(.class);接着我们使用java.exe命令对某个字节码文件进行解释运行,相当于将某个字节码文件加载到内存中。此过程就成为类的加载。
    加载到内存中的类,就称为运行时类,此运行时类,就作为java.lang.Class的一个实例。
  2. Class的实例就对应着一个运行时类
  3. 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。

获取Class实例的方式

@Test
    public void test3(){
        //方式一:调用运行时类的属性:.class
        Class clazz1 = Person.class;
        System.out.println(clazz1);
        //方式二:通过运行时类的对象,调用getClass()
        Person p1 = new Person();
        Class clazz2 = p1.getClass();
        System.out.println(clazz2);
        //方式三:调用Class的静态方法:forName(String classPath)
        try {
            Class clazz3 = Class.forName("java.lang.String");
            System.out.println(clazz3);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
//        class learn.others.reflect.Person
//        class learn.others.reflect.Person
//        class java.lang.String

            System.out.println(clazz1 == clazz2);//true
    }

哪些类型可以有Class对象

  1. class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
  2. interface
  3. []:数组
  4. enum
  5. annotation
  6. primitive type:基本数据类型
  7. void
@Test
    public void test4(){
        Class c1 = Object.class;//class java.lang.Object
        Class c2 = Comparable.class;//interface java.lang.Comparable
        Class c3 = String[].class;//class [Ljava.lang.String;
        Class c4 = int[][].class;//class [[I
        Class c5 = ElementType.class;//class java.lang.annotation.ElementType
        Class c6 = Override.class;//interface java.lang.Override
        Class c7 = int.class;//int
        Class c8 = void.class;//void
        Class c9 = Class.class;//class java.lang.Class
        
        int[] a = new int[10];
        int[] b = new int[100];
        System.out.println(a.getClass()==b.getClass());//true
    }

3. 类的加载与ClassLoader的理解

类的加载过程

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化:

  • 类的加载:将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成
  • 类的链接:将类的二进制数据合并到JRE中
  • 类的初始化:JVM负责对类进行初始化

类加载器

类加载器的作用

  • 类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口
  • 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。

类加载器读取配置文件

 public void test5(){
        Properties pros = new Properties();
        //读取配置文件方法一:
//        FileInputStream fis = new FileInputStream("src\\learn\\others\\reflect\\jdbc.properties");
//        pros.load(fis);
        //读取配置文件方法二:
        //配置文件默认是识别为当前module的src下
        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
//        InputStream is = classLoader.getResourceAsStream("jdbc.properties");

        try (InputStream is = classLoader.getResourceAsStream("jdbc.properties");){
            pros.load(is);

            String user = pros.getProperty("user");
            String pswd = pros.getProperty("password");
            System.out.println("user:" + user + ",password:" + pswd);//user:km,password:123
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }

    }

4. 创建运行时类的对象

new Instance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参构造器。

Class<Person> clazz = Person.class;
Person p = clazz.newInstance();
System.out.println(p);

要想此方法正常的创建运行时类的对象,要求:

  1. 运行时类必须提供空参的构造器
  2. 空参的构造器的访问权限足够。通常设置为public

在java bean中要求提供一个public的空参构造器,原因:

  1. 便于通过反射,创建运行时类的实例
  2. 便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器

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

所在包、注解、接口、父类
属性、方法、构造器

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

属性、方法、构造器

public class ReflectionTest {

    //属性
    @Test
    public void test1() {
        Class clazz = Person.class;

        try {
            final Person p = (Person) clazz.newInstance();
            final Field id = clazz.getDeclaredField("id");
            final Field name = clazz.getDeclaredField("name");
            /*
                设置对象的属性值
             */
            id.setAccessible(true);
            id.set(p,1001);
            System.out.println(id.get(p));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

    //方法
    @Test
    public void test2() {

        Class clazz = Person.class;
        try {
            final Person p = (Person) clazz.newInstance();
            final Method showNation = clazz.getDeclaredMethod("showNation", String.class);
            showNation.setAccessible(true);

            showNation.invoke(p,"china");


        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    //静态方法
    @Test
    public void test3() {

        Class clazz = Person.class;
        try {
            final Person p = (Person) clazz.newInstance();
            final Method display = clazz.getDeclaredMethod("display", String.class, int.class);
            display.setAccessible(true);
            //如果没有返回值,返回null
            final Object aStatic = display.invoke(p,"static", 0);//static0
            System.out.println(aStatic);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }
}

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

  • 代理设计模式的原理
    使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
  • 静态代理:特征是代理类和目标对象的类都是在编译期间就确定下来,这样不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来,程序开发中必然产生过多的代理。最好通过一个代理类来完成全部的代理

静态代理实例

interface ClothFactory{
    void produceCloth();
}
class ProxyClothFactory implements ClothFactory{
    private ClothFactory factory;

    public ProxyClothFactory(ClothFactory factory) {
        this.factory = factory;
    }

    @Override
    public void produceCloth() {
        System.out.println("代理工厂pre工作");
        factory.produceCloth();
        System.out.println("代理工厂post工作");
    }
}
class NikeClothFactory implements ClothFactory{
    @Override
    public void produceCloth() {
        System.out.println("Nike 工厂生产衣服");
    }
}
public class StaticProxyTest{
    @Test
    public void test1(){
        final NikeClothFactory nikeClothFactory = new NikeClothFactory();
        final ProxyClothFactory proxyClothFactory = new ProxyClothFactory(nikeClothFactory);
        proxyClothFactory.produceCloth();
    }
}

动态代理实例

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface Human{
    String getBelief();
    void eat(String food);
}
//被代理类
class SuperMan implements Human{

    @Override
    public String getBelief() {
        return "i believe i can fly";
    }

    @Override
    public void eat(String food) {
        System.out.println("I like "+food);
    }
}
/*
动态代理,需要解决:
1. 如何根据被代理类,动态的创建代理类
2. 动态调用方法
 */
class ProxyFactory{
    public static Object getProxyInstance(Object obj){
        MyInvocationHandler handler = new MyInvocationHandler();
        handler.bind(obj);
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
    }
}
class MyInvocationHandler implements InvocationHandler{
    private Object obj ;

    public void bind(Object obj) {
        this.obj = obj;
    }

    //当通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()
    //将被代理类要执行的方法a的功能就声明在invoke()中
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //method 即为代理类调用的方法,即为被代理类调用的方法
        System.out.println("pre invoke ...");
        Object returnValue = method.invoke(obj, args);
        System.out.println("post invoke ...");
        return returnValue;
    }
}
public class ProxyTest {
    public static void main(String[] args) {
        final SuperMan superMan = new SuperMan();
//        final ProxyFactory factory = new ProxyFactory();
        Human human = (Human) ProxyFactory.getProxyInstance(superMan);
        human.eat("sud");

    }
}

动态代理与AOP

把代码段1、2和3中相同的代码段分离

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值