优化您的ApplicationContext
Spring有一個(gè)問題,已經(jīng)存在了一段時(shí)間,我在許多項(xiàng)目中都遇到過。 與Spring或Spring的Guys無關(guān),這取決于像您和我這樣的Spring用戶。 讓我解釋一下……在Spring 2的過去,您必須手動(dòng)配置Application Context,手動(dòng)創(chuàng)建一個(gè)包含所有bean定義的XML配置文件。 這種技術(shù)的缺點(diǎn)是創(chuàng)建這些XML文件很費(fèi)時(shí),然后您就很難維護(hù)這個(gè)越來越復(fù)雜的文件。 我似乎記得當(dāng)時(shí)它被稱為“ Spring Config Hell”。 從好的方面來說,至少您對(duì)加載到上下文中的所有內(nèi)容都有一個(gè)中央記錄。 順應(yīng)需求和流行的注釋方式,Spring 3引入了大量的原型設(shè)計(jì)類,例如@Service @Controller , @Repository @Component , @Controller和@Repository ,以及<context:component-scan/>的XML配置文件<context:component-scan/>元素。 從編程的角度來看,這使事情變得簡單得多,并且是構(gòu)造Spring上下文的一種非常流行的方式。
但是,使用Spring注釋時(shí)要放任不管,并使用@Service @Component , @Controller @Component , @Controller或@Repository來添加所有內(nèi)容,這在大型代碼庫中尤其麻煩。 問題是您的上下文被不需要的東西污染了,這是一個(gè)問題,因?yàn)?#xff1a;
- 您不必要地用完了燙發(fā)空間,從而導(dǎo)致更多“燙發(fā)空間錯(cuò)誤”的風(fēng)險(xiǎn)。
- 您不必要地耗盡了堆空間。
- 您的應(yīng)用程序可能需要更長的時(shí)間才能加載。
- 不需要的對(duì)象可以“隨便做”,特別是如果它們是多線程的,則具有start()方法或?qū)崿F(xiàn)InitializingBean 。
- 不需要的對(duì)象只會(huì)阻止您的應(yīng)用程序正常工作……
在小型應(yīng)用程序中,我猜想在Spring上下文中是否有幾個(gè)額外的對(duì)象并不重要,但是,正如我上面說的,如果您的應(yīng)用程序很大,處理器密集型或占用內(nèi)存,則這尤其麻煩。 在這一點(diǎn)上,有必要對(duì)這種情況進(jìn)行分類,并且要做到這一點(diǎn),您必須首先弄清楚要加載到Spring上下文中的是什么。
一種方法是通過在log4j屬性中添加以下內(nèi)容來啟用com.springsource包上的調(diào)試功能:
log4j.logger.com.springsource=DEBUG將以上內(nèi)容添加到您的log4j屬性(在本例中為log4j 1.x)中,您將獲得有關(guān)Spring上下文的大量信息–我的意思是很多。 如果您是Spring的專家之一,并且正在研究Spring源代碼,那么實(shí)際上這只是您需要做的事情。
另一種更簡潔的方法是在您的應(yīng)用程序中添加一個(gè)類,該類將準(zhǔn)確報(bào)告正在Spring上下文中加載的內(nèi)容。 然后,您可以檢查報(bào)告并進(jìn)行任何適當(dāng)?shù)母摹?
該博客的示例代碼包含一個(gè)類,這是我之前寫過兩三遍的文章,分別為不同的公司從事不同的項(xiàng)目。 它依賴于Spring的幾個(gè)功能。 也就是說,在Context加載后,Spring可以在您的類中調(diào)用一個(gè)方法,并且Spring的ApplicationContext接口包含一些方法,這些方法可以告訴您有關(guān)其內(nèi)部的所有信息。
@Service public class ApplicationContextReport implements ApplicationContextAware, InitializingBean { private static final String LINE = "====================================================================================================\n"; private static final Logger logger = LoggerFactory.getLogger("ContextReport"); private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Override public void afterPropertiesSet() throws Exception { report(); } public void report() { StringBuilder sb = new StringBuilder("\n" + LINE); sb.append("Application Context Report\n"); sb.append(LINE); createHeader(sb); createBody(sb); sb.append(LINE); logger.info(sb.toString()); } private void createHeader(StringBuilder sb) { addField(sb, "Application Name: ", applicationContext.getApplicationName()); addField(sb, "Display Name: ", applicationContext.getDisplayName()); String startupDate = getStartupDate(applicationContext.getStartupDate()); addField(sb, "Start Date: ", startupDate); Environment env = applicationContext.getEnvironment(); String[] activeProfiles = env.getActiveProfiles(); if (activeProfiles.length > 0) { addField(sb, "Active Profiles: ", activeProfiles); } } private void addField(StringBuilder sb, String name, String... values) { sb.append(name); for (String val : values) { sb.append(val); sb.append(", "); } sb.setLength(sb.length() - 2); sb.append("\n"); } private String getStartupDate(long startupDate) { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); return df.format(new Date(startupDate)); } private void createBody(StringBuilder sb) { addColumnHeaders(sb); addColumnValues(sb); } private void addColumnHeaders(StringBuilder sb) { sb.append("\nBean Name\tSimple Name\tSingleton\tFull Class Name\n"); sb.append(LINE); } private void addColumnValues(StringBuilder sb) { String[] beanNames = applicationContext.getBeanDefinitionNames(); for (String name : beanNames) { addRow(name, sb); } } private void addRow(String name, StringBuilder sb) { Object obj = applicationContext.getBean(name); String fullClassName = obj.getClass().getName(); if (!fullClassName.contains("org.springframework")) { sb.append(name); sb.append("\t"); String simpleName = obj.getClass().getSimpleName(); sb.append(simpleName); sb.append("\t"); boolean singleton = applicationContext.isSingleton(name); sb.append(singleton ? "YES" : "NO"); sb.append("\t"); sb.append(fullClassName); sb.append("\n"); } } }首先要注意的是,此版本的代碼實(shí)現(xiàn)了Spring的InitializingBean接口。 當(dāng)Spring將一個(gè)類加載到上下文中時(shí),Spring將檢查此接口。 如果找到它,它將調(diào)用AfterPropertiesSet()方法。
這不是讓Spring在啟動(dòng)時(shí)調(diào)用您的類的唯一方法,請(qǐng)參閱: 三種Spring Bean生命周期技術(shù)以及使用JSR-250的@PostConstruct注釋替換Spring的InitializingBean
接下來要注意的是,該報(bào)告類實(shí)現(xiàn)了Spring的ApplicationContextAware接口。 這是另一個(gè)有用的Spring主力接口,通常永遠(yuǎn)不需要每天使用。 該接口背后的理由是使您的類可以訪問應(yīng)用程序的ApplicationContext 。 它包含一個(gè)方法: setApplicationContext(...) ,由Spring調(diào)用以將ApplicationContext注入您的類中。 在這種情況下,我只是將ApplicationContext參數(shù)保存為實(shí)例變量。
主報(bào)告的生成是通過report()方法完成的(由afterPropertiesSet()調(diào)用)。 report()方法所做的全部工作就是創(chuàng)建一個(gè)StringBuilder()類,然后附加大量信息。 我不會(huì)逐一介紹每一行,因?yàn)檫@種代碼是線性的,而且很無聊。 突出顯示形式為createBody(...)調(diào)用的addColumnValues(...)方法。
private void addColumnValues(StringBuilder sb) { String[] beanNames = applicationContext.getBeanDefinitionNames(); for (String name : beanNames) { addRow(name, sb); } }此方法調(diào)用applicationContext.getBeanDefinitionNames()以獲取包含此上下文加載的所有bean的名稱的數(shù)組。 獲得這些信息后,我將遍歷數(shù)組,在每個(gè)bean名稱上調(diào)用applicationContext.getBean(...) 。 一旦擁有bean本身,就可以在報(bào)告中將其類詳細(xì)信息添加到StringBuilder中。
創(chuàng)建報(bào)告后,編寫自己的文件處理代碼(將StringBuilder的內(nèi)容保存到磁盤)并沒有多大意義。 這種代碼已經(jīng)被寫過很多次了。 在這種情況下,我選擇通過在上面的Java代碼中添加以下記錄器行來利用Log4j(通過slf4j):
private static final Logger logger = LoggerFactory.getLogger("ContextReport");…并通過將以下內(nèi)容添加到我的log4j XML配置文件中:
<appender name="fileAppender" class="org.apache.log4j.RollingFileAppender"><param name="Threshold" value="INFO" /><param name="File" value="/tmp/report.log"/><layout class="org.apache.log4j.PatternLayout"><param name="ConversionPattern" value="%d %-5p [%c{1}] %m %n" /></layout></appender><logger name="ContextReport" additivity="false"><level value="info"/> <appender-ref ref="fileAppender"/></logger>請(qǐng)注意,如果您使用的是log4j 2.x,則XML可能會(huì)有所不同,但這超出了本博客的范圍。
這里要注意的是,我使用RollingFileAppender ,它將一個(gè)名為report.log的文件寫入/tmp -盡管該文件顯然可以位于任何地方。
注意的另一個(gè)配置點(diǎn)是ContextReport Logger。 這會(huì)將其所有日志輸出定向到fileAppender并且由于具有additivity="false"屬性,因此僅將fileAppender到其他地方。
配置唯一需要記住的其他部分是將report包添加到Spring的component-scan元素中,以便Spring將檢測(cè)@Service批注并加載該類。
<context:component-scan base-package="com.captaindebug.report" />為了證明它有效,我還創(chuàng)建了一個(gè)JUnit測(cè)試用例,如下所示:
@RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration({ "file:src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml" }) public class ApplicationContextReportTest { @Autowired private ApplicationContextReport instance; @Test public void testReport() { System.out.println("The report should now be in /tmp"); } }這使用SpringJUnit4ClassRunner和@ContextConfiguration批注來加載應(yīng)用程序的實(shí)時(shí) servlet-context.xml文件。 我還包括了@WebAppConfiguration批注,以告訴Spring這是一個(gè)Web應(yīng)用程序。
如果運(yùn)行JUnit測(cè)試,您將獲得一個(gè)report.log ,其中包含以下內(nèi)容:
2014-01-26 18:30:25,920 INFO [ContextReport] ==================================================================================================== Application Context Report ==================================================================================================== Application Name: Display Name: org.springframework.web.context.support.GenericWebApplicationContext@74607cd0 Start Date: 2014-01-26T18:30:23.552+0000Bean Name Simple Name Singleton Full Class Name ==================================================================================================== deferredMatchUpdateController DeferredMatchUpdateController YES com.captaindebug.longpoll.DeferredMatchUpdateController homeController HomeController YES com.captaindebug.longpoll.HomeController DeferredService DeferredResultService YES com.captaindebug.longpoll.service.DeferredResultService SimpleService SimpleMatchUpdateService YES com.captaindebug.longpoll.service.SimpleMatchUpdateService shutdownService ShutdownService YES com.captaindebug.longpoll.shutdown.ShutdownService simpleMatchUpdateController SimpleMatchUpdateController YES com.captaindebug.longpoll.SimpleMatchUpdateController applicationContextReport ApplicationContextReport YES com.captaindebug.report.ApplicationContextReport the-match Match YES com.captaindebug.longpoll.source.Match theQueue LinkedBlockingQueue YES java.util.concurrent.LinkedBlockingQueue BillSykes MatchReporter YES com.captaindebug.longpoll.source.MatchReporter ====================================================================================================該報(bào)告包含一個(gè)標(biāo)題,該標(biāo)題包含諸如Display Name和Start Date后跟主體。 主體是一個(gè)制表符分隔的表,其中包含以下幾列:Bean名稱,簡單類名稱,該Bean是否為單例或原型以及完整的類名稱。
現(xiàn)在,您可以使用此報(bào)告來發(fā)現(xiàn)不需要加載到Spring Context中的類。 例如,如果你決定,你不希望加載BillSykes實(shí)例com.captaindebug.longpoll.source.MatchReporter ,那么你有以下幾種選擇。
首先,可能是BillSykes bean已裝入的情況,因?yàn)樗b在錯(cuò)誤的程序包中。 當(dāng)您嘗試沿著類類型線組織項(xiàng)目結(jié)構(gòu)時(shí),通常會(huì)發(fā)生這種情況,例如,將所有服務(wù)放在一個(gè)service包中,而所有控制器放在一個(gè)controller包中; 因此,將服務(wù)模塊包含到應(yīng)用程序中將加載所有服務(wù)類,即使您不需要的類也可能會(huì)導(dǎo)致問題。 通常最好按照“ 如何組織Maven子模塊”中所述的功能來組織。 。
不幸的是,重組整個(gè)項(xiàng)目的成本特別高,并且不會(huì)產(chǎn)生很多收入。 解決該問題的另一種較便宜的方法是對(duì)Spring context:component-scan Element進(jìn)行調(diào)整,并排除那些引起問題的類。
<context:component-scan base-package="com.captaindebug.longpoll" /><context:exclude-filter type="regex" expression="com\.captaindebug\.longpoll\.source\.MatchReporter"/></context:component-scan>…或任何給定包中的所有類:
<context:component-scan base-package="com.captaindebug.longpoll" /><context:exclude-filter type="regex" expression="com\.captaindebug\.longpoll\.source\..*"/></context:component-scan>使用exclude-filter是一種有用的技術(shù),但是與之相對(duì)應(yīng)的還有很多文章:include-filter,因此對(duì)此XML配置的完整解釋超出了本博客的范圍,但是也許我會(huì)在下面進(jìn)行介紹。以后再約會(huì)。
- 該博客的代碼可在GitHub上作為長期民意測(cè)驗(yàn)項(xiàng)目的一部分獲得,網(wǎng)址為:https://github.com/roghughe/captaindebug/tree/master/long-poll
翻譯自: https://www.javacodegeeks.com/2014/02/optimising-your-applicationcontext.html
總結(jié)
以上是生活随笔為你收集整理的优化您的ApplicationContext的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AI退潮了?二季度全球AI领域投资总额暴
- 下一篇: 每分钟 0.03 美元,微软为 Team