标签归档:java

Java Unsafe类实现任意实例浅克隆

了解java并发包或nio底层的都应该知道Unsafe这个类,如并发包的锁,通过Unsafe#park() 和Unsafe#unPark()来实现线程阻塞和恢复运行的,这个类没有公布源码,但是有很多比较有用的方法,它可以直接操作内存,使用的时候务必要谨慎,不小心可能会造成内存泄漏。

实现浅克隆思路
为了表述方便,用S代表要克隆的对象,D表示克隆后的对象,SD表示S的内存地址,DD表示D的内存地址,SIZE表示该对象在内存中的大小。

1,获取原对象的所在的内存地址SD
2,计算原对象在内存中的大小SIZE
3,新分配一块内存,大小为原对象大小SIZE,记录新分配内存的地址DD。
4,从原对象内存地址SD处复制大小为SIZE的内存,复制到DD处
5,DD处的SIZE大小的内存就是原对象的浅克隆对象,强制转换为源对象类型就可以了

unsafe方法介绍
unsafe有很多比较牛逼的方法,刚开始接触可能会感到不可思议,下面介绍几个比较实用的方法。
1,Object allocateInstance(Class aClass)
这个方法是分配一个实例,它的牛逼之处在于,只要是非abstract的类它都能实例化,即使这个类没有public的构造方法,它甚至能绕过各种JVM安全检查,不运行任何构造方法,当你用这个方法初始化类后,通过getClassLoader()方法视图去获取他的classloader会发现它返回的是null。是不是很神奇
2,long objectFieldOffset(Field f)
获取对象属性偏移量
3,int arrayBaseOffset(Class arrayClass)
这个方法是返回一个数组第一个元素的偏移量,这个可以用在获取对象内存地址的时候,因为unsafe没有提供直接获取对象实例内存地址的方法,只有获取通过对象属性偏移量获取属性内存地址的方法,所以我们可以通过构建一个数组对象,通过数组元素偏移量获取元素的内存地址,可以参考后面的代码。
4,long allocateMemory(long bytes)
这个方法是直接分配一个bytes大小的内存,然后返回内存的起始地址。
5,long getLong(Object o, long offset)
获取对象o的偏移量为offset位置的long值,这个long值是该位置后64位二进制的long值,同样的方法还有getInt,getByte等
6, putLong(Object o, long offset, long x);
把long x的2进制值放在对象o的offset偏移量的位置。

offset偏移量:是只相对于另一个地址便宜的byte数;

获取Unsafe实例
Unsafe这个类是在sun.misc包下,构造器是私有的,提供一个getUnsafe()的方法获取单例,仅供java类库的类使用,即只能是BootstrapClassLoader加载器加载的类使用,源码如下:

    private Unsafe() {}

    private static final Unsafe theUnsafe = new Unsafe();

    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class cc = Reflection.getCallerClass();
        if (cc.getClassLoader() != null)
            throw new SecurityException("Unsafe");
        return theUnsafe;
    }

可以通过反射的方式获取它的实例:

        Field theUnsafe = null;
        try {
            theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            unsafe = (Unsafe) theUnsafe.get(null);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

1,获取原对象的内存地址

unsafe类没有提供直接获取实例对象内存地址的方法,但是可以间接获取,大概思路是:构建一个新对象N,N包含了S对象的引用,只要获取到N对S的引用地址就是S的内存地址了;
unsafe类获取内存地址的方法只有一个,就是getLong(Object o, long offset);为了方便,可以构建一个包含S的Object[]数组,获取到Object[]的S引用就可以了,代码如下:

    private static long getAddr(Object obj) {
        Object[] array = new Object[]{obj};
        long baseOffset = unsafe.arrayBaseOffset(Object[].class);
        return unsafe.getLong(array, baseOffset);
    }

2,计算原对象在内存中的大小
我们新建一个对象实例后,jvm做的其实只是在堆中分配非static的Field的内存,其他的static属性,或者方法在加载期间就已经放到内存中去了,所以当我们计算对象大小时只要计算field的大小就行了,jvm分配内存时单个实例中的每个field内存都是连续的,所以我们只需要获得最大偏移量的Field的偏移量,然后加上这个field的大小就可以了,代码如下:

    public static long sizeOf(Class<?> clazz) {
        long maximumOffset = 0;
        Class<?> maxiNumFieldClass = null;
        do {
            for (Field f : clazz.getDeclaredFields()) {
                if (!Modifier.isStatic(f.getModifiers())) {
                    long tmp = unsafe.objectFieldOffset(f);
                    if(tmp>maximumOffset){
                        maximumOffset = unsafe.objectFieldOffset(f);
                        maxiNumFieldClass = f.getType();
                    }
                }
            }
        } while ((clazz = clazz.getSuperclass()) != null);
        long last = byte.class.equals(maxiNumFieldClass)?1:
                ( short.class.equals(maxiNumFieldClass) || char.class.equals(maxiNumFieldClass))?2:
                        (long.class.equals(maxiNumFieldClass)||double.class.equals(maxiNumFieldClass))?8:4;
        return maximumOffset + last;
    }

3,新分配一块大小为SIZE的内存
Unsafe提供了方法 long allocateMemory(long bytes); 可直接分配一块内存,返回内存地址。

4,从原对象内存地址SD处复制大小为SIZE的内存到DD位置
见Unsafe方法:void copyMemory(long srcAddress, long destAddress, long bytes)

5,DD处的SIZE大小的内存赋值给目标对象
Unsafe没有提供直接读内存转为java对象的方法,但是可以通过类似获取对象内存地址的方法来实现:
先新建一个包含S类型属性的对象,让后把DD的内存地址赋值给S类型的属性变量就可以了:

    private static <T> T fromAddress(long addr, long size) {
        Object[] array = new Object[]{null};
        long baseOffset = unsafe.arrayBaseOffset(Object[].class);
        unsafe.putLong(array, baseOffset, addr);
        return (T) array[0];
    }

克隆方法
如果是数组,这个方法就不适用了,因为数组比较特殊,数组类是jvm在运行时动态生成的,有兴趣可以去研究下jvm对数组的处理。最终的克隆方法代码如下:

    public static <T> T shallowClone(T t) throws InstantiationException {
        Class clazz = t.getClass();
        if(clazz.isArray()){
            Object[] os = (Object[])t;
            return (T)Arrays.copyOf(os,os.length);
        }
        long srcAddr = getAddr(t);
        long size = sizeOf(clazz);
        long destAddr = unsafe.allocateMemory(size);
        unsafe.copyMemory(srcAddr, destAddr, size);
        return fromAddress(destAddr, size);
    }

测试

        Object s = new Foo(8,888L,"test")
        Object s2 = shallowClone(s);
        Assert.assertEquals(s, s2);
        Assert.assertTrue(s != s2);

ps:用Unsafe分配的内存不在jvm管理的范围内,所以,jvm不会自动去回收这一块内存,你得通过Unsafe#freeMemory(long address) 去释放这块的内存。

Java 8 lambda表达式

jdk8最重要的特性-lambda表达式,意味着java可以函数式编程那样直接定义一个函数了,因此java8先从学习ambda开始。先看熟悉的例子,新建一个线程:

new Thread(new Runnable() {
@Override
public void run() {
System.out.println("this is thread run method");
}
});

用lambda表达式的代码:


new Thread(() -> System.out.println("this is thread run method")).start()

上面“ () -> System.out.println(“this is thread run method”) ”这部分就是lambda表达式了。
这一行代码运行了一个新建的线程,并用lambda表达式传递了一个 System.out.println(“this is thread run method”)的run方法体,相比以前代码简洁了不少。

Java8加入lambda表达式后,意味着方法可以进行传递了,Java也能像python的filter,map,reduce这样进行玩耍了。

在介绍lambda表达式之前,我们先得知道java8的函数接口:

JSR335中对函数式接口的描述是:

functional interface is an interface that has just one abstract method (aside from the methods of Object), and thus represents a single function contract. (In some cases, this “single” method may take the form of multiple abstract methods with override-equivalent signatures inherited from superinterfaces; in this case, the inherited methods logically represent a single method.)

函数接口是声明有且只有一个抽象方法的接口,这个抽象方法也可以是继承其他接口的,函数接口也可以有java8新增的default方法。上面这段代码用lambda表达式替换了继承Runnable接口的匿名类,这里Runnable就是所谓的函数接口了。在jdk8新增了一个函数接口的注解“ @FunctionalInterface” ,加上这个注解后,该接口就必须符合函数接口的规范,否则无法编译。

申明了函数接口后,我们就可以通过lambda表达式来实现这个接口的方法及这个接口的调用了。

lambda的申明有两部分组成,分别为参数和方法体,由“->”隔开,
参数部分:当没有参数时,我们用”()”表示,有参数时,我们在括号内申明参数,如”(int arg1,String arg2)”;编译器可以对参数类型自动推导,因此我们还可以写成”(arg1,arg2)”,如果只有一个参数,甚至可以去掉”()”,直接写成”arg1″;

方法体部分:我们可以用”{}”来表示,当该函数接口有返回值的时候我们可以这么表示{return value;} ,当然我们也可以直接写成“value”,如果有函数接口的方法有返回值则会自动返回value。

例:

函数接口:


@FunctionalInterface
interface Addable{
 int add(int i);
}

调用接口方法:

public void testAdd(Addable fn,Integer num){
System.out.println(fn.add(num));
 }

因此我们可以这么调用testAdd方法:

testAdd((int i)->{return i+100;},88);

我们可以把类型定义去掉,方法体只有一句代码我们也可以把“{}”去掉,同时把return去掉,变成了下面的代码:

testAdd((i) -> i + 100,88);

由于参数只有一个,我们可以把参数部分的”()”都去掉,最简洁的代码是:

testAdd(i -> i + 100,88);

以上的运行结果当然都是188。

以后也可以这样引用一个lambda,代码:


Comparator<String> compareLength = (a,b)->a.length()-b.length();

lambda表达式我们可以理解为就是实现了一个函数接口的匿名类的实例。
但是实际上java8并不是仅仅只在编译器上做个编译转换处理完事了,我们把上面的代码编译的class类的字节码扒出来看一下就知道了。
java代码:

public class LambdaTest {
 public static void main(String[] args) {
 new Thread(() -> System.out.println("hello lambda")).start();
 }
}

在命令行切换到class目录,运行命令:

javap -verbose LambdaTest.class

找到如下的打印的信息:

public static void main(java.lang.String[]);
 descriptor: ([Ljava/lang/String;)V
 flags: ACC_PUBLIC, ACC_STATIC
 Code:
 stack=3, locals=1, args_size=1
 0: new #2 // class java/lang/Thread
 3: dup
 4: invokedynamic #3, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
 9: invokespecial #4 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
 12: invokevirtual #5 // Method java/lang/Thread.start:()V
 15: return

可以看到“4: invokedynamic #3, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;”,这里有一个invokedynamic的指令。而且没有生成“Method com/sed/LambdaTest$1.”这种带$符号的匿名类。所以这里只是定义了一个方法调用点,只有在执行的时候才能知道具体调用的是哪个方法,这就是所谓的延迟执行。

了解了lamdba表达式,我们可以像Python一样使用filter,map,reduce类似的方法了。

在集合中,java8在Collection接口新增了一个stream()的default方法,可通过返回的Stream对象进行filter和forEach操作,代码:


List<String> list = Arrays.asList("aaa","bba","ccc");
 list.stream().filter(s->s.contains("a"))
              .forEach(s-> System.out.println(s+"_"));

运行结果如下:


aaa_

bba_

怎么样,java8的lambda是不是很酷!!!

java线程重排序问题

以前在看《java并发编程实战》的时候,看到重排序的时候始终不能很好理解,做了很多demo也未能实现书中所述效果,见CSDN上的提问

按作者的意思是,当线程A运行的时候,在没有同步或者使用锁的情况下,其他线程看到的线程A内的代码运行顺序可能被打乱。即后面的代码可能会先运行。

代码:

</pre>
public class NoVisibility {
private static boolean ready;
private static int     number;

private static class ReaderThread extends Thread {
public void run() {
while (!ready) {
Thread.yield();
}
System.out.println(number);
}
}

public static void main(String[] args) {
new ReaderThread().start();
number = 42;
ready = true;
}
}
<pre>

ReaderThread线程可能会先看到主线程中ready=true。但是经过多次实验均未看到此效果。 今天偶然看到关于系统架构的文章,觉得可能问题出在系统的架构差异导致的。

从系统架构来看,目前的商用服务器大体可以分为三类,即对称多处理器结构 (SMP : Symmetric Multi-Processor) ,非一致存储访问结构 (NUMA : Non-Uniform Memory Access) ,以及海量并行处理结构 (MPP : Massive Parallel Processing)  本人DEMO运行环境为intel I3处理器. 在SMP系统架构中,所有的CPU共享全部资源,如总线,内存和I/O系统等,操作系统或管理数据库的复本只有一个,这种系统有一个最大的特点就是共享所有资源。多个CPU之间没有区别,平等地访问内存、外设、一个操作系统。操作系统管理着一个队列,每个处理器依次处理队列中的进程。此外SMP架构还存在内存屏障,保障指令的顺序执行。

本人运行环境是在intel I3处理器下运行的,估计此款CPU采用的就是SMP架构。

而在NUMA架构中,不同CPU之间具有独立的本地内存、 I/O 槽口等,CPU之间的通讯是通过互联模块 ( 如称为 Crossbar Switch) 进行连接和信息交互,因此每个 CPU 可以访问整个系统的内存 ( 这是 NUMA 系统与 MPP 系统的重要差别 ),这将导致在不同CPU通讯的时候,读取数据的CPU没有读到写CPU的实际数据。

另外除了CPU对指令的乱序执行外,编译器在编译的时候也可能会对代码进行重排序。CPU在乱序执行中对指令的执行顺序是不会打乱的,而只是可能不会等到前面的指令执行完在执行后面的指令,如同并发执行,相比于CPU的乱序,编译器的乱序才是真正对指令顺序做了调整。但是编译器的乱序也必须保证程序上下文的因果关系不发生改变。

本文只做笔记,关于重排序更深的理解还需对JVM内存模型,系统架构等做更深入研究

参考: SMP、NUMA、MPP体系结构介绍

中央处理器

为什么要指令重排序和屏障的作用

JAVA学习笔记–4.多线程编程 part1.背景知识和内存模型

 

 

定长线程池newFixedThreadPool

在多线程“生产者-消费者”模式中,为了避免线程陷入竞争而耗尽资源,通常可以通过Executors.newFixedThreadPool(num)限制线程的数量来解决问题,但是如果“生产者”数量持续大于“消费者”数量问题就容易产生了:

    public static void main(String[] args) throws InterruptedException {
        int num = 10000000;
        ExecutorService es = Executors.newFixedThreadPool(2);
        for (int i = 0; i < num; i++) {
            es.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                    }
                }
            });
        }
        es.shutdown();
        System.out.println("main end...");
    }

这里创建了一千万个线程,限制线程池大小为2;为了让”消费者”消费速度小于生产速度更明显,让创建的线程休息1秒。本人运行环境机器是4G内存,jvm堆空间默认大小,运行后抛出OutOfMemoryError,这是因为线程创建线程后,但是由于消费速度小于生产速度,线程池满后线程虽然没有立即执行,而是放在了等待队列中,当持续消费速度小于生产速度时,等待队列则持续增长,最终把堆空间撑爆了。

解决这个问题可以配合信号变量使用,使线程池满后,生产者暂停生产,陷入阻塞状态:

public static void main(String[] args) throws InterruptedException {
        int num = 10000000;
        int threadNum = 2;
        ExecutorService es = Executors.newFixedThreadPool(threadNum);
        final Semaphore semaphore = new Semaphore(threadNum);    //创建与线程池大小相当的信号变量
        for (int i = 0; i < num; i++) {
            semaphore.acquire(); //线程池满后主线程将暂时阻塞
            es.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                    }finally{
                        semaphore.release(); //线程运行完后信号释放,主线程恢复运行
                    }
                }
            });
        }
        es.shutdown();
        System.out.println("main end...");
    }

如此,堆空间将不再担心资源枯竭,只是主线程会受到阻塞,只需另起一个线程代替主线程即可。

Java(Android)线程池【转】


介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用。本文是基础篇,后面会分享下线程池一些高级功能。
1、new Thread的弊端
执行一个异步任务你还只是如下new Thread吗?
[Java]
new Thread(new Runnable() {

@Override
public void run() {
// TODO Auto-generated method stub
}
}).start();[/java]
那你就out太多了,new Thread的弊端如下:
a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
相比new Thread,Java提供的四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。

2、Java 线程池
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
(1). newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。示例代码如下:

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
    final int index = i;
    try {
        Thread.sleep(index * 1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
 
    cachedThreadPool.execute(new Runnable() {
 
        @Override
        public void run() {
            System.out.println(index);
        }
    });
}

线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

(2). newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。示例代码如下:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
    final int index = i;
    fixedThreadPool.execute(new Runnable() {
 
        @Override
        public void run() {
            try {
                System.out.println(index);
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    });
}

因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。
定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()。可参考PreloadDataCache。

(3) newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
 
    @Override
    public void run() {
        System.out.println("delay 3 seconds");
    }
}, 3, TimeUnit.SECONDS);

表示延迟3秒执行。

定期执行示例代码如下:

scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
 
    @Override
    public void run() {
        System.out.println("delay 1 seconds, and excute every 3 seconds");
    }
}, 1, 3, TimeUnit.SECONDS);

表示延迟1秒后每3秒执行一次。
ScheduledExecutorService比Timer更安全,功能更强大,后面会有一篇单独进行对比。

(4)、newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:


ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
    final int index = i;
    singleThreadExecutor.execute(new Runnable() {
 
        @Override
        public void run() {
            try {
                System.out.println(index);
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    });
}

结果依次输出,相当于顺序执行各个任务。
现行大多数GUI程序都是单线程的。Android中单线程可用于数据库操作,文件操作,应用批量安装,应用批量删除等不适合并发但可能IO阻塞性及影响UI线程响应的操作。

Data URI scheme

这几天都在抢票 ,在研究360抢票插件时发现验证码的src为一串非正常URL的字符“data:image/png;base64,… …”,于是对这串字符感到好奇而进行了学习。
这串字符叫做:Data URI scheme。Data URI scheme是在RFC2397中定义的,目的是将一些小的数据,直接嵌入到网页中,从而不用再从外部文件载入。
字符组成:
data:表示取得数据的协定名称
image/png:表示MIME TYPE数据类型.
base64:编码方式。
逗号后面的是编码后的字符。
目前支持的类型有:

data:,文本数据
data:text/plain,文本数据
data:text/html,HTML代码
data:text/html;base64,base64编码的HTML代码
data:text/css,CSS代码
data:text/css;base64,base64编码的CSS代码
data:text/javascript,Javascript代码
data:text/javascript;base64,base64编码的Javascript代码
data:image/gif;base64,base64编码的gif图片数据
data:image/png;base64,base64编码的png图片数据
data:image/jpeg;base64,base64编码的jpeg图片数据
data:image/x-icon;base64,base64编码的icon图片数据

在php中base64用如下编码方法即可:

base64_encode(file_get_contents('test.png'))

在java中用sun.misc.BASE64Encoder进行base64编码文件:

File file = new File("test.png");
FileInputStream fis = new FileInputStream(file);
byte[] buf = new byte[1024];
int len = 0;
int count = 0;
byte[] total = new byte[]{};
while((len=fis.read(buf))!=-1){
	count = total.length+len;
	byte[] tmp = new byte[count];
	System.arraycopy(total, 0, tmp, 0, total.length);
	System.arraycopy(buf, 0, tmp, total.length ,len);
}
fis.close();
BASE64Encoder be = new BASE64Encoder();
String string = be.encode(total);

使用data URI scheme与普通的http URI scheme区别如下:
1,浏览器对编码的数据不会进行缓存,并且随html同时加载,不另起请求;
2,便于传输,不会被拦截,只要页面能加载,便能获取到编码后的数据,这样360抢票插件就能将验证码图片进行编码后传输,在服务器后台进行识别,而不必去关心session问题;
3,保存数据的大小上受限制(Firefox 1.5支持高达100KB的数据);
4,编码后会增加图片大小;
3,但如果使用过多会增加html的大小,可用css解决,即在css的background-image中使用data URI scheme,如:

background-image: url("data:image/png;base64/9j/4AA....+b0//2Q==");

但是IE8以下版本不兼容。ie以下只得用http URI scheme.

参考:http URI scheme与data URI scheme

什么是data URI scheme及如何使用data URI scheme

struts2上传大小为0的文件问题解决

当struts上传大小为0kb的文件的时候,struts默认当文件不存在,后台接收的文件file.exist()的值为false;

我使用的struts版本为:2.1.8.1;其他版本可能会没有这个问题,未做测试;

参考:Struts 2.3.1 GA 版发布

  • [WW-3562] – FileUploadInterceptor: File not uploaded when file is empty (0 bytes)

struts 配置有个属性struts.multipart.parser,该属性指定处理multipart/form-data的MIME类型(文件上传)请求的框架,该属性支持cos、pell和jakarta等属性值,即分别对应使用cos的文件上传框架、pell上传及common-fileupload文件上传框架。该属性的默认值为jakarta。

为jakarta时候,如果上传的文件大小为0kb,则struts读取文件则是不存在的:

When not selecting any file to upload in a file input, Struts does not ignore the 0 byte file
returned by jakarta FileUpload.
This means that a file is injected in the action even if nothing was uploaded.

In webwork the check was done in the constructor of  com.opensymphony.webwork.dispatcher.multipart.JakartaMultiPartRequest:

else if (item.getSize() == 0) {
log.warn("Item is a file upload of 0 size, ignoring");
}

In struts the parsing of the request moved from the constructor to a parse method but the
above check disappeared.

Additionally this behaviour is inconsistant with the "pell" implementation that ignores 0
byte files.

--
This message is automatically generated by JIRA.

这里是STRUTS_MULTIPART_PARSER的官方文档说明:

STRUTS_MULTIPART_PARSER

public static final String STRUTS_MULTIPART_PARSER

The org.apache.struts2.dispatcher.multipart.MultiPartRequest parser implementation for a multipart request (file upload)

See Also:
Constant Field Values

注意  如果需要使用cos或者pell的文件上传方式,则应该将对应的JAR文件复制到Web应用中。
例如,使用cos上传方式,则需要自己下载cos框架的JAR文件,并将该文件放在WEB-INF/lib路径下。但是我测试仍然为通过,原因未知。

解决办法2:

if (!srcFile.exists()) {
 try {
   destFile.createNewFile();//创建空文件
 } catch (IOException e) {
   LogUtil.error(e);
   return;
 }
} else {
//文件操作
}

参考:

struts2 的struts.properties配置详解

http://www.iteye.com/problems/19141

URLConnection

代码:

  HttpURLConnection conn = null;
  InputStream is = null;
  OutputStream os = null;
  try {
   conn = (HttpURLConnection) new URL(url).openConnection();
   is = conn.getInputStream();
 // code
  } catch (MalformedURLException e) {
  } catch (IOException e) {
  } finally {
   try {
     if (conn != null)
       conn.disconnect();
     if (is != null)
      is.close();
   } catch (IOException e) {
   }
  }
 

在linux下,当连接数多的时候会发现有很多CLOSE_WAIT的连接。windows下好像会好些。

这种情况是因为:在http1.1中所有的conntection默认都是persist Connection(keep-alive)。当Connection.getInputStream() close后,HTTP protocol handler会试图去清理Connection,成功的话会将conntection放入connection缓存,以便下次能重复利用。

修改默认keep-alive:

System.setProperty("http.keepAlive", "false");

修改connection缓存机制:

connection.setUseCaches(false);

参考:Persistent Connections

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6367268

http://stackoverflow.com/questions/4767553/safe-use-of-httpurlconnection

http://hea-www.harvard.edu/~fine/Tech/addrinuse.html

关于findBugs的几个问题

1,Method returning array may expose internal repersentation

一个静态的公共函数,它返回了一个私有的静态数组的引用。任何调用这个静态公共函数的代码,都有可能改变这个私有的静态数组。实例代码如下:

Public String[] getArr(){
return arr;
}

这样get获取到这个数组后,可以改变数组的内容,如getArr()[0]=”abc”;

解决办法是返回他的clone:

Public String[] getArr(){

return arr.clone();

}

2,Storing reference to mutable object

这是因为set设置了一个对象后,还可以通过这个对象的引用改变该对象set的值:

public class Test{

private Date regDate;

public void setRegDate(Date regDate) {
this.regDate = regDate;
}

}&lt;/pre&gt;
&amp;nbsp;
&lt;pre title=&quot;&quot;&gt;Test t=new Test();

Date now = new Date();

t.setRegDate(now);

now.setYear(4000);

解决办法是set赋值对象的clone:

public class Test{

private Date regDate;

public void setRegDate(Date regDate) {
this.regDate = regDate.clone();
}

}

参考:

[hyddd的FindBugs分析记录][M V EI2] May expose internal representation by incorporating reference to mutable object

[hyddd的FindBugs分析记录][M V MS] Public static method may expose internal representation by returning array

java浏览器插件-applet

这两天项目中需要在浏览器中调用本地设备,研究了下java的applet,把学习笔记在此分享。

一,

首先需要建立一个继承import javax.swing.JApplet的类;

JApplet两个我比较关心的方法:

public void init()

:初始化applet时候会调用此方法,在生命周期中只调用一次,一般界面初始化代码写在这里,或者写在构造器方法中

public void destory()

:销毁方法,同样在生命周期中只调用多次。

另外还有 start() ,stop() 方法,可在生命周期中调用多次。

浏览器操作中方法调用顺序:

浏览器加载Applet:init–>start

浏览器刷新: stop–>destory–>init–>start

浏览器推出:stop–>destory

二,

applet写完后,就是用eclipse或者jar命令打包了,

打包后运行一些基本逻辑就没问题了。

但是如果要访问本地文件之类的操作可能就没这个权限了,解决办法是给jar包数字签名:

1,生成store证书文件scancard.store,scancard为自己指定的名字 :

keytool -genkey -keystore scancard.store -alias scancard

提示输入密码,记住自己输入的密码,下一步需要,其他的组织名,公司名等任意填写或一路回车 2,给jar包签名:

jarsigner -keystore scancard.store ScanCard.jar scancard

scancard.store为刚才生成的store, ScanCard.jar为刚才的jar包 ,最后的scancard貌似任意填写都可以 这样你的jar包权限就提升了,放在浏览器中就能做进一步的操作了 运行这个命令后如果你发现提示你证书6个月过期,可以在第一个命令后面加上如下参数指定有效期,单位(天),这里指定有效期为10年:-validity 3650
*这里好像还可以将证书导入,用于浏览器的某些认证,目前还没用到:

keytool -export -keystore scancard.store -alias scancard -file scancard.cert

三,

将jar包嵌入到html中:

两种方式:

1,jsp实现,

<jsp:plugin codebase="." archive="ScanCard.jar"
 code="com.cloudview.ScanCard.class" name="scanCardPlugin" width="0"
 height="0" type="applet">
 <jsp:fallback>您的浏览器不支持JAVA Applet,请更换浏览器,推荐用Chrome或Firefox</jsp:fallback>
 </jsp:plugin>

codebase为一般配置为”.”,表示jar包为当前路径

archive为嵌入的jar包

code为需要调用的applet class,貌似可以不写”.class”

name为自己指定的一个标志,可以任意写,这个为js调用提供的,后续会讲到,

*这里还能给applet传递参数,在jsp:plugin内加入如下代码:

<jsp:param name="param1" value="<%=#request.getAttribute("param1")%>" />

在applet类 init() 方法中接收方法:

String param1 = getParameter("param1");

2,html实现,

<applet codebase="." archive="ScanCard.jar" code="com.cloudview.ScanCard.class" name="scanCardPlugin2" width="0" height="0">
</applet>

这种貌似浏览器兼容性没有第一种好,但是,这种方法在有的浏览器中某些功能兼容性又更好。

参数同第一种

另外,javascript和applet可以互相访问,applet权限还可以进一步提高到操作调用本地设备,运行Runtime的exec方法以调用浏览器端的dos命令或shell命令,未完待续。。。见:下一篇

参考:http://www.cnblogs.com/ziziwu/archive/2012/03/21/2410159.html