一、@PostConstruct
1.1、源由
今天看到几行代码,这里@PostConstruct的作用是什么呢?
@Value("${pic.compress.target:100}")
public int targetSize = 100;
public static int staticTargetSize = 100;
@Value("${pic.compress.quality:0.4}")
public double quality = 0.4;
public static double staticQuality = 0.4;
// 业务代码……
// 传递给静态变量
@PostConstruct
public void init() {
staticTargetSize = targetSize;
staticQuality = quality;
log.info("【图片压缩】 | 压缩目标大小={}kb | 压缩质量比={}", staticTargetSize, staticQuality);
} 这么做的意义
1.2、分析
@PostConstruct 执行的前提是class有被注册到Bean,它的执行顺序在构造函数之后。在一个Bean对象实例化的过程中,首先会调用其构造器来创建Bean实例,然后容器会使用各种方式(如注解、XML配置)将属性值注入到Bean中,一旦所有属性都被成功注入,Spring容器会执行任何与Bean关联的初始化回调方法,比如使用@PostConstruct
注解标记的方法。因此,执行顺序是构造器 -> 组件初始化和属性注入(@Value)-> @postConstruct。
关于@postConstruct其他注意点:
- 在class被载入服务前,这个方法会被调用
- 在它定义在非拦截器类型 (non-interceptor) 的class中时,它不能有任何参数: void METHOD_NAME()
- 在它定义在拦截器类型 (interceptor) 的class中时,它需要有一个 InvocationContext 类型的参数:void METHOD_NAME(InvocationContext)
- 它的访问修饰符应该是 public, protected, package-private 或 private的
- 当它在应用程序客户端中时,它不能是静态的(static)
- 它或许应该是用 final 修饰的
因此如上的操作,就是在通过@Value属性注入后,通过@PostConstruct回调init方法,来对进行赋值。
1.3、相关文档
相关的测试和证明可以参考如下文章:
https://blog.csdn.net/u013269298/article/details/124007049
这里引用一下证明过程:
TestCompoent1 :
@Component
public class TestComponent1 {
public TestComponent1()
{
System.out.println("-----------------------------------------");
System.out.println("TestComponent1 constructed");
System.out.println("-----------------------------------------");
}
@PostConstruct
public void init()
{
System.out.println("-----------------------------------------");
System.out.println("TestComponent1 @PostConstruct annotated method executed");
System.out.println("-----------------------------------------");
}
@Bean
public TestComponent3 component3()
{
return new TestComponent3();
}
}
TestComponent2:
public class TestComponent2 {
public TestComponent2()
{
System.out.println("-----------------------------------------");
System.out.println("TestComponent2 constructed ");
System.out.println("-----------------------------------------");
}
@PostConstruct
public void init()
{
System.out.println("-----------------------------------------");
System.out.println("TestComponent2 @PostConstruct init() executed ");
System.out.println("-----------------------------------------");
}
}
TestComponent3:
public class TestComponent3 {
public TestComponent3() {
System.out.println("-----------------------------------------");
System.out.println("TestComponent3 constructed ");
System.out.println("-----------------------------------------");
}
@PostConstruct
public void init()
{
System.out.println("-----------------------------------------");
System.out.println("TestComponent3 @PostConstruct annotated method executed");
System.out.println("-----------------------------------------");
}
}
TestComponent4:
public class TestComponent4 {
public TestComponent4()
{
System.out.println("-----------------------------------------");
System.out.println("TestCompoent4 default construct executed");
System.out.println("-----------------------------------------");
}
@PostConstruct
public void init()
{
System.out.println("-----------------------------------------");
System.out.println("TestCompoent4 @PostConstruct annotated method executed");
System.out.println("-----------------------------------------");
}
}
应用:
TestApplication.run(MsGatewayApplication.class, args);
TestComponent4 testComponent4 =new TestComponent4();
运行结果:
- TestComponent1 和 TestComponent3 的构造函数和 @PostConstruct 修饰的 init() 方法都有有被执行。
- TestComponent2 的则都没有执行。
- TestComponent4 有执行构造函数,但是没有执行 init() 方法。
TestComponent1 constructed
TestComponent1 @PostConstruct annotated method executed
TestComponent3 constructed
TestComponent3 @PostConstruct annotated method executed
二、输入输出流序列化失败问题
2.1、源由
今天编写一个接口:
控制层:
/**
* 批量获取文件流S3存储桶中的对象
* @param sysFileDto
* @return
*/
@PostMapping("/batch")
public R<List<S3Object>> getFileBatch(@RequestBody List<SysFileDto> sysFileDto){
log.debug("调用附近批量查询接口,入参:{}", sysFileDto);
return R.ok(sysFileService.getFileBatch(sysFileDto));
}
业务层:
@Override
public List<S3Object> getFileBatch(List<SysFileDto> sysFileDto) {
List<S3Object> ret = new ArrayList<>();
if(CollectionUtil.isNotEmpty(sysFileDto)){
sysFileDto.stream().forEach(r -> {
S3Object s3Object = fileTemplate.getObject(r.getBucket(), r.getFileName());
ret.add(s3Object);
});
}
return ret;
}
但是在调用这个接口的时候报错:
这个异常通常表示在尝试序列化对象时遇到了问题,具体来说,是因为在序列化过程中找不到合适的序列化器来处理 java.io.BufferedInputStream
类型的对象。这可能是因为该对象的序列化器没有正确配置或者该对象的序列化器不存在。
全局异常信息 ex=Type definition error: [simple type, class java.io.BufferedInputStream]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.io.BufferedInputStream and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.pig4cloud.pigx.common.core.util.R["data"]->java.util.ArrayList[0]->com.amazonaws.services.s3.model.S3Object["objectContent"]->com.amazonaws.services.s3.model.S3ObjectInputStream["delegateStream"])
2.2、分析
S3Object对象中维护了输入输出流。
这个异常信息说明了在序列化过程中遇到了java.io.BufferedInputStream
类,而Jackson序列化器在尝试序列化它时出现了问题,因为它找不到对应的序列化器。这并不意味着输入输出类不能被序列化,而是说明Jackson序列化器默认情况下不支持java.io.BufferedInputStream
类的序列化。
在Java中,确实有些输入输出类是不支持直接序列化的,因为它们通常表示与底层I/O资源的交互。然而,并不是所有的输入输出类都不能被序列化。实际上,许多输入输出类是可以序列化的,只需确保它们可以被正确地序列化和反序列化。
三、文件修改后缀
原自己手写方法:
String originalUrl = medRecordAttachment.getOriginalImageUrl();
String preUrl = originalUrl.substring(0, originalUrl.lastIndexOf("/") + 1);
String thumbUrl = preUrl + ThumbnailPrefix + originalUrl.substring(originalUrl.lastIndexOf("/") + 2);
优化后代码:
String originalUrl = medRecordAttachment.getOriginalImageUrl();
String filename = Paths.get(originalUrl).getFileName().toString();
String thumbUrl = Paths.get(originalUrl).getParent().toString() + File.separatorChar + ThumbnailPrefix + filename;
medRecordAttachment.setThumbImageUrl(thumbUrl.replaceAll("\\\\", "/"));