springboot static關(guān)鍵字真能提高Bean的優(yōu)先級(jí)(厲害了)
生命太短暫,不要去做一些根本沒有人想要的東西。本文已被 https://www.yourbatman.cn 收錄,里面一并有Spring技術(shù)棧、MyBatis、JVM、中間件等小而美的專欄供以免費(fèi)學(xué)習(xí)。關(guān)注公眾號(hào)【BAT的烏托邦】逐個(gè)擊破,深入掌握,拒絕淺嘗輒止。
前言
各位小伙伴大家好,我是A哥。關(guān)于Spring初始化Bean的順序問題,是個(gè)老生常談的話題了,結(jié)論可總結(jié)為一句話:全局無序,局部有序。Spring Bean
整體上是無序的,而現(xiàn)實(shí)是大多數(shù)情況下我們真的無需關(guān)心,無序就無序唄,無所謂嘍。但是(此處應(yīng)該有但是哈),我有理由相信,對(duì)于有一定從業(yè)經(jīng)驗(yàn)的Javaer來說,或多或少都經(jīng)歷過Bean初始化順序帶來的“困擾”,也許是因?yàn)闆]有對(duì)你的功能造成影響,也許可能是你全然“不知情”,所以最終就不了了之~
隱患終歸隱患,依照墨菲定律來講,擔(dān)心的事它總歸是會(huì)發(fā)生的。A哥經(jīng)常“教唆”程序員要面向工資編程,雖然這價(jià)值觀有點(diǎn)扭曲,但不可否認(rèn)很多小伙伴真是這么想的(命中你了沒有😄),稍加粉飾了而已。話粗理不粗哦,almost所有的Javaer都在用Spring,你憑什么工資比你身邊同事的高呢?
Spring對(duì)Bean的(生命周期)管理是它最為核心的能力,同時(shí)也是很復(fù)雜、很難掌握的一個(gè)知識(shí)點(diǎn)。現(xiàn)在就可以啟動(dòng)你的工程,有木有這句日志:
"Bean 'xxx' of type [xxxx] is not eligible for getting processed by all BeanPostProcessors"
+ "(for example: not eligible for auto-proxying)"
這是一個(gè)典型的Spring Bean過早初始化問題,搜搜看你日志里是否有此句嘍。這句日志是由Spring的BeanPostProcessorChecker
這個(gè)類負(fù)責(zé)輸出,含義為:你的Bean xxx不能被所有的BeanPostProcessors
處理到(有的生命周期觸達(dá)不到),提醒你注意。此句日志在低些的版本里是warn警告級(jí)別,在本文約定的版本里官方把它改為了info級(jí)別。
絕大多數(shù)情況下,此句日志的輸出不會(huì)對(duì)你的功能造成影響,因此無需搭理。這也是Spring官方為何把它從warn調(diào)低為info級(jí)別的原因
我在CSDN上寫過一篇“Spring Bean過早初始化導(dǎo)致的誤傷”的文章,訪問量達(dá)近4w:
從這個(gè)數(shù)據(jù)(訪問量)上來看,這件事“并不簡(jiǎn)單”,遇到此麻煩的小伙伴不在少數(shù)且確實(shí)難倒了一眾人。關(guān)于Spring Bean的順序,全局是不可控的,但是局部上它提供了多種方式來方便使用者提高/降低優(yōu)先級(jí)(比如前面的使用@AutoConfigureBefore調(diào)整配置順序竟沒生效?這篇文章),本文就聊聊static關(guān)鍵字對(duì)于提供Bean的優(yōu)先級(jí)的功效。
版本約定
本文內(nèi)容若沒做特殊說明,均基于以下版本:
JDK:1.8
Spring Framework:5.2.2.RELEASE
正文
本文采用從 問題提出-結(jié)果分析-解決方案-原理剖析 這4個(gè)步驟,層層遞進(jìn)的去感受static關(guān)鍵字在Spring Bean上的魅力~
警告一:來自BeanPostProcessorChecker
這是最為常見的一種警告,特別當(dāng)你的工程使用了shiro
做鑒權(quán)框架的時(shí)候。在我記憶中這一年來有N多位小伙伴問過我此問題,可見一斑。
@Configuration class AppConfig { AppConfig() { System.out.println("AppConfig init..."); } @Bean BeanPostProcessor postProcessor() { return new MyBeanPostProcessor(); } } class MyBeanPostProcessor implements BeanPostProcessor { MyBeanPostProcessor() { System.out.println("MyBeanPostProcessor init..."); } }
運(yùn)行程序,輸出結(jié)果:
AppConfig init...
2020-05-31 07:40:50.979 INFO 15740 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'appConfig'
of type [com.yourbatman.config.AppConfig$$EnhancerBySpringCGLIB$$29b523c8] is not eligible for getting
processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
MyBeanPostProcessor init...
...
結(jié)果分析(問題點(diǎn)/沖突點(diǎn)):
AppConfig
優(yōu)先于MyBeanPostProcessor
進(jìn)行實(shí)例化常識(shí)是:MyBeanPostProcessor
作為一個(gè)后置處理器理應(yīng)是先被初始化的,而AppConfig
僅僅是個(gè)普通Bean而已,初始化理應(yīng)靠后
出現(xiàn)了BeanPostProcessorChecker
日志:表示AppConfig
這個(gè)Bena不能被所有的BeanPostProcessors處理,所以有可能會(huì)讓它“錯(cuò)過”容器對(duì)Bean的某些生命周期管理,因此可能損失某些能力(比如不能被自動(dòng)代理),存在隱患但凡只要你工程里出現(xiàn)了BeanPostProcessorChecker
輸出日志,理應(yīng)都得引起你的注意,因?yàn)檫@屬于Spring的警告日志(雖然新版本已下調(diào)為了info級(jí)別)
說明:這是一個(gè)Info日志,并非warn/error級(jí)別。絕大多數(shù)情況下你確實(shí)無需關(guān)注,但是如果你是一個(gè)容器開發(fā)者,建議請(qǐng)務(wù)必解決此問題(畢竟貌似大多數(shù)中間件開發(fā)者都有一定代碼潔癖😄)
解決方案:static關(guān)鍵字提升優(yōu)先級(jí)
基于上例,我們僅需做如下小改動(dòng):
AppConfig: //@Bean //BeanPostProcessor postProcessor() { // return new MyBeanPostProcessor(); //} // 方法前面加上static關(guān)鍵字 @Bean static BeanPostProcessor postProcessor() { return new MyBeanPostProcessor(); }
運(yùn)行程序,結(jié)果輸出:
MyBeanPostProcessor init...
...
AppConfig init...
...
那個(gè)煩人的BeanPostProcessorChecker
日志就不見了,清爽了很多。同時(shí)亦可發(fā)現(xiàn)AppConfig
是在MyBeanPostProcessor
之后實(shí)例化的,這才符合我們所想的“正?!边壿嬄铩?/p>
警告二:Configuration配置類增強(qiáng)失敗
這個(gè)“警告”就比上一個(gè)嚴(yán)重得多了,它有極大的可能導(dǎo)致你程序錯(cuò)誤,并且你還很難定位問題所在。
@Configuration class AppConfig { AppConfig() { System.out.println("AppConfig init..."); } @Bean BeanDefinitionRegistryPostProcessor postProcessor() { return new MyBeanDefinitionRegistryPostProcessor(); } /////////////////////////////// @Bean Son son(){ return new Son(); } @Bean Parent parent(){ return new Parent(son()); } } class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { MyBeanDefinitionRegistryPostProcessor() { System.out.println("MyBeanDefinitionRegistryPostProcessor init..."); } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } }
運(yùn)行程序,結(jié)果輸出:
AppConfig init...
MyBeanDefinitionRegistryPostProcessor init...
2020-05-31 07:59:06.363 INFO 37512 --- [ main] o.s.c.a.ConfigurationClassPostProcessor : Cannot enhance
@Configuration bean definition 'appConfig' since its singleton instance has been created too early. The typical
cause is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor return type: Consider declaring
such methods as 'static'.
...
son init...hashCode() = 1300528434
son init...hashCode() = 1598434875
Parent init...
結(jié)果分析(問題點(diǎn)/沖突點(diǎn)):
- AppConfig竟然比MyBeanDefinitionRegistryPostProcessor的初始化時(shí)機(jī)還早,這本就不合理
- 從
ConfigurationClassPostProcessor
的日志中可看到:AppConfig配置類enhance增強(qiáng)失敗 - Son對(duì)象竟然被創(chuàng)建了兩個(gè)不同的實(shí)例,這將會(huì)直接導(dǎo)致功能性錯(cuò)誤
這三步結(jié)果環(huán)環(huán)相扣,因?yàn)?導(dǎo)致了2的增強(qiáng)失敗,因?yàn)?的增強(qiáng)失敗導(dǎo)致了3的創(chuàng)建多個(gè)實(shí)例,真可謂一步錯(cuò),步步錯(cuò)。需要注意的是:這里ConfigurationClassPostProcessor輸出的依舊是info日志(我個(gè)人認(rèn)為,Spring把這個(gè)輸出調(diào)整為warn級(jí)別是更為合理的,因?yàn)樗绊戄^大)。
說明:對(duì)這個(gè)結(jié)果的理解基于對(duì)Spring配置類的理解,因此強(qiáng)烈建議你進(jìn)我公眾號(hào)參閱那個(gè)可能是寫的最全、最好的Spring配置類專欄學(xué)習(xí)(文章不多,6篇足矣)
源碼處解釋:
ConfigurationClassPostProcessor: // 對(duì)Full模式的配置類嘗試使用CGLIB字節(jié)碼提升 public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) { ... // 對(duì)Full模式的配置類有個(gè)判斷/校驗(yàn) if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) { if (!(beanDef instanceof AbstractBeanDefinition)) { throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" + beanName + "' since it is not stored in an AbstractBeanDefinition subclass"); } // 若判斷發(fā)現(xiàn)此時(shí)該配置類已經(jīng)是個(gè)單例Bean了(說明已初始化完成) // 那就不再做處理,并且輸出警告日志告知使用者(雖然是info日志) else if (logger.isInfoEnabled() && beanFactory.containsSingleton(beanName)) { logger.info("Cannot enhance @Configuration bean definition '" + beanName + "' since its singleton instance has been created too early. The typical cause " + "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " + "return type: Consider declaring such methods as 'static'."); } configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef); } ... }
由于配置類增強(qiáng)是在BeanFactoryPostProcessor#postProcessBeanFactory()
聲明周期階段去做的,而BeanDefinitionRegistryPostProcessor
它會(huì)優(yōu)先于該步驟完成實(shí)例化(其實(shí)主要是優(yōu)先級(jí)比BeanFactoryPostProcessor
高),從而間接帶動(dòng) AppConfig提前初始化導(dǎo)致了問題,這便是根本原因所在。
提問點(diǎn):本處使用了個(gè)自定義的BeanDefinitionRegistryPostProcessor
模擬了效果,那如果你是使用的BeanFactoryPostProcessor
能出來這個(gè)效果嗎???答案是不能的,具體原因留給讀者思考,可參考:PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors
這段流程輔助理解。
解決方案:static關(guān)鍵字提升優(yōu)先級(jí)
來吧,繼續(xù)使用static關(guān)鍵字改造一下:
AppConfig: //@Bean //BeanDefinitionRegistryPostProcessor postProcessor() { // return new MyBeanDefinitionRegistryPostProcessor(); //} @Bean static BeanDefinitionRegistryPostProcessor postProcessor() { return new MyBeanDefinitionRegistryPostProcessor(); }
運(yùn)行程序,結(jié)果輸出:
MyBeanDefinitionRegistryPostProcessor init...
...
AppConfig init...
son init...hashCode() = 2090289474
Parent init...
...
完美。
警告三:非靜態(tài)@Bean方法導(dǎo)致@Autowired等注解失效
@Configuration class AppConfig { @Autowired private Parent parent; @PostConstruct void init() { System.out.println("AppConfig.parent = " + parent); } AppConfig() { System.out.println("AppConfig init..."); } @Bean BeanFactoryPostProcessor postProcessor() { return new MyBeanFactoryPostProcessor(); } @Bean Son son() { return new Son(); } @Bean Parent parent() { return new Parent(son()); } } class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { MyBeanFactoryPostProcessor() { System.out.println("MyBeanFactoryPostProcessor init..."); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } }
運(yùn)行程序,結(jié)果輸出:
AppConfig init...
2020-05-31 08:28:06.550 INFO 1464 --- [ main] o.s.c.a.ConfigurationClassEnhancer : @Bean method
AppConfig.postProcessor is non-static and returns an object assignable to Spring's BeanFactoryPostProcessor
interface. This will result in a failure to process annotations such as @Autowired, @Resource and
@PostConstruct within the method's declaring @Configuration class. Add the 'static' modifier to
this method to avoid these container lifecycle issues; see @Bean javadoc for complete details.
MyBeanFactoryPostProcessor init...
...
son init...hashCode() = 882706486
Parent init...
結(jié)果分析(問題點(diǎn)/沖突點(diǎn)):
- AppConfig提前于
MyBeanFactoryPostProcessor
初始化 @Autowired/@PostConstruct
等注解沒有生效,這個(gè)問題很大
需要強(qiáng)調(diào)的是:此時(shí)的AppConfig是被enhance增強(qiáng)成功了的,這樣才有可能進(jìn)入到BeanMethodInterceptor
攔截里面,才有可能輸出這句日志(該攔截器會(huì)攔截Full模式配置列的所有的@Bean方法的執(zhí)行)
這句日志由ConfigurationClassEnhancer.BeanMethodInterceptor
輸出,含義為:你的@Bean標(biāo)注的方法是非static的并且返回了一個(gè)BeanFactoryPostProcessor
類型的實(shí)例,這就導(dǎo)致了配置類里面的@Autowired, @Resource,@PostConstruct
等注解都將得不到解析,這是比較危險(xiǎn)的(所以其實(shí)這個(gè)日志調(diào)整為warn級(jí)別也是闊儀的)。
小細(xì)節(jié):為毛日志看起來是ConfigurationClassEnhancer這個(gè)類輸出的呢?這是因?yàn)?code>BeanMethodInterceptor是它的靜態(tài)內(nèi)部類,和它共用的一個(gè)logger
源碼處解釋:
ConfigurationClassEnhancer.BeanMethodInterceptor: if (isCurrentlyInvokedFactoryMethod(beanMethod)) { if (logger.isInfoEnabled() && BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) { logger.info(String.format("@Bean method %s.%s is non-static and returns an object " + "assignable to Spring's BeanFactoryPostProcessor interface. This will " + "result in a failure to process annotations such as @Autowired, " + "@Resource and @PostConstruct within the method's declaring " + "@Configuration class. Add the 'static' modifier to this method to avoid " + "these container lifecycle issues; see @Bean javadoc for complete details.", beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName())); } return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs); }
解釋為:如果當(dāng)前正在執(zhí)行的@Bean方法(鐵定不是static,因?yàn)殪o態(tài)方法它也攔截不到嘛)返回類型是BeanFactoryPostProcessor
類型,那就輸出此警告日志來提醒使用者要當(dāng)心。
解決方案:static關(guān)鍵字提升優(yōu)先級(jí)
AppConfig: //@Bean //BeanFactoryPostProcessor postProcessor() { // return new MyBeanFactoryPostProcessor(); //} @Bean static BeanFactoryPostProcessor postProcessor() { return new MyBeanFactoryPostProcessor(); }
運(yùn)行程序,結(jié)果輸出:
MyBeanFactoryPostProcessor init...
AppConfig init...
son init...hashCode() = 1906549136
Parent init...
// @PostConstruct注解生效嘍
AppConfig.parent = com.yourbatman.bean.Parent@baf1bb3
...
世界一下子又清爽了有木有。
原因總結(jié)
以上三個(gè)case是有共同點(diǎn)的,粗略的講導(dǎo)致它們的原因甚至是同一個(gè):AppConfig這個(gè)Bean被過早初始化。然而我們的解決方案似乎也是同一個(gè):使用static提升Bean的優(yōu)先級(jí)。
那么為何AppConfig會(huì)被提前初始化呢?為何使用static關(guān)鍵字就沒有問題了呢?根本原因可提前劇透:static靜態(tài)方法屬于類,執(zhí)行靜態(tài)方法時(shí)并不需要初始化所在類的實(shí)例;而實(shí)例方法屬于實(shí)例,執(zhí)行它時(shí)必須先初始化所在類的實(shí)例。聽起來是不是非常的簡(jiǎn)單,JavaSE的東西嘛,當(dāng)然只知曉到這個(gè)層次肯定是遠(yuǎn)遠(yuǎn)不夠的,限于篇幅原因,關(guān)于Spring是如何處理的源碼級(jí)別的分析我放在了下篇文章,請(qǐng)別走開喲~
static靜態(tài)方法一定優(yōu)先執(zhí)行嗎?
看完本文,有些小伙伴就忍不住躍躍欲試了,甚至很武斷的得出結(jié)論:static標(biāo)注的@Bean方法優(yōu)先級(jí)更高,其實(shí)這是錯(cuò)誤的,比如你看如下示例:
@Configuration class AppConfig2 { AppConfig2(){ System.out.println("AppConfig2 init..."); } @Bean Son son() { return new Son(); } @Bean Daughter daughter() { return new Daughter(); } @Bean Parent Parent() { return new Parent(); } }
運(yùn)行程序,結(jié)果輸出:
AppConfig2 init...
son init...
Daughter init...
Parent init...
這時(shí)候你想讓Parent在Son之前初始化,因此你想著在用static關(guān)鍵字來提升優(yōu)先級(jí),這么做:
AppConfig2: //@Bean //Parent Parent() { // return new Parent(); //} @Bean static Parent Parent() { return new Parent(); }
結(jié)果:你徒勞了,static貌似并沒有生效,怎么回事?
原因淺析
為了滿足你的好奇心,這里給個(gè)淺析,道出關(guān)鍵因素。我們知道@Bean方法(不管是靜態(tài)方法還是實(shí)例方法)最終都會(huì)被封裝進(jìn)ConfigurationClass
實(shí)例里面,使用Set<BeanMethod> beanMethods
存儲(chǔ)著,關(guān)鍵點(diǎn)在于它是個(gè)LinkedHashSet
所以是有序的(存放順序),而存入的順序底層是由clazz.getDeclaredMethods()
來決定的,由此可知@Bean方法執(zhí)行順序和有無static沒有半毛錢關(guān)系。
說明:clazz.getDeclaredMethods()
得到的是Method[]數(shù)組,是有序的。這個(gè)順序由字節(jié)碼(定義順序)來保證:先定義,先服務(wù)。
由此可見,static并不是真正意義上的提高Bean優(yōu)先級(jí),對(duì)于如上你的需求case,你可以使用@DependsOn
注解來保證,它也是和Bean順序息息相關(guān)的一個(gè)注解,在本專欄后續(xù)文章中將會(huì)詳細(xì)講到。
所以關(guān)于@Bean方法的執(zhí)行順序的正確結(jié)論應(yīng)該是:在同一配置類內(nèi),在無其它“干擾”情況下(無@DependsOn、@Lazy等注解
),@Bean方法的執(zhí)行順序遵從的是定義順序(后置處理器類型除外)。
小提問:如果是垮@Configuration配置類的情況,順序如何界定呢?那么這就不是同一層級(jí)的問題了,首先考慮的應(yīng)該是@Configuration配置類的順序問題,前面有文章提到過配置類是支持有限的的@Order注解排序的,具體分析請(qǐng)依舊保持關(guān)注A哥后續(xù)文章詳解哈...
static關(guān)鍵字使用注意事項(xiàng)
在同一個(gè)@Configuration
配置類內(nèi),對(duì)static關(guān)鍵字的使用做出如下說明,供以參考:
對(duì)于普通類型(非后置處理器類型)的@Bean方法,使用static關(guān)鍵字并不能改變順序(按照方法定義順序執(zhí)行),所以別指望它static關(guān)鍵字一般有且僅用于@Bean方法返回為BeanPostProcessor
、BeanFactoryPostProcessor
等類型的方法,并且建議此種方法請(qǐng)務(wù)必使用static修飾,否則容易導(dǎo)致隱患,埋雷
static關(guān)鍵字不要濫用(其實(shí)任何關(guān)鍵字皆勿亂用),在同一配置類內(nèi),與其說它是提升了Bean的優(yōu)先級(jí),倒不如說它讓@Bean方法靜態(tài)化從而不再需要依賴所在類的實(shí)例即可獨(dú)立運(yùn)行。另外我們知道,static關(guān)鍵還可以修飾(內(nèi)部)類,那么如果放在類上它又是什么表現(xiàn)呢?同樣的,你先思考,下篇文章我們接著聊~
說明:使用static修飾Class類在Spring Boot自動(dòng)配置類里特別特別常見,所以掌握起來很具價(jià)值
思考題:
今天的思考題比較簡(jiǎn)單:為何文首三種case的警告信息都是info級(jí)別呢?是否有級(jí)別過低之嫌?
總結(jié)
本文還是蠻干的哈,不出意外它能夠幫你解決你工程中的某些問題,排除掉一些隱患,畢竟墨菲定律被驗(yàn)證了你擔(dān)心的事它總會(huì)發(fā)生,防患于未然才能把自己置于安全高地嘛。
你可能詫異,A哥竟能把static關(guān)鍵字在Spring中的應(yīng)用都能寫出個(gè)專欄出來,是的,這不是就是本公眾號(hào)的定位么 ,小而美和拒絕淺嘗輒止嘛。對(duì)于一些知識(shí)(比如本文的static關(guān)鍵字的使用)我并不推崇強(qiáng)行記憶,因?yàn)槟钦娴暮苋菀淄?,快速使用可以?jiǎn)單記記,但真想記得牢(甚至成為永久記憶),那必須得去深水區(qū)看看。來吧,下文將授之以漁~
很多小伙伴去強(qiáng)行記憶Spring Boot支持的那17種外部化配置,此時(shí)你應(yīng)該問自己:現(xiàn)在你可能記得,一周以后呢?一個(gè)月以后呢?所以你需要另辟蹊徑,那就持續(xù)關(guān)注我吧😄
到此這篇關(guān)于springboot static關(guān)鍵字真能提高Bean的優(yōu)先級(jí)(厲害了)的文章就介紹到這了,更多相關(guān)spring boot static關(guān)鍵字內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決java Graphics drawImage 無法顯示圖片的問題
這篇文章主要介紹了解決java Graphics drawImage 無法顯示圖片的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11解決執(zhí)行Junit單元測(cè)試報(bào)錯(cuò)java.lang.ClassNotFoundException問題
這篇文章主要介紹了解決執(zhí)行Junit單元測(cè)試報(bào)錯(cuò)java.lang.ClassNotFoundException問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11Elasticsearch索引庫和文檔的相關(guān)操作詳細(xì)指南
這篇文章主要給大家介紹了關(guān)于Elasticsearch索引庫和文檔的相關(guān)操作的相關(guān)資料,Elasticsearch是用Java開發(fā)并且是當(dāng)前最流行的開源的企業(yè)級(jí)搜索引擎,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11AJAX+JAVA用戶登陸注冊(cè)驗(yàn)證的實(shí)現(xiàn)代碼
這篇文章主要介紹了AJAX+JAVA用戶登陸注冊(cè)驗(yàn)證的實(shí)現(xiàn)代碼,通過ajax異步刷新頁面驗(yàn)證用戶輸入的賬號(hào)密碼是否在數(shù)據(jù)庫中存在。非常具有實(shí)用價(jià)值,需要的朋友可以參考下2018-06-06JAVA LinkedList和ArrayList的使用及性能分析
JAVA LinkedList和ArrayList的使用及性能分析,這篇文章也是以JAVA List的總結(jié)。2013-11-11Springmvc nginx實(shí)現(xiàn)動(dòng)靜分離過程詳解
這篇文章主要介紹了Springmvc nginx實(shí)現(xiàn)動(dòng)靜分離過程詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09Java反射機(jī)制,反射相關(guān)API,反射API使用方式(反射獲取實(shí)體類字段名和注解值)
這篇文章主要介紹了Java反射機(jī)制,反射相關(guān)API,反射API使用方式(反射獲取實(shí)體類字段名和注解值),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07Springboot中登錄后關(guān)于cookie和session攔截問題的案例分析
這篇文章主要介紹了Springboot中登錄后關(guān)于cookie和session攔截案例,本文通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08