SpringMVC视图解析器InternalResourceViewResolver问题分析

今天在搭建SpringMVC开发框架的时候,出现freemarker的视图没有找到,报404错误。我的配置代码如下:

<!--freemarker -->
<mvc:view-controller path="/" view-name="homepage/index”/>

<!-- jsp -->
<mvc:view-controller path="/login" view-name="login”/>

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
    <property name="contentType" value="text/html;charset=UTF-8"/>
    <property name="order" value="2"/>
</bean>

<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
    <property name="prefix" value="" />
    <property name="suffix" value=".ftl" />
    <property name="contentType" value="text/html;charset=UTF-8"/>
    <property name="order" value="3"/>
</bean>

我配置了两个视图解析器,所以视图解析是链式的,如果一个视图解析器没有找到对应的view-name,则开始找第二的解析器。InternalResourceViewResolver的property中的order值是2,FreeMarkerViewResolver的property的order为3,我们知道order,从小到大,越小的优先级越高,那么InternalResourceViewResolver一定是先运行的。

InternalResourceViewResolver的父类UrlBasedViewResolver中有一个方法loadView用于创建加载视图,源码如下:

/**
 * Delegates to {@code buildView} for creating a new instance of the
 * specified view class, and applies the following Spring lifecycle methods
 * (as supported by the generic Spring bean factory):
 * <ul>
 * <li>ApplicationContextAware's {@code setApplicationContext}
 * <li>InitializingBean's {@code afterPropertiesSet}
 * </ul>
 *
 * @param viewName the name of the view to retrieve
 * @return the View instance
 * @throws Exception if the view couldn't be resolved
 * @see #buildView(String)
 * @see org.springframework.context.ApplicationContextAware#setApplicationContext
 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
 */
@Override
protected View loadView(String viewName, Locale locale) throws Exception {
    AbstractUrlBasedView view = buildView(viewName);
    View result = applyLifecycleMethods(viewName, view);
    return (view.checkResource(locale) ? result : null);
}

最后一行用到了view的checkResource方法。
我们看到InternalResourceViewResolver对应的view是InternalResourceView,它的子类只有JstlView,下面是view的类图关系。

查看InternalResourceView的父类AbstractUrlBasedView,可以找到它里面的checkResource方法,源码如下:

/**
 * Check whether the underlying resource that the configured URL points to
 * actually exists.
 *
 * @param locale the desired Locale that we're looking for
 * @return {@code true} if the resource exists (or is assumed to exist);
 * {@code false} if we know that it does not exist
 * @throws Exception if the resource exists but is invalid (e.g. could not be parsed)
 */
public boolean checkResource(Locale locale) throws Exception {
    return true;
}

可以看到这个方法直接返回true,根本就没有check。
至此我们就找到问题的原因了,InternalResourceView是不会check资源文件是否存在,当InternalResourceViewResolver先运行的时候,遇到其他的view-name如本例的freemarker的文件根本不会做check,导致最终出现404的情况。

如何解决这个问题呢?
第一种方法:把order的值修改下,把InternalResourceViewResolver的order改成最大的,即最后解析让其他的会check文件是否存在的解析器先运行。
第二种方法:自定义一个view类继承JstlView,自己写一个checkResource将父类的的checkResource override掉。代码如下:

package com.tonitech.ancestor.common;

import org.springframework.web.servlet.view.JstlView;

import java.io.File;
import java.util.Locale;

/**
* 解决多个ViewResolver时jsp获取不到时,跳转到下一个ViewResolver
*/
public class DefaultJstlView extends JstlView {

    @Override
    public boolean checkResource(Locale locale) throws Exception {
        File file = new File(this.getServletContext().getRealPath("/") + getUrl());
        return file.exists();//判断该jsp页面是否存在
    }
}

然后在InternalResourceViewResolver的bean配置里加一行:

<property name="viewClass" value="com.tonitech.ancestor.common.DefaultJstlView"/>

通过这两种方法都可以解决问题。

Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.apache.log4j.Log4jLoggerFactory解决方法

今天在搭建一个项目单元测试环境跑测试用例的时候遇到了致命错误:
Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.apache.log4j.Log4jLoggerFactory
这个是什么情况?
翻看控制台输出信息看到一段:
SLF4J: Detected both log4j-over-slf4j.jar AND slf4j-log4j12.jar on the class path, preempting StackOverflowError.
SLF4J: See also http://www.slf4j.org/codes.html#log4jDelegationLoop for more details.

翻看了上面说的网站提示信息:
The purpose of slf4j-log4j12 module is to delegate or redirect calls made to an SLF4J logger to log4j. The purpose of the log4j-over-slf4j module is to redirect calls made to a log4j logger to SLF4J. If SLF4J is bound with slf4j-log4j12.jar and log4j-over-slf4j.jar is also present on the class path, a StackOverflowError will inevitably occur immediately after the first invocation of an SLF4J or a log4j logger.

意思是slf4j-log4j12是用来代理或者重定向调用使一个SLF4J的日志通过log4j输出,log4j-over-slf4j的目的是将log4j的日志重定向输出到SLF4J,如果它俩在一个class path下造成StackOverflow的错误,因为它俩会陷入一个死循环。如果不明白SLF4J和log4j的同学可以转到:http://www.importnew.com/7450.html 仔细阅读。

于是我寻找了项目的maven依赖,发现有3个依赖中出现了slf4j-log4j12.jar,在每个依赖里加上:

<exclusion>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
</exclusion>

最终解决了问题。

再次跑测试用例的时候,还有一堆报错也不见了:
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/Users/wangzhiang/.m2/repository/ch/qos/logback/logback-classic/1.1.3/logback-classic-1.1.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/wangzhiang/.m2/repository/org/slf4j/slf4j-log4j12/1.6.1/slf4j-log4j12-1.6.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]

原因是:SLF4J会在编译时会绑定import org.slf4j.impl.StaticLoggerBinder; 该类里面实现对具体日志方案的绑定接入。任何一种基于SLF4J的实现都要有一个这个类。如:slf4j-log4j12-1.6.1提供对log4j的一种适配实现。我的项目中存在logback-classic-1.1.3和slf4j-log4j12-1.6.1两个实现SLF4J的包同时出现,就出现了上面的报错,加上依赖的exclusion去掉slf4j-log4j12即可。

一次JVM引起的服务器load过高问题排查经历

2016-11-08早上9:25分,公司运维的同事给我报警,称我们团队的一台服务器CPU的压力很大,zabbix的图显示:

1接到警报后,我在该服务器上使用w和top命令查看服务器的load和CPU情况,1分钟的load average在3-5之间,系统在高负荷运转,查看了我们的线上应用系统发现网页打开正常,但一个別列表的查询缓慢,chrome浏览器的network查看http接口的timing在十几秒。这部分列表走的是搜索引擎,我第一反应是会不会是搜索引擎出问题了?

于是我在服务器上查看tomcat日志,获取我们的应用调用搜索引擎的接口和参数,拼装后使用

curl -v '$url'

命令访问,发现访问的速度没有问题。那说明搜索引擎没有问题,那一定是我们的应用从搜索引擎返回值到展示给用户的部分处理有问题。

由于有大量用户反馈系统速度慢,于是我使用jps获取java进程的pid,随后使用:

jmap -dump:format=b,file=crm.bin [pid]

命令dump jvm的信息“保留犯罪现场”,然后重启线上应用。

启动应用后检测到服务器的load渐渐越来越高,最后保持在重启前的样子,于是使用

jstat -gc [pid] 3000

查看服务器每隔3秒的gc情况,发现3秒钟的full gc能够达到两次,jvm的状况十分异常。在启动服务器的这个时间里我利用eclipse MAT工具分析jmap dump文件信息,未诊断出异常。

后来我把原来jvm的Xms参数配置由1024m提高到3072m,发现双机的full gc次数分别稳定在0和2后再也没有full gc过了,但是young GC却十分频繁,3秒钟young GC次数可以达到15-20次。

到底是什么东西这么占内存???我使用jmap -histo pid查看,发现排在第一位的[C对象经常飙升到1个多G然后降到200MB,就是young GC频繁的原因,这通常是String类型的对象存在问题。

随后我使用

top -H -p [pid]

看到了如下结果:

2这一排的PID都占用了大量的CPU资源,于是随机取了一个PID为1077的进程,使用

printf "%x\n" 1077

转换成16进制的数字为435

随后使用jstack工具:

jstack [pid] | less

输出当前的堆栈信息,然后搜索435,发现如下内容:

3这块代码就是调用搜索引擎的代码,它处于RUNNABLE的状态,一直在执行中,说明这段代码一定是追魁祸首!打开代码看看!原来是这样的一段代码:

while ((line = in.readline()) != null) {
    result += line;
}

真相就是:循环里每次对String的操作都会生成新的String对象,不仅效率低下,而且大量浪费有限的内存空间。

将String改成StringBuilder,发布上线后load过高的问题解决。这就解释了为什么一般的页面打开速度正常,而搜索客户列表接口速度缓慢以及为什么char类型的数组占用大量内存。

-Dmaven.multiModuleProjectDirectory system propery is not set. Check $M2_HOME environment variable and mvn script match.[ERROR] Maven execution terminated abnormally (exit code 1)解决方法

今天给同事的Mac配置JAVA项目IDEA开发环境的时候遇到了一个问题:
-Dmaven.multiModuleProjectDirectory system propery is not set. Check $M2_HOME environment variable and mvn script match.[ERROR] Maven execution terminated abnormally (exit code 1)
Check了电脑的M2_HOME系统环境变量,都没有错误,于是在IDEA中File->Maven->Runner的VM options中增加配置:
-Dmaven.multiModuleProjectDirectory=$M2_HOME
保存解决问题。

IDEA出现[FATAL_ERROR] Cannot start Maven: Project JDK is not specified. 的解决办法

最近在给一个同事的Mac配置JAVA项目的开发环境的时候,启动项目IDEA报错:
[FATAL_ERROR] Cannot start Maven: Project JDK is not specified. <a href=’#’>Configure</a>
那么估计就是IDEA的JDK没有配置好,打开IDEA的preference,找到Maven->Runner,发现JRE项目的配置是“Use Project JDK (not defined yet)”,而那个Internal JRE的版本太低,所以我们需要配置Project JDK,配置的方法是点击顶部的File->Project Structure…->左边的“Project”->Project SDK
jdk
这时候现实的应该是“<No SDK>”,点开看看有没有其他选项,如果没有就点击New…->JDK,选择你的JDK目录到“Contents/Home”下。如果有,就选择配置好的例如:“1.7”。
以上操作之后,问题就解决了。