Constructor 与对象创建
大翔(项目总监):"昨天我看Spring源码,它那个Bean容器初始化的时候,是怎么做到
new一个它根本不认识的类的?"白歌(架构师):"靠
Constructor.newInstance()。反射不只让你'看'一个类长什么样,还能动态调用构造方法创建对象。哪怕这个类是private的,用setAccessible(true)也能强行攻破——这也是单例模式可以被反射破坏的根源。"小崔(后端开发):"等等,
private构造也能破?那封装还有什么意义?"白歌:"封装是写给遵守规则的人看的,反射是框架级别的'特权通道'。Spring不会拿反射去破坏业务逻辑的单例,但JUnit必须用反射调用你的
private测试方法。"
Constructor API 概述
java.lang.reflect.Constructor<T> 是反射中用于描述类构造方法的类型。每个Constructor对象代表类的一个构造方法。
| 获取方法 | 返回 | 说明 |
|---|---|---|
Class.getConstructor(Class<?>... paramTypes) | Constructor<T> | 获取public构造方法,按参数类型匹配 |
Class.getDeclaredConstructor(Class<?>... paramTypes) | Constructor<T> | 获取任意访问权限的构造方法(private也可以) |
Class.getConstructors() | Constructor<?>[] | 获取所有public构造方法 |
Class.getDeclaredConstructors() | Constructor<?>[] | 获取所有构造方法(含private) |
| Constructor方法 | 返回 | 说明 |
|---|---|---|
newInstance(Object... initargs) | T | 调用构造方法创建对象 |
getParameterTypes() | Class<?>[] | 获取参数类型数组 |
getParameterCount() | int | 获取参数个数(JDK 8新增) |
getModifiers() | int | 获取修饰符编码 |
getName() | String | 返回构造方法名(即类名) |
setAccessible(boolean flag) | void | 设置是否绕过访问控制检查 |
isAccessible() | boolean | 判断是否可访问 |
isVarArgs() | boolean | 判断是否为可变参数构造 |
getConstructorvsgetDeclaredConstructor:前者只返回public构造,后者返回所有访问级别的构造(private/protected/默认/public)。这是Java反射API的通用命名惯例——带有Declared的方法返回本类直接声明的所有成员,不限于public。
代码示例一:通用对象工厂
场景:飞翔科技正在开发一个轻量级插件系统,需要根据配置文件中的类名和构造参数动态创建插件实例。白歌让小崔先写一个通用对象工厂的demo。
package com.feixiang.reflect;
import java.lang.reflect.Constructor;
/**
* 飞翔科技 - 通用对象工厂
* 需求:根据类名和构造参数,动态创建任意类的实例
*/
public class ObjectFactory {
/**
* 通用对象创建方法
* @param className 全限定类名
* @param paramTypes 构造参数类型(用于匹配构造方法)
* @param paramValues 构造参数值
* @return 创建的对象实例
*/
@SuppressWarnings("unchecked")
public static <T> T create(String className, Class<?>[] paramTypes, Object[] paramValues)
throws Exception {
// 1. 加载类
Class<?> clazz = Class.forName(className);
// 2. 获取匹配的构造方法
Constructor<?> constructor = clazz.getDeclaredConstructor(paramTypes);
// 3. 调用构造方法创建对象
return (T) constructor.newInstance(paramValues);
}
// ========== 测试用类 ==========
public static class DatabaseConfig {
private String host;
private int port;
private String dbName;
public DatabaseConfig(String host, int port, String dbName) {
this.host = host;
this.port = port;
this.dbName = dbName;
}
@Override
public String toString() {
return String.format("DatabaseConfig{host='%s', port=%d, dbName='%s'}", host, port, dbName);
}
}
public static class CacheConfig {
private String type;
private int maxSize;
// 无参构造
public CacheConfig() {
this.type = "default";
this.maxSize = 100;
}
// 有参构造
public CacheConfig(String type, int maxSize) {
this.type = type;
this.maxSize = maxSize;
}
@Override
public String toString() {
return String.format("CacheConfig{type='%s', maxSize=%d}", type, maxSize);
}
}
public static void main(String[] args) throws Exception {
System.out.println("========== 飞翔科技通用对象工厂测试 ==========\n");
// 测试1:有参构造创建对象
System.out.println(">>> 测试1: 创建 DatabaseConfig(有参构造)");
DatabaseConfig dbConfig = create(
"com.feixiang.reflect.ObjectFactory$DatabaseConfig",
new Class<?>[]{String.class, int.class, String.class},
new Object[]{"192.168.1.100", 3306, "feixiang_erp"}
);
System.out.println("创建成功: " + dbConfig);
// 测试2:无参构造创建对象
System.out.println("\n>>> 测试2: 创建 CacheConfig(无参构造)");
CacheConfig cacheConfig1 = create(
"com.feixiang.reflect.ObjectFactory$CacheConfig",
new Class<?>[]{}, // 空参数类型数组
new Object[]{} // 空参数值数组
);
System.out.println("创建成功: " + cacheConfig1);
// 测试3:另一个有参构造
System.out.println("\n>>> 测试3: 创建 CacheConfig(有参构造)");
CacheConfig cacheConfig2 = create(
"com.feixiang.reflect.ObjectFactory$CacheConfig",
new Class<?>[]{String.class, int.class},
new Object[]{"redis", 500}
);
System.out.println("创建成功: " + cacheConfig2);
}
}
运行输出:
========== 飞翔科技通用对象工厂测试 ==========
>>> 测试1: 创建 DatabaseConfig(有参构造)
创建成功: DatabaseConfig{host='192.168.1.100', port=3306, dbName='feixiang_erp'}
>>> 测试2: 创建 CacheConfig(无参构造)
创建成功: CacheConfig{type='default', maxSize=100}
>>> 测试3: 创建 CacheConfig(有参构造)
创建成功: CacheConfig{type='redis', maxSize=500}
setAccessible(true):绕过访问控制
setAccessible(true) 是反射中最具争议也最常用的方法之一。它关闭Java的访问控制检查,允许访问private成员。
// 假设有一个工具类,构造方法私有(防止外部实例化)
public class SecurityManager {
private String secretKey;
private SecurityManager(String secretKey) { // private 构造!
this.secretKey = secretKey;
}
public String getSecretKey() {
return secretKey;
}
}
// 反射强行调用 private 构造
Constructor<SecurityManager> constructor = SecurityManager.class
.getDeclaredConstructor(String.class);
constructor.setAccessible(true); // 关键:关闭访问检查
SecurityManager instance = constructor.newInstance("mySecret123");
System.out.println(instance.getSecretKey()); // 输出:mySecret123
它的底层实现:HotSpot在每次反射调用前检查AccessibleObject.override标志位。如果为true,跳过Reflection.verifyMemberAccess()的权限验证。这个检查在HotSpot源码reflection.cpp中的Reflection::verify_class_access()里完成。
代码示例二:私有构造破解与单例防御
场景:白歌在飞翔科技内部培训中演示了反射如何攻破经典单例模式,并教大家如何加防御代码。
package com.feixiang.reflect;
import java.lang.reflect.Constructor;
/**
* 飞翔科技 - 反射攻击单例演示
* 场景:内部安全培训——反射如何绕过 private 构造
*/
public class SingletonAttackDemo {
// ========== 经典饿汉单例(无防御) ==========
public static class VulnerableSingleton {
private static final VulnerableSingleton INSTANCE = new VulnerableSingleton();
private VulnerableSingleton() {
System.out.println("[VulnerableSingleton] 构造方法调用");
}
public static VulnerableSingleton getInstance() {
return INSTANCE;
}
public void doSomething() {
System.out.println("[VulnerableSingleton] 执行操作,实例hash: " + this.hashCode());
}
}
// ========== 带反射防御的单例 ==========
public static class SafeSingleton {
private static final SafeSingleton INSTANCE = new SafeSingleton();
private static boolean constructed = false;
private SafeSingleton() {
if (constructed) {
throw new RuntimeException("单例模式禁止通过反射创建第二个实例!");
}
constructed = true;
System.out.println("[SafeSingleton] 构造方法调用(首次)");
}
public static SafeSingleton getInstance() {
return INSTANCE;
}
public void doSomething() {
System.out.println("[SafeSingleton] 执行操作,实例hash: " + this.hashCode());
}
}
public static void main(String[] args) throws Exception {
System.out.println("========== 飞翔科技单例攻击与防御演练 ==========\n");
// ===== 攻击1:攻击无防御单例 =====
System.out.println("--- 攻击目标1: VulnerableSingleton(无防御)---");
VulnerableSingleton normal1 = VulnerableSingleton.getInstance();
System.out.println("正常获取实例: " + normal1.hashCode());
// 反射攻击
Constructor<VulnerableSingleton> vCons = VulnerableSingleton.class
.getDeclaredConstructor();
vCons.setAccessible(true); // 关闭访问检查
VulnerableSingleton attacked = vCons.newInstance();
System.out.println("反射创建实例: " + attacked.hashCode());
System.out.println("是否为同一实例? " + (normal1 == attacked ? "是" : "否——单例被攻破!"));
System.out.println();
// ===== 攻击2:攻击带防御的单例 =====
System.out.println("--- 攻击目标2: SafeSingleton(有防御)---");
SafeSingleton normal2 = SafeSingleton.getInstance();
System.out.println("正常获取实例: " + normal2.hashCode());
try {
Constructor<SafeSingleton> sCons = SafeSingleton.class
.getDeclaredConstructor();
sCons.setAccessible(true);
SafeSingleton attacked2 = sCons.newInstance(); // 此处抛出异常
System.out.println("反射创建实例: " + attacked2.hashCode());
} catch (Exception e) {
System.out.println("攻击被阻止!异常: " + e.getCause().getMessage());
}
System.out.println("\n========== 结论 ==========");
System.out.println("无防御单例可被反射轻易攻破。");
System.out.println("防御方法:在构造方法中加标志位判断,拒绝二次创建。");
}
}
运行输出:
========== 飞翔科技单例攻击与防御演练 ==========
--- 攻击目标1: VulnerableSingleton(无防御)---
[VulnerableSingleton] 构造方法调用
正常获取实例: 366712642
[VulnerableSingleton] 构造方法调用
反射创建实例: 1829164700
是否为同一实例? 否——单例被攻破!
--- 攻击目标2: SafeSingleton(有防御)---
[SafeSingleton] 构造方法调用(首次)
正常获取实例: 2018699554
攻击被阻止!异常: 单例模式禁止通过反射创建第二个实例!
========== 结论 ==========
无防御单例可被反射轻易攻破。
防御方法:在构造方法中加标志位判断,拒绝二次创建。
深度原理:Constructor.newInstance() 的调用链
Inflation 机制
HotSpot JVM对反射调用有一个Inflation(膨胀)机制:
- 前15次调用:使用
NativeConstructorAccessorImpl,走JNI(Native代码)。启动开销低。 - 第16次起:JVM自动生成一个
GeneratedConstructorAccessor,使用sun.misc.Unsafe或字节码直接调用构造方法。不再走JNI,性能显著提升。
可以用-Dsun.reflect.inflationThreshold=N调整这个阈值。设-Dsun.reflect.noInflation=true直接跳过Native阶段。
Constructor 参数与修饰符探查
package com.feixiang.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter; // JDK 8 新增
/**
* 飞翔科技 - 构造方法探查器
* 打印类的所有构造方法签名和参数信息
*/
public class ConstructorInspector {
public static void inspect(Class<?> clazz) {
System.out.println("========== " + clazz.getSimpleName() + " 构造方法列表 ==========");
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor<?> cons : constructors) {
// 修饰符
String modifier = Modifier.toString(cons.getModifiers());
System.out.print(" " + modifier + " " + clazz.getSimpleName() + "(");
// JDK 8 可以直接获取参数名(编译时需加 -parameters 参数)
Parameter[] params = cons.getParameters();
for (int i = 0; i < params.length; i++) {
Parameter p = params[i];
String paramName = p.isNamePresent() ? p.getName() : "arg" + i;
System.out.print(p.getType().getSimpleName() + " " + paramName);
if (i < params.length - 1) {
System.out.print(", ");
}
}
System.out.println(")");
// 可变参数标记
if (cons.isVarArgs()) {
System.out.println(" ↳ 可变参数构造");
}
}
System.out.println("总构造方法数: " + constructors.length);
System.out.println();
}
public static void main(String[] args) {
// 测试:探查 ArrayList 的构造方法
inspect(java.util.ArrayList.class);
// 测试:探查自定义类
inspect(java.util.HashMap.class);
}
}
运行输出:
========== ArrayList 构造方法列表 ==========
public ArrayList(int arg0)
public ArrayList()
public ArrayList(Collection arg0)
总构造方法数: 3
========== HashMap 构造方法列表 ==========
public HashMap(int arg0, float arg1)
public HashMap(int arg0)
public HashMap()
public HashMap(Map arg0)
总构造方法数: 4
提示:要获取真正的参数名(非
arg0),编译时需要加javac -parameters参数,或在IDE中开启"Store information about method parameters"选项。JDK 8之前参数名在字节码中默认不保留。
易错场景与纠正
反例一:getConstructor 找不到非 public 构造
小崔想通过反射创建一个具有默认(包级私有)构造函数的类的实例:
// 包 com.feixiang.internal 中的类
class PackagePrivateService { // 默认访问级别
PackagePrivateService() { // 默认访问级别构造
System.out.println("服务启动");
}
}
// ❌ 错误:getConstructor 只能找到 public 构造
public class MainInDifferentPackage {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.feixiang.internal.PackagePrivateService");
Constructor<?> cons = clazz.getConstructor(); // 抛出 NoSuchMethodException!
cons.newInstance();
}
}
纠正:
// ✅ 正确:使用 getDeclaredConstructor 获取任意访问级别的构造
Constructor<?> cons = clazz.getDeclaredConstructor(); // 能找到非 public 构造
// 如果调用方在不同包且构造不是 public,还需要
cons.setAccessible(true); // 绕过访问控制
cons.newInstance();
反例二:newInstance 参数类型不匹配
// 类定义
public class Employee {
public Employee(String name, int age) { ... }
}
// ❌ 错误:参数类型不精确匹配
Constructor<?> cons = clazz.getConstructor(String.class, Integer.class); // 写成了包装类
// 抛出 NoSuchMethodException!因为构造方法是 (String, int) 不是 (String, Integer)
// ❌ 错误:参数值顺序或个数不对
Constructor<?> cons = clazz.getConstructor(String.class, int.class);
cons.newInstance(25, "小崔"); // 参数顺序反了!抛出 IllegalArgumentException
纠正:
// ✅ 正确:精确匹配基本类型和包装类型
Constructor<?> cons = clazz.getConstructor(String.class, int.class); // int.class 非 Integer.class
cons.newInstance("小崔", 25); // 顺序正确
// ✅ 也可以通过 getDeclaredConstructors() 遍历找到想要的构造
for (Constructor<?> c : clazz.getDeclaredConstructors()) {
if (c.getParameterCount() == 2) { ... }
}
反例三:尝试用 newInstance() 调用有参构造而不传参
// ❌ 错误:Class.newInstance() 只能调用无参构造——JDK 9 已弃用!
Class<?> clazz = SomeClass.class;
SomeClass obj = (SomeClass) clazz.newInstance(); // 如果 SomeClass 没有无参构造,抛异常
纠正:
// ✅ 正确:通过 Constructor.newInstance() 可传参
Constructor<SomeClass> cons = clazz.getDeclaredConstructor(String.class, int.class);
SomeClass obj = cons.newInstance("参数1", 42);
面试考点
Q1:getConstructor 和 getDeclaredConstructor 有什么区别?
getConstructor(Class<?>... params)只能获取public构造方法,并且包括从父类继承的public构造。getDeclaredConstructor(Class<?>... params)可以获取本类中声明的任何访问级别的构造方法(private/默认/protected/public),但不包括继承的。实际开发中访问非public构造时使用后者。
Q2:setAccessible(true) 是永久生效的吗?对性能有何影响?
setAccessible(true)对该Constructor对象是永久生效的——一旦设置,后续通过该对象的所有调用都不再检查访问权限。但这只影响这一个Constructor对象,其他Constructor对象不受影响。从性能角度看,setAccessible(true)减少了每次调用时的安全检查开销,反而有微小性能提升。但核心性能瓶颈不在安全检查,而在JNI调用和参数装箱拆箱。
Q3:反射可以调用 private 构造,那单例模式是否毫无意义?
反射确实可以破坏经典单例模式(如饿汉、懒汉、双重检查锁等),但可以在构造方法中添加防御代码(如标志位判断抛异常)。此外,枚举单例天然免疫反射攻击——
Constructor.newInstance()源码中对枚举类型有专门判断:if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects");因此,如果追求绝对的单例安全,使用枚举实现是最佳实践。
Q4:Constructor.newInstance() 和 Class.newInstance() 有何不同?
①
Class.newInstance()只能调用无参构造,且要求构造方法是public可访问;Constructor.newInstance()可调用任意构造。 ②Class.newInstance()会直接向上抛出构造方法中可能抛出的受检异常(不包装);Constructor.newInstance()会将异常包装在InvocationTargetException中。 ③ 从JDK 9开始,Class.newInstance()已被标记为@Deprecated,推荐统一使用Constructor.newInstance()。