欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java反射,注解,以及动态代理

程序员文章站 2022-04-08 21:24:44
Java反射,注解,以及动态代理 基础 最近在准备实习面试,被学长问到了Java反射,注解和动态代理的内容,发现有点自己有点懵,这几天查了很多资料,就来说下自己的理解吧【如有错误,望指正】 Java反射 首先,我们得弄清一个,什么是反射(Reflection)。简单的来说,反射就是让我们在程序运行的 ......

java反射,注解,以及动态代理

最近在准备实习面试,被学长问到了java反射,注解和动态代理的内容,发现有点自己有点懵,这几天查了很多资料,就来说下自己的理解吧【如有错误,望指正】

java反射

首先,我们得弄清一个,什么是反射(reflection)。简单的来说,反射就是让我们在程序运行的时候能够查看到类的信息,获取并调用类的任意方法和属性。

在java运行时,系统会将所有的对象维护一个被称为运行是的类型标识,然后这个信息跟踪这每个对象所属的类,我们可以和根据java专门的类访问这些信息,这个类就是class【实际上class对象表示的是一个类型,它不一定是类,可能是基本数据类型,比如int】。

class获取方法

  1. 通过getclass()获取

    student stu = new student;
    class c =  stu.getclass();
    //如果这个类在包里面,则会将包名也打印出来
    // getsimplename只获得类名
    system.out.println(c.getname());
    
  2. 使用forname获取
    同样,我们也可以使用静态方法forname获得class对象。例如:

    string classname= "java.util.random";
    class c2 = class.forname(classname);
    

    当然,classname必须为接口或者类名才能成功。

  3. 直接获取

    class c3 = student.class;
    

由class得到对象

  1. 使用class对象的newinstance()方法来创建class对象

    class c3 = test.class;
    object o = c3.newinstance();
    

    其中newinstance()会根据类的默认构造器【无参构造器】创建新的对象,如果没有默认的构造器,就会报错。假如我们的构造器里面有参数怎么办,这时候我们就需要使用java.lang.reflect.constructor中的newinstance()方法了。

  2. 使用constructor类中的newinstance()

    // getconstructor里面是构造参数的形参内容
    constructor constructor = c3.getconstructor(string.class);
    object o = constructor.newinstance("你好");  
    
    

java反射中最重要的内容——检查类的结构

在java的java.lang.reflect中有三个类:field、method、constructor分别来描述类的域【也就是变量】,方法和构造器。

  1. field的获取以及方法

    class textclass = test.class;
    // getdeclaredfields()获得这个类的全部域
    // getfield()获得公有域以及其父类的公有域
    field[] fields = textclass.getdeclaredfields();
    

    简单的来说,通过field可以获得:

    变量的权限——getmodifiers(),返回int,然后通过modifier.tostring(int)获得访问权限

    获得变量的类型——gettype()

    变量的名字——getname()

  2. method的获取以及方法

    class textclass = test.class;
    // 同样可以使用getmethods()和getdeclaredmethods()返回接口和类的方法
    method[] methods = textclass.getmethods();
    

    通过method可以获取:

    方法的权限——getgetmodifiers()

    方法的返回值类型——getreturntype(),方法返回类型为class,然后你懂得。

    方法的所有参数——parameter[] parameters = method.getparameters();

    方法的执行——invoke()。在获取一个方法后,我们可以使用invoke()来调用这个方法。

    object invoke(object obj,object...args),obj为实例化后的对象【对于静态方法可以被设置null】,args为方法调用的参数

    例如,

    public class test {
    
        public void say(string msg){
            system.out.println(msg);
        }
        public static void main(string[] args) throws classnotfoundexception, illegalaccessexception, instantiationexception, nosuchmethodexception, invocationtargetexception {
    
            class c = test.class;
            // 返回唯一的方法,第一个参数是方法的名字,第二个是方法参数的类型
            method method = c.getmethod("say", string.class);
            object o = c.newinstance();
            method.invoke(o,"你好");
        }
    }
    
  3. constructor的获取以及方法

    class textclass = test.class;
    // 同样getdeclaredconstructors()和getconstructors()
    constructor[] constructors = aclass.getconstructors();
    

    方法的的使用和method差不多,但是它没有getreturntype()方法。

这些方法我只是简单的介绍了一下,详细信息可以参考api。

神奇的java注解

java注解可以很简单的说,就是为方法或者其他数据提供描述的东西。

它的本质就是一个接口,一个继承了annotation的接口。

  1. 基本java注解的类型
    【元注解】:也就是在自定义一个注解时,可以注解在注解上面,有以下几个元注解——>

    • @target:注解的作用目标,用来指明注解可以作用的目标是谁,例如类,方法或者字段属性,里面的value【为一个elementtype数组】可以指明值如下:

      elementtype.type:允许被修饰的注解作用在类、接口和枚举上

      elementtype.field:允许作用在属性字段上

      elementtype.method:允许作用在方法上

      elementtype.parameter:允许作用在方法参数上

      elementtype.constructor:允许作用在构造器上

      elementtype.local_variable:允许作用在本地局部变量上

      elementtype.annotation_type:允许作用在注解上

      elementtype.package:允许作用在包上

    • @retention:注解的生命周期,里面的value【枚举类型】可以指明值如下:

      retentionpolicy.source:当前注解编译期可见,不会写入 class 文件

      retentionpolicy.class:类加载阶段丢弃,会写入 class 文件

      retentionpolicy.runtime:永久保存,可以反射获取
      - @documented:注解是否应当被包含在 javadoc 文档中
      - @inherited:是否允许子类继承该注解
      - @repeatable:重复注解,允许这个注解在某个方法和其他数据上面重复使用

    【java内置三大注解】:除了上述元注解,java还内置了另外三种注解——>

    • @override:子类重写父类的方法时,会使用该注解。用于检查父类是否包含该注解
    • @deprecated:当某一方法和字段不推荐使用时,使用该注解标注。
    • @suppresswarnings:压制java的警告
  2. java注解的自定义以及实现

    java注解的自定义如下

    @target(value = {elementtype.method,elementtype.type}) // 注解的作用地方
    @retention(value = retentionpolicy.runtime)  // 注解的生命周期
    public @interface testannotation {
        string name() default "这是个类";
        int time();
    }
    

    那么我们该如果如何使用注解发挥作用呢?我们可以想想,如果我们能够获得注解的信息,那么我们是不是就可以根据注解的信息来对方法做适当的调整。这时候,当然是大名鼎鼎的反射出马了。

    • java.lang.package.getannotation(class<a> annotationclass) 获得这个指令类型的注解。

    使用如下:

    @testannotation(time = 0)
    public class test {
    
        @testannotation(name = "这是个方法",time = 1)
        public void say(){
        }
        public static void main(string[] args) throws classnotfoundexception, illegalaccessexception, instantiationexception, nosuchmethodexception, invocationtargetexception {
            // 获得类上面的注解
            testannotation classannotation = test.class.getannotation(testannotation.class);
            system.out.println("类的名字为:"+classannotation.name()+"------类的时间是"+classannotation.time());
            method method = test.class.getmethod("say");
    
            // 获得方法上面的注解
            testannotation methodannotation = method.getannotation(testannotation.class);
            system.out.println("方法的名字是:"+methodannotation.name()+"------方法的时间是"+methodannotation.time());
        }
    }
    
    // 输出:
    // 类的名字为:这是个类------类的时间是0
    // 方法的名字是:这是个方法------方法的时间是1
    

    现在我们知道如何进行自定义注解的使用了,那么我们怎么能够根据注释内容的不同去改变方法的执行呢?这时候,我们我们就可以使用invoke()方法了。

    举个最简单的栗子:

    @testannotation(name = "你好")
        public void say(string msg){
            system.out.println("信息是:"+msg);
        }
        public static void main(string[] args) throws nosuchmethodexception, illegalaccessexception, instantiationexception, invocationtargetexception {
    
            method method = test.class.getmethod("say",string.class);
            // 获得方法上面的注解
            testannotation methodannotation = method.getannotation(testannotation.class);
            // 执行方法
            method.invoke(test.class.newinstance(),methodannotation.name());
        }
        // 输出结果:
        // 信息是:你好
    

代理

代理就是给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象。
代理分为:

  • 静态代理:代理类是在编译时就已经实现好了,成为了一个class文件
  • 动态代理:是在程序运行时动态地生成类字节码,然后加载到jvm中

有几个概念:

  1. 抽象角色:接口类
  2. 实现角色:实现类
  3. 代理角色:代理实现的类,最终使用的对象

静态代理

在说动态代理之前,我们先说一下静态代理,静态代理很简单,就是工厂模式。

那么就让我们来实现一下静态代理吧

抽象角色:接口类

public interface testservice {
    void say();
    void play();
}

实现角色:实现类

public class testserviceimpl implements testservice {

    @override
    public void say() {
        system.out.println("说话乎");
    }

    @override
    public void play() {
        system.out.println("浪的飞起");
    }
}

代理类

public class test implements testservice{

    private testservice testservice;

    public test(testservice testservice) {
        this.testservice = testservice;
    }

    @override
    public void say() {
        system.out.println("开始说话");
        testservice.say();
        system.out.println("结束说话");
    }

    @override
    public void play() {
        system.out.println("开始浪");
        testservice.play();
        system.out.println("是个狼人");
    }

    public static void main(string[] args) {
        testserviceimpl testimpl = new testserviceimpl();
        test test = new test(testimpl);
        test.play();
        test.say();
    }
}

在这里面,我们可以看到,从外表看起来say()play()方法都是由test这个代理来完成的,但实际上,真正的执行者是testserviceimpl来完成的,test只是在执行的时候加了一些事务逻辑。

既然有了静态代理,为什么我们还需要动态代理呢?从代码中可以看出,代理类和实现类是一一对应的,如果我们有n个实现类,都要在方法执行前加一样的逻辑,那么我们不得不创建n个代理类。这时候,我们就需要使用动态代理了。

动态代理

本次动态代理是针对jdk动态代理进行探讨。

正如前面所说,如果我们要在很多类使用同一种逻辑时,会心态爆炸,那么我们怎么去解决这个问题呢,这时候,我们可以想一想反射。

在使用的动态代理的过程中,有两个关键的东东,一个是invocationhandler接口,一个是proxy类。

  • invocationhandler

每一个动态代理类都必须要实现invocationhandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由invocationhandler这个接口的 invoke 方法来进行调用。

object invoke(object proxy, method method, object[] args) throws throwable

proxy:  指代我们所代理的那个真实对象,也就是实现类

method:  指代的是我们所要调用真实对象的某个方法的method对象

args:  指代的是调用真实对象某个方法时接受的参数

  • proxy

proxy这个类的作用就是用来动态创建一个代理对象的类

其中我们使用最多是newproxyinstance()去创建代理类

public static object newproxyinstance(classloader loader, class<?>[] interfaces, invocationhandler h) throws illegalargumentexception

loader:一个classloader对象,定义了由哪个classloader对象来对生成的代理对象进行加载

interfaces:一个interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了

h:一个invocationhandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个invocationhandler对象上

创建一个代理类,实现方法调用前或后的逻辑

public class testhandler implements invocationhandler{

    // object为实现类的对象
    private object object;

    public testhandler(object object) {
        this.object = object;
    }

    @override
    public object invoke(object proxy, method method, object[] args) throws throwable {
        system.out.println("开始方法执行");
        object o = method.invoke(object,args);
        system.out.println("方法结束");
        return o;
    }
}

实例化代理类,并

public static void main(string[] args) {

    // 实现类
    testservice testservice = new testserviceimpl();

    // 里面传入要代理的实现类对象
    testhandler testhandler = new testhandler(testservice);
    /**
    * testservice.getclass().getclassloader()  代表我们使用这个类来加载我们代理对象
    * testservice.getclass().getinterfaces()  代表我们调用这些接口中的方法
    * testhandler  将代理对象与testhandler关联
    */
    testservice service = (testservice) proxy.newproxyinstance(testservice.getclass().getclassloader(),
            testservice.getclass().getinterfaces(),testhandler);
    service.play();
    service.say();

反射,注解,以及动态代理就简单地介绍完了,可以这样说反射是注解以及动态代理的基础,注解的实现和动态代理都要靠反射发挥作用。

还是多读下书吧,面试实习是把杀猪刀

 

Java反射,注解,以及动态代理
 

 

 

Java反射,注解,以及动态代理