Spring AOP系列(五)

您所在的位置:网站首页 java反射能做什么 Spring AOP系列(五)

Spring AOP系列(五)

2023-12-01 06:48| 来源: 网络整理| 查看: 265

前言

前面我们进行了代理模式、静态代理、动态代理的学习。而动态代理就是利用Java的反射技术(Java Reflection),在运行时创建一个实现某些给定接口的新类(也称“动态代理类”)及其实例(对象)。所以接下来我们有必要学习一下Java中的反射。

一、基础知识 1.1 反射是什么?

在讲反射之前,先提一个问题:假如现在有一个类User,我想创建一个User对象并且获取到其name属性,我该怎么做呢? User.java

package com.reflect; /** * @author: create by lengzefu * @description: com.reflect * @date:2020-09-29 */ public class User { private String name = "小明"; Integer age = 18; public User(){ } public User(String name, Integer age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }

方式很简单:

import com.reflect.User; public class Main { public static void main(String[] args) { User user = new User(); System.out.println(user.getName()); } }

这种方式是我们日常在写代码时经常用到的一种方式。这是因为我们在使用某个对象时,总是预先知道自己需要使用到哪个类,因此可以使用直接 new 的方式获取类的对象,进而调用类的方法。 那假设我们预先并不知道自己要使用的类是什么呢?这种场景其实很常见,比如动态代理,我们事先并不知道代理类是什么,代理类是在运行时才生成的。这种情况我们就要用到今天的主角:反射

1.1.1 反射的定义

JAVA反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。 注意:这里特别强调的是运行状态。

1.2 反射能做什么?

定义已经给了我们答案。反射可以使得程序在运行时可以获取到任意类的任意属性和方法并调用。

1.3 反射为什么能做到?

这里我们需要讲一个“类对象”的概念。java中“面向对象”的理念贯彻的比较彻底,它强调“万事万物皆对象”。那么“类”是不是也可以认为是一个对象呢?java中有一种特殊的类:Class,它的对象是“类”,比如“String”类,“Thread”类都是它的对象。

java.lang.Class是访问类型元数据的接口,也是实现反射的关键数据。通过Class提供的接口,可以访问一个类型的方法、字段等信息。

以上已经解答了“反射为什么能做到可以使得程序在运行时可以获取到任意类的任意属性和方法并调用”的问题,它是依赖.class字节码文件做到的。那么首先我们需要解决的问题是如何获取字节码文件对象(Class对象)。

1.3.1 获取Class对象

对于一个类,如上文的User,我想获取User的相关信息(由于Users属于Class类的对象,所以一般称该行为为“获取类对象”),该怎么做呢? 有以下三种方式

import com.reflect.User; public class Main { public static void main(String[] args) throws ClassNotFoundException { // 1.已实例化的对象可调用 getClass() 方法来获取,通常应用在:传过来一个 Object类型的对象,不知道具体是什么类,用这种方法 User user = new User(); Class clz1 = user.getClass(); // 2.类名.class 的方式得到,该方法最为安全可靠,程序性能更高,这说明任何一个类都有一个隐含的静态成员变量 class Class clz2 = User.class; // 通过类的全路径名获取Class对象,用的最多,如果根据类路径找不到这个类那么就会抛出 ClassNotFoundException异常。 Class clz3 = Class.forName("com.reflect.User"); // 一个类在 JVM 中只会有一个 Class 实例,即我们对上面获取的 clz1,clz2,clz3进行 equals 比较,发现都是true。 System.out.println(clz1.equals(clz2)); System.out.println(clz2.equals(clz3)); } } 1.3.2 Class API

获取公共构造器 getConstructors() 获取所有构造器 getDeclaredConstructors() 获取该类对象 newInstance() 获取类名包含包路径 getName() 获取类名不包含包路径 getSimpleName() 获取类公共类型的所有属性 getFields() 获取类的所有属性 getDeclaredFields() 获取类公共类型的指定属性 getField(String name) 获取类全部类型的指定属性 getDeclaredField(String name) 获取类公共类型的方法 getMethods() 获取类的所有方法 getDeclaredMethods() 获得类的特定公共类型方法: getMethod(String name, Class[] parameterTypes) 获取内部类 getDeclaredClasses() 获取外部类 getDeclaringClass() 获取修饰符 getModifiers() 获取所在包 getPackage() 获取所实现的接口 getInterfaces()

具体如何使用不再赘述

二、反射原理解析 2.1 反射与类加载的关系

java类的执行需要经历以下过程,

编译:java文件编译后生成.class字节码文件 加载:类加载器负责根据一个类的全限定名来读取此类的二进制字节流到 JVM 内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的 java.lang.Class 对象实例 链接:

验证:格式(class文件规范) 语义(final类是否有子类) 操作 准备:静态变量赋初值和内存空间,final修饰的内存空间直接赋原值,此处不是用户指定的初值。 解析:符号引用转化为直接引用,分配地址

初始化:有父类先初始化父类,然后初始化自己;将static修饰代码执行一遍,如果是静态变量,则用用户指定值覆盖原有初值;如果是代码块,则执行一遍操作。

Java的反射就是利用上面第二步加载到jvm中的.class文件来进行操作的。.第二步加载到jvm中的.class文件来进行操作的。.class文件中包含java类的所有信息,当你不知道某个类具体信息时,可以使用反射获取class,然后进行各种操作。

首先我们来看看如何使用反射来实现方法的调用的:

public class Main { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // 1.获取类对象 Class clz3 = Class.forName("com.reflect.User"); // 2.获取类的构造函数 Constructor constructor = clz3.getConstructor(String.class, Integer.class); // 3.创建一个对象 User user = (User)constructor.newInstance("璐璐", 17); // 4.获取方法getName Method method = clz3.getMethod("getName"); // 5.调用该方法 String name = (String) method.invoke(user); System.out.println(name); } }

接下来主要解析4,5两个过程:获取Method对象和Methode.invoke

2.2 获取 Method 对象 2.2.1 获取 Method 的API介绍

Class API中关于获取Method对象的方法有如下几个: getMethod/getMethods 和 getDeclaredMethod/getDeclaredMethod 后缀有无“s”的区别 有“s”表示获取的所有的,无“s”表示获取的是特定的(由方法参数指定)。 getMethod和getDeclaredMethod的区别 Method对应的是Member.PUBLIC,DeclaredMethod对应的是Member.DECLARED 两者定义如下:

public interface Member { /** * Identifies the set of all public members of a class or interface, * including inherited members. * 标识类或接口的所有公共成员的集合,包括父类的公共成员。 */ public static final int PUBLIC = 0; /** * Identifies the set of declared members of a class or interface. * Inherited members are not included. * 标识类或接口所有声明的成员的集合(public、protected,private),但是不包括父类成员 */ public static final int DECLARED = 1; }

其实不管是getMethod还是getDeclaredMethod,底层都调用了同一个方法:privateGetDeclaredMethods,因此我们只分析其中一个方法即可。

2.2.2 getMethod 方法源码分析 seq1 // 4.获取方法getName Method method = clz3.getMethod("getName");

客户端调用Class.getMethod()方法。

seq2 // 参数“name”为方法名,参数“parameterTypes”为方法的参数,由于参数可能有多个且类型不同,所以这里使用到了泛型及可变参数的设定 public Method getMethod(String name, Class... parameterTypes) throws NoSuchMethodException, SecurityException { // 权限安全检查,无权限则抛出 SecurityException checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true); Method method = getMethod0(name, parameterTypes, true); // 获取到的method为空,抛出 NoSuchMethodException 异常 if (method == null) { throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes)); } return method; }

该方法的核心方法是 getMethod0。

seq3 private Method getMethod0(String name, Class[] parameterTypes, boolean includeStaticMethods) { // 保存接口中的方法,最多只有1个,但MethodArray初始化大小至少为2 MethodArray interfaceCandidates = new MethodArray(2); // 递归获取方法,之所以递归,正是因为getMethod是需要获取父类中的方法,与前面关于getMethod的介绍对应 Method res = privateGetMethodRecursive(name, parameterTypes, includeStaticMethods, interfaceCandidates); // 获取到本类或父类中的方法,直接返回结果 if (res != null) return res; // Not found on class or superclass directly // 在本类或父类中没有找到对应的方法,则尝试去接口中的方法寻找 // removeLessSpecifics:移除更不具体的方法,保留具有更具体实现的方法 interfaceCandidates.removeLessSpecifics(); return interfaceCandidates.getFirst(); // may be null }

接下来分析privateGetMethodRecursive

seq4 private Method privateGetMethodRecursive(String name, Class[] parameterTypes, boolean includeStaticMethods, MethodArray allInterfaceCandidates) { // Must _not_ return root methods Method res; // Search declared public methods 搜索本来中声明的公共方法 if ((res = searchMethods(privateGetDeclaredMethods(true), name, parameterTypes)) != null) { if (includeStaticMethods || !Modifier.isStatic(res.getModifiers())) // res 不为空,返回 return res; } // Search superclass's methods res为空,继续向父类搜索 if (!isInterface()) { // 接口必然无父类 Class


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3