深入了解SpringMVC初始化流程
前言
框架源碼是我們 Coding 晉級(jí)中的必修課,SSM 應(yīng)該算是小伙伴們?nèi)粘=佑|最多的框架了,這其中 SpringMVC 初始化流程相對(duì)來說要簡單一些,因此今天松哥就先來和大家分析一下 SpringMVC 初始化流程。
本文算是 SpringMVC 用法的一個(gè)進(jìn)階,如果小伙伴們對(duì) SpringMVC 的基礎(chǔ)用法還不熟悉,可以在公眾號(hào)后臺(tái)回復(fù) ssm,有松哥錄制的免費(fèi)視頻教程。
即使你沒看過 SpringMVC 的源碼,估計(jì)也聽說過:DispatcherServlet 是 SpringMVC 的大腦,它負(fù)責(zé)整個(gè) SpringMVC 的調(diào)度工作,是 SpringMVC 中最最核心的類,SpringMVC 整個(gè)頂層架構(gòu)設(shè)計(jì)都體現(xiàn)在這里,所以搞明白 DispatcherServlet 的源碼,基本上 SpringMVC 的工作原理也就了然于胸了。
然而 DispatcherServlet 繼承自 FrameworkServlet,F(xiàn)rameworkServlet 又繼承自 HttpServletBean,如下圖:
因此我們的分析就從 HttpServletBean 開始。
1.HttpServletBean
HttpServletBean
繼承自 HttpServlet
,它負(fù)責(zé)將 init-param
中的參數(shù)注入到當(dāng)前 Servlet
實(shí)例的屬性中,同時(shí)也為子類提供了增加 requiredProperties
的能力,需要注意的是 HttpServletBean
并不依賴于 Spring
容器。
大家知道,HttpServlet 的初始化是從 init 方法開始的,所以我們就先從 HttpServletBean 的 init 方法開始看起:
@Override public?final?void?init()?throws?ServletException?{ ?//?Set?bean?properties?from?init?parameters. ?PropertyValues?pvs?=?new?ServletConfigPropertyValues(getServletConfig(),?this.requiredProperties); ?if?(!pvs.isEmpty())?{ ??try?{ ???BeanWrapper?bw?=?PropertyAccessorFactory.forBeanPropertyAccess(this); ???ResourceLoader?resourceLoader?=?new?ServletContextResourceLoader(getServletContext()); ???bw.registerCustomEditor(Resource.class,?new?ResourceEditor(resourceLoader,?getEnvironment())); ???initBeanWrapper(bw); ???bw.setPropertyValues(pvs,?true); ??} ??catch?(BeansException?ex)?{ ???if?(logger.isErrorEnabled())?{ ????logger.error("Failed?to?set?bean?properties?on?servlet?'"?+?getServletName()?+?"'",?ex); ???} ???throw?ex; ??} ?} ?//?Let?subclasses?do?whatever?initialization?they?like. ?initServletBean(); }
在這個(gè)方法里,首先獲取到 Servlet 的所有配置并轉(zhuǎn)為 PropertyValues,然后通過 BeanWrapper 修改目標(biāo) Servlet 的相關(guān)屬性。BeanWrapper 是 Spring 中提供一個(gè)工具,使用它可以修改一個(gè)對(duì)象的屬性,像下面這樣:
public?class?Main?{ ????public?static?void?main(String[]?args)?{ ????????User?user?=?new?User(); ????????BeanWrapper?beanWrapper?=?PropertyAccessorFactory.forBeanPropertyAccess(user); ????????beanWrapper.setPropertyValue("username",?"itboyhub"); ????????PropertyValue?pv?=?new?PropertyValue("address",?"www.itboyhub.com"); ????????beanWrapper.setPropertyValue(pv); ????????System.out.println("user?=?"?+?user); ????} }
最終輸出:
user = User{username='itboyhub', address='www.itboyhub.com'}
所以前面的 bw 實(shí)際上就代表當(dāng)前 DispatcherServlet 對(duì)象。
通過 BeanWrapper 修改目標(biāo) Servlet 的相關(guān)屬性時(shí),有一個(gè) initBeanWrapper 方法是空方法,開發(fā)者如有需要可以在子類中實(shí)現(xiàn)該方法,并且完成一些初始化操作。
屬性配置完成后,最終調(diào)用 initServletBean 方法進(jìn)行 Servlet 初始化,然而該方法也是一個(gè)空方法,在子類中實(shí)現(xiàn)。
這就是 HttpServletBean 所做的事情,比較簡單,加載 Servlet 相關(guān)屬性并設(shè)置給當(dāng)前 Servlet 對(duì)象,然后調(diào)用 initServletBean 方法繼續(xù)完成 Servlet 的初始化操作。
2.FrameworkServlet
從前面的介紹可知,F(xiàn)rameworkServlet 初始化的入口方法就是 initServletBean,因此我們就從 FrameworkServlet#initServletBean 方法開始看起:
@Override protected?final?void?initServletBean()?throws?ServletException?{ ?//省略... ?try?{ ??this.webApplicationContext?=?initWebApplicationContext(); ??initFrameworkServlet(); ?} ?catch?(ServletException?|?RuntimeException?ex)?{ ??//省略... ?} }
這個(gè)方法原本挺長的,但是拋開日志打印異常拋出,剩下的核心代碼其實(shí)就兩行:
- initWebApplicationContext 方法用來初始化 WebApplicationContext。
- initFrameworkServlet 方法用來初始化 FrameworkServlet,但是這個(gè)方法是一個(gè)空方法,沒有具體的實(shí)現(xiàn)。本來子類可以重寫該方法做一些初始化操作,但是實(shí)際上子類并沒有重寫該方法,所以這個(gè)方法我們就暫且忽略之,不去分析了。
那么這里最為重要的其實(shí)就是 initWebApplicationContext 方法了,我們一起來看下:
protected?WebApplicationContext?initWebApplicationContext()?{ ?WebApplicationContext?rootContext?= ???WebApplicationContextUtils.getWebApplicationContext(getServletContext()); ?WebApplicationContext?wac?=?null; ?if?(this.webApplicationContext?!=?null)?{ ??wac?=?this.webApplicationContext; ??if?(wac?instanceof?ConfigurableWebApplicationContext)?{ ???ConfigurableWebApplicationContext?cwac?=?(ConfigurableWebApplicationContext)?wac; ???if?(!cwac.isActive())?{ ????if?(cwac.getParent()?==?null)?{ ?????cwac.setParent(rootContext); ????} ????configureAndRefreshWebApplicationContext(cwac); ???} ??} ?} ?if?(wac?==?null)?{ ??wac?=?findWebApplicationContext(); ?} ?if?(wac?==?null)?{ ??wac?=?createWebApplicationContext(rootContext); ?} ?if?(!this.refreshEventReceived)?{ ??synchronized?(this.onRefreshMonitor)?{ ???onRefresh(wac); ??} ?} ?if?(this.publishContext)?{ ??String?attrName?=?getServletContextAttributeName(); ??getServletContext().setAttribute(attrName,?wac); ?} ?return?wac; }
這里的邏輯也比較清晰:
- 首先獲取 rootContext。在默認(rèn)情況下,Spring 會(huì)將容器設(shè)置為 ServletContext 的一個(gè)屬性,屬性的 key 為
org.springframework.web.context.WebApplicationContext.ROOT
,所以根據(jù)這個(gè) key 就可以調(diào)用 ServletContext#getAttribute 方法獲取到 rootContext 了。 - 獲取 WebApplicationContext 實(shí)例,也就是給 wac 變量賦值的過程,這里存在三種可能性:1.如果已經(jīng)通過構(gòu)造方法給 webApplicationContext 賦值了,則直接將其賦給 wac 變量,同時(shí),如果需要設(shè)置 parent 就設(shè)置,需要刷新就刷新。這種方式適用于 Servlet3.0 以后的環(huán)境,因?yàn)閺?Servlet3.0 開始,才支持直接調(diào)用 ServletContext.addServlet 方法去注冊 Servlet,手動(dòng)注冊的時(shí)候就可以使用自己提前準(zhǔn)備好的 WebApplicationContext 了,這塊松哥在我錄制的 Spring Boot 視頻中也講過,感興趣的小伙伴可以在公眾號(hào)后臺(tái)回復(fù) vhr 查看視頻詳情;2.如果第一步?jīng)]能成功給 wac 賦值,那么調(diào)用 findWebApplicationContext 方法嘗試去 ServletContext 中查找 WebApplicationContext 對(duì)象,找到了就賦值給 wac;3.如果第二步?jīng)]能成功給 wac 賦值,那么調(diào)用 createWebApplicationContext 方法創(chuàng)建一個(gè) WebApplicationContext 對(duì)象并賦值給 wac,一般來說都是通過這種方式創(chuàng)建的 WebApplicationContext。這三套組合拳下來,wac 肯定是有值了。
- 當(dāng) ContextRefreshedEvent 事件沒有觸發(fā)時(shí),調(diào)用 onRefresh 方法完成容器刷新(由于第一種和第三種獲取 WebApplicationContext 的方式最終都會(huì)調(diào)用 configureAndRefreshWebApplicationContext 方法,然后發(fā)布事件,再將 refreshEventReceived 變量標(biāo)記為 true,所以實(shí)際上只有第二種方式獲取 wac 實(shí)例的時(shí)候,這里才會(huì)刷新,具體可以看下文分析)。
- 最后將 wac 保存到到 ServletContext 中。保存的時(shí)候會(huì)根據(jù) publishContext 變量的值來決定是否保存,publishContext 可以在 web.xml 中配置 Servlet 時(shí)通過 init-param 進(jìn)行配置,保存的目的是為了方便獲取。
上面的這些步驟中,通過 createWebApplicationContext 方法創(chuàng)建 WebApplicationContext 對(duì)象需要和大家細(xì)說下,因?yàn)橐话闱闆r下就是通過這種方式創(chuàng)建的 WebApplicationContext。我們來看一下相關(guān)的方法:
protected?WebApplicationContext?createWebApplicationContext(@Nullable?ApplicationContext?parent)?{ ?Class<?>?contextClass?=?getContextClass(); ?if?(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass))?{ ??throw?new?ApplicationContextException( ????"Fatal?initialization?error?in?servlet?with?name?'"?+?getServletName()?+ ????"':?custom?WebApplicationContext?class?["?+?contextClass.getName()?+ ????"]?is?not?of?type?ConfigurableWebApplicationContext"); ?} ?ConfigurableWebApplicationContext?wac?= ???(ConfigurableWebApplicationContext)?BeanUtils.instantiateClass(contextClass); ?wac.setEnvironment(getEnvironment()); ?wac.setParent(parent); ?String?configLocation?=?getContextConfigLocation(); ?if?(configLocation?!=?null)?{ ??wac.setConfigLocation(configLocation); ?} ?configureAndRefreshWebApplicationContext(wac); ?return?wac; } protected?void?configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext?wac)?{ ?if?(ObjectUtils.identityToString(wac).equals(wac.getId()))?{ ??//?The?application?context?id?is?still?set?to?its?original?default?value ??//?->?assign?a?more?useful?id?based?on?available?information ??if?(this.contextId?!=?null)?{ ???wac.setId(this.contextId); ??} ??else?{ ???//?Generate?default?id... ???wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX?+ ?????ObjectUtils.getDisplayString(getServletContext().getContextPath())?+?'/'?+?getServletName()); ??} ?} ?wac.setServletContext(getServletContext()); ?wac.setServletConfig(getServletConfig()); ?wac.setNamespace(getNamespace()); ?wac.addApplicationListener(new?SourceFilteringListener(wac,?new?ContextRefreshListener())); ?//?The?wac?environment's?#initPropertySources?will?be?called?in?any?case?when?the?context ?//?is?refreshed;?do?it?eagerly?here?to?ensure?servlet?property?sources?are?in?place?for ?//?use?in?any?post-processing?or?initialization?that?occurs?below?prior?to?#refresh ?ConfigurableEnvironment?env?=?wac.getEnvironment(); ?if?(env?instanceof?ConfigurableWebEnvironment)?{ ??((ConfigurableWebEnvironment)?env).initPropertySources(getServletContext(),?getServletConfig()); ?} ?postProcessWebApplicationContext(wac); ?applyInitializers(wac); ?wac.refresh(); }
這里一共涉及到兩個(gè)方法:
createWebApplicationContext
首先獲取到創(chuàng)建類型,并檢查創(chuàng)建類型,沒問題的話調(diào)用 instantiateClass 方法完成創(chuàng)建工作,然后給創(chuàng)建好的 wac 對(duì)象配置各種屬性,配置的 configLocation 就是我們在 web.xml 文件中配置的 SpringMVC 配置文件路徑,默認(rèn)的文件路徑是 /WEB-INF/[servletName]-servlet.xml
。
configureAndRefreshWebApplicationContext
configureAndRefreshWebApplicationContext 方法主要也是配置&刷新 WebApplicationContext,在這個(gè)方法里會(huì)調(diào)用 addApplicationListener 為 wac 添加一個(gè)監(jiān)聽器,監(jiān)聽的是 ContextRefreshedEvent 事件,當(dāng)收到該事件后,會(huì)調(diào)用 FrameworkServlet 的 onApplicationEvent 方法,并在該方法中調(diào)用 onRefresh 方法完成刷新,刷新之后,會(huì)將 refreshEventReceived 變量標(biāo)記為 true。
public?void?onApplicationEvent(ContextRefreshedEvent?event)?{ ?this.refreshEventReceived?=?true; ?synchronized?(this.onRefreshMonitor)?{ ??onRefresh(event.getApplicationContext()); ?} }
這就是 FrameworkServlet#initServletBean 方法的大致工作邏輯。這里涉及到了 onRefresh 方法,但是這是一個(gè)空方法,在子類 DispatcherServlet 中實(shí)現(xiàn)了,所以接下來我們就來看 DispatcherServlet。
3.DispatcherServlet
這里我們就不廢話了,直接來看 onRefresh 方法,如下:
@Override protected?void?onRefresh(ApplicationContext?context)?{ ?initStrategies(context); } protected?void?initStrategies(ApplicationContext?context)?{ ?initMultipartResolver(context); ?initLocaleResolver(context); ?initThemeResolver(context); ?initHandlerMappings(context); ?initHandlerAdapters(context); ?initHandlerExceptionResolvers(context); ?initRequestToViewNameTranslator(context); ?initViewResolvers(context); ?initFlashMapManager(context); }
在 onRefresh 方法中調(diào)用了 initStrategies 進(jìn)行初始化操作。initStrategies 的內(nèi)容其實(shí)很簡單,就是九個(gè)組件的初始化。九個(gè)的初始化流程比較類似,這里我們以常見的視圖解析器的初始化方法 initViewResolvers 為例,來一起看看初始化流程:
private?void?initViewResolvers(ApplicationContext?context)?{ ?this.viewResolvers?=?null; ?if?(this.detectAllViewResolvers)?{ ??//?Find?all?ViewResolvers?in?the?ApplicationContext,?including?ancestor?contexts. ??Map<String,?ViewResolver>?matchingBeans?= ????BeanFactoryUtils.beansOfTypeIncludingAncestors(context,?ViewResolver.class,?true,?false); ??if?(!matchingBeans.isEmpty())?{ ???this.viewResolvers?=?new?ArrayList<>(matchingBeans.values()); ???//?We?keep?ViewResolvers?in?sorted?order. ???AnnotationAwareOrderComparator.sort(this.viewResolvers); ??} ?} ?else?{ ??try?{ ???ViewResolver?vr?=?context.getBean(VIEW_RESOLVER_BEAN_NAME,?ViewResolver.class); ???this.viewResolvers?=?Collections.singletonList(vr); ??} ??catch?(NoSuchBeanDefinitionException?ex)?{ ???//?Ignore,?we'll?add?a?default?ViewResolver?later. ??} ?} ?//?Ensure?we?have?at?least?one?ViewResolver,?by?registering ?//?a?default?ViewResolver?if?no?other?resolvers?are?found. ?if?(this.viewResolvers?==?null)?{ ??this.viewResolvers?=?getDefaultStrategies(context,?ViewResolver.class); ??if?(logger.isTraceEnabled())?{ ???logger.trace("No?ViewResolvers?declared?for?servlet?'"?+?getServletName()?+ ?????"':?using?default?strategies?from?DispatcherServlet.properties"); ??} ?} }
一開始的 viewResolvers 變量是一個(gè)集合,解析出來的視圖解析器對(duì)象都將放入這個(gè)集合中。
首先判斷 detectAllViewResolvers 變量是否為 true,如果為 true,則直接去查找 Spring 容器中的所有視圖解析器,將查找結(jié)果賦值給 viewResolvers,然后進(jìn)行排序。默認(rèn)情況下 detectAllViewResolvers 變量的值為 true,如果有需要,可以在 web.xml 中進(jìn)行配置,像下面這樣:
<servlet> ????<servlet-name>springmvc</servlet-name> ????<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> ????<init-param> ????????<param-name>contextConfigLocation</param-name> ????????<param-value>classpath:spring-servlet.xml</param-value> ????</init-param> ????<init-param> ????????<param-name>detectAllViewResolvers</param-name> ????????<param-value>false</param-value> ????</init-param> ????<load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> ????<servlet-name>springmvc</servlet-name> ????<url-pattern>/</url-pattern> </servlet-mapping>
如果 detectAllViewResolvers 的值為 false,那么接下來就會(huì)去 Spring 容器中查找一個(gè)名為 viewResolver 的視圖解析器,此時(shí)查找到的就是一個(gè)單獨(dú)的視圖解析器。
一般來說,我們并不需要在 web.xml 中去配置 detectAllViewResolvers 的值,視圖解析器有多少個(gè)就加載多少個(gè)。
舉個(gè)簡單例子,我們在 SpringMVC 的配置文件中可能像下面這樣配置視圖解析器:
<bean?class="org.springframework.web.servlet.view.InternalResourceViewResolver"?id="viewResolver"> ????<property?name="prefix"?value="/WEB-INF/jsp/"/> ????<property?name="suffix"?value=".jsp"/> </bean>
默認(rèn)情況下,這個(gè) bean 的 id 有沒有都行,如果有,取什么值都可以,反正最終都是通過類型而不是 id 去查找的視圖解析器。但是如果你在 web.xml 中將 detectAllViewResolvers 修改為 false,那么這個(gè) bean 的 id 取值就比較重要了,就一定要是 viewResolver。
如果在 Spring 容器中通過這兩種方式(通過類型查找或通過 id 查找)都沒有找到 ViewResolver 實(shí)例,那么會(huì)調(diào)用 getDefaultStrategies 方法去獲取一個(gè)默認(rèn)的 ViewResolver 實(shí)例。默認(rèn)實(shí)例的獲取方式如下:
protected?<T>?List<T>?getDefaultStrategies(ApplicationContext?context,?Class<T>?strategyInterface)?{ ?if?(defaultStrategies?==?null)?{ ??try?{ ???//?Load?default?strategy?implementations?from?properties?file. ???//?This?is?currently?strictly?internal?and?not?meant?to?be?customized ???//?by?application?developers. ???ClassPathResource?resource?=?new?ClassPathResource(DEFAULT_STRATEGIES_PATH,?DispatcherServlet.class); ???defaultStrategies?=?PropertiesLoaderUtils.loadProperties(resource); ??} ??catch?(IOException?ex)?{ ???throw?new?IllegalStateException("Could?not?load?'"?+?DEFAULT_STRATEGIES_PATH?+?"':?"?+?ex.getMessage()); ??} ?} ?String?key?=?strategyInterface.getName(); ?String?value?=?defaultStrategies.getProperty(key); ?if?(value?!=?null)?{ ??String[]?classNames?=?StringUtils.commaDelimitedListToStringArray(value); ??List<T>?strategies?=?new?ArrayList<>(classNames.length); ??for?(String?className?:?classNames)?{ ???try?{ ????Class<?>?clazz?=?ClassUtils.forName(className,?DispatcherServlet.class.getClassLoader()); ????Object?strategy?=?createDefaultStrategy(context,?clazz); ????strategies.add((T)?strategy); ???} ???catch?(ClassNotFoundException?ex)?{ ????throw?new?BeanInitializationException( ??????"Could?not?find?DispatcherServlet's?default?strategy?class?["?+?className?+ ??????"]?for?interface?["?+?key?+?"]",?ex); ???} ???catch?(LinkageError?err)?{ ????throw?new?BeanInitializationException( ??????"Unresolvable?class?definition?for?DispatcherServlet's?default?strategy?class?["?+ ??????className?+?"]?for?interface?["?+?key?+?"]",?err); ???} ??} ??return?strategies; ?} ?else?{ ??return?Collections.emptyList(); ?} }
這段代碼其實(shí)也比較簡單,就是通過反射去獲取默認(rèn)的視圖解析器。
首先給 defaultStrategies 賦值,defaultStrategies 的值實(shí)際上就是從 DispatcherServlet.properties 文件中加載到的,我們來看下這個(gè)文件內(nèi)容:
可以看到,這里一共定義了 8 個(gè)默認(rèn)的鍵值對(duì),有的值是一個(gè),有的值是多個(gè)。前面 initStrategies 方法中一共要初始化九個(gè)組件,這里默認(rèn)只定義了 8 個(gè),少了一個(gè) MultipartResolver,這也好理解,并非所有的項(xiàng)目都有文件上傳,而且即使有文件上傳,用哪一個(gè)具體的 MultipartResolver 也不好確定,還是要開發(fā)者自己決定。
defaultStrategies 其實(shí)加載到的就是這 8 個(gè)鍵值對(duì),其中視圖解析器對(duì)應(yīng)的是 org.springframework.web.servlet.view.InternalResourceViewResolver,通過反射創(chuàng)建該類的實(shí)例,當(dāng) Spring 容器中不存在任何視圖解析器的時(shí)候,默認(rèn)的視圖解析器即此。
這就是 initViewResolvers 的工作流程,另外 8 個(gè)也和它差不多,唯一不同的是 initMultipartResolver,如下:
private?void?initMultipartResolver(ApplicationContext?context)?{ ?try?{ ??this.multipartResolver?=?context.getBean(MULTIPART_RESOLVER_BEAN_NAME,?MultipartResolver.class); ?} ?catch?(NoSuchBeanDefinitionException?ex)?{ ??this.multipartResolver?=?null; ?} }
可以看到,它只是根據(jù) bean 的名字去查找 bean 實(shí)例,沒有去查找默認(rèn)的 MultipartResolver。
說到這里,松哥和大家多說一句 SpringMVC 配置中的小細(xì)節(jié),
<bean?class="org.springframework.web.servlet.view.InternalResourceViewResolver"?id="viewResolver"> ????<property?name="prefix"?value="/WEB-INF/jsp/"/> ????<property?name="suffix"?value=".jsp"/> </bean> <bean?class="org.springframework.web.multipart.commons.CommonsMultipartResolver"?id="multipartResolver"> </bean>
上面這個(gè)關(guān)于視圖解析器和文件上傳解析器的配置,不知道小伙伴們有沒有注意過,視圖解析器的 id 可有可無,而文件上傳解析器的 id 必須是 multipartResolver,回顧我們上面的源碼分析,你就知道為啥了!
4.小結(jié)
好啦,這就是松哥和小伙伴們分享的 SpringMVC 的初始化流程,主要涉及到了 HttpServletBean、FrameworkServlet 以及 DispatcherServlet 三個(gè)實(shí)例,HttpServletBean 主要是加載 Servlet 配置的各種屬性并設(shè)置到 Servlet 上;FrameworkServlet 則主要是初始化了 WebApplicationContext;DispatcherServlet 則主要是初始化了自身的九個(gè)組件。
到此這篇關(guān)于深入了解SpringMVC初始化流程的文章就介紹到這了,更多相關(guān)SpringMVC初始化流程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java實(shí)現(xiàn)批量導(dǎo)入.csv文件到mysql數(shù)據(jù)庫
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)批量導(dǎo)入.csv文件到mysql數(shù)據(jù)庫,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08SpringBoot詳解如何進(jìn)行整合Druid數(shù)據(jù)源
Druid是阿里開發(fā)的一款開源的數(shù)據(jù)源,被很多人認(rèn)為是Java語言中最好的數(shù)據(jù)庫連接池,本文主要介紹了SpringBoot整合Druid數(shù)據(jù)源的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06Java中l(wèi)ist集合的clear方法及空字符串的區(qū)別
這篇文章主要介紹了Java中l(wèi)ist集合的clear方法及空字符串的區(qū)別,在使用list?結(jié)合的時(shí)候習(xí)慣了?list=null?;在創(chuàng)建這樣的方式,但是發(fā)現(xiàn)使用list的clear?方法很不錯(cuò),尤其是有大量循環(huán)的時(shí)候<BR>list.clear()與list?=?null?區(qū)別,需要的朋友可以參考下2023-08-08java程序中指定某個(gè)瀏覽器打開的實(shí)現(xiàn)方法
最近工作中遇到一個(gè)需求,是要利用java打開指定瀏覽器,整理后發(fā)現(xiàn)有四種解決的方法,所以想著分享出來,下面這篇文章主要給大家介紹了java程序中指定某個(gè)瀏覽器打開的實(shí)現(xiàn)方法,,需要的朋友可以參考下。2017-03-03SpringBoot如何使用applicationContext.xml配置文件
這篇文章主要介紹了SpringBoot使用applicationContext.xml配置文件,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06SpringBoot實(shí)現(xiàn)過濾器、攔截器與切片的實(shí)現(xiàn)和區(qū)別
本文詳細(xì)介紹了使用過濾器、攔截器與切片實(shí)現(xiàn)每個(gè)請(qǐng)求耗時(shí)的統(tǒng)計(jì),并比較三者的區(qū)別與聯(lián)系,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-02-02Java用BigDecimal解決double類型相減時(shí)可能存在的誤差
這篇文章主要介紹了Java用BigDecimal解決double類型相減時(shí)可能存在的誤差,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05