Java基础(22)-Java反射总览(把反射基本了解一遍)

1.Java反射机制概述

  • Java Reflection允许允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性和方法;(属于动态语言的关键)

  • 反射主要的API:java.lang.Class(代表一个类);java.lang.reflect.Method(代表一个方法);等等

  • 反射可以调用类私有的构造器,方法,属性,当然是通过部分方法进行调用,后面介绍;

  • 使用反射时,最开始的就是要先创建一个Class对象,但是这个对象并非是new 出来的,它的对象是加载到内存中的类(运行时类),这个运行时类就可以作为Class的对象,比如有一个类Person,那么创建一个Class的对象的方式;

     public static void main(String[] args) throws ClassNotFoundException {
        // 创建Class类对象的第一种方式;
        Class clazz1 = Person.class;
        //第二种方式,通过对象创建
        Person p = new Person("神秘的天");
        Class clazz = p.getClass();
        //第三种方式,通过Class.forName创建;会出现异常;但是比较灵活,因为它传入的是字符串,不会再编译的时候就肯定,也就是可以进行修改;
        Class name = Class.forName("cn.dxs.Person");
        //第四种方式,通过ClassLoader实现,这个属于系统类加载器;主要用来加载自定义的类;
        ClassLoader classLoader = TextUDP.class.getClassLoader();
        Class aClass = classLoader.loadClass("cn.dxs.Person");
    }
    
  • 上面讲到,使用反射时,开始就要创建一个Class的对象,前面也介绍如何通过一个普通的类,创建Class的对象,接下来就记录一下,到底有多少的结构可以创建Class对象;
    (1)class:各种类(如:匿名内部类、外部类等)
    (2)interface(接口)
    (3)数组
    (4)enum(枚举类)
    (5)annotation(注解)
    (6)primitive(基本数据类型)
    (7) void

    public void text(){
        //class类
        Class clazz1 = study.class;
        //interface
        Class clazz2 = Closeable.class;
        //数组
        Class clazz3 = int[].class;
        //枚举类,这个state是Thread中的一个枚举类,主要表示线程生命周期;
        Class clazz4 = Thread.State.class;
        //注解;
        Class clazz5 = Override.class;
        //基本类型
        Class clazz6 = int.class;
        //void也可以创建Class对象;
        Class class7 = void.class;
    }
    
  • 除了系统类加载器,还有扩展类加载器和引导类加载器,其中第一个可以用来加载自定义的类;第二个用于加载jre的扩展目录,lib/ext中的jar包;第三个是用于加载Java的核心类;

  • 通过反射去创建需要的类的对象;

    public void test() throws IllegalAccessException, InstantiationException {
        //先创建一个class的对象;
        Class<Person> clazz = Person.class;
        //然后再通过这个创建的对象clazz去创建person的对象;会抛出异常
        Person p = clazz.newInstance();
    }
    

    在上面程序中的两个异常,IllegalAccessException表示非法接入,意思就是指你想要创建的类的对象必须要有一个可以使用的构造方法,如果构造方法是私有的,那么就会出现这个异常;InstantiationException 表示要有构造器,否则抛异常;

  • 通过反射可以获得类中的很多结构,比如:构造器,属性,方法,注解,等;都是通过Class对象调用的方法实现;
    有的时候,就比如属性,可能是被private修饰的,那么就算反射可以取到属性也是不能直接修改的,那么就需要用到setAccessible(boolean)方法,使其能被使用;(boolean设置成true即可)

  • 在使用sevlet时,有一种叫做servlet抽取(详情可百度)的方法,这种方法,主要就是将一些servlet综合在一起,这样可以避免写过多的servelet,它实现的核心就是通过反射,下面简单介绍一下,它的核心实现模式;

    首先要先创建一个所有类的父类,这个父类继承了HttpServlet类,就相当于一个servlet了,然后在这个主类中,使用一个反射创建当前类的Class对象clazz,然后通过clazz调用method方法,当然你在该子类里面必须要将名字确定好,这样父类通过getMethod(方法名,参数)取到子类中的方法之后,就可以直接通过invoke(对象,参数)调用该方法; getMethod()中的参数,是需要(.class)的比如String就应该是String.class作为参数,具体例子如下:

    Method md = clazz.getMethod(method, HttpServletRequest.class, HttpServletResponse.class);

  • 通过反射的方式读取配置文件,这里我是通过IDEA中运行的,一开始一直报空指针错误,如果不希望出现空指针错误,最好是将这个配置文件放在src同级;当然如果通过文件流读取就不会出现这种错误,随便配置文件放在哪里;

      public static void main(String[] args) {
        ClassLoader classLoader = TextUDP.class.getClassLoader();
        //直接通过ClassLoader对象加载文件;
        InputStream input = classLoader.getResourceAsStream("mything.properties");
        Properties pro = new Properties();
        try {
            pro.load(input);
            String name = pro.getProperty("Name");
            String password = pro.getProperty("Password");
            System.out.println(name + "\t" + password);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

2. 动态代理

  1. 动态代理:通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的对象;
  2. 静态代理,简单的理解就是已经规定好了某些特定类,无法直接在程序上进行修改;我发现静态代理和装饰器模式有点像,仔细比较了一下,发现在装饰器模式中,并没有像静态代理中的代理类一样创建对象;
    //这个作为运行类;
    public class StaticAgent {
        public static void main(String[] args) {
            //先创建一个被代理类的对象;
         LOLGame lolGame = new LOLGame();
         //创建一个代理类的对象;将被代理类的对象放入;
        GameDesign gameDesign = new GameDesign(lolGame);
         //这个对象实际是GameDesign的。所以可以调用GameDesign    的produce方法;
         gameDesign.produce();
      }
    }
    
    //这个是最开始的原始接口,
    interface GameFactory {
    //定义一个抽象方法
    void produce();
    }
    
    //定义一个代理的类
    class GameDesign implements GameFactory {
    
    //定义一个这样的对象(这个对象属于GameFactory的实现类即可),方便使用多态;
    private GameFactory gameFactory;
    
       //定义一个构造方法,将被代理类的对象作为参数放进来;方便在 后面的方法中调用;
        public GameDesign(GameFactory gameFactory) {
            this.gameFactory = gameFactory;
        }
    
        @Override
        public void produce() {
            //这里只是简单介绍,就不写过多的逻辑了;
            System.out.println("开始设计游戏!");
            //在这里运行被代理类的对象;
            gameFactory.produce();
            System.out.println("游戏设计完成!");
        }
    }
    
    //创建一个被代理类;实现原始的接口;
    class LOLGame implements GameFactory {
    
        //在代理类中重写接口方法;
        @Override
        public void produce() {
            System.out.println("LoL设计中");
        }
    }
    
  3. 动态代理,和上面的静态代理相比,动态的更加灵活,也就是只要你创建那个被代理,而不需要一直创建代理类,在我看来;这就是动态和静态的区别;以下是动态代理的基本结构;
        public class DynamicAgent {//运行主类
    
        public static void main(String[] args) {
            //创建一个被代理类的实例;
            Student stu = new Student();
            //然后就是获得代理类的对象;
            Person per = (Person) AgentPer.ProxyThis(stu);
            String doing = per.doing();
            System.out.println(doing);
            per.sing();
        }
    }
    
    //首先准备一个接口,在其中写好可以使用方法;
    interface Person {
        void sing();
    
        String doing();
    }
    
    //要准备与代理类类似功能的类,比如获得一个代理类的对象;
    class AgentPer {//从静态代理就可以知道,我们需要一个代理类的对象调用方法;
    
        //定义一个Static方法,这样用于获取代理类对象;这里之所以返回Object类是由于不确定会得到什么类;
        public static Object ProxyThis(Object obj) {//这里的obj是指被代理类的对象;
            //由于下面要用到实现接口InvocationHandler的类,所以下面创建它的对象;
            MyInvocation handler = new MyInvocation(obj);
        /*    handler.bind(obj);*/
    
        //这里必须要用这个方法创建代理类的对象;第三个参数需要接口InvocationHandler的实现类,所以要自定义一下;
            return Proxy.newProxyInstance(AgentPer.class.getClassLoader(), obj.getClass().getInterfaces(), handler);
        }
    }
    
    class MyInvocation implements InvocationHandler {
        //定义一个对象;
        private Object obj;
    
        public MyInvocation() {
        }
    
        //这里是直接调用一个构造参数将被代理对象放进来;也可以用下面的那个方法bind;用方法的话,就需要调用一下;
        public MyInvocation(Object obj) {
            this.obj = obj;
        }
    
      /*  //定义一个方法,为这个对象赋值
        public void bind(Object obj) {
            this.obj = obj;
        }*/
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws     Throwable {
            //这里面重新写一下需要调用的方法,obj是指被代理类的对象;args是参    数;
            Object thisobj = method.invoke(obj, args);
            return thisobj;
        }
    }
    
    //定义一个测试的被代理类,方便验证代码的正确性;
    class Student implements Person {
    
        @Override
        public void sing() {
            System.out.println("I can sing");
        }
    
        @Override
        public String doing() {
            return "do homework";
        }
    }
    

运行结果:

3. Java杂记(学到什么记录什么)

  1. 如何读取配置文件(properties后缀)
     public static void main(String[] args)  {
     //定义一个Properties对象
        Properties pro = new Properties();
        try {
        //通过文件路径,定义文件输入流
            FileInputStream in = new FileInputStream("src\\cn\\dxs\\net\\mything.properties");
            //通过properties对象加载文件输入流
            pro.load(in);
            //通过键值对取值
            String name = pro.getProperty("Name");
            String password = pro.getProperty("Password");
            System.out.println(name+"****\t"+password);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

2.Interface中的方法为抽象方法,默认修饰为public abstract;变量默认为public static final;

猜你喜欢

转载自blog.csdn.net/dxsdcyy/article/details/106950991