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