分类 JAVA 下的文章

一个fastjson转换JSON字符串的报错排查

今天给一个java类加了几个字段,没想到转为json字符串的时候报错了,定位了一下原因,觉得这种情况遇到的应该不多,又想起来很久没写博客了,于是就把遇到的问题以及定位过程记下来,省了以后遇到的人再花时间定位了

调用的是JSON.toJSONString(Object object)方法,具体的报错信息如下:

Caused by: com.alibaba.fastjson.JSONException: write javaBean error
    at com.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:234)
    at Serializer_5.write1(Unknown Source)
    at Serializer_5.write(Unknown Source)
    at com.alibaba.fastjson.serializer.JSONSerializer.write(JSONSerializer.java:369)
    at com.alibaba.fastjson.JSON.toJSONString(JSON.java:393)
    at com.alibaba.fastjson.JSON.toJSONString(JSON.java:567)
    at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    at org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.invokeJoinpoint(Cglib2AopProxy.java:689)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:55)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at 
    ... 81 common frames omitted
Caused by: com.alibaba.fastjson.JSONException: create asm serializer error, class class com.feifei.bean.TestBean
    at com.alibaba.fastjson.serializer.SerializeConfig.createJavaBeanSerializer(SerializeConfig.java:113)
    at com.alibaba.fastjson.serializer.JSONSerializer.getObjectWriter(JSONSerializer.java:527)
    at com.alibaba.fastjson.serializer.ListSerializer.write(ListSerializer.java:81)
    at com.alibaba.fastjson.serializer.ObjectFieldSerializer.writeValue(ObjectFieldSerializer.java:118)
    at com.alibaba.fastjson.serializer.ObjectFieldSerializer.writeProperty(ObjectFieldSerializer.java:67)
    at com.alibaba.fastjson.serializer.JavaBeanSerializer.write(JavaBeanSerializer.java:216)
    ... 94 common frames omitted
Caused by: java.lang.ClassFormatError: JVMCFRE042 bytecode array size > 65535; class=Serializer_57, offset=53261
    at java.lang.ClassLoader.defineClassImpl(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:275)
    at com.alibaba.fastjson.util.ASMClassLoader.defineClassPublic(ASMClassLoader.java:42)
    at com.alibaba.fastjson.serializer.ASMSerializerFactory.createJavaBeanSerializer(ASMSerializerFactory.java:287)
    at com.alibaba.fastjson.serializer.ASMSerializerFactory.createJavaBeanSerializer(ASMSerializerFactory.java:36)
    at com.alibaba.fastjson.serializer.SerializeConfig.createASMSerializer(SerializeConfig.java:78)
    at com.alibaba.fastjson.serializer.SerializeConfig.createJavaBeanSerializer(SerializeConfig.java:106)
    ... 99 common frames omitted

可以看到,代码在执行到ASMClassLoader.defineClassPublic这个方法时报错了,报的错是:

JVMCFRE042 bytecode array size > 65535

字节数组超长,这个长度65535,很显然是长度规定了2的16次方(65536),也就是64K大小。

再来看看ASMClassLoader这个类,这个类继承了java的类装载器ClassLoader,fastjson内嵌了ASM框架来动态生成类;

public class ASMClassLoader extends ClassLoader {

并且报错的方法其实就是调用了ClassLoader的类定义方法defineClass,本质上调用的是JVM的native功能

public Class<?> defineClassPublic(String name, byte[] b, int off, int len) throws ClassFormatError {
        Class<?> clazz = defineClass(name, b, off, len, DOMAIN);

    return clazz;
}

这里其实也就是传的需要生成的类的字节码,在这里也就对应着我们需要转换成JSON串的对象类的字节码,所以这个报错应该就是对象类字节码超长了,也就是类里面的东西太多了,试着删掉这个bean里的一些变量,测试一把,果然不报错了。

那到底是什么太长了呢?这也好办,继续写个单元测试,把ASMSerializerFactory类拷出来,把字节码输出看看

单元测试:

@Test
public void test(){
    try{
        this.asmSerial(TestBean.class);
    }catch(Exception e){
        System.out.println(e);
    }
}

private void asmSerial(Class<?> clazz) throws Exception{
    ASMSerializerFactory fac = new ASMSerializerFactory();
    fac.createJavaBeanSerializer(clazz);
}

在拷出来的ASMSerializerFactory类的createJavaBeanSerializer方法中新增输出逻辑:

byte[] code = cw.toByteArray();
// 输出class文件
File outputFile = new File("C:\\Users\\feifei\\Desktop\\ooo\\out.class");  
FileOutputStream outputFileStream = null;  
try {  
    outputFileStream = new FileOutputStream(outputFile);  
} catch (FileNotFoundException e) {  
}  
outputFileStream.write(code);
System.out.println("code:length: " + code.length);

执行一把,果然报错了,而且class文件也成功生成了,先看看控制台输出:

[TestNG] Running:
  C:\Users\feifei\AppData\Local\Temp\testng-eclipse-1963807168\testng-customsuite.xml

code:length: 191423
FAILED: test
java.lang.ClassFormatError: Invalid method Code length 65904 in class file Serializer_1
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(Unknown Source)
    at com.alibaba.fastjson.util.ASMClassLoader.defineClassPublic(ASMClassLoader.java:42)
    at ASMSerializerFactory.createJavaBeanSerializer(ASMSerializerFactory.java:299)
    at ASMSerializerFactory.createJavaBeanSerializer(ASMSerializerFactory.java:42)
    at tt.asmSerial(tt.java:19)
    at tt.test(tt.java:11)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)

可见报错更加细化了

java.lang.ClassFormatError: Invalid method Code length 65904 in class file Serializer_1

这个报错意思更加明显了,方法的长度不合法,这个方法的长度是65904,结合上面报出来的大于65535,那么问题就很明显了,java里规定了类的一个方法最大不能超过64K
我们继续打开刚刚生成的class文件,可以看到,我们bean里面的字段都在里面,而里面最大的方法是write和write1,分别占13000多行,所以应该就是这两方法超长了。
writeMeth.png
至此,问题就定位清晰了,就是bean里面字段太多,导致序列化后,生成class文件的方法超长,也就导致转为json字符串失败了,这个除了拆分bean,貌似也没什么好办法了,而且这个bean确实太大了,拆开可读性也会好很多

Ubuntu 14.04安装java+nginx+tomcat7简要步骤

之前一直用centOS,试用了一次Ubuntu感觉这是一个对新手很友好的系统,用apt-get安装环境很方便,前端时间换了VPS,重新安装环境很快就搞定了,做个笔记,下次再配置就不用再找了

  1. nignx安装

Ubuntu14.04默认安装是是1.4.6版本的,而新的稳定版本都到1.8了,所以我要装个更加新的稳定版本,参考nginx的官方教程http://nginx.org/en/linux_packages.html#stable

先下载一个key,http://nginx.org/keys/nginx_signing.key
添加到apt里

apt-key add nginx_signing.key

再修改/etc/apt/sources.list文件,
新增配置:

deb http://nginx.org/packages/mainline/ubuntu/ trusty nginx
deb-src http://nginx.org/packages/mainline/ubuntu/ trusty nginx

保存之后,执行

apt-get update
apt-get install nginx

这样nginx就一路安装完毕了

安装完的nginx配置在/etc/nginx/conf.d/目录

  1. jdk安装


- 阅读剩余部分 -

记一次mysql问题告警和排查

今天突然监控宝报警,网站无法访问了,自己一上,果然上不了,提示数据库连接错误

由于VPS上除了博客还有其他的两个站,一上,都没法访问,其中一个java站报错:

com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Data source rejected establishment of connection, message from server: "Too many connections" 

- 阅读剩余部分 -

一个简单的基于JFinal的分页扩展

前几天试了试JFinal框架,感觉用起来还是蛮方便的,作者似乎借鉴了一些Ruby的思想,并且集成了一些常用的功能类,比如分页,图片验证码等等。个人觉得,与其说这是一个java的开发框架,不如说它是一个简洁的建站解决方案。

JFinal的分页类感觉还是很方便的,官方的示例很简单也很清楚,直接调Model的paginate方法,传入当前页码,每页显示数量等参数就行了。不过,这只是分页查询数据库得到的数据列表,除此以外,还得把分页的信息展示在前端页面上,这时候就得自己实现一些分页逻辑以及相应的前端了。

所以,我就简单封装了一个分页类,把一些分页需要的页面元素计算封装进去,配合freemarker,实现了一个功能还算完全的分页扩展,其实也不能叫扩展,只是一种简单的实现方式,贴出来给大家参考参考。

- 阅读剩余部分 -

vps上部署jetty多个端口多个webapp

做为一个专业挖坑20年的互联网爱好者,这几天有新的小(大)项(坑)目(啊)要发布,于是发现原先的jetty不够用了,只放一个项目怎么可以,于是决定重新配置一下,多配几个端口,然后一个端口对应一个项目,分别用nginx反代上去。
经过查看jetty的官方文档,配合google之后,搞定了,简单总结下,给需要的朋友做个参考

注:我用的是jetty-distribution-9.1.4.v20140401这个版本的,jetty9需要JDK1.7的支持,要是java环境是1.6会报错的。此外,这个配置方法是自己摸索的,可能有不完善的地方,希望大家能给点改进意见


- 阅读剩余部分 -

vps安装JDK,无法建立 SSL 连接链接问题

今天想把VPS的JDK重装一下,因为之前装的是JRE,貌似jetty跟JRE有点不兼容,老是打错误日志,提示java.err.nojdk

http://www.oracle.com/technetwork/java/javase/downloads/java-archive-downloads-javase6-419409.html上找到下载链接,直接wget,然后就报错了

[root@vps java]# wget http://download.oracle.com/otn/java/jdk/6u45-b06/jdk-6u45-linux-i586.bin
--2014-02-18 21:47:58--  http://download.oracle.com/otn/java/jdk/6u45-b06/jdk-6u45-linux-i586.bin
正在解析主机 download.oracle.com... 65.122.127.49, 65.122.127.43
Connecting to download.oracle.com|65.122.127.49|:80... 已连接。
已发出 HTTP 请求,正在等待回应... 302 Moved Temporarily
位置:https://edelivery.oracle.com/akam/otn/java/jdk/6u45-b06/jdk-6u45-linux-i586.bin [跟随至新的 URL]
--2014-02-18 21:47:59--  https://edelivery.oracle.com/akam/otn/java/jdk/6u45-b06/jdk-6u45-linux-i586.bin
正在解析主机 edelivery.oracle.com... 23.214.22.140
Connecting to edelivery.oracle.com|23.214.22.140|:443... 已连接。
ERROR: certificate common name www.oracle.com' doesn't match requested host name edelivery.oracle.com'.
To connect to edelivery.oracle.com insecurely, use `--no-check-certificate'.
无法建立 SSL 连接。

- 阅读剩余部分 -

linux下jetty替换部署war包步骤小笔记

今天给自己的小网站做了点小更新,部署的时候发现……有段时间没管,忘了文件在哪儿了,也忘了怎么操作了。
没办法,简单google了一下,总算记起一些,于是决定做个小记录,下次省的搜了

就几个简单的linux命令,用来对war包做重新部署的。第一次部署的时候jetty的安装配置就免了,需要的可以自行google:

1. 打开jetty安装目录下的bin目录。由于安装的时候配置了环境变量,这边就很方便了

cd ${JETTY_HOME}/bin

2.关闭jetty。bin目录下的jetty.sh里包含了jetty的基本操作指令(stop,start,restart等等)

./jetty.sh stop

3.用其他工具上传新的war包并覆盖以前的旧war包(一般在jetty安装目录的webapps目录下)

4.启动jetty

./jetty.sh start

搞定,掩面飘过,这么简单的步骤都能忘……

playframework2.0简单的自定义分页样式

过年在家,玩到提不起兴致写代码,更别说写博客了……罪过罪过……

于是今天把自己折腾的play!项目的分页整理完善了一下,做个简单记录,免得博客长草~

需求很简单,如下图所示:

分页样式

显示5个页码,其他的用省略号表示

代码如下:

@defining((pageNavMap("currentPageNum"), pageNavMap("totalPageNum"))) { case (page_curPageNum, page_totalPageNum) =>
<div class="page-navigation">
    @if(page_curPageNum > 1){
    <a href="/person/@userprofile.id">首页</a>
    <a href='/person/@userprofile.id/p/@(page_curPageNum-1)'>上一页</a>
    }
    @if(page_totalPageNum < 6){
    @for(i <- 1 to page_totalPageNum){
        @if(i == page_curPageNum){
        <span class="curPage">@page_curPageNum</span>
        }else{
        <a href='/person/@userprofile.id/p/@i'>@i</a>
        }
    }
    }else{
    @if(page_curPageNum < 4){
        @for(i <- 1 to 5){
        @if(i == page_curPageNum){
        <span class="curPage">@page_curPageNum</span>
        }else{
        <a href='/person/@userprofile.id/p/@i'>@i</a>
        }
        }
        <span class="pagebreak">…</span>
    }else{
    @if(page_curPageNum > (page_totalPageNum-2)){
    <span class="pagebreak">…</span>
    <a href='/person/@userprofile.id/p/@(page_curPageNum-3)'>@(page_curPageNum-3)</a>
    <a href='/person/@userprofile.id/p/@(page_curPageNum-2)'>@(page_curPageNum-2)</a>
    <a href='/person/@userprofile.id/p/@(page_curPageNum-1)'>@(page_curPageNum-1)</a>
    <span class="curPage">@page_curPageNum</span>
    @if(page_curPageNum != page_totalPageNum){
    <a href='/person/@userprofile.id/p/@(page_totalPageNum)'>@(page_totalPageNum)</a>
    }
    }else{
    <span class="pagebreak">…</span>
    <a href='/person/@userprofile.id/p/@(page_curPageNum-2)'>@(page_curPageNum-2)</a>
    <a href='/person/@userprofile.id/p/@(page_curPageNum-1)'>@(page_curPageNum-1)</a>
    <span class="curPage">@page_curPageNum</span>
    <a href='/person/@userprofile.id/p/@(page_curPageNum+1)'>@(page_curPageNum+1)</a>
    <a  href='/person/@userprofile.id/p/@(page_curPageNum+2)'>@(page_curPageNum+2)</a>
    <span class="pagebreak">…</span>
    }
}
    }
    
@if(page_curPageNum < page_totalPageNum){
    <a href='/person/@userprofile.id/p/@(page_curPageNum+1)'>下一页</a>
    <a href='/person/@userprofile.id/p/@page_totalPageNum'>末页</a>
    }

</div>
}

其中,“pageNavMap”是保存分页信息的Map,后台定义如下:

Map pageMap = new HashMap();
pageNavMap.put("totalPageNum", totalPageCount);
pageNavMap.put("currentPageNum", curPageNum>1?curPageNum:1);

这只是用于页面显示的代码,至于后台分页的代码,可以很简单的通过play!内的EBean自带的“ PagingList”接口搞定

PagingList的api地址:http://www.avaje.org/static/javadoc/pub/com/avaje/ebean/PagingList.html

java中值与引用问题的简单笔记

java中传值与传引用的问题由来已久,今天上网无意中又发现了这个问题的讨论,于是自己也复习了一下,想想还是整理一下思路,记下来供概念还有些模糊的童鞋参考参考。

关于值与引用的问题,最直观的可以用一段代码来表示:

public class Test {
    public static void main(String[] args) {
        String a = new String("hello");
        String b = new String("hello");
        if(a==b) System.out.println("true");
        else System.out.println("false");
    }
}

这段代码返回的是“flase”,显然可以看出来:比较(==)两个String数据的时候,并不是比较的两个数据的“值”,而是比较两个数据的“引用”是否指向同一个对象

PS.进行“比较”,也就是“==”的时候,
如果比较的是两个基本数据类型(char,byte,short,int,long,float,double,boolean),则是判断它们的值是否相等。
如果比较的不是两个基本数据类型,而是两个对象变量,则是判断它们的引用是否指向同一个对象,也就是看引用地址是否一致。

引申一下:java的基本数据类型都是传“值”的,其他的则是传“引用” (“引用”指的是内存里保存这个“值”的地址标识,本质上还是一个“值”)

所谓的“值”指的是内存里的数据值,这里是“hello”;而“引用”指的是内存里这个数据值的地址。这段代码里a与b是分别实例化的,在内存里的地址显然是不同,所以比较的结果就是“false”。

========================

这边偏一下题:创建String对象除了new,常用还有通过引号来创建,比如

public class Test {
    public static void main(String[] args) {
        String a = "hello";
        String b = "hello";
        if(a==b) System.out.println("true");
        else System.out.println("false");
    }
}

这中情况下返回的是“true”!这个就与JAVA虚拟机(JVM)中的字符串池有关系了。字符串池里的String对象是可以被共享使用的,因此它提高了效率。并且由于String类是final的,它的值一经创建就不可改变,因此不会因为String对象共享而带来程序的混乱。

当执行String a = "hello";的时候,JVM首先在字符串池中查找是否已经存在了值为"hello"的这么一个对象,它的判断依据是String类equals(Object obj)方法的返回值。如果字符串池中已经存在,则不再创建新的对象,直接返回已存在对象的引用;如果不存在,则先创建这个值为“hello”的对象,再把这个对象加入到字符串池中,最后将这个对象的引用返回。

因此,在这段程序中,执行String a= "hello";的时候,由于字符串池中不存在“hello”,程序创建了一个新的对象“hello”,保存到池里,并把这个对象的引用地址返回;当执行String b = "hello";的时候,由于字符串池中已经存在了刚刚创建的“hello”对象,所以直接获得了这个hello对象的引用地址,并赋值给了b。

所以这个时候,a跟b进行比较的时候,a跟b的引用地址是一样的(都是执行String a= "hello";的时候返回的那个引用地址),所以最终的比较结果是“true”。

========================

回到正题:再来看一段简单的代码

- 阅读剩余部分 -

热评文章

最新文章

最近回复

归档

其它