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