ReflectionData(int redefinedCount) {
this.redefinedCount = redefinedCount;
}
}
// 这个是SoftReference,在内存资源紧张的时候可能会被回收
// volatile保证多线程环境下读写的正确性
private volatile transient SoftReference<RefelectionData> reflectionData;
// 这个主要用于和ReflectionData中的redefinedCount进行比较
// 如果两个值不相等,说明ReflectionData缓存的数据已经过期了
private volatile transient classRedefinedCount = 0;
反射的主要用途
-
反射最重要的用途就是开发各种通用框架
-
很多框架都是配置化的,通过 XML 文件配置 Bean
-
为了保证框架的通用性,需要根据配置文件加载不同的对象或者类,调用不同的方法
-
要运用反射,运行时动态加载需要加载的对象
-
示例:
-
在运用 Struts 2 框架的开发中会在 struts.xml 中配置 Action:
<action name=“login”
class=“org.ScZyhSoft.test.action.SimpleLoginAction”
method=“execute”>
/shop/shop-index.jsp
login.jsp
-
配置文件与 Action 建立映射关系
-
当 View 层发出请求时,请求会被 StrutsPrepareAndExecuteFilter 拦截
-
StrutsPrepareAndExecuteFilter会动态地创建Action实例
-
请求 login.action
-
StrutsPrepareAndExecuteFilter 会解析 struts.xml 文件
-
检索 action 中 name 为 login 的 Action
-
根据 class 属性创建 SimpleLoginAction 实例
-
使用 invoke 方法调用 execute 方法
-
反射是各种容器实现的核心
-
反射相关的类在 StrutsPrepareAndExecuteFilter 包
-
反射可以用于:
-
判断对象所属的类
-
获得class对象
-
构造任意一个对象
-
调用一个对象
-
九大预定义的Class对象:
-
基本的Java类型: boolean, byte, char, short, int, long, float, double
-
关键字void通过class属性的也表示为Class对象
-
包装类或者void类的静态TYPE字段:
-
Integer.TYPE == int.class
-
Integer.class == int.class
-
数组类型的Class实例对象:
-
Class<String[]> clz = String[].class;
-
数组的Class对象如何比较是否相等:
-
数组的维数
-
数组的类型
-
Class类中的 isArray() ,用来判断是否表示一个数组类型
获得Class对象
- 使用Class类的forName静态方法:
public static Class<?> forName(String className);
/* 在JDBC中使用这个方法加载数据库驱动 */
Class.forName(driver);
- 直接获取一个对象的class:
Class<?> klass=int.class;
Class<?> classInt=Integer.TYPE;
- 调用对象的getClass()方法:
StringBuilder str=new StringBuilder(“A”);
Class<?> klass=str.getClass();
判断是否是某个类的实例
-
一般来说,使用 instanceof 关键字判断是否为某个类的实例
-
在反射中,可以使用 Class 对象的 isInstance() 方法来判断是否为某个类的实例,这是一个 native 方法
public native boolean isInstance(Object obj);
创建实例
通过反射生成对象的实例主要有两种方式:
- 使用Class对象的newInstance()方法来创建Class对象对应类的实例:
Class<?> c = String.class;
Object str = c.newInstance();
- 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例: 可以用指定的构造器构造类的实例
/* 获取String所对应的Class对象 */
Class<?> c=String.class;
/* 获取String类带一个String参数的构造器 */
Constructor constructor=c.getConstructor(String.class);
/* 根据构造器创建实例 */
Object obj=constructor.newInstance(“abc”);
System.out.println(obj);
获取方法
获取Class对象的方法集合,主要有三种方法:
- getDeclaredMethods():
返回类或接口声明的所有方法:
-
包括公共,保护,默认(包)访问和私有方法
-
不包括继承的方法
public Method[] getDeclaredMethods() throws SecurityException {}
-
getMethods():返回某个类所有的public方法
-
包括继承类的 public 方法
public Method[] getMethods() throws SecurityException {}
- getMethod():
返回一个特定的方法
-
第一个参数 :方法名称
-
后面的参数 :方法的参数对应Class的对象
public Method getMethod(String name,Class<?>… parameterType) {}
- 获取方法示例:
public class MethodTest {
public static void methodTest() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> c = methodClass.class;
Object object = c.newInstance();
Method[] methods = c.getMethods();
Method[] declaredMethods = c.getDeclaredMethods();
// 获取methodClass类中的add方法
Method method = c.getMethod(“add”, int.class, int.class);
// getMethods()方法获取的所有方法
System.out.println(“getMethods获取的方法:”);
for (Method m:methods)
System.out.println(m);
// getDeclaredMethods()方法获取的所有方法
System.out.println(“getDeclaredMethods获取的方法:”);
for (Method m:declaredMethods)
System.out.println(m);
}
}
class methodClass {
public final int n = 3;
public int add(int a, int b) {
return a + b;
}
public int sub(int a, int b) {
return a * b;
}
}
程序运行结果:
getMethods获取的方法:
public int org.ScZyhSoft.common.methodClass.add(int,int)
public int org.ScZyhSoft.common.methodClass.sub(int,int)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
getDeclaredMethods获取的方法:
public int org.ScZyhSoft.common.methodClass.add(int,int)
public int org.ScZyhSoft.common.methodClass.sub(int,int)
通过 getMethods() 获取的方法可以获取到父类的方法
获取构造器信息
-
通过 Class 类的 getConstructor 方法得到 Constructor 类的一个实例
-
Constructor 类中 newInstance 方法可以创建一个对象的实例:
public T newInstance(Objec … initargs)
newInstance方法可以根据传入的参数来调用对应的 Constructor 创建对象的实例
获取类的成员变量信息
-
getFileds: 获取公有的成员变量
-
getDeclaredFields: 获取所有已声明的成员变量,但是不能得到父类的成员变量
调用方法
- 从类中获取一个方法后,可以使用 invoke() 来调用这个方法
public Object invoke(Object obj, Object … args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {}
- 示例:
public class InvokeTest {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> klass = method.class;
// 创建methodClass的实例
Object obj = klass.newInstance();
// 获取methodClass的add方法
Method method = klass.getMethod(“add”, int.class, int.class);
// 调用method对应的方法,实现add(1,4)
Object result = method.invoke(obj, 1, 4);
System.out.println(result);
}
}
class methodClass {
public final int n = 3;
public int add(int a, int b) {
return a + b;
}
public int sub(int a,int b) {
return a * b;
}
}
利用反射创建数组
-
数组是Java中一种特殊的数据类型,可以赋值给一个 Object Reference
-
利用反射创建数组的示例:
public static void ArrayTest() throws ClassNotFoundException {
Class<?> cls = class.forName(“java.lang.String”);
Object array = Array.newInstance(cls, 25);
// 在数组中添加数据
Array.set(array, 0, “C”);
Array.set(array, 1, “Java”);
Array.set(array, 2, “Python”);
Array.set(array, 3, “Scala”);
Array.set(array, 4, “Docker”);
// 获取数据中的某一项内容
System.out.println(Array.get(array, 3));
}
Array类是 java.lang.reflect.Array 类,通过 Array.newInstance() 创建数组对象:
public static Object newInstance(Class<?> componentType, int length) throws NegativeArraySizeException {
return newArray(componentType, length);
}
newArray方法是一个 native 方法,具体实现在 HotSpot JVM 中,源码如下:
private static native Object newArray(Class<?> componentType, int length) throws NegativeArraySizeException;
- newArray源码目录: openjdk\hotspot\src\share\vm\runtime\reflection.cpp
arrayOop Reflection::reflect_new_array(oop element_mirror, jint length, TRAPS) {
if (element_mirror == NULL) {
THROW_0(vmSymbols::java_lang_NullPointerException());
}
if (length < 0) {
THROW_0(vmSymbols::java_lang_NegativeArraySizeException());
}
if (java_lang_Class::is_primitive(element_mirror)) {
Klass* tak = basic_type_mirror_to_arrayklass(element_mirror, CHECK_NULL);
return TypeArrayKlass::cast(tak)->allocate(length, THREAD);
} else {
Klass* k = java_lang_Class::as_Klass(element_mirror);
if (k->oop_is_array() && ArrayKlass::cast(k)->dimension() >= MAX_DIM) {
THROW_0(vmSymbols::java_lang_IllegalArgumentException());
}
return oopFactory::new_objArray(k, length, THREAD);
}
}
-
Array类的set和get方法都是native方法,具体实现在HotSpot JVM中,对应关系如下:
-
set: Reflection::array_set
-
get: Reflection::array_get
- 在Java中很多方法都会调用 invoke 方法,很多异常的抛出多会定位到 invoke 方法:
java.lang.NullPointerException
at …
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
invoke执行过程
- invoke 方法用来在运行时动态地调用某个实例的方法,实现如下:
@CallSensitive
public Object invoke(Object obj, Object … args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Refelection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor;
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
权限检查
-
AccessibleObject类是Field,Method和Constructor对象的基类:
-
提供将反射的对象标记为在使用时取消默认Java语言访问控制检查的能力
-
invoke方法会首先检查AccessibleObject的override属性的值:
-
override默认值为false:
-
表示需要权限调用规则,调用方法时需要检查权限
-
也可以使用 setAccessible() 设置为 true
-
override如果值为true:
-
表示忽略权限规则,调用方法时无需检查权限
-
也就是说,可以调用任意private方法,违反了封装
-
如果override属性为默认值false,则进行进一步的权限检查:
- 首先用 Reflection.quickCheckMemberAccess(clazz, modifiers) 方法检查方法是否为 public
1.1 如果是public方法的话,就跳过本步
1.2 如果不是public方法的话,就用Reflection.getCallerClass()方法获取调用这个方法的Class对象,这是一个 native 方法
@CallerSensitive
public static native Class<?> getCallerClass();
- 在OpenJDK中可以找到getCallerClass方法的JNI入口-Reflection.c
JNIEXPORT jclass JNICALL Java_sun_reflect_Reflection_getCallerClass__
(JNIEnv *env, jclass unused)
{
return JVM_GetCallerClass(env, JVM_CALLER_DEPTH);
}
- JVM_GetCallerClass的源码位于jvm.cpp中
VM_ENTRY(jclass, JVM_GetCallerClass(JNIEnv* env, int depth))
JVMWrapper(“JVM_GetCallerClass”);
// Pre-JDK 8 and early builds of JDK 8 don’t have a CallerSensitive annotation; or
// sun.reflect.Reflection.getCallerClass with a depth parameter is provided
// temporarily for existing code to use until a replacement API is defined.
if (SystemDictionary::reflect_CallerSensitive_klass() == NULL || depth != JVM_CALLER_DEPTH) {
Klass* k = thread->security_get_caller_class(depth);
return (k == NULL) ? NULL : (jclass) JNIHandles::make_local(env, k->java_mirror());
}
// Getting the class of the caller frame.
//
// The call stack at this point looks something like this:
//
// [0] [ @CallerSensitive public sun.reflect.Reflection.getCallerClass ]
// [1] [ @CallerSensitive API.method ]
// [.] [ (skipped intermediate frames) ]
// [n] [ caller ]
vframeStream vfst(thread);
// Cf. LibraryCallKit::inline_native_Reflection_getCallerClass
for (int n = 0; !vfst.at_end(); vfst.security_next(), n++) {
Method* m = vfst.method();
assert(m != NULL, “sanity”);
switch (n) {
case 0:
// This must only be called from Reflection.getCallerClass
if (m->intrinsic_id() != vmIntrinsics::_getCallerClass) {
THROW_MSG_NULL(vmSymbols::java_lang_InternalError(), “JVM_GetCallerClass must only be called from Reflection.getCallerClass”);
}
// fall-through
case 1:
// Frame 0 and 1 must be caller sensitive.
if (!m->caller_sensitive()) {
THROW_MSG_NULL(vmSymbols::java_lang_InternalError(), err_msg(“CallerSensitive annotation expected at frame %d”, n));
}
break;
default:
if (!m->is_ignored_by_security_stack_walk()) {
// We have reached the desired frame; return the holder class.
return (jclass) JNIHandles::make_local(env, m->method_holder()->java_mirror());
}
break;
}
}
return NULL;
JVM_END
- 获取 Class 对象 caller 后使用 checkAccess 方法进行一次快速的权限校验 ,checkAccess 方法实现如下:
volatile Object securityCheckCache;
void checkAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers) throws IllegalAccessException {
if(caller == clazz){ // 快速校验
return; // 权限通过校验
}
Object cache = securityCheckCache; // 读取volatile
Class<?> targetClass = clazz;
if (obj != null && Modifier.isProtected(modifiers) && ((targetClass = obj.getClass()) != clazz)) { // 必须匹配caller,targetClass中的一个
if (cache instanceof Class[]) {
Class<?>[] cache2 = (Class<?>[]) cache;
if (cache2[1] == targetClass && cache[0] == caller) {
return; // 校验通过
}
}
} else if (cache == caller) {
return; // 校验通过
}
slowCheckMemberAccess(caller, clazz, obj, modifiers, targetClass);
}
首先先执行一次快速校验,一旦 Class 正确则权限检查通过;如果未通过,则创建一个缓存,中间再进行检查
- 如果上面所有的权限检查都未通过,将会执行更详细的检查:
void slowCheckMemberAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers, Class<?> targetClass) throws IllegalAccessException {
Refelection.ensureMemberAccess(caller, clazz, obj, modifiers);
// 如果成功,就更新缓存
Object cache = ((targetClass == clazz) ? caller : new Class<?>[] {caller, targetClass});
securityCheckCache = cache;
}
用 Reflection.ensureMemberAccess 方法继续检查权限.若检查通过就更新缓存,这样下一次同一个类调用同一个方法时就不用执行权限检查了,这是一种简单的缓存机制
由于 JMM 的 happens-before 规则能够保证缓存初始化能够在写缓存之间发生,因此两个cache不需要声明为volatile
- 检查权限的工作到此结束.如果没有通过检查就会抛出异常,如果通过检查就会到下一步
调用MethodAccessor的invoke方法
-
Method.invoke() 不是自身实现反射调用逻辑,而是通过 sun.refelect.MethodAccessor 来处理
-
Method对象的基本构成:
-
每个 Java 方法有且只有一个 Method 对象作为 root, 相当于根对象,对用户不可见
-
当创建 Method 对象时,代码中获得的 Method 对象相当于其副本或者引用
-
root 对象持有一个 MethodAccessor 对象,所有获取到的 Method 对象都共享这一个 MethodAccessor 对象
-
必须保证 MethodAccessor 在内存中的可见性
-
root对象及其声明:
private volatile MethodAccessor methodAccessor;
/**
-
For sharing of MethodAccessors. This branching structure is
-
currently only two levels deep (i.e., one root Method and
-
potentially many Method objects pointing to it.)
-
If this branching structure would ever contain cycles, deadlocks can
-
occur in annotation code.
*/
private Method root;
- MethodAccessor:
/**
-
This interface provides the declaration for
-
java.lang.reflect.Method.invoke(). Each Method object is
-
configured with a (possibly dynamically-generated) class which
-
implements this interface
*/
public interface MethodAccessor {
// Matches specification in {@link java.lang.reflect.Method}
public Object invoke(Object obj, Object[] args) throws IllegalArgumentException, InvocationTargetException;
}
MethodAccessor是一个接口,定义了 invoke() 方法,通过 Usage 可以看出 MethodAccessor 的具体实现类:
-
sun.reflect.DelegatingMethodAccessorImpl
-
sun.reflect.MethodAccessorImpl
-
sun.reflect.NativeMethodAccessorImpl
-
第一次调用 Java 方法对应的 Method 对象的 invoke() 方法之前,实现调用逻辑的 MethodAccess 对象还没有创建
-
第一次调用时,才开始创建 MethodAccessor 并更新为 root, 然后调用 MethodAccessor.invoke() 完成反射调用
/**
-
NOTE that there is no synchronization used here.
-
It is correct(though not efficient) to generate more than one MethodAccessor for a given Method.
-
However, avoiding synchronization will probably make the implementation more scalable.
*/
最后
为什么我不完全主张自学?
①平台上的大牛基本上都有很多年的工作经验了,你有没有想过之前行业的门槛是什么样的,现在行业门槛是什么样的?以前企业对于程序员能力要求没有这么高,甚至十多年前你只要会写个“Hello World”,你都可以入门这个行业,所以以前要入门是完全可以入门的。
②现在也有一些优秀的年轻大牛,他们或许也是自学成才,但是他们一定是具备优秀的学习能力,优秀的自我管理能力(时间管理,静心坚持等方面)以及善于发现问题并总结问题。
如果说你认为你的目标十分明确,能做到第②点所说的几个点,以目前的市场来看,你才真正的适合去自学。
除此之外,对于绝大部分人来说,报班一定是最好的一种快速成长的方式。但是有个问题,现在市场上的培训机构质量参差不齐,如果你没有找准一个好的培训班,完全是浪费精力,时间以及金钱,这个需要自己去甄别选择。
我个人建议线上比线下的性价比更高,线下培训价格基本上没2W是下不来的,线上教育现在比较成熟了,此次疫情期间,学生基本上都感受过线上的学习模式。相比线下而言,线上的优势以我的了解主要是以下几个方面:
①价格:线上的价格基本上是线下的一半;
②老师:相对而言线上教育的师资力量比线下更强大也更加丰富,资源更好协调;
③时间:学习时间相对而言更自由,不用裸辞学习,适合边学边工作,降低生活压力;
④课程:从课程内容来说,确实要比线下讲的更加深入。
应该学哪些技术才能达到企业的要求?(下图总结)
的 invoke() 方法之前,实现调用逻辑的 MethodAccess 对象还没有创建
- 第一次调用时,才开始创建 MethodAccessor 并更新为 root, 然后调用 MethodAccessor.invoke() 完成反射调用
/**
-
NOTE that there is no synchronization used here.
-
It is correct(though not efficient) to generate more than one MethodAccessor for a given Method.
-
However, avoiding synchronization will probably make the implementation more scalable.
*/
最后
为什么我不完全主张自学?
①平台上的大牛基本上都有很多年的工作经验了,你有没有想过之前行业的门槛是什么样的,现在行业门槛是什么样的?以前企业对于程序员能力要求没有这么高,甚至十多年前你只要会写个“Hello World”,你都可以入门这个行业,所以以前要入门是完全可以入门的。
②现在也有一些优秀的年轻大牛,他们或许也是自学成才,但是他们一定是具备优秀的学习能力,优秀的自我管理能力(时间管理,静心坚持等方面)以及善于发现问题并总结问题。
如果说你认为你的目标十分明确,能做到第②点所说的几个点,以目前的市场来看,你才真正的适合去自学。
除此之外,对于绝大部分人来说,报班一定是最好的一种快速成长的方式。但是有个问题,现在市场上的培训机构质量参差不齐,如果你没有找准一个好的培训班,完全是浪费精力,时间以及金钱,这个需要自己去甄别选择。
我个人建议线上比线下的性价比更高,线下培训价格基本上没2W是下不来的,线上教育现在比较成熟了,此次疫情期间,学生基本上都感受过线上的学习模式。相比线下而言,线上的优势以我的了解主要是以下几个方面:
①价格:线上的价格基本上是线下的一半;
②老师:相对而言线上教育的师资力量比线下更强大也更加丰富,资源更好协调;
③时间:学习时间相对而言更自由,不用裸辞学习,适合边学边工作,降低生活压力;
④课程:从课程内容来说,确实要比线下讲的更加深入。
应该学哪些技术才能达到企业的要求?(下图总结)
[外链图片转存中…(img-Al6fFdqd-1714740361862)]
[外链图片转存中…(img-PiKLOyRZ-1714740361863)]