一、云服务的定义

云 = 一堆联网的高性能电脑 + 可以随时调用的资源。
云服务的核心是:
“把这些服务器的资源(算力、存储、网络等)拆分成服务提供给用户。”
也就是说:你不需要自己买机器,只要通过互联网调用这些云里的资源。
你看到中间的:
Alibaba(阿里云)
Huawei(华为云)
Baidu(百度智能云)
网易云(网易云服务)
这些都是提供云服务的公司。
它们都有自己的数据中心,里面放了成千上万台服务器。
然后把计算力、存储空间、短信接口、AI模型等资源封装成“服务”供开发者使用。

二、阿里云OSS
图片存储用什么?
如果你要存图片(比如网站头像、商品图),就会用“云存储(Cloud Storage)”这一类服务。
不同公司叫法不同:
阿里云 → OSS(Object Storage Service)
腾讯云 → COS
华为云 → OBS
AWS → S3
图片上传到云端后,会生成一个网址链接:
https://my-bucket.oss-cn-hangzhou.aliyuncs.com/pic001.jpg
别人访问这个链接就能看到图片。

三、用第三方服务的通用思路
3-1、什么是“第三方服务”
第三方服务(Third-party service) = 别人提供的、你可以通过网络去调用的功能或资源。
比如:
阿里云OSS → 图片/文件存储
微信API → 发消息
支付宝API → 支付
OpenAI API → 调用ChatGPT
Google Maps API → 获取地图和定位
这些都不是你自己开发的,而是别人已经开发好、封装好的功能,你只需要“调用”。
3-2、通用的使用思路(五步法)
可以记住这个口诀:
“申请 → 连接 → 调用 → 接收 → 封装”
① 申请(Apply)
先到第三方平台注册账号,开通对应服务。
比如要用阿里云OSS,你得先:
注册阿里云账号
开通OSS服务
创建一个“Bucket”(相当于文件夹,是一个存储空间)
拿到 AccessKeyId 和 AccessKeySecret
这些是“身份凭证”,就像钥匙,证明你是谁、你能做什么。
当你在阿里云控制台(或 RAM 控制台)创建 AccessKey 时,会看到两部分:
AccessKey ID(永久可见,可查询)
AccessKey Secret(只显示一次)
这时系统会提示:
“请妥善保存 AccessKey Secret,离开此页面后将无法再次查看。”
你应该:立即保存到安全地方(例如本地加密文件、密码管理器等)
AccessKey 默认没有时间限制!!!
阿里云官方说明:
AccessKey 没有默认有效期,只要你不手动禁用或删除,它就一直有效。
也就是说:
创建后可以长期使用;
不会自动到期;
不会因为“时间太久”而失效。
② 连接(Connect)
接下来要让你的程序能连上这个服务。
一般有两种方式:
| 方式 | 说明 |
|---|---|
| SDK | 官方提供的开发包,比如 aliyun-oss-sdk、openai 等 |
| API | 直接用 HTTP 请求访问(通常是 RESTful 接口) |
连接时通常需要配置:
endpoint: https://oss-cn-hangzhou.aliyuncs.com
accessKeyId: <你的AccessKeyId>
accessKeySecret: <你的AccessKeySecret>
注意,因为要调用SDK接口,就需要maven安装依赖了,直接在阿里云的官网找依赖的gav即可。
③ 调用(Call)
连接上以后,就可以用服务的接口。
例如调用上传图片的接口:
ossClient.put_object("my-bucket", "photo.jpg", file)
或者HTTP接口:
POST https://api.openai.com/v1/chat/completions
Authorization: Bearer
这些接口通常都有固定格式:
URL + Method(GET/POST)+ 参数 + 认证头
具体的接口调用的参数,返回,示例,均可从官网获取:

④ 接收(Receive)
第三方服务会返回一个结果(通常是JSON格式)。
例如:
{"status": "success","url": "https://my-bucket.oss-cn-hangzhou.aliyuncs.com/photo.jpg"
}
你只要解析返回值就能拿到你想要的信息(比如图片URL、生成文本等)。
⑤ 封装(Wrap)
最后一步,是把这个功能整合进自己的系统里。
比如:
用户上传头像 → 你的系统调用OSS → 得到URL → 存数据库。
用户输入文字 → 你的系统调用ChatGPT API → 返回生成结果。
也就是说,你不是“直接用”,而是把它作为你系统的一个“模块”。
3-3、思维结构总结图
你的程序↓
[SDK / API 调用层]↓
网络请求(带认证信息)↓
第三方服务(云 / 平台)↓
返回数据(JSON)↓
你的系统处理显示
3-4、常见第三方服务类别
| 类型 | 举例 | 常见用途 |
|---|---|---|
| 云计算 / 存储 | 阿里云、腾讯云、AWS | 部署网站、存图片 |
| AI 服务 | OpenAI、百度千帆、阿里PAI | 文本生成、语音识别 |
| 支付服务 | 支付宝、Stripe、PayPal | 付款、账单 |
| 地图服务 | 高德、Google Maps | 定位、导航 |
| 通讯服务 | Twilio、阿里云短信 | 发短信、推送通知 |
无论哪种服务,思路都一样:
注册 → 获取Key → SDK/API连接 → 调用接口 → 用返回结果。
四、上传文件SDK代码示例
4-1、添加依赖

4-2、使用官方SDK,改造为Utils工具
1、定义一个配置类(OssConfiguration)
package org.jeecg.config.oss;
import org.jeecg.common.util.oss.OssBootUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/*** 云存储 配置* @author: jeecg-boot*/
@Configuration
// 只有当配置文件里存在 jeecg.oss.endpoint 时,这个配置类才会生效。
@ConditionalOnProperty(prefix = "jeecg.oss", name = "endpoint")
public class OssConfiguration {@Value("${jeecg.oss.endpoint}")private String endpoint;@Value("${jeecg.oss.accessKey}")private String accessKeyId;@Value("${jeecg.oss.secretKey}")private String accessKeySecret;@Value("${jeecg.oss.bucketName}")private String bucketName;@Value("${jeecg.oss.staticDomain:}")private String staticDomain;@Beanpublic void initOssBootConfiguration() {OssBootUtil.setEndPoint(endpoint);OssBootUtil.setAccessKeyId(accessKeyId);OssBootUtil.setAccessKeySecret(accessKeySecret);OssBootUtil.setBucketName(bucketName);OssBootUtil.setStaticDomain(staticDomain);}
}
这个类的职责是:
从配置文件里读取阿里云 OSS 的参数,然后统一注入到工具类里。
也就是:
endpoint、accessKey、secretKey、bucketName、staticDomain
这些值本来散落在代码中;现在用一个配置类,集中管理、解耦代码与配置。
(1)为什么要用 @Configuration + @Bean
Spring Boot 的机制是:
启动时扫描带
@Configuration的类;调用类中
@Bean方法;把结果注册进 Spring 容器。
这意味着:
你在别处可以直接使用这些配置(通过
@Autowired);或者像这里一样,初始化静态工具类。

这行代码保证了:
Spring 一启动,OssBootUtil 的配置就准备好了。
其他类可以直接调用OssBootUtil.upload()而不需要再手动传参。
(2)为什么加 @ConditionalOnProperty
@ConditionalOnProperty(prefix = "jeecg.oss", name = "endpoint")
意思是:
只有当配置文件里存在
jeecg.oss.endpoint时,这个配置类才会生效。
好处是:
如果某个环境没有配置 OSS,就不加载这段逻辑;
防止项目启动时报错(因为没有配置项);
方便本地开发或测试环境不接 OSS。
2、配置application.yml:

这样,项目切换环境时,只改配置文件,不动任何 Java 代码。
这就是 “配置与逻辑分离”。
3、编写上传文件工具类OssBootUtil.java
/*** 静态工具类(所有属性和方法都是 static 静态的;)* 设计思路是 任何地方都能直接调用 OssBootUtil.upload(...),不需要注入;** @Description: 阿里云 oss 上传工具类(高依赖版)* @Date: 2019/5/10* @author: jeecg-boot*/
@Slf4j
public class OssBootUtil {private static String endPoint;private static String accessKeyId;private static String accessKeySecret;private static String bucketName;private static String staticDomain;public static void setEndPoint(String endPoint) {OssBootUtil.endPoint = endPoint;}public static void setAccessKeyId(String accessKeyId) {OssBootUtil.accessKeyId = accessKeyId;}public static void setAccessKeySecret(String accessKeySecret) {OssBootUtil.accessKeySecret = accessKeySecret;}public static void setBucketName(String bucketName) {OssBootUtil.bucketName = bucketName;}public static void setStaticDomain(String staticDomain) {OssBootUtil.staticDomain = staticDomain;}public static String getStaticDomain() {return staticDomain;}public static String getEndPoint() {return endPoint;}public static String getAccessKeyId() {return accessKeyId;}public static String getAccessKeySecret() {return accessKeySecret;}public static String getBucketName() {return bucketName;}public static OSSClient getOssClient() {return ossClient;}/*** oss 工具客户端*/private static OSSClient ossClient = null;/*** 上传文件至阿里云 OSS* 文件上传成功,返回文件完整访问路径* 文件上传失败,返回 null** @param file 待上传文件* @param fileDir 文件保存目录* @return oss 中的相对文件路径*/public static String upload(MultipartFile file, String fileDir,String customBucket) throws Exception {//update-begin-author:liusq date:20210809 for: 过滤上传文件类型SsrfFileTypeFilter.checkUploadFileType(file);//update-end-author:liusq date:20210809 for: 过滤上传文件类型String filePath = null;initOss(endPoint, accessKeyId, accessKeySecret);StringBuilder fileUrl = new StringBuilder();String newBucket = bucketName;if(oConvertUtils.isNotEmpty(customBucket)){newBucket = customBucket;}try {//判断桶是否存在,不存在则创建桶if(!ossClient.doesBucketExist(newBucket)){ossClient.createBucket(newBucket);}// 获取文件名String orgName = file.getOriginalFilename();if("" == orgName){orgName=file.getName();}orgName = CommonUtils.getFileName(orgName);String fileName = orgName.indexOf(".")==-1?orgName + "_" + System.currentTimeMillis():orgName.substring(0, orgName.lastIndexOf(".")) + "_" + System.currentTimeMillis() + orgName.substring(orgName.lastIndexOf("."));if (!fileDir.endsWith(SymbolConstant.SINGLE_SLASH)) {fileDir = fileDir.concat(SymbolConstant.SINGLE_SLASH);}//update-begin-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击fileDir=StrAttackFilter.filter(fileDir);//update-end-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击fileUrl = fileUrl.append(fileDir + fileName);if (oConvertUtils.isNotEmpty(staticDomain) && staticDomain.toLowerCase().startsWith(CommonConstant.STR_HTTP)) {filePath = staticDomain + SymbolConstant.SINGLE_SLASH + fileUrl;} else {filePath = "https://" + newBucket + "." + endPoint + SymbolConstant.SINGLE_SLASH + fileUrl;}PutObjectResult result = ossClient.putObject(newBucket, fileUrl.toString(), file.getInputStream());// 设置权限(公开读)
// ossClient.setBucketAcl(newBucket, CannedAccessControlList.PublicRead);if (result != null) {log.info("------OSS文件上传成功------" + fileUrl);}} catch (IOException e) {log.error(e.getMessage(),e);return null;}catch (Exception e) {log.error(e.getMessage(),e);return null;}return filePath;}
}
4、总结关系图

这个结构分层其实是 Spring Boot 的“配置分离”思想 的体现。
五、JeecgBoot文件上传结构讲解
不可以直接把配置的值写在application.yml,然后在OssBootUtil.java中用@Value("${jeecg.oss.endpoint}")获取,不也能实现Spring Boot 的“配置分离”思想吗?
这种方式——在 OssBootUtil 里直接用 @Value("${jeecg.oss.endpoint}") 注入配置——确实可以工作,也确实是“配置分离”的一种形式。
但为什么大型项目(例如 JeecgBoot)还要再包一层 OssConfiguration 呢?
原因在于 “Spring 管理范围、静态工具类限制、解耦与可维护性”。
5-1、先确认:你说的方法是可行的
如果你把 OssBootUtil 写成普通的 Spring Bean(即不是全静态的工具类),确实可以:
@Component
public class OssBootUtil {@Value("${jeecg.oss.endpoint}")private String endpoint;@Value("${jeecg.oss.accessKey}")private String accessKeyId;@Value("${jeecg.oss.secretKey}")private String accessKeySecret;public String upload(...) { ... }
}
然后在别的地方 @Autowired 进来使用:
@Autowired
private OssBootUtil ossBootUtil;
这完全符合 Spring Boot “配置分离”的思路。
但是!现在这个类就必须是 由 Spring 管理的 Bean。
5-2、问题来了:原始版本里它不是 Bean,而是静态工具类
在 JeecgBoot 的结构中:
public class OssBootUtil {private static String endPoint;private static OSSClient ossClient;...public static String upload(MultipartFile file, String fileDir, String customBucket) { ... }
}
特点:
所有属性和方法都是 static 静态的;
设计思路是 任何地方都能直接调用
OssBootUtil.upload(...),不需要注入;它本身 不是 Spring Bean;
所以 Spring 无法对它做依赖注入(
@Value、@Autowired都不生效)。
这就是为什么必须通过一个配置类来在系统启动时手动注入这些静态字段。
也就是说:
OssConfiguration就是专门给静态工具类“喂配置”的桥梁。
5-3、为什么不直接改成非静态 Bean?
这是一个“设计取舍”:
| 方式 | 优点 | 缺点 |
|---|---|---|
| 静态工具类 + 配置类注入 | 用法简单,任何地方都能直接调用 OssBootUtil.upload(),兼容旧代码 | 不符合 IoC 思想,难单元测试,不易扩展 |
| 非静态 Bean + @Value 注入 | 完全符合 Spring IoC,可 mock,可切换实现 | 所有使用处都必须 @Autowired 进来,老代码需要改动 |
JeecgBoot 是个老牌框架,它保留了静态工具类的写法,
因为很多地方可能直接用:
String url = OssBootUtil.upload(file, "images/", null);
这样就不需要依赖注入。
但为了仍然能从配置文件里读到配置,就引入了 OssConfiguration 来在 Spring 启动时初始化静态变量。
5-4、总结对比
| 设计方式 | 是否是 Bean | @Value 可用吗 | 调用方式 | 特点 |
|---|---|---|---|---|
| 你说的方式:@Value 直接注入 OssBootUtil | ✅ 是 Bean | ✅ 可用 | @Autowired 调用 | 完全 Spring 化 |
| Jeecg 方式:OssBootUtil 静态 + OssConfiguration 初始化 | ❌ 不是 Bean | ❌ 不可用 | 直接 OssBootUtil.upload() | 全局静态工具类,简单但耦合高 |
5-5、什么时候该用哪种方式?
| 场景 | 推荐写法 |
|---|---|
| 个人或新项目,从零写 | ✅ 用 Bean + @Value 注入(现代、优雅、可测试) |
| 框架级工具类、被大量静态调用 | ✅ 保留静态工具类,但用配置类初始化(兼容旧代码) |
| 需要动态切换配置或多云支持(OSS / COS / S3) | ✅ 用 Bean + Spring Profile / Factory 模式更灵活 |
一句话总结:
直接在工具类用 @Value 是可以的,但仅当它是一个 Spring Bean。
JeecgBoot 之所以额外写了OssConfiguration,是因为它要在 Spring 启动时把配置“注入到静态工具类”,
这样既能保持旧有的静态调用方式,又能享受 Spring Boot 的配置管理。
六、@Bean的用法
@Bean 是 Spring 框架里最核心的注解之一,理解它就能彻底搞清楚 Spring 的 Bean 是怎么创建的、怎么被管理的、怎么被注入的。
6-1、@Bean 是干什么的?
一句话:
@Bean是用来告诉 Spring:“这个方法的返回值要交给我(Spring 容器)管理。”
也就是说:
这个方法返回的对象会被 Spring 注册成一个 Bean;
在容器启动时创建、放进 Spring 容器中;
之后你就能在项目任何地方用
@Autowired拿到这个 Bean。
6-2、最基本的使用方式
@Configuration // 声明一个配置类,相当于一个工厂
public class AppConfig {@Beanpublic UserService userService() {return new UserServiceImpl();}
}
这段代码会在启动时执行:
Spring 扫描到
@Configuration;调用其中带
@Bean的方法;把返回的
UserServiceImpl实例放进容器;你之后就可以用:
@Autowired private UserService userService;来自动注入这个对象。
类比理解:
@Bean= “告诉 Spring,我要手动造一个对象,请你帮我管起来。”
6-3、它与 @Component 的区别
| 比较项 | @Bean | @Component |
|---|---|---|
| 用在 | 方法上 | 类上 |
| 谁来创建对象 | 你自己 new | Spring 自己通过反射创建 |
| 适合场景 | 第三方类、需要自定义初始化逻辑的 Bean | 你自己写的普通业务类 |
是否需要 @Configuration | 通常需要 | 不需要 |
例子:
// 你想用第三方类,不方便加@Component
@Configuration
public class WebConfig {@Beanpublic RestTemplate restTemplate() {return new RestTemplate(); // 手动创建}
}
这样 Spring 才能帮你管理 RestTemplate 对象,否则它不会被自动注入。
6-4、@Bean 的高级用法
① 指定 Bean 名称
默认 Bean 名称是方法名:
@Bean("myRestTemplate")
public RestTemplate restTemplate() { ... }
之后可以通过名称注入:
@Autowired
@Qualifier("myRestTemplate")
private RestTemplate restTemplate;
② 设置作用域(Scope)
@Bean
@Scope("prototype") // 每次注入都 new 一个
public UserService userService() {return new UserServiceImpl();
}
常见取值:
singleton(默认):全局只有一个对象;prototype:每次注入都创建新实例;还有 Web 环境专用的
request、session等。
③ 初始化与销毁方法
@Bean(initMethod = "init", destroyMethod = "close")
public DataSource dataSource() {return new HikariDataSource();
}
当 Spring 启动时调用 init(),关闭时调用 close()。
执行顺序是:dataSource() → init() → (程序运行中使用 Bean)→ close()
【注意】:调用的是:
- hikariDataSource.init();
- hikariDataSource.close();
④ 条件加载(配合 @ConditionalOnProperty)
你可以让某个 Bean 只在配置文件中某个条件满足时才创建:
@Bean
@ConditionalOnProperty(name = "app.cache.enabled", havingValue = "true")
public CacheService cacheService() {return new RedisCacheService();
}
如果配置文件里 app.cache.enabled=true,Spring 才会加载这个 Bean。
6-5、你提到的例子中的 @Bean
回到你之前的例子:
@Bean
public void initOssBootConfiguration() {OssBootUtil.setEndPoint(endpoint);OssBootUtil.setAccessKeyId(accessKeyId);...
}
这里的意思是:
Spring 启动时执行这个方法;
这个方法虽然返回
void,但它完成了“初始化工作”;它不是要创建 Bean,而是利用
@Bean的“初始化时机”;实际上可以换成
@PostConstruct效果更清晰(但写法略不同)。
所以这里的 @Bean 用法略“取巧”,利用 Spring 的加载机制在容器初始化阶段执行逻辑。
七、 @PostConstruct讲解
@PostConstruct 是 Spring(更准确地说是 JSR-250 规范)中另一种实现Bean 初始化逻辑 的方式,作用与 @Bean(initMethod="...") 类似,但写法更简洁。
7-1、@PostConstruct 的作用
@PostConstruct 用在 Bean 的实例方法上,表示:
“当 Spring 把这个 Bean 创建并注入完依赖后,立刻执行这个方法。”
所以它相当于:
替代
@Bean(initMethod = "init")或者替代实现
InitializingBean.afterPropertiesSet()
7-2、用法示例
原来的写法是:
@Bean(initMethod = "init", destroyMethod = "close")
public DataSource dataSource() {return new HikariDataSource();
}
换成 @PostConstruct 的写法,需要把初始化逻辑放在 Bean 对象内部。
也就是在类中定义一个方法,并用 @PostConstruct 标注:
@Component
public class MyDataSource extends HikariDataSource {@PostConstructpublic void init() {// 这里写初始化逻辑,比如连接池准备、配置加载System.out.println("⭐ 数据源初始化完成");}@PreDestroypublic void close() {// 这里写销毁逻辑,比如关闭连接池System.out.println(" 数据源已关闭");}
}
然后在配置类中,只需简单地:
@Bean
public DataSource dataSource() {return new MyDataSource();
}
这样:
Spring 创建
MyDataSource对象;自动调用
@PostConstruct标注的方法;容器关闭时自动调用
@PreDestroy。
7-3、执行顺序(结合前面对比)
| 阶段 | 旧写法 | 新写法 |
|---|---|---|
| Bean 创建 | 调用 dataSource() | 调用 dataSource() |
| 初始化 | 调用 init()(来自 @Bean(initMethod="init")) | 调用 @PostConstruct 方法 |
| 销毁 | 调用 close()(来自 @Bean(destroyMethod="close")) | 调用 @PreDestroy 方法 |
最终执行顺序一致:
dataSource() → @PostConstruct(init) → (程序运行中)→ @PreDestroy(close)
7-4、两种方式对比总结
| 比较项 | @Bean(initMethod/destroyMethod) | @PostConstruct / @PreDestroy |
|---|---|---|
| 写法位置 | 在配置类(外部定义) | 在 Bean 类内部 |
| 可读性 | 逻辑集中在配置层 | 逻辑集中在类内部 |
| 适合场景 | 第三方类(源码不可改) | 自己定义的类 |
| 销毁方式 | destroyMethod 指定 | @PreDestroy 自动执行 |
| 生命周期 | Bean 初始化/销毁阶段 | 同上 |
7-5、如果你用的是第三方类怎么办?
比如 HikariDataSource 是外部库的类,你没法在里面加 @PostConstruct。
这时仍然得用@Bean(initMethod="init", destroyMethod="close")。
所以:
自己写的类 → 用
@PostConstruct/@PreDestroy更自然;别人写的类(比如数据库连接池) → 用
@Bean(initMethod, destroyMethod)。
一句话总结:
@PostConstruct是写在 Bean 类内部的初始化注解,
Spring 创建 Bean 后会自动执行它,
对应@Bean(initMethod="...")的作用;
同理,销毁阶段可用@PreDestroy代替destroyMethod="..."。
7-6、initOssBootConfiguration换成 @PostConstruct 的写法
package org.jeecg.config.oss;
import jakarta.annotation.PostConstruct;
import org.jeecg.common.util.oss.OssBootUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
/*** 云存储 配置* @author: jeecg-boot*/
@Configuration
@ConditionalOnProperty(prefix = "jeecg.oss", name = "endpoint")
public class OssConfiguration {@Value("${jeecg.oss.endpoint}")private String endpoint;@Value("${jeecg.oss.accessKey}")private String accessKeyId;@Value("${jeecg.oss.secretKey}")private String accessKeySecret;@Value("${jeecg.oss.bucketName}")private String bucketName;@Value("${jeecg.oss.staticDomain:}")private String staticDomain;/*** 使用 @PostConstruct 在 Spring 完成属性注入后执行初始化逻辑*/@PostConstructpublic void initOssBootConfiguration() {OssBootUtil.setEndPoint(endpoint);OssBootUtil.setAccessKeyId(accessKeyId);OssBootUtil.setAccessKeySecret(accessKeySecret);OssBootUtil.setBucketName(bucketName);OssBootUtil.setStaticDomain(staticDomain);}
}
执行顺序对比
| 步骤 | @Bean 写法 | @PostConstruct 写法 |
|---|---|---|
| 1 | Spring 扫描配置类 | Spring 扫描配置类 |
| 2 | 执行 @Bean 方法(立即执行) | 先实例化配置类,再注入属性(@Value) |
| 3 | —— | 执行 @PostConstruct 方法(属性已可用) |
| 4 | 结束 | 结束 |
在你的场景下,两者的功能完全等价,但:
@Bean用来“产生 Bean”;@PostConstruct用来“初始化类中已有 Bean 或执行启动逻辑”。
所以用 @PostConstruct 更语义化,也不会在 Spring 容器里产生一个“多余的 Bean”(void 的 @Bean 方法其实也会注册一个空 Bean 定义)。
7-7、它在生命周期中的执行顺序
完整顺序(常见三件套):
构造方法(
new)依赖注入(给
@Autowired、@Value这些字段赋值)@PostConstruct← 你关心的就是这一步(可选)
InitializingBean.afterPropertiesSet()(可选)
@Bean(initMethod="...")—— 应用运行 ——
@PreDestroy(容器关闭时)(可选)
DisposableBean.destroy()(可选)
@Bean(destroyMethod="...")
小结:
@PostConstruct比initMethod更早执行。