文档章节

类加载器与单例

乒乓狂魔
 乒乓狂魔
发布于 2015/02/07 10:38
字数 802
阅读 254
收藏 2
当使用不同的类加载器时,也会使单例失效,如下:
单例为:

public final class Singleton{
	
	private static final Singleton instance=new Singleton();
	
	private Singleton(){
                System.out.println("执行构造函数");
		System.out.println("类加载器="+this.getClass().getClassLoader());
	}
	
	public static Singleton getInstance(){
		return instance;
	}

}

自定义的类加载器为:
public class MyClassLoader extends ClassLoader{
	
	private String name;
	private String classPath;
	
	public MyClassLoader(String name){
        super(null);
        this.name = name;
    }
	
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		byte[] b=getClassBytes(name);
		return this.defineClass(name, b,0,b.length);
	}

	private byte[] getClassBytes(String name) {
		String classFullPath=classPath+"/"+name.replace(".","/")+".class";
		byte[] data=null;
		try {
			FileInputStream fileInputStream=new FileInputStream(classFullPath);
			ByteArrayOutputStream out=new ByteArrayOutputStream();
			IOUtils.copy(fileInputStream,out);
			data=out.toByteArray();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return data;
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getClassPath() {
		return classPath;
	}

	public void setClassPath(String classPath) {
		this.classPath = classPath;
	}

}

测试案例如下:
public static void testClassLoader() throws Exception{
		Singleton singleton=Singleton.getInstance();
		
		MyClassLoader myClassLoader=new MyClassLoader("myClassLoader");
		myClassLoader.setClassPath("D:/important");
		Class singletonClass=myClassLoader.findClass("com.lg.design.singleton.hungry.Singleton");
		System.out.println("singletonClass.getClassLoader() : "+singletonClass.getClassLoader());
		
		System.out.println("Singleton.class==singletonClass : "+(Singleton.class==singletonClass));
		System.out.println("Singleton.class.equals(singletonClass) : "+(Singleton.class.equals(singletonClass)));
		
		Constructor constructor1=Singleton.class.getDeclaredConstructor();
		Constructor constructor2=Singleton.class.getDeclaredConstructor();
		Constructor constructor3=singletonClass.getDeclaredConstructor();
		System.out.println("constructor1==constructor2 : "+(constructor1==constructor2));
		System.out.println("constructor1.equals(constructor2) : "+constructor1.equals(constructor2));
		System.out.println("constructor1==constructor : "+(constructor1==constructor3));
		System.out.println("constructor1.equals(constructor3) : "+constructor1.equals(constructor3));
		
		constructor1.setAccessible(true);
		Object singleton1=constructor1.newInstance();
		constructor3.setAccessible(true);
		Object singleton3=constructor3.newInstance();
		
		System.out.println("singleton : "+singleton);
		System.out.println("singleton1 : "+singleton1);
		System.out.println("singleton3 : "+singleton3);
		System.out.println("singleton1==singleton3 : "+(singleton1==singleton3));
	}

输出结果为:
执行构造函数
类加载器=sun.misc.Launcher$AppClassLoader@417470d0
singletonClass.getClassLoader() : com.lg.design.singleton.hungry.MyClassLoader@470d1f30
Singleton.class==singletonClass : false
Singleton.class.equals(singletonClass) : false
constructor1==constructor2 : false
constructor1.equals(constructor2) : true
constructor1==constructor : false
constructor1.equals(constructor3) : false
执行构造函数
类加载器=sun.misc.Launcher$AppClassLoader@417470d0
singleton : com.lg.design.singleton.hungry.Singleton@77e3cabd
singleton1 : com.lg.design.singleton.hungry.Singleton@c137bc9
singleton3 : com.lg.design.singleton.hungry.Singleton@5323cf50
singleton1==singleton3 : false

咱们慢慢来看这些信息。
1 Singleton.class与singletonClass
前者是系统类加载器加载器的,后者是我们自定义的类加载器加载的,虽然他们的字节码相同,但由不同的类加载器加载后就是不同的类了,所以两者的==和eaquals都为false。
2 constructor1、constructor2、constructor3
constructor1、constructor2都是通过调用Singleton.class.getDeclaredConstructor()得来的,但是两者并不是同一个对象,他们的==为false,equals为true。看getDeclaredConstructor源码就可以理解:

private Constructor<T> getConstructor0(Class<?>[] parameterTypes,
                                        int which) throws NoSuchMethodException
    {
        Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
        for (Constructor<T> constructor : constructors) {
            if (arrayContentsEq(parameterTypes,
                                constructor.getParameterTypes())) {
                //这里在获取构造器的时候就是用的复制
                return getReflectionFactory().copyConstructor(constructor);
            }
        }
        throw new NoSuchMethodException(getName() + ".<init>" + argumentTypesToString(parameterTypes));
    }

再看构造器的eequals方法
public boolean equals(Object obj) {
        if (obj != null && obj instanceof Constructor) {
            Constructor<?> other = (Constructor<?>)obj;
            if (getDeclaringClass() == other.getDeclaringClass()) {
                /* Avoid unnecessary cloning */
                Class<?>[] params1 = parameterTypes;
                Class<?>[] params2 = other.parameterTypes;
                if (params1.length == params2.length) {
                    for (int i = 0; i < params1.length; i++) {
                        if (params1[i] != params2[i])
                            return false;
                    }
                    return true;
                }
            }
        }
        return false;
    }

先通过比较是否是同一个类的构造器,然后再比较他们的参数是否一致,所以constructor1和constructor2的equals方法为true。对于constructor3和constructor1、constructor2,他们所属的类就是不一样的,即getDeclaringClass() == other.getDeclaringClass()为false。

3 singleton1和singleton3
singleton1是由constructor1构造器通过反射生成的对象,constructor3是通过constructor3构造器通过反射生成的对象,这些对象肯定都不是同一个对象。我有个疑问就是:通过constructor1.newInstance()会去执行Singleton的无参构造函数,打印出

执行构造函数
类加载器=sun.misc.Launcher$AppClassLoader@417470d0

然而执行constructor3.newInstance()却并没有打印出无参构造函数中的信息,这背后的原理希望你们能帮我解答。
有关类加载器的内容,请见后续文章

若想转载请注明出处:   http://lgbolgger.iteye.com/blog/2161094
作者:iteye的乒乓狂魔

© 著作权归作者所有

共有 人打赏支持
乒乓狂魔
粉丝 996
博文 105
码字总数 271356
作品 0
长宁
程序员
加载中

评论(4)

零度冰川

引用来自“乒乓狂魔”的评论

对于第一个问题,这两个对象本应该就是不一样的,一旦能用反射,就没有单例可言了,枚举但是可以阻止反射。对于第二个问题,就是在加载类的时候就会去执行static方法,执行的时候发现是new 本身,就去执行构造函数了。类加载完成之后,紧接着执行constructor3.newinstance方法,又会执行一次构造方法,所以会执行两次
哦,多谢回答
乒乓狂魔
乒乓狂魔

引用来自“零度冰川”的评论

博主,有两个问题:
1、我分别用上面说的constructor1和constructor2执行了newInstance(),返回obj1和obj2,然后比对了两个的结果发现new出来的实例不相等,我查看了了一下两个对象的hashcode发现不一样,按说这两个都是同一个系统类加载器加载出来的单例对象,应该equal才对,题主可以帮忙解答一下吗?
2、我使用constructor3.newInstance(),打印出两次无参构造函数里面的信息,我单步了一下,发现constructor3.newInstance()的时候先执行的是无参构造函数,然后再执行的private static final Singleton instance=new Singleton();这一点我有点理解不了,博主知道为什么吗?

见上面回复
乒乓狂魔
乒乓狂魔
对于第一个问题,这两个对象本应该就是不一样的,一旦能用反射,就没有单例可言了,枚举但是可以阻止反射。对于第二个问题,就是在加载类的时候就会去执行static方法,执行的时候发现是new 本身,就去执行构造函数了。类加载完成之后,紧接着执行constructor3.newinstance方法,又会执行一次构造方法,所以会执行两次
零度冰川
博主,有两个问题:
1、我分别用上面说的constructor1和constructor2执行了newInstance(),返回obj1和obj2,然后比对了两个的结果发现new出来的实例不相等,我查看了了一下两个对象的hashcode发现不一样,按说这两个都是同一个系统类加载器加载出来的单例对象,应该equal才对,题主可以帮忙解答一下吗?
2、我使用constructor3.newInstance(),打印出两次无参构造函数里面的信息,我单步了一下,发现constructor3.newInstance()的时候先执行的是无参构造函数,然后再执行的private static final Singleton instance=new Singleton();这一点我有点理解不了,博主知道为什么吗?
单例类在多个classLoader里怎么保持唯一性的问题

在看单例的帖子时看到一个问题: 如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如...

jakic
2013/03/21
1K
2
单例设计模式 (2)

1.静态内部类实现 在上一版的时候用的是懒汉和饿汉来做单例模式,如果我们采用静态内部类的话,就可以通过classloader来懒加载单例 用静态内部类实现单例模式: 这里有几个需要注意的点: 1....

蠢廿
2017/12/04
0
0
设计模式-单例模式

单例模式 单例模式特点 保证一个类只有一个实例,并且提供一个访问该实例的全局访问点 windows中的任务管理器 操作系统的文件系统,一个操作系统只能有一个文件系统 servlet编程中,每个ser...

wjk_snail
2016/03/16
36
0
你敢说自己了解单例模式?

一、背景   最近在学习设计模式,在看到单例模式的时候,我一开始以为直接很了解单例模式了,实现起来也很简单,但是实际上单例模式有着好几个变种,并且多线程中涉及到线程安全问题,那么...

hafiz.zhang
2017/09/27
0
0
Singleton 单例模式

动机 有些情况下,一个类只能有一个实例是很重要的。比如说,在操作系统中只能有一个窗口管理器的(文件系统或打印机程序)。通常, 单实例用于对内部或外部资源的集中式管理,同时它们提供一...

holysu
2017/07/06
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Mac OS X下Maven的安装与配置

Mac OS X 安装Maven: 下载 Maven, 并解压到某个目录。例如/Users/robbie/apache-maven-3.3.3 打开Terminal,输入以下命令,设置Maven classpath $ vi ~/.bash_profile 添加下列两行代码,之后...

TonyStarkSir
今天
3
0
关于编程,你的练习是不是有效的?

最近由于工作及Solution项目的影响,我在重新学习DDD和领域建模的一些知识。然后,我突然就想到了这个问题,以及我是怎么做的? 对于我来说,提升技能的项目会有四种: 纯兴趣驱动的项目。即...

问题终结者
今天
4
0
打开eclipse出现an error has occurred see the log file

解决方法: 1,打开eclipse安装目录下的eclipse.ini文件; 2,打开的文本文件最后添加一行 --add-modules=ALL-SYSTEM 3,保存重新打开Eclipse。...

任梁荣
昨天
4
0
搞定Northwind示例数据库,无论哪个版本的SQLServer都受用

Northwind数据库 从这里可以找到突破口: http://social.msdn.microsoft.com/Forums/zh-CN/Vsexpressvb/thread/8490a1c6-9018-40c9-aafb-df9f79d29cde 下面是MSDN: http://msdn2.microsoft......

QQZZFT
昨天
1
0
mysql主从同步,安装配置操作

准备 两台mysql服务,我这里准备了如下: 主库:192.168.176.128 从库:192.168.176.131 如何在Linux上安装mysql服务,请看https://blog.csdn.net/qq_18860653/article/details/80250499 操作...

小致dad
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部