对象是什么时候被创建的

本文探讨了对象创建的过程,指出构造方法并非真正创建对象,而是负责初始化和赋值。对象在构造器调用前已存在于内存中,构造器主要任务是执行初始化和将对象地址赋给引用变量。理解对象创建的步骤有助于深入掌握Java对象实例化原理。

1. 大部分人的直觉:new+构造方法

首先我们思考一下我们如何创建一个对象,最普通的方式就是:new+构造方法。所以很正常,我们会认为做完这件事之后,一个对象就被创建了。其实这并没错,因为我们完成这件事之后我们就可以对该对象做各种操作:访问某些字段,调用某些方法等。但是这取决于,你怎么理解“创建对象”对象这件事。

2. 构造方法其实是一个很矛盾的存在

我们在构造方法中最常做的事情是这样的:

public ClassA(int a) {
	this.a = a;
}

这里的this,指代的就是当前对象。但是我们用构造方法来创建一个对象,我们的理解应该是:构造方法结束之后,该对象才被创建、实例化完成。那么为什么在构造方法内部就可以引用这个对象呢?

所以我们可以推断出来:在构造器被invoke之前,这个对象的空间就已经存在了。只是可能我们没有办法引用到。而真正开辟了这块空间的指令就是new

3. 构造器在对象被创建之后才执行

除了上述的逻辑推理和思考,我们也可以用代码来验证:“在构造器被invoke之前,对象就已经存在了”这件事。

具体的代码可以看这篇:JAVA对象实例是何时被创建的?

4. 构造器做了什么事情?

好吧,那既然构造器没有给这个对象开辟内存空间,那它做了什么事情呢?

构造器其实并不是严格意义上的“创建了一个对象”,它做的任务有两个:

  1. 给对象进行构造器内部的初始化
  2. 把堆内对象的首地址赋给栈内的引用变量

5. 对象创建的步骤

第一步:判断类是否被加载、连接、初始化(类加载的过程,具体请看JVM类加载器的相关知识)。【这里是clinit方法,所以只涉及到类变量和静态方法】都在方法区里,因为涉及的是类。

第二步:为对象分配内存,这个就叫new;就是在堆里分配一块空间,这个时候分配多少已经可以知道了。如果是int这种就4个字节double就8个字节。如果是引用类型的话,也是4个字节,因为它只起一个引用的作用,没有真正把对象放在这里。

第三步:处理并发安全问题;因为对象的创建还是比较频繁的,肯定不能两个对象都抢同一块内存吧。

第四步:初始化分配到的空间;所有属性设置默认值,保证对象实例化字段在不赋值的时可以直接使用。

​ 属性的默认初始化【4】,显式初始化/代码块中初始化【6】,构造器初始化【6】

第五步:设置对象的对象头。对象头里记录一下自己所属的类->指向方法区。还记录一些乱七八糟的东西hashCode, gc信息,锁之类的。

第六步:执行init()方法进行初始化:显式初始化,这里才涉及到类的构造器的调用。

在Java程序的视角来看,初始化才正式开始。初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内的对象的首地址赋值给引用变量。

因此一般来说(由字节码中是否跟随者invokespecial指令所决定),new指令之后会接着就是执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全创建出来。

6. 总结

说到这里,我们得出结论:真正为对象开辟内存空间的指令是new,构造器在被调用之前该对象就已经存在了。

构造器其实并不是严格意义上的“创建了一个对象”,它做的任务有两个:1. 给对象进行构造器内部的初始化 2. 把堆内对象的首地址赋给栈内的引用变量。

构造器本身就是一个很矛盾的东西:你在里面可以写this.属性 = 形参这样初始化。this指代的是什么?其实就是当前对象。而我们以往认识的构造器是什么:是用来创建对象的,我们觉得,构造器内所有的语句执行完成了,这个对象才算是被创建了。但为什么可以在构造器内部直接指明当前对象呢?

所以推断出来:在构造器被invoke之前,这个对象的空间就已经存在了。只是可能我们没有办法引用到。而真正开辟了这块空间的指令就是new

我们为什么总认为:是构造器创建了这个对象呢?

从人类而非机器的角度来思考这件事,说“使用构造器创建了一个对象”是完全可以理解的,这取决于你如何定义“一个对象被创建”这件事

如果你认为,一个对象有了自己的内存空间就算作被创建了,那么构造器确实没有为对象分配空间,对象是在构造器被invoke之前创建的。

但是对于一个人类来说,你想要使用这个对象,必须要通过构造器给引用变量赋值的方式,才能使用对象中的字段和方法。并且构造器之前对于对象的修饰和默认初始化(比如int默认初始化为0)是远远达不到我们使用对象的标准的,我们想要达到的,是通过构造器,把我们想要的参数传入,并赋值给字段。每个字段根据自我意愿赋值后的这样一个对象,才是我们希望去使用的。

<think>好的,我需要回答用户关于Spring Security中UserDetails对象创建时机的问题。首先,我应该回顾一下用户提供的引用内容,从中提取相关信息。 引用1提到,当没有配置用户源时,Spring Security会自动创建一个用户,用户名为user,密码是自动生成的。这发生在应用启动时,属于饿汉式加载。同时,创建的User实例的密码会去掉{noop}前缀。这说明默认情况下,UserDetails对象是在应用启动时自动生成的。 引用3和引用4涉及到DaoAuthenticationProvider的retrieveUser方法,这个方法在认证过程中被调用,用于根据用户名加载用户信息。这说明在认证的时候,UserDetails对象会被创建或获取,例如从数据库或其他用户存储中查询。 所以,UserDetails对象创建时机可能有两个情况:一是在应用启动时自动生成默认用户;二是在用户认证时通过UserDetailsService加载用户信息,这时候可能从配置的用户源中创建UserDetails对象。 用户的问题需要明确这两个不同的创建时机。一个是默认自动创建,另一个是根据配置的用户源在认证时动态创建。需要进一步确认是否有其他情况,例如通过配置Bean提前加载用户,或者在启动时通过UserDetailsManager初始化用户。 总结起来,UserDetails对象创建时机包括启动时的默认生成和认证时的动态加载。需要结合用户提供的引用来组织答案,确保准确引用相关引用编号。</think>Spring Security中UserDetails对象创建时机主要分为以下两种场景: ### 一、默认自动创建(启动时生成) 当应用未配置任何用户源时,框架会在启动阶段自动生成默认用户: $$UserDetails\ user = User.withUsername("user").password(generatedPassword).roles("none").build()$$ 此时会生成用户名为"user"的账户,密码采用随机UUID格式,并自动去除密码编码前缀{noop}。该行为属于饿汉式初始化,确保系统始终存在可用用户凭证[^1]。 ### 二、认证时动态创建 当配置了用户源(如内存用户、数据库用户等),UserDetails对象通过认证流程创建: ```java // 认证核心流程伪代码 protected Authentication authenticate(Authentication authentication) { String username = authentication.getName(); UserDetails user = retrieveUser(username, authentication); // 调用UserDetailsService // 后续密码验证逻辑... } ``` 1. **DaoAuthenticationProvider**通过`retrieveUser()`方法触发用户加载 2. 该方法委托给`UserDetailsService`接口实现类 3. 实际创建时机取决于具体实现: - 内存用户:启动时预加载 - JDBC用户:首次认证时查询数据库 - LDAP用户:实时连接目录服务获取 ### 三、自定义创建方式 开发者可通过以下方式主动创建: ```java @Bean public UserDetailsManager users() { UserDetails admin = User.builder() .username("admin") .password("{bcrypt}$2a$10$...") .roles("ADMIN") .build(); return new InMemoryUserDetailsManager(admin); } ``` 这种配置方式会在Spring容器初始化阶段创建UserDetails对象[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值