sulong 于 2010-08-25
没有评论 »
静态域的作用范围是在同一个类之内,类不仅有名称上的区别,还有classloader,即类加载器的问题,同一个class文件被不同的类加载器加载后,即便它们有相同的名称,但是它们并不是同一个类。因此在不同类加载器中同名类的静态变量是不共享的。下面做一个测试,来验证它。下面的Test类有一个静态域value,两个方法分别增加value的值和打印状态。
public class Test
{
private static int value
= 1;
public void increase
() {
value
++;
}
public void print
() {
System.
out.
println("My Class Loader: "
+ this.
getClass().
getClassLoader() + ", Value: " + value
);
}
}
下面的类StaticTest,运行时创建两个类加载器,分别载入同一个class文件,并通过反射依次调用print, increase,print方法。如果静态域在整个JVM内共享,那么value被加了两次,最后会输出3;否则应该输出2。由于类加载器会先向父类加载器请求类,找不到的时候才会自己加载,所以在运行这个测试时,一定不要把Test类先放到classpath中。
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
public class StaticTest
{
public static void main
(String[] args
) throws Exception {
URL[] classURLs
= new URL[] {
new URL("file:/home/sulong/tmp/")
};
URLClassLoader classLoader1
= new URLClassLoader(classURLs
);
URLClassLoader classLoader2
= new URLClassLoader(classURLs
);
test
(classLoader1
);
System.
out.
println("--------------------");
test
(classLoader2
);
}
private static void test
(ClassLoader loader
)
throws Exception {
Class clazz
= loader.
loadClass("Test");
Object testObj
= clazz.
newInstance();
invoke
(clazz, testObj,
"print");
invoke
(clazz, testObj,
"increase");
invoke
(clazz, testObj,
"print");
}
private static void invoke
(Class clazz,
Object obj,
String name
)
throws Exception {
Method method1
= clazz.
getMethod(name,
new Class[]{});
method1.
invoke(obj,
new Object[]{});
}
}
下面是我的机器上运行时的测试结果:
My Class Loader: java.net.URLClassLoader@7987aeca, Value: 1
My Class Loader: java.net.URLClassLoader@7987aeca, Value: 2
--------------------
My Class Loader: java.net.URLClassLoader@5d0385c1, Value: 1
My Class Loader: java.net.URLClassLoader@5d0385c1, Value: 2
sulong 于 2010-08-24
没有评论 »
由于非技术原因,公司的专门成立的支付部,招一批Java程序员,做了支付系统;又由于非技术原因,两个月内,这个部门从领导到程序员全走光了。然后这个支付系统就移交给我们这帮受苦受难的兄弟们维护了。在看他们代码的时候发现了很有趣的东西,就是在tomcat内共享Spring context。
1,实现方法
1.1 建立一个全局的jndi资源
这个全局的jndi资源代表了一个共享的spring上下文,每次通过jndi查找资源时,都返回同一个spring上下文实例。修改tomcat/conf/context.xml文件,加入以下代码:
<Resource auth="Container"
contextConfigLocation="/com/test/application-context.xml"
factory="com.test.TomcatWebApplicationContextResourceFactory"
name="bean/RootApplicationContextFactory"
type="org.springframework.context.ApplicationContext"/>
上面这段配置的意思就是,当有人通过jndi名称”bean/RootApplicationContextFactory”来查找对象时,容器就新建一个TomcatWebApplicationContextResourceFactory类实例,并调用它的getObjectInstance方法来获得资源对象。这个工厂类要是想javax.naming.spi.ObjectFactory接口。代码如下:
package com.test;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TomcatWebApplicationContextResourceFactory
implements
ObjectFactory {
private static final String PARAM
= "contextConfigLocation";
private static final String DEFAULT = "application-context.xml";
private static ApplicationContext context
; //被共享的spring上下文
private void init
(String confFile
) {
context
= new ClassPathXmlApplicationContext
(confFile
);
}
public Object getObjectInstance
(Object obj,
Name name,
Context nameCtx,
Hashtable
<?,
?> environment
) throws Exception {
if (null == context
) {
// Customize the bean properties from our attributes
Reference ref
= (Reference) obj
;
//从xml配置文件里取得contextConfigLocation元素的值
RefAddr addr
= ref.
get(PARAM
);
if (null != addr
) {
String value
= (String) addr.
getContent();
init
(value
);
} else {
init
(DEFAULT);
}
}
return context
;
}
}
1.2 在web应用中引用上面定义的资源
修改tomcat/conf/web.xml,添加以下的内容,这样在这个tomcat下的所有的web应用就都可以访问共享的spring上下文了。
<resource-env-ref>
<description>Object factory for Root applicationcontext</description>
<resource-env-ref-name> bean/RootApplicationContextFactory
</resource-env-ref-name>
<resource-env-ref-type> org.springframework.context.ApplicationContext
</resource-env-ref-type>
</resource-env-ref>
1.3 加入依赖的包
从第一步可以看出,tomcat容器需要访问spring的一些类,需要初始化上下文,所以要把初始化上下文时用到的类共享给tomcat。最简单的方法是把所有相关的类和jar包复制到tomcat/lib目录。还可以修改catalina.properties实现,如下:
shared.loader=/usr/local/jars/*.jar
上面的代码让tomcat载入自定目录里面所有的jar文件。
2 我的看法
一个庞大的spring上下文是要占用很多内存的,所有应用共享一个上下文,而不是每个web应用一份,就可以节省不少的内存。但是由此却带来了部署和配置的麻烦。如果有人利用这种机制,让多个web应用通过共享spring bean来实现一些奇怪的功能,结果导致这些应用间相互耦合,那么以后想拆分开也不容易了。总体来说,我不喜欢这样方案,但是如果真的很穷很缺内存的话,这也不失一个好方法。
sulong 于 2010-07-22
1篇评论 »
LINK5终于上线了完成了,好累。回顾这个项目,在业务的驱动下,我们更改了产品商品模型并且增加了赠券的营销活动。在技术的驱动下,我们合并了order,inventory,market和coupon数据库,合并了order,inventory,market和coupon应用,由多个项目分布式部署,变成单个项目非分布式,减少了大量系统间通过jms的集成和不必要的异步,明确了不少程序的事务边界。在目前的业务量,简化的方案才是更好的。我还得再简化系统的路上走下去,让系统更简单,更易于维护,更灵活。
sulong 于 2010-07-20
1篇评论 »
想知道我的Java应用程序在运行时到底执行了那些SQL,以及这些SQL运行时耗费多少时间。起初试图通过mysql的日志来查看,调查了一下发现很麻烦,于是就是用mysql的jdbc驱动程序提供的功能来查看,很方便。开启的方法很简单,就是在jdbc url上加上如下的参数:
jdbc:mysql://localhost/mydb?profileSQL=true
还有很多其它很有用的参数,如:
includeInnodbStatusInDeadlockExceptions=true
可以在发生innodb死锁时打印innodb status.详细情况看最新的mysql J connector 文档。
打开日志后,发现一般的sql语句的执行时间都< 1ms, 但是有一条sql的执行时间是180ms以上。这条sql是
SELECT max(oid) FROM inventory_entry WHERE sku = ? GROUP BY lot ;
这条sql是要查出相同sku记录按lot分组后最新的一条记录的主键。经查,因为lot在新业务模式下将不再被使用,所以可以把group by 去掉了,去掉之后执行时间降到100ms左右。select max(0id) 的作用是为了取得最新的记录,但是可以通过主键排序直接拿到的oid,因为oid是自增长的。改成:
SELECT oid FROM inventory_entry WHERE sku = ? ORDER BY oid DESC LIMIT 1;
后,时间降到1ms左右了,达到了性能要求。