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反射,注解,以及动态代理的更多相关文章

  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基础加强-(注解,动态代理,类加载器,servlet3.0新特性)

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

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

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

  7. java.lang.Class<T> -- 反射机制及动态代理

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

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

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

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

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

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

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

随机推荐

  1. Maven学习笔记2(坐标和依赖)

    1.坐标 Maven坐标为各个构件建立了秩序,任何一个构件都必须明确自己的坐标,一个maven坐标是由一些元素确定的 <groupId>com.alivn.account</grou ...

  2. JavaScriptDOM

    DOM简介 1.HTML DOM:网页被加载时,浏览器会创建文档对象模型 2.DOM操作HTML:改变HTML的元素.属性.CSS样式.对所有事件作出反应 DOM操作HTML 1.改变HTML输出流 ...

  3. Centos7 网络报错Job for iptables.service failed because the control process exited with error code.

    今天在进行项目联系的时候,启动在待机的虚拟机,发现虚拟机的网络设置又出现了问题. 我以为像往常一样重启网卡服务就能成功,但是它却报了Job for iptables.service failed be ...

  4. git 创建项目

    Command line instructions Git global setup git config --global user.name "quliangliang" gi ...

  5. github使用步骤

    首先需要注册一个github账号 1.认识github首页界面 2.如何新建一个自己的仓库 3.创建README文件 4.创建自己的文件 5.解析文件 6.生成地址 7.如何修改编辑文件

  6. Delegate、Thread、Task、ThreadPool几种方式创建异步任务性能对比

    开始预测的结果是 Task>Delegate>ThreadPool>>Thread. (一)测试代码 static async Task<int> AsyncTas ...

  7. offset系列、scroll系列与client系列

    offset系列: offsetLeft:获取元素距离最左边的距离,自身的margin包括在内,不包括自身的border offsetTop:获取元素距离最上边的距离,自身的margin包括在内,不包 ...

  8. Python之路【第五篇】函数

    4.1 函数的定义 函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可 4.2 函数的创建 函数名的命名规则: 1.函数名必须以下划线或字母开头,可以包含任 ...

  9. jQuery 购物车

    html代码 <!--shoppingCar start-->  <table id="TB">   <tr>    <td colspa ...

  10. Maven3-依赖

    依赖配置 我们先来看一份简单的依赖声明: <project> ... <dependencies> <dependency> <groupId>...& ...