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

时间:2022-10-23 10:43:09

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反射,注解,以及动态代理
 

Java反射,注解,以及动态代理的更多相关文章

  1. Java反射机制以及动态代理

    Java反射机制以及动态代理 Java反射机制 含义与功能 Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类 ...

  2. java反射机制与动态代理

    在学习HadoopRPC时.用到了函数调用.函数调用都是採用的java的反射机制和动态代理来实现的,所以如今回想下java的反射和动态代理的相关知识. 一.反射 JAVA反射机制定义: JAVA反射机 ...

  3. Java 反射之JDK动态代理

    Proxy提供用于创建动态代理类和代理对象的静态方法,它也是所有动态代理类的父类.如果我们在程序中为一个或多个接口动态地生成实现类,就可以使用Proxy来创建动态代理类:如果需要为一个或多个接口动态的 ...

  4. java反射中的动态代理机制(有实例)

    在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...

  5. Java基础加强-&lpar;注解,动态代理,类加载器,servlet3&period;0新特性&rpar;

    1.   Annotation注解 1.1.  Annotation概述 Annotation是JDK 5.0以后提供对元数据的支持,可以在编译.加载和运行时被读取,并执行相应的处理.所谓Annota ...

  6. Java的反射机制和动态代理

    介绍Java注解的时候,多次提到了Java的反射API.与javax.lang.model不同的是,通过反射API可以获取程序在运行时刻的内部结构.反射API中提供的动态代理也是非常强大的功能,可以原 ...

  7. java&period;lang&period;Class&lt&semi;T&gt&semi; -- 反射机制及动态代理

    Interface : Person package java_.lang_.component.bean; public interface Person { String area = &quot ...

  8. Java Proxy和CGLIB动态代理原理

    动态代理在Java中有着广泛的应用,比如Spring AOP,Hibernate数据查询.测试框架的后端mock.RPC,Java注解对象获取等.静态代理的代理关系在编译时就确定了,而动态代理的代理关 ...

  9. 【译】8&period; Java反射——注解

    原文地址:http://tutorials.jenkov.com/java-reflection/annotations.html ================================== ...

  10. 【译】12&period; Java反射——类的动态加载和重新加载

    原文地址:http://tutorials.jenkov.com/java-reflection/dynamic-class-loading-reloading.html 博主最近比较忙,争取每周翻译 ...

随机推荐

  1. 前端构建 build 技术 nodejs gulp

    https://www.sitepoint.com/introduction-gulp-js/ 参照这个例子做一遍,就会明白,中间会有个问题 npm install jshint 需要修正为 npm ...

  2. thinkphp 常见问题

    0.写在最前面的不断更新 (1)trace不起作用 A:必须要输出到模板,才会有trace信息 (2)提示“您浏览的页面暂时发生了错误!请稍后再试-” A:检查控制器(看看能进到控制器没有,设断点输出 ...

  3. nodejs学习随笔

    <一> 简述nodejs (社区:www.npmjs.com)可查找一些第三方模块. nodejs是可以让js运行在浏览器之外的服务器端的平台,实现了文件系统.模块.包.操作系统API.网 ...

  4. js判断对象为空 JSON&period;stringify&lpar;obj&rpar;

    JSON.stringify(obj) : 用于从一个对象解析出字符串 var c = {}; if(JSON.stringify(obj) == "{}"){ console.l ...

  5. JVM虚拟机(1)---常用JVM配置参数

    常用JVM配置参数 常用JVM配置参数主要有:Trace跟踪参数.堆的分配参数.栈的分配参数. 一.Trace跟踪参数 跟踪参数用于跟踪监控JVM,对于开发人员来讲用于JVM调优以及故障排查的. 1. ...

  6. C&num;版 - Leetcode 201&period; 数字范围按位与&lpar;bitwise AND&rpar; - 题解

    C#版 - Leetcode 201. 数字范围按位与(bitwise AND) - 题解 在线提交: https://leetcode.com/problems/bitwise-and-of-num ...

  7. autotools

    文章目录 原文地址 Autotools上手指南1--autoconf基本思想 Autotools上手指南2--autoscan生成configure.ac Autotools上手指南3--autohe ...

  8. Spring Boot &plus; Spring Cloud 实现权限管理系统 后端篇(十八):注册中心(Spring Cloud Consul)

    什么是 Consul Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置.与其它分布式服务注册与发现的方案,Consul 的方案更“一站式”,内置了服务注册与 ...

  9. FastDFS client for &period;net

    fastDfs Code: https://code.google.com/p/fastdfs/ FastDfs_Client_DotNet:https://code.google.com/p/fas ...

  10. iOS开发--底部按钮和应用图标显示未读消息

    我们要实现的效果如下: 我们使用系统自带的,实际上,代码量很少,在我们要显示的按钮上,打上下面一句代码即可: self.tabBarItem.badgeValue = @"1"; ...