问题背景
遇到一个问题:业务异常了,抛出ApiException。工程代码里没有打印error,日志中却发现了error日志(我们的业务异常不允许打印error日志,因为error日志会产生告警,而业务异常不应告警)。经排查,是org.apache.catalina.core.StandardWrapperValve类中捕获了ServletException,打印了error异常。
解决方案
这里我们看到,紧急是apache.catalina的源码打印日志级别的问题。自然想到就是能在捕获异常的地方,区分下是不是ApiException,如果是则打印info日志,如果不是,还是按原来的方式打印error日志。
涉及到改源码,改了如何生效呢?
1、下载整个jar的源码,改了源码,然后编译成新的jar,替换。【过程复杂,且不好维护,放弃】
2、在工程下,创建个类:org.apache.catalina.core.StandardWrapperValve,修改StandardWrapperValve源码,然后利用类加载机制将修改后的源码加载到jvm中,老的源码就不会加载了。
双亲委派
为什么可以用方案2这种方式呢?回顾下双亲委派机制
当一个类(我们自己写的或者Apache等三方框架的)文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。
如何确定被加载过了呢?
JVM加载过的内缓存起来,类似放在Map中。jvm中类相同的条件:类加载器实例+包名+类名。显然我们写的StandardWrapperValve和Apache catalina的StandardWrapperValve类加载器都是AppClassLoader,并且包名和类名都是一样的。所以只要保证JVM先加载我们改写过的StandardWrapperValve,JVM就不会去加载Apache catalina的StandardWrapperValve了