Spring中11個(gè)最常用的擴(kuò)展點(diǎn)總結(jié),你知道幾個(gè)
前言
在使用spring的過(guò)程中,我們有沒(méi)有發(fā)現(xiàn)它的擴(kuò)展能力很強(qiáng)呢? 由于這個(gè)優(yōu)勢(shì)的存在,使得spring具有很強(qiáng)的包容性,所以很多第三方應(yīng)用或者框架可以很容易的投入到spring的懷抱中。今天我們主要來(lái)學(xué)習(xí)Spring中很常用的11個(gè)擴(kuò)展點(diǎn),你用過(guò)幾個(gè)呢?
1. 類(lèi)型轉(zhuǎn)換器
如果接口中接收參數(shù)的實(shí)體對(duì)象中,有一個(gè)字段類(lèi)型為Date,但實(shí)際傳遞的參數(shù)是字符串類(lèi)型:2022-12-15 10:20:15,該如何處理?
Spring提供了一個(gè)擴(kuò)展點(diǎn),類(lèi)型轉(zhuǎn)換器Type Converter
,具體分為3類(lèi):
Converter<S,T>
: 將類(lèi)型 S 的對(duì)象轉(zhuǎn)換為類(lèi)型 T 的對(duì)象ConverterFactory<S, R>
: 將 S 類(lèi)型對(duì)象轉(zhuǎn)換為 R 類(lèi)型或其子類(lèi)對(duì)象GenericConverter
:它支持多種源和目標(biāo)類(lèi)型的轉(zhuǎn)換,還提供了源和目標(biāo)類(lèi)型的上下文。 此上下文允許您根據(jù)注釋或?qū)傩孕畔?zhí)行類(lèi)型轉(zhuǎn)換。
還是不明白的話,我們舉個(gè)例子吧。
- 定義一個(gè)用戶(hù)對(duì)象
@Data public class User { private Long id; private String name; private Date registerDate; }
- 實(shí)現(xiàn)
Converter
接口
public class DateConverter implements Converter<String, Date> { private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public Date convert(String source) { if (source != null && !"".equals(source)) { try { simpleDateFormat.parse(source); } catch (ParseException e) { e.printStackTrace(); } } return null; } }
- 將新定義的類(lèi)型轉(zhuǎn)換器注入到Spring容器中
@Configuration public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new DateConverter()); } }
- 調(diào)用接口測(cè)試
@RequestMapping("/user") @RestController public class UserController { @RequestMapping("/save") public String save(@RequestBody User user) { return "success"; } }
請(qǐng)求接口時(shí),前端傳入的日期字符串,會(huì)自動(dòng)轉(zhuǎn)換成Date類(lèi)型。
2. 獲取容器Bean
在我們?nèi)粘i_(kāi)發(fā)中,經(jīng)常需要從Spring容器中獲取bean,但是你知道如何獲取Spring容器對(duì)象嗎?
2.1 BeanFactoryAware
@Service public class PersonService implements BeanFactoryAware { private BeanFactory beanFactory; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } public void add() { Person person = (Person) beanFactory.getBean("person"); } }
實(shí)現(xiàn)BeanFactoryAware接口,然后重寫(xiě)setBeanFactory方法,可以從方法中獲取spring容器對(duì)象。
2.2 ApplicationContextAware
@Service public class PersonService2 implements ApplicationContextAware { private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } public void add() { Person person = (Person) applicationContext.getBean("person"); } }
實(shí)現(xiàn)ApplicationContextAware
接口,然后重寫(xiě)setApplicationContext
方法,也可以通過(guò)該方法獲取spring容器對(duì)象。
2.3 ApplicationListener
@Service public class PersonService3 implements ApplicationListener<ContextRefreshedEvent> { private ApplicationContext applicationContext; @Override public void onApplicationEvent(ContextRefreshedEvent event) { applicationContext = event.getApplicationContext(); } public void add() { Person person = (Person) applicationContext.getBean("person"); } }
3. 全局異常處理
以往我們?cè)陂_(kāi)發(fā)界面的時(shí)候,如果出現(xiàn)異常,要給用戶(hù)更友好的提示,例如:
@RequestMapping("/test") @RestController public class TestController { @GetMapping("/add") public String add() { int a = 10 / 0; return "su"; } }
如果不對(duì)請(qǐng)求添加接口結(jié)果做任何處理,會(huì)直接報(bào)錯(cuò):
用戶(hù)可以直接看到錯(cuò)誤信息嗎?
這種交互給用戶(hù)帶來(lái)的體驗(yàn)非常差。 為了解決這個(gè)問(wèn)題,我們通常在接口中捕獲異常:
@GetMapping("/add") public String add() { String result = "success"; try { int a = 10 / 0; } catch (Exception e) { result = "error"; } return result; }
界面修改后,出現(xiàn)異常時(shí)會(huì)提示:“數(shù)據(jù)異常”,更加人性化。
看起來(lái)不錯(cuò),但是有一個(gè)問(wèn)題。
如果只是一個(gè)接口還好,但是如果項(xiàng)目中有成百上千個(gè)接口,還得加異常捕獲代碼嗎?
答案是否定的,這就是全局異常處理派上用場(chǎng)的地方:RestControllerAdvice
。
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public String handleException(Exception e) { if (e instanceof ArithmeticException) { return "data error"; } if (e instanceof Exception) { return "service error"; } retur null; } }
方法中處理異常只需要handleException
,在業(yè)務(wù)接口中就可以安心使用,不再需要捕獲異常(統(tǒng)一有人處理)。
4. 自定義攔截器
Spring MVC攔截器,它可以獲得HttpServletRequest
和HttpServletResponse
等web對(duì)象實(shí)例。
Spring MVC攔截器的頂層接口是HandlerInterceptor
,它包含三個(gè)方法:
preHandle
在目標(biāo)方法執(zhí)行之前執(zhí)行- 執(zhí)行目標(biāo)方法后執(zhí)行的
postHandle
afterCompletion
在請(qǐng)求完成時(shí)執(zhí)行
為了方便,我們一般繼承HandlerInterceptorAdapter
,它實(shí)現(xiàn)了HandlerInterceptor
。
如果有授權(quán)鑒權(quán)、日志、統(tǒng)計(jì)等場(chǎng)景,可以使用該攔截器,我們來(lái)演示下吧。
- 寫(xiě)一個(gè)類(lèi)繼承
HandlerInterceptorAdapter
:
public class AuthInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String requestUrl = request.getRequestURI(); if (checkAuth(requestUrl)) { return true; } return false; } private boolean checkAuth(String requestUrl) { return true; } }
- 將攔截器注冊(cè)到spring容器中
@Configuration public class WebAuthConfig extends WebMvcConfigurerAdapter { @Bean public AuthInterceptor getAuthInterceptor() { return new AuthInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor()); } }
- Spring MVC在請(qǐng)求接口時(shí)可以自動(dòng)攔截接口,并通過(guò)攔截器驗(yàn)證權(quán)限。
5. 導(dǎo)入配置
有時(shí)我們需要在某個(gè)配置類(lèi)中引入其他的類(lèi),引入的類(lèi)也加入到Spring容器中。 這時(shí)候可以使用注解@Import
來(lái)完成這個(gè)功能。
如果你查看它的源代碼,你會(huì)發(fā)現(xiàn)導(dǎo)入的類(lèi)支持三種不同的類(lèi)型。
但是我覺(jué)得最好把普通類(lèi)的配置類(lèi)和@Configuration
注解分開(kāi)解釋?zhuān)粤谐隽怂姆N不同的類(lèi)型:
5.1 通用類(lèi)
這種引入方式是最簡(jiǎn)單的,引入的類(lèi)會(huì)被實(shí)例化為一個(gè)bean對(duì)象。
public class A { } @Import(A.class) @Configuration public class TestConfiguration { }
通過(guò)@Import
注解引入類(lèi)A,spring可以自動(dòng)實(shí)例化A對(duì)象,然后在需要使用的地方通過(guò)注解@Autowired
注入:
@Autowired private A a;
5.2 配置類(lèi)
這種引入方式是最復(fù)雜的,因?yàn)锧Configuration支持還支持多種組合注解,比如:
@Import
@ImportResource
@PropertySource
public class A { } public class B { } @Import(B.class) @Configuration public class AConfiguration { @Bean public A a() { return new A(); } } @Import(AConfiguration.class) @Configuration public class TestConfiguration { }
@Configuration
注解的配置類(lèi)通過(guò)@Import
注解導(dǎo)入,配置類(lèi)@Import
、@ImportResource
相關(guān)注解引入的類(lèi)會(huì)一次性全部遞歸引入@PropertySource
所在的屬性。
5.3 ImportSelector
該導(dǎo)入方法需要實(shí)現(xiàn)ImportSelector
接口
public class AImportSelector implements ImportSelector { private static final String CLASS_NAME = "com.sue.cache.service.test13.A"; public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[]{CLASS_NAME}; } } @Import(AImportSelector.class) @Configuration public class TestConfiguration { }
這種方法的好處是selectImports
方法返回的是一個(gè)數(shù)組,也就是說(shuō)可以同時(shí)引入多個(gè)類(lèi),非常方便。
5.4 ImportBeanDefinitionRegistrar
該導(dǎo)入方法需要實(shí)現(xiàn)ImportBeanDefinitionRegistrar
接口:
public class AImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(A.class); registry.registerBeanDefinition("a", rootBeanDefinition); } } @Import(AImportBeanDefinitionRegistrar.class) @Configuration public class TestConfiguration { }
這種方法是最靈活的。 容器注冊(cè)對(duì)象可以在registerBeanDefinitions
方法中獲取,可以手動(dòng)創(chuàng)建BeanDefinition
注冊(cè)到BeanDefinitionRegistry
種。
6. 當(dāng)工程啟動(dòng)時(shí)
有時(shí)候我們需要在項(xiàng)目啟動(dòng)的時(shí)候自定義一些額外的功能,比如加載一些系統(tǒng)參數(shù),完成初始化,預(yù)熱本地緩存等。 我們應(yīng)該做什么?
好消息是 SpringBoot 提供了:
CommandLineRunner
ApplicationRunner
這兩個(gè)接口幫助我們實(shí)現(xiàn)了上面的需求。
它們的用法很簡(jiǎn)單,以ApplicationRunner
接口為例:
@Component public class TestRunner implements ApplicationRunner { @Autowired private LoadDataService loadDataService; public void run(ApplicationArguments args) throws Exception { loadDataService.load(); } }
實(shí)現(xiàn)ApplicationRunner
接口,重寫(xiě)run
方法,在該方法中實(shí)現(xiàn)您的自定義需求。
如果項(xiàng)目中有多個(gè)類(lèi)實(shí)現(xiàn)了ApplicationRunner
接口,如何指定它們的執(zhí)行順序?
答案是使用@Order(n)注解,n的值越小越早執(zhí)行。 當(dāng)然,順序也可以通過(guò)@Priority
注解來(lái)指定。
7. 修改BeanDefinition
在實(shí)例化Bean對(duì)象之前,Spring IOC
需要讀取Bean的相關(guān)屬性,保存在BeanDefinition
對(duì)象中,然后通過(guò)BeanDefinition
對(duì)象實(shí)例化Bean
對(duì)象。
如果要修改BeanDefinition對(duì)象中的屬性怎么辦?
答案:我們可以實(shí)現(xiàn) BeanFactoryPostProcessor
接口。
@Component public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory; BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class); beanDefinitionBuilder.addPropertyValue("id", 123); beanDefinitionBuilder.addPropertyValue("name", "Tom"); defaultListableBeanFactory.registerBeanDefinition("user", beanDefinitionBuilder.getBeanDefinition()); } }
在postProcessBeanFactory
方法中,可以獲取BeanDefinition
的相關(guān)對(duì)象,修改對(duì)象的屬性。
8. 初始化 Bean 前和后
有時(shí),您想在 bean 初始化前后實(shí)現(xiàn)一些您自己的邏輯。
這時(shí)候就可以實(shí)現(xiàn):BeanPostProcessor
接口。
該接口目前有兩個(gè)方法:
postProcessBeforeInitialization
:應(yīng)該在初始化方法之前調(diào)用。postProcessAfterInitialization
:此方法在初始化方法之后調(diào)用。
@Component public class MyBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof User) { ((User) bean).setUserName("Tom"); } return bean; } }
我們經(jīng)常使用的@Autowired
、@Value
、@Resource
、@PostConstruct
等注解都是通過(guò)AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
來(lái)實(shí)現(xiàn)的。
9. 初始化方法
目前在Spring中初始化bean的方式有很多種:
- 使用
@PostConstruct
注解 - 實(shí)現(xiàn)
InitializingBean
接口
9.1 使用 @PostConstruct
@Service public class AService { @PostConstruct public void init() { System.out.println("===init==="); } }
為需要初始化的方法添加注解@PostConstruct
,使其在Bean初始化時(shí)執(zhí)行。
9.2 實(shí)現(xiàn)初始化接口InitializingBean
@Service public class BService implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { System.out.println("===init==="); } }
實(shí)現(xiàn)InitializingBean
接口,重寫(xiě)afterPropertiesSet
方法,在該方法中可以完成初始化功能。
10. 關(guān)閉Spring容器前
有時(shí)候,我們需要在關(guān)閉spring容器之前做一些額外的工作,比如關(guān)閉資源文件。
此時(shí)你可以實(shí)現(xiàn) DisposableBean
接口并重寫(xiě)它的 destroy
方法。
@Service public class DService implements InitializingBean, DisposableBean { @Override public void destroy() throws Exception { System.out.println("DisposableBean destroy"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("InitializingBean afterPropertiesSet"); } }
這樣,在spring容器銷(xiāo)毀之前,會(huì)調(diào)用destroy
方法做一些額外的工作。
通常我們會(huì)同時(shí)實(shí)現(xiàn)InitializingBean
和DisposableBean
接口,重寫(xiě)初始化方法和銷(xiāo)毀方法。
11. 自定義Bean的scope
我們都知道spring core默認(rèn)只支持兩種Scope
:
Singleton
單例,從spring容器中獲取的每一個(gè)bean都是同一個(gè)對(duì)象。prototype
多實(shí)例,每次從spring容器中獲取的bean都是不同的對(duì)象。
Spring Web 再次擴(kuò)展了 Scope,添加
RequestScope
:同一個(gè)請(qǐng)求中從spring容器中獲取的bean都是同一個(gè)對(duì)象。SessionScope
:同一個(gè)session從spring容器中獲取的bean都是同一個(gè)對(duì)象。
盡管如此,有些場(chǎng)景還是不符合我們的要求。
比如我們?cè)谕粋€(gè)線程中要從spring
容器中獲取的bean
都是同一個(gè)對(duì)象,怎么辦?
答案:這需要一個(gè)自定義范圍。
- 實(shí)現(xiàn)
Scope
接口
public class ThreadLocalScope implements Scope { private static final ThreadLocal THREAD_LOCAL_SCOPE = new ThreadLocal(); @Override public Object get(String name, ObjectFactory<?> objectFactory) { Object value = THREAD_LOCAL_SCOPE.get(); if (value != null) { return value; } Object object = objectFactory.getObject(); THREAD_LOCAL_SCOPE.set(object); return object; } @Override public Object remove(String name) { THREAD_LOCAL_SCOPE.remove(); return null; } @Override public void registerDestructionCallback(String name, Runnable callback) { } @Override public Object resolveContextualObject(String key) { return null; } @Override public String getConversationId() { return null; } }
- 將新定義的Scope注入到Spring容器中
@Component public class ThreadLocalBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { beanFactory.registerScope("threadLocalScope", new ThreadLocalScope()); } }
- 使用新定義的Scope
@Scope("threadLocalScope") @Service public class CService { public void add() { } }
總結(jié)
本文總結(jié)了Spring中很常用的11個(gè)擴(kuò)展點(diǎn),可以在Bean創(chuàng)建、初始化到銷(xiāo)毀各個(gè)階段注入自己想要的邏輯,也有Spring MVC相關(guān)的攔截器等擴(kuò)展點(diǎn),希望對(duì)大家有幫助。
到此這篇關(guān)于Spring中11個(gè)最常用的擴(kuò)展點(diǎn)的文章就介紹到這了,更多相關(guān)Spring常用擴(kuò)展點(diǎn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringCloud使用AOP統(tǒng)一處理Web請(qǐng)求日志實(shí)現(xiàn)步驟
這篇文章主要為大家介紹了SpringCloud使用AOP統(tǒng)一處理Web請(qǐng)求日志實(shí)現(xiàn)步驟,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08關(guān)于@Configuration的作用說(shuō)明
這篇文章主要介紹了關(guān)于@Configuration的作用說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01Java基于正則表達(dá)式獲取指定HTML標(biāo)簽指定屬性值的方法
這篇文章主要介紹了Java基于正則表達(dá)式獲取指定HTML標(biāo)簽指定屬性值的方法,涉及java基于正則的HTML元素匹配相關(guān)操作技巧,需要的朋友可以參考下2017-01-01java實(shí)現(xiàn)的n*n矩陣求值及求逆矩陣算法示例
這篇文章主要介紹了java實(shí)現(xiàn)的n*n矩陣求值及求逆矩陣算法,結(jié)合具體實(shí)例形式分析了java基于數(shù)組的矩陣定義、遍歷、運(yùn)算等相關(guān)操作技巧,需要的朋友可以參考下2017-09-09SpringBoot簡(jiǎn)單實(shí)現(xiàn)文件上傳
這篇文章主要介紹了SpringBoot簡(jiǎn)單實(shí)現(xiàn)文件上傳,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-09-09詳解Java的readBytes是怎么實(shí)現(xiàn)的
眾所周知,Java是一門(mén)跨平臺(tái)語(yǔ)言,針對(duì)不同的操作系統(tǒng)有不同的實(shí)現(xiàn),下面小編就來(lái)從一個(gè)非常簡(jiǎn)單的api調(diào)用帶大家來(lái)看看Java具體是怎么做的吧2023-07-07Java實(shí)現(xiàn)的對(duì)稱(chēng)加密算法AES定義與用法詳解
這篇文章主要介紹了Java實(shí)現(xiàn)的對(duì)稱(chēng)加密算法AES,結(jié)合實(shí)例形式分析了對(duì)稱(chēng)加密算法AES的定義、特點(diǎn)、用法及使用場(chǎng)景,需要的朋友可以參考下2018-04-04