前言
前几篇文章讲述Java反射核心功能与用法,基本的常用方法都已经囊括里面了,本篇针是对前几篇文章进行补充。
AccessibleObject
AccessibleObject类是前几篇文章所述的Field、Method、Constructor的基类。它提供了将反射的对象标记为在使用时取消默认Java语言访问控制检查的能力,它可以在在反射对象中设置accessible标志而获取特权,大部分时间我们称这种方式为“暴力反射”。
它们之间的关系如下:
对于公共成员、默认成员、受保护成员、私有成员,在使用Field、Method、Constructor对象来设置或获取字段、调用方法,又或者创建和初始化类的新实例的时候,会执行访问检查。但是AccessibleObject类在反射对象中设置accessible标志允许具有足够权限,以通常被禁止的方式操作对象。
/**
* The AccessibleObject class is the base class for Field, Method and
* Constructor objects. It provides the ability to flag a reflected
* object as suppressing default Java language access control checks
* when it is used. The access checks--for public, default (package)
* access, protected, and private members--are performed when Fields,
* Methods or Constructors are used to set or get fields, to invoke
* methods, or to create and initialize new instances of classes,
* respectively.
*
* <p>Setting the {@code accessible} flag in a reflected object
* permits sophisticated applications with sufficient privilege, such
* as Java Object Serialization or other persistence mechanisms, to
* manipulate objects in a manner that would normally be prohibited.
* <p>By default, a reflected object is <em>not</em> accessible.
*/
public class AccessibleObject implements AnnotatedElement {
/**
* 用于检查客户端是否具有足够的权限来阻止java语言访问控制检查
* The Permission object that is used to check whether a client
* has sufficient privilege to defeat Java language access
* control checks.
Permission对象,
*/
static final private java.security.Permission ACCESS_PERMISSION = new ReflectPermission("suppressAccessChecks");
/**
* 使用单一安全检查来设置对象数组的可访问标志的一个方便的方法(为了效率)
* Convenience method to set the {@code accessible} flag for an
* array of objects with a single security check (for efficiency).
*/
public static void setAccessible(AccessibleObject[] array, boolean flag) throws SecurityException {
// //获取系统安全的接口
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
for (int i = 0; i < array.length; i++) {
setAccessible0(array[i], flag);
}
}
/**
设置可访问标志设置
*/
public void setAccessible(boolean flag) throws SecurityException {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
setAccessible0(this, flag);
}
/*
* Check that you aren't exposing java.lang.Class.<init> or sensitive
fields in java.lang.Class.
*/
private static void setAccessible0(AccessibleObject obj, boolean flag) throws SecurityException {
if (obj instanceof Constructor && flag == true) {
Constructor<?> c = (Constructor<?>)obj;
if (c.getDeclaringClass() == Class.class) {
throw new SecurityException("Cannot make a java.lang.Class" +
" constructor accessible");
}
}
obj.override = flag;
}
/**
* 获取accessible标志
*/
public boolean isAccessible() {
return override;
}
/**
* 构造函数,仅JVM使用
* Constructor: only used by the Java Virtual Machine.
*/
protected AccessibleObject() {}
// Indicates whether language-level access checks are overridden
// by this object. Initializes to "false". This field is used by
// Field, Method, and Constructor.
// 通过这个对象,指示是否覆盖语言级别访问检查权限,初始化为false,该字段用于Field, Method和Constructor
// NOTE: for security purposes, this field must not be visible
// outside this package. 出于安全考虑,此字段不得显示出包外
boolean override;
// Reflection factory used by subclasses for creating field,
// method, and constructor accessors. Note that this is called
// very early in the bootstrapping process. 子类用于创建字段,方法和构造函数的反射工厂
static final ReflectionFactory reflectionFactory = AccessController.doPrivileged(
new sun.reflect.ReflectionFactory.GetReflectionFactoryAction()
);
// .....
}
方法如下:
其他的方法,本文不做展开,具体的请参阅文档,我们重点来看setAccessible方法。
setAccessible
- 值为true时代表反射的对象在使用时应该取消Java语言的访问检查。
- 值为false时代表反射的对象应该实施Java语言访问检查。
由于JDK的安全检查耗时较多,所以通过 setAccessible(true) 的方式关闭安全检查来提升反射速度。
Example
如何通过反射在访问类的private属性/方法/构造方法?
@Data
@NoArgsConstructor
@AllArgsConstructor
static class User {
private String name;
private Integer age;
private void print(String msg) {
System.out.println(msg);
System.out.println("hi,我是:" + name + ",年龄:" + age);
}
}
// 访问私有的成员变量
public static void main(String[] args) throws Exception {
User user = new User("Duansg", 18);
// 访问私有的成员变量
Field field = User.class.getDeclaredField("name");
field.setAccessible(true);
// Duansg
System.out.println(field.get(user));
}
// 改变私有的成员变量值
public static void main(String[] args) throws Exception {
User user = new User("Duansg", 18);
// 访问私有的成员变量
Field field = User.class.getDeclaredField("name");
field.setAccessible(true);
field.set(user, "马云");
// 马云
System.out.println(field.get(user));
}
// 访问private方法
public static void main(String[] args) throws Exception {
User user = new User("Duansg", 18);
Method method = User.class.getDeclaredMethod("print", String.class);
method.setAccessible(true);
method.invoke(user, "hi,我是晓断,今年22");
// hi,我是晓断,今年22
// hi,我是:Duansg,年龄:18
}
PropertyDescriptor
PropertyDescriptor类是一个属性的描述器,它表示JavaBean类通过存储器导出一个属性。详细的可了解Javad的内省机制,PropertyDescriptor主要方法:
从上图不难看出,它就是用于处理属性相关的信息,比如读写方法的设置和读取,获取属性的类型等等操作。FeatureDescriptor的顶层类,它的子类有PropertyDescriptor、MethodDescriptor、BeanDescriptor等等。它们的关系如下:
package java.beans;
/**
* A PropertyDescriptor describes one property that a Java Bean
* exports via a pair of accessor methods.
*/
public class PropertyDescriptor extends FeatureDescriptor {
private Reference<? extends Class<?>> propertyTypeRef;
private final MethodRef readMethodRef = new MethodRef();
private final MethodRef writeMethodRef = new MethodRef();
private Reference<? extends Class<?>> propertyEditorClassRef;
private boolean bound;
private boolean constrained;
// The base name of the method name which will be prefixed with the
// read and write method. If name == "foo" then the baseName is "Foo"
private String baseName;
private String writeMethodName;
private String readMethodName;
// ......
}
Constructs
/**
* Constructs a PropertyDescriptor for a property that follows
* the standard Java convention by having getFoo and setFoo
* accessor methods. Thus if the argument name is "fred", it will
* assume that the writer method is "setFred" and the reader method
* is "getFred" (or "isFred" for a boolean property). Note that the
* property name should start with a lower case character, which will
* be capitalized in the method names.
* 通过调用getFoo和setFoo存取方法,为符合标准Java约定的属性构造一个PropertyDescriptor。
* 如果参数名为"fred",则假定writer方法为"setFred",reader方法为"getFred"
*(对于boolean属性则为"isFred"),注意:属性名应该以小写字母开头,而方法名称中的首写字母将是大写的。
* @param propertyName The programmatic name of the property.
* @param beanClass The Class object for the target bean. For
* example sun.beans.OurButton.class.
* @exception IntrospectionException if an exception occurs during
* introspection.
*/
public PropertyDescriptor(String propertyName, Class<?> beanClass)
throws IntrospectionException {
this(propertyName, beanClass,
Introspector.IS_PREFIX + NameGenerator.capitalize(propertyName),
Introspector.SET_PREFIX + NameGenerator.capitalize(propertyName));
}
示例
@Data
@NoArgsConstructor
@AllArgsConstructor
class User {
private String name;
private Integer age;
}
public static void main(String[] args) throws Exception {
PropertyDescriptor descriptor = new PropertyDescriptor("name", User.class);
// class java.lang.String
System.out.println(descriptor.getPropertyType());
// null
System.out.println(descriptor.getPropertyEditorClass());
// public java.lang.String cn.gov.zcy.item.User.getName()
System.out.println(descriptor.getReadMethod());
// public void cn.gov.zcy.item.User.setName(java.lang.String)
System.out.println(descriptor.getWriteMethod());
}
getReadMethod
- 获取读取属性值的方法。
@Data
@NoArgsConstructor
@AllArgsConstructor
class User {
private String name;
private Integer age;
}
public static void main(String[] args) throws Exception {
PropertyDescriptor descriptor = new PropertyDescriptor("name", User.class);
// public java.lang.String cn.gov.zcy.item.User.getName()
System.out.println(descriptor.getReadMethod());
}
getWriteMethod
- 获取写入属性值的方法。
@Data
@NoArgsConstructor
@AllArgsConstructor
class User {
private String name;
private Integer age;
}
public static void main(String[] args) throws Exception {
Object catObj = User.class.newInstance();
//获取Name属性
PropertyDescriptor descriptor = new PropertyDescriptor("name", User.class);
//得到对应的写方法
Method method = descriptor.getWriteMethod();
//将值赋进这个类中
method.invoke(catObj,"Duansg");
User cat = (User)catObj;
// User(name=Duansg, age=null)
System.out.println(cat);
}