3.5.3
use MySQL 5.6.16 SpringBoot 2.3.2.RELEASE
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};
}
use http client request TestController#get method
select orgCode and set User#orgCode
throw java.lang.NoClassDefFoundError
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