這六個Spring高級開發(fā)技巧掌握了嗎?
環(huán)境:Spring5.3.33
1. Lifecycle接口
Lifecycle接口是一個定義啟動/停止生命周期控制方法的通用接口。它允許Bean對象和容器(通常是Spring的ApplicationContext本身)實現(xiàn)啟動和停止操作,接口定義:
public interface Lifecycle {
// Spring容器啟動之前執(zhí)行
void start();
// Spring容器在要關(guān)閉時執(zhí)行
void stop();
// 判斷是否正在運行
boolean isRunning();
}
注意,常規(guī)的org.springframework.context.Lifecycle接口是顯式啟動和停止通知的簡單約定,并不意味著在上下文刷新時自動啟動。為了細(xì)粒度地控制自動啟動和特定bean的優(yōu)雅停止(包括啟動和停止階段),你應(yīng)該實現(xiàn)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執(zhí)行時機
start方法執(zhí)行
public abstract class AbstractApplicationContext {
public void refresh() {
// ...
// 實例化單例bean
finishBeanFactoryInitialization(beanFactory);
// 完成上下文刷新操作最后一步執(zhí)行
finishRefresh();
}
protected void finishRefresh() {
// 通過LifecycleProcessor#onRefresh方法執(zhí)行Lifecycle#start方法
getLifecycleProcessor().onRefresh();
}
}
stop方法執(zhí)行
public abstract class AbstractApplicationContext {
// 當(dāng)容器關(guān)閉時執(zhí)行
public void close() {
doClose();
}
protected void doClose() {
// 通過LifecycleProcessor#onClose方法執(zhí)行Lifecycle#stop方法
this.lifecycleProcessor.onClose();
}
}
你可以通過自定義Lifecycle,在容器啟動完成時和容器關(guān)閉時做你需要的人和事。
2. FactoryBean接口
如果你想自定義完全控制bean的實例化,那么你可以通過實現(xiàn)FactoryBean接口。
FactoryBean<T>接口提供了三種方法:
- T getObject(): 返回此工廠創(chuàng)建的對象的實例。實例可能是共享的,這取決于此工廠返回的是單件還是原型。
- boolean isSingleton(): 如果此FactoryBean返回singletons,則返回true;否則返回false。此方法的默認(rèn)實現(xiàn)返回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環(huán)境優(yōu)雅關(guān)閉容器
如果在非web應(yīng)用程序環(huán)境中(例如,在富客戶端桌面環(huán)境中)使用Spring的IoC容器,請向JVM注冊關(guān)閉掛鉤。這樣做可以確保正常關(guān)閉,并在單例bean上調(diào)用相關(guān)的destroy方法,從而釋放所有資源。通過容器對象ConfigurableApplicationContext#registerShutdownHook()方法注冊關(guān)閉鉤子。
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) ;
// 該方法會啟動一個線程,該線程會關(guān)閉onClose方法;這樣bean相關(guān)的生命周期方法都能被調(diào)用
context.registerShutdownHook() ;
context.refresh() ;
}
// 控制臺輸出;如果沒有調(diào)用registerShutdownHook則不會有任何輸出
User Object destroy...
注意:在SpringBoot環(huán)境下,上面的registerShutdownHook是自動調(diào)用。
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()) ;
}
也可以注入資源數(shù)組;
@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接口是一個特殊的回調(diào)接口,用于標(biāo)識期望為其提供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還可以實現(xiàn)ApplicationContextAware接口,并直接使用提供的應(yīng)用程序上下文來加載資源。但是,一般來說,如果你只需要專用的ResourceLoader接口,那么最好使用該接口。代碼將只耦合到資源加載接口(可以被視為實用程序接口),而不耦合到整個Spring ApplicationContext接口。
5. 參數(shù)驗證
參數(shù)驗證一般都只是用在Controller請求方法上,如下示例:
@PostMapping("")
public Object save(@Validated @RequestBody User user, BindingResult errors) {
// TODO
}
在SpringBoot環(huán)境下(SpringBoot當(dāng)你引入了validation模塊后,會自動配置Validator),你可以在任意管理的Bean中使用參數(shù)驗證功能,如下示例:
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環(huán)境下,那么你可以手動注冊Validator
@Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean() ;
}
有關(guān)參數(shù)驗證的其它更加全面的知識,請查看下面這篇文章:
必讀!SpringBoot接口參數(shù)校驗N種實用技巧大揭秘
SpringBoot參數(shù)驗證@Validated和@Valid分清楚了嗎?這些驗證細(xì)節(jié)你知道嗎?
6. 類型轉(zhuǎn)換
如果在SpringWeb項目中,類型轉(zhuǎn)換功能通常是由框架內(nèi)部自動處理的,尤其是在Spring MVC的Controller層,當(dāng)請求參數(shù)需要綁定到方法的參數(shù)時。然而,在應(yīng)用程序的其他部分,比如Service層或其他組件中,有時我們確實需要手動執(zhí)行類型轉(zhuǎn)換。在這些情況下,我們可以利用Spring提供的ConversionService接口來完成數(shù)據(jù)類型之間的轉(zhuǎn)換。
在Spring Boot環(huán)境下,系統(tǒng)自動為我們配置了ConversionService可以被注入到任何Bean對象中,以便我們在需要的時候使用它。如下示例:
private final ConversionService conversionService ;
public PackComponent(ConversionService conversionService) {
this.conversionService = conversionService ;
}
public Object convert(Object source, Class<?> targetType) {
// 檢查源對象和目標(biāo)類型是否為null
if (source == null || targetType == null) {
throw new IllegalArgumentException("Source or target type cannot be null");
}
// 嘗試進(jìn)行類型轉(zhuǎn)換
return conversionService.convert(source, targetType) ;
}
非常方便的進(jìn)行類型轉(zhuǎn)換。