Mybatis-3: Support catch NoClassDefFoundError exception When use ClassLoaderWrapper load Class

Created on 20 Jan 2021  ·  6Comments  ·  Source: mybatis/mybatis-3

MyBatis version

3.5.3

Database vendor and version

use MySQL 5.6.16 SpringBoot 2.3.2.RELEASE

Test case or example project

When MyBatis integrates Spring Boot, the Mapper XML uses OGNL expressions to refer to nested enumeration constant classes, causing a NoClassDefFoundError exception

The below constains OrgEnum, TestController class, User entity class, UserMapper interface and user.xml mapper

@Getter
@RequiredArgsConstructor
public enum OrgEnum {

    //org info
    AB("000001", "ABorg"),
    CD("000002", "CDorg");

    private final String code;
    private final String name;

    //constants

    public static final String AB_ORG_CODE = AB.getCode();
    public static final String CD_ORG_CODE = CD.getCode();

    public static final String D1_DEPARTMENT_CODE = OrgEnum.DepartmentEnum.D1_DEPARTMENT.getCode();

    /**
     * 保司信息
     */
    @Getter
    @RequiredArgsConstructor
    public enum DepartmentEnum {

        // department  info
        D1_DEPARTMENT(OrgEnum.AB, "D001", "D001Info..."),
        D2_DEPARTMENT(OrgEnum.AB, "D002", "D002Info..."),
        D3_DEPARTMENT(OrgEnum.CD, "D003", "D003Info..."),
        ;

        private final OrgEnum org;
        private final String code;
        private final String name;
    }
}

```java
@RestController
@RequestMapping("test")
@RequiredArgsConstructor
public class TestController {

private final UserMapper userMapper;

@GetMapping
public User get(Long id) {
    Objects.requireNonNull(id);
    return userMapper.getOne(id);
}

}

```java
@Data
public class User {

    private String orgCode;
    private String name;
}

```java
public interface UserMapper {

User getOne(Long id);

}

The below is sql use ognl expression in user.xml
```sql
 <select id="getOne" resultMap="com.example.demo.t.User">
    SELECT
        '${@com.example.demo.t.OrgEnum@AB_ORG_CODE}' AS orgCode,
        name
    FROM
        user
    WHERE
        id = #{id}
</select>

When client request TestController#get method,The Mybatis throw java.lang.NoClassDefFoundError exception ,the below is detial info

org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: Could not initialize class com.example.demo.t.OrgEnum
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1055)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:652)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1589)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.NoClassDefFoundError: Could not initialize class com.example.demo.t.OrgEnum
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    at org.apache.ibatis.io.ClassLoaderWrapper.classForName(ClassLoaderWrapper.java:186)
    at org.apache.ibatis.io.ClassLoaderWrapper.classForName(ClassLoaderWrapper.java:89)
    at org.apache.ibatis.io.Resources.classForName(Resources.java:261)
    at org.apache.ibatis.scripting.xmltags.OgnlClassResolver.toClassForName(OgnlClassResolver.java:34)
    at org.apache.ibatis.ognl.DefaultClassResolver.classForName(DefaultClassResolver.java:58)
    at org.apache.ibatis.ognl.OgnlRuntime.classForName(OgnlRuntime.java:1167)
    at org.apache.ibatis.ognl.OgnlRuntime.getStaticField(OgnlRuntime.java:2059)
    at org.apache.ibatis.ognl.ASTStaticField.getValueBody(ASTStaticField.java:68)
    at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212)
    at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:258)
    at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:493)
    at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:457)
    at org.apache.ibatis.scripting.xmltags.OgnlCache.getValue(OgnlCache.java:46)
    at org.apache.ibatis.scripting.xmltags.TextSqlNode$BindingTokenParser.handleToken(TextSqlNode.java:77)
    at org.apache.ibatis.parsing.GenericTokenParser.parse(GenericTokenParser.java:77)
    at org.apache.ibatis.scripting.xmltags.TextSqlNode.apply(TextSqlNode.java:51)
    at org.apache.ibatis.scripting.xmltags.MixedSqlNode.lambda$apply$0(MixedSqlNode.java:32)
    at java.util.ArrayList.forEach(ArrayList.java:1259)
    at org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:32)
    at org.apache.ibatis.scripting.xmltags.DynamicSqlSource.getBoundSql(DynamicSqlSource.java:39)
    at org.apache.ibatis.mapping.MappedStatement.getBoundSql(MappedStatement.java:297)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:81)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:147)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:426)
    at com.sun.proxy.$Proxy123.selectList(Unknown Source)
    at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:223)
    at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:147)
    at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:80)
    at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:93)
    at com.sun.proxy.$Proxy130.selectGift2Short(Unknown Source)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)

when I debug it ,I find the method org.apache.ibatis.io.ClassLoaderWrapper#classForName(java.lang.String, java.lang.ClassLoader[]) second args contains three ClassLoaders,and first ClassLoader is org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader,anoter ClassLoader is AppClassLoader,
while the second args ClassLoader[] is from the method org.apache.ibatis.io.ClassLoaderWrapper#getClassLoaders, and have a Thread.currentThread().getContextClassLoader(),that is TomcatEmbeddedWebappClassLoader,of this time, when custom ClassLoader not load Class and throw NoClassDefFoundError, the Mybatis framework should catch it, rather that just catch ClassNotFoundException
The method ClassLoaderWrapper#classForName have below code

for (ClassLoader cl : classLoader) {

      if (null != cl) {

        try {

          Class<?> c = Class.forName(name, true, cl);

          if (null != c) {
            return c;
          }

        } catch (ClassNotFoundException e) {
          // we'll ignore this until all classloaders fail to locate the class
        }

      }

    }

The method org.apache.ibatis.io.ClassLoaderWrapper#getClassLoaders code is

return new ClassLoader[]{
        classLoader,
        defaultClassLoader,
        Thread.currentThread().getContextClassLoader(),
        getClass().getClassLoader(),
        systemClassLoader};
  }

Steps to reproduce

use http client request TestController#get method

Expected result

select orgCode and set User#orgCode

Actual result

throw java.lang.NoClassDefFoundError

waiting for feedback

All 6 comments

Hello @FuriousPws002 ,

Could you provide a project that reproduces the problem with minimum setup?

Hello @FuriousPws002 ,

Could you provide a project that reproduces the problem with minimum setup?

@harawata Thank you for your reply,and there is demo demo-mybatis-issue2161

public static final String D1_DEPARTMENT_CODE = OrgEnum.DepartmentEnum.D1_DEPARTMENT.getCode();

There is a circular dependency,When I try to remove this line , it's ok.

public static final String D1_DEPARTMENT_CODE = OrgEnum.DepartmentEnum.D1_DEPARTMENT.getCode();

There is a circular dependency,When I try to remove this line , it's ok.

Thanks for you reply,Removed this line of code is fine,as far as I am concerned,MyBatis as a popular framework, should catch this exception information. After all, MyBatis cannot guarantee that other people's custom ClassLoader will correctly load the Class.After all, use the Class.forName ("com.example.demo.t.Org.Enum") is normal.

Thanks for the project, @FuriousPws002 !

We should not catch NoClassDefFoundError or any Error.
Here is an excerpt from the doc :

An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch. Most such errors are abnormal conditions.

I was going to look for the 'abnormal condition' in your app, but @wuwen5 found it (thanks!).
So, may I close the issue?

It's OK,Thanks for your answers

Was this page helpful?
0 / 5 - 0 ratings