优化您的ApplicationContext
Spring有一個問題,已經存在了一段時間,我在許多項目中都遇到過。 與Spring或Spring的Guys無關,這取決于像您和我這樣的Spring用戶。 讓我解釋一下……在Spring 2的過去,您必須手動配置Application Context,手動創建一個包含所有bean定義的XML配置文件。 這種技術的缺點是創建這些XML文件很費時,然后您就很難維護這個越來越復雜的文件。 我似乎記得當時它被稱為“ Spring Config Hell”。 從好的方面來說,至少您對加載到上下文中的所有內容都有一個中央記錄。 順應需求和流行的注釋方式,Spring 3引入了大量的原型設計類,例如@Service @Controller , @Repository @Component , @Controller和@Repository ,以及<context:component-scan/>的XML配置文件<context:component-scan/>元素。 從編程的角度來看,這使事情變得簡單得多,并且是構造Spring上下文的一種非常流行的方式。
但是,使用Spring注釋時要放任不管,并使用@Service @Component , @Controller @Component , @Controller或@Repository來添加所有內容,這在大型代碼庫中尤其麻煩。 問題是您的上下文被不需要的東西污染了,這是一個問題,因為:
- 您不必要地用完了燙發空間,從而導致更多“燙發空間錯誤”的風險。
- 您不必要地耗盡了堆空間。
- 您的應用程序可能需要更長的時間才能加載。
- 不需要的對象可以“隨便做”,特別是如果它們是多線程的,則具有start()方法或實現InitializingBean 。
- 不需要的對象只會阻止您的應用程序正常工作……
在小型應用程序中,我猜想在Spring上下文中是否有幾個額外的對象并不重要,但是,正如我上面說的,如果您的應用程序很大,處理器密集型或占用內存,則這尤其麻煩。 在這一點上,有必要對這種情況進行分類,并且要做到這一點,您必須首先弄清楚要加載到Spring上下文中的是什么。
一種方法是通過在log4j屬性中添加以下內容來啟用com.springsource包上的調試功能:
log4j.logger.com.springsource=DEBUG將以上內容添加到您的log4j屬性(在本例中為log4j 1.x)中,您將獲得有關Spring上下文的大量信息–我的意思是很多。 如果您是Spring的專家之一,并且正在研究Spring源代碼,那么實際上這只是您需要做的事情。
另一種更簡潔的方法是在您的應用程序中添加一個類,該類將準確報告正在Spring上下文中加載的內容。 然后,您可以檢查報告并進行任何適當的更改。
該博客的示例代碼包含一個類,這是我之前寫過兩三遍的文章,分別為不同的公司從事不同的項目。 它依賴于Spring的幾個功能。 也就是說,在Context加載后,Spring可以在您的類中調用一個方法,并且Spring的ApplicationContext接口包含一些方法,這些方法可以告訴您有關其內部的所有信息。
@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"); } } }首先要注意的是,此版本的代碼實現了Spring的InitializingBean接口。 當Spring將一個類加載到上下文中時,Spring將檢查此接口。 如果找到它,它將調用AfterPropertiesSet()方法。
這不是讓Spring在啟動時調用您的類的唯一方法,請參閱: 三種Spring Bean生命周期技術以及使用JSR-250的@PostConstruct注釋替換Spring的InitializingBean
接下來要注意的是,該報告類實現了Spring的ApplicationContextAware接口。 這是另一個有用的Spring主力接口,通常永遠不需要每天使用。 該接口背后的理由是使您的類可以訪問應用程序的ApplicationContext 。 它包含一個方法: setApplicationContext(...) ,由Spring調用以將ApplicationContext注入您的類中。 在這種情況下,我只是將ApplicationContext參數保存為實例變量。
主報告的生成是通過report()方法完成的(由afterPropertiesSet()調用)。 report()方法所做的全部工作就是創建一個StringBuilder()類,然后附加大量信息。 我不會逐一介紹每一行,因為這種代碼是線性的,而且很無聊。 突出顯示形式為createBody(...)調用的addColumnValues(...)方法。
private void addColumnValues(StringBuilder sb) { String[] beanNames = applicationContext.getBeanDefinitionNames(); for (String name : beanNames) { addRow(name, sb); } }此方法調用applicationContext.getBeanDefinitionNames()以獲取包含此上下文加載的所有bean的名稱的數組。 獲得這些信息后,我將遍歷數組,在每個bean名稱上調用applicationContext.getBean(...) 。 一旦擁有bean本身,就可以在報告中將其類詳細信息添加到StringBuilder中。
創建報告后,編寫自己的文件處理代碼(將StringBuilder的內容保存到磁盤)并沒有多大意義。 這種代碼已經被寫過很多次了。 在這種情況下,我選擇通過在上面的Java代碼中添加以下記錄器行來利用Log4j(通過slf4j):
private static final Logger logger = LoggerFactory.getLogger("ContextReport");…并通過將以下內容添加到我的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>請注意,如果您使用的是log4j 2.x,則XML可能會有所不同,但這超出了本博客的范圍。
這里要注意的是,我使用RollingFileAppender ,它將一個名為report.log的文件寫入/tmp -盡管該文件顯然可以位于任何地方。
注意的另一個配置點是ContextReport Logger。 這會將其所有日志輸出定向到fileAppender并且由于具有additivity="false"屬性,因此僅將fileAppender到其他地方。
配置唯一需要記住的其他部分是將report包添加到Spring的component-scan元素中,以便Spring將檢測@Service批注并加載該類。
<context:component-scan base-package="com.captaindebug.report" />為了證明它有效,我還創建了一個JUnit測試用例,如下所示:
@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批注來加載應用程序的實時 servlet-context.xml文件。 我還包括了@WebAppConfiguration批注,以告訴Spring這是一個Web應用程序。
如果運行JUnit測試,您將獲得一個report.log ,其中包含以下內容:
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 ====================================================================================================該報告包含一個標題,該標題包含諸如Display Name和Start Date后跟主體。 主體是一個制表符分隔的表,其中包含以下幾列:Bean名稱,簡單類名稱,該Bean是否為單例或原型以及完整的類名稱。
現在,您可以使用此報告來發現不需要加載到Spring Context中的類。 例如,如果你決定,你不希望加載BillSykes實例com.captaindebug.longpoll.source.MatchReporter ,那么你有以下幾種選擇。
首先,可能是BillSykes bean已裝入的情況,因為它裝在錯誤的程序包中。 當您嘗試沿著類類型線組織項目結構時,通常會發生這種情況,例如,將所有服務放在一個service包中,而所有控制器放在一個controller包中; 因此,將服務模塊包含到應用程序中將加載所有服務類,即使您不需要的類也可能會導致問題。 通常最好按照“ 如何組織Maven子模塊”中所述的功能來組織。 。
不幸的是,重組整個項目的成本特別高,并且不會產生很多收入。 解決該問題的另一種較便宜的方法是對Spring context:component-scan Element進行調整,并排除那些引起問題的類。
<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是一種有用的技術,但是與之相對應的還有很多文章:include-filter,因此對此XML配置的完整解釋超出了本博客的范圍,但是也許我會在下面進行介紹。以后再約會。
- 該博客的代碼可在GitHub上作為長期民意測驗項目的一部分獲得,網址為:https://github.com/roghughe/captaindebug/tree/master/long-poll
翻譯自: https://www.javacodegeeks.com/2014/02/optimising-your-applicationcontext.html
總結
以上是生活随笔為你收集整理的优化您的ApplicationContext的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AI退潮了?二季度全球AI领域投资总额暴
- 下一篇: 在单元测试中访问私有字段