环境:Spring5.3.33
1. Lifecycle接口
Lifecycle接口是一个定义启动/停止生命周期控制方法的通用接口。它允许Bean对象和容器(通常是Spring的ApplicationContext本身)实现启动和停止操作,接口定义:
public interface Lifecycle {
// Spring容器启动之前执行
void start();
// Spring容器在要关闭时执行
void stop();
// 判断是否正在运行
boolean isRunning();
}
注意,常规的org.springframework.context.Lifecycle接口是显式启动和停止通知的简单约定,并不意味着在上下文刷新时自动启动。为了细粒度地控制自动启动和特定bean的优雅停止(包括启动和停止阶段),你应该实现org.springframework.context.SmartLifecycle接口。
如下示例:
public class PackLifecycle implements SmartLifecycle {
private volatile boolean running ;
@Override
public void start() {
this.running = true;
System.out.println("lifecycle start ... ") ;
}
@Override
public void stop() {
this.running = false ;
System.out.println("lifecycle stop ... ") ;
}
@Override
public boolean isRunning() {
return running ;
}
}
start/stop执行时机
start方法执行
public abstract class AbstractApplicationContext {
public void refresh() {
// ...
// 实例化单例bean
finishBeanFactoryInitialization(beanFactory);
// 完成上下文刷新操作最后一步执行
finishRefresh();
}
protected void finishRefresh() {
// 通过LifecycleProcessor#onRefresh方法执行Lifecycle#start方法
getLifecycleProcessor().onRefresh();
}
}
stop方法执行
public abstract class AbstractApplicationContext {
// 当容器关闭时执行
public void close() {
doClose();
}
protected void doClose() {
// 通过LifecycleProcessor#onClose方法执行Lifecycle#stop方法
this.lifecycleProcessor.onClose();
}
}
你可以通过自定义Lifecycle,在容器启动完成时和容器关闭时做你需要的人和事。
2. FactoryBean接口
如果你想自定义完全控制bean的实例化,那么你可以通过实现FactoryBean接口。
FactoryBean<T>接口提供了三种方法:
- T getObject(): 返回此工厂创建的对象的实例。实例可能是共享的,这取决于此工厂返回的是单件还是原型。
- boolean isSingleton(): 如果此FactoryBean返回singletons,则返回true;否则返回false。此方法的默认实现返回true。
- Class<?> getObjectType(): 返回getObject()方法返回的对象类型,如果类型事先未知,则返回null。
如下示例:
public class User {}
@Component("user")
public class UserFactoryBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
// 自定义对象实例化
User user = new User() ;
return user ;
}
@Override
public Class<?> getObjectType() {
return User.class;
}
}
虽然我们定义的是FactoryBean实例,但是我们使用的时候还是可以按照User类型注入使用即可,如下示例:
@Resource
private User user;
那如何获取UserFactoryBean这个对象呢?我们可以通过如下方式:
try (GenericApplicationContext context = new GenericApplicationContext()) {
// ...
System.out.println(context.getBean("&user")) ;
}
在beanName之前添加'&'符合即可获取真实的UserFactoryBean对象。
3. 非web环境优雅关闭容器
如果在非web应用程序环境中(例如,在富客户端桌面环境中)使用Spring的IoC容器,请向JVM注册关闭挂钩。这样做可以确保正常关闭,并在单例bean上调用相关的destroy方法,从而释放所有资源。通过容器对象ConfigurableApplicationContext#registerShutdownHook()方法注册关闭钩子。
public class User implements DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("User Object destroy...") ;
}
}
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext() ;
context.registerBean(User.class) ;
// 该方法会启动一个线程,该线程会关闭onClose方法;这样bean相关的生命周期方法都能被调用
context.registerShutdownHook() ;
context.refresh() ;
}
// 控制台输出;如果没有调用registerShutdownHook则不会有任何输出
User Object destroy...
注意:在SpringBoot环境下,上面的registerShutdownHook是自动调用。
4. 资源注入
我们可以直接通过@Value注解注入资源,如下示例:
@Value("${pack.images:file:///d:/images/1.png}")
private Resource res ;
// 将上面注入的资源,将图片直接输出到浏览器、
@GetMapping("/res0")
public void res0(HttpServletResponse response) throws Exception {
response.setContentType("image/png") ;
StreamUtils.copy(res.getInputStream(), response.getOutputStream()) ;
}
也可以注入资源数组;
@Component
public class PackResource {
private final Resource[] templates ;
public PackResource(@Value("${pack.templates.path}") Resource[] templates) {
this.templates = templates;
}
}
资源路径配置;
pack:
templates:
path: classpath*:com/pack/templates/*.ftl
ResourceLoaderAware接口
ResourceLoaderAware接口是一个特殊的回调接口,用于标识期望为其提供ResourceLoader引用的组件,如下示例:
@Component
public class PackResourceLoader implements ResourceLoaderAware {
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
Resource resource = resourceLoader.getResource("classpath:com/pack/templates/1.txt") ;
System.out.println(StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8)) ;
}
}
注意:由于ApplicationContext是ResourceLoader,因此bean还可以实现ApplicationContextAware接口,并直接使用提供的应用程序上下文来加载资源。但是,一般来说,如果你只需要专用的ResourceLoader接口,那么最好使用该接口。代码将只耦合到资源加载接口(可以被视为实用程序接口),而不耦合到整个Spring ApplicationContext接口。
5. 参数验证
参数验证一般都只是用在Controller请求方法上,如下示例:
@PostMapping("")
public Object save(@Validated @RequestBody User user, BindingResult errors) {
// TODO
}
在SpringBoot环境下(SpringBoot当你引入了validation模块后,会自动配置Validator),你可以在任意管理的Bean中使用参数验证功能,如下示例:
private final Validator validator ;
public UserService(Validator validator) {
this.validator = validator ;
}
public void save(User user) {
Errors errors = ...
this.validator.validate(user, errors) ;
if (errors.hasErrors()) {
// TODO
}
}
如果你不在SpringBoot环境下,那么你可以手动注册Validator
@Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean() ;
}
有关参数验证的其它更加全面的知识,请查看下面这篇文章:
必读!SpringBoot接口参数校验N种实用技巧大揭秘
SpringBoot参数验证@Validated和@Valid分清楚了吗?这些验证细节你知道吗?
6. 类型转换
如果在SpringWeb项目中,类型转换功能通常是由框架内部自动处理的,尤其是在Spring MVC的Controller层,当请求参数需要绑定到方法的参数时。然而,在应用程序的其他部分,比如Service层或其他组件中,有时我们确实需要手动执行类型转换。在这些情况下,我们可以利用Spring提供的ConversionService接口来完成数据类型之间的转换。
在Spring Boot环境下,系统自动为我们配置了ConversionService可以被注入到任何Bean对象中,以便我们在需要的时候使用它。如下示例:
private final ConversionService conversionService ;
public PackComponent(ConversionService conversionService) {
this.conversionService = conversionService ;
}
public Object convert(Object source, Class<?> targetType) {
// 检查源对象和目标类型是否为null
if (source == null || targetType == null) {
throw new IllegalArgumentException("Source or target type cannot be null");
}
// 尝试进行类型转换
return conversionService.convert(source, targetType) ;
}
非常方便的进行类型转换。