zoukankan      html  css  js  c++  java
  • java基础(三):反射、反序列化破解单列模式和解决方式

      单例模式指的是一个类只有一个对象,通过一些措施达到达到这个目的。但是反射和反序列化可以获得多个不同的对象。


      先简单的认识一下单例模式

    一:单例模式

      通过私有构造器,声明一个该类的静态对象成员,提供一个获得对象的静态方法实现单例模式。单列模式有饿汉式和懒汉式,饿汉式是声明的同时就为该对象赋值。

    懒汉式指的是使用到的时候再创建。虚拟机的实现会保证:类加载会确保类和对象的初始化方法在多线程场景下能够正确的同步加锁,即饿汉式声明并赋值是原子操作,不会存在同步问题。懒汉式为了应付同步问题出现了加锁,内部类延时加载等。

      单例模式博客地址:http://www.cnblogs.com/tfper/p/9890971.html

    二:单例模式存在的漏洞

      1.反射破解单例模式

        java的访问控制是停留在编译层的,也就是它不在在class文件中保留下任何痕迹,只在编译的时候进行访问控制的检查。通过反射的手段可以访问类中的成员,比如私有构造方法。

        我们先写一个简单的懒汉式的类:

    package cnblogs.bean;
    
    public class Singleton {
        private static Singleton sl; //懒汉式
        
        private Singleton() {}
        
        public static Singleton getInstance() {
            if(sl == null)
                sl = new Singleton();
            return sl;
        }
    }

    通过反射获取单例对象。

    package cnblogs.test;
    
    import java.lang.reflect.Constructor;
    
    import cnblogs.bean.Singleton;
    
    public class Test2 {
    
    	@SuppressWarnings("unchecked")
    	public static void main(String[] args) throws Exception {
    		//正常获得单例模式对象
    		Singleton s1 = Singleton.getInstance();
    		System.out.println(s1);
    		
    		//通过反射获得单例模式对象
    		Class<Singleton> cl = (Class<Singleton>) 
    				Class.forName("cnblogs.bean.Singleton");
    		Constructor<Singleton> constructor = cl.getDeclaredConstructor();
    		constructor.setAccessible(true);
    		Singleton s2 = constructor.newInstance();
    		
    		System.out.println(s2);
    		System.out.println("s1 == s2? " + (s1 == s2));
    	}
    
    }
    

    控制台输出:

      cnblogs.bean.Singleton@6d06d69c
      cnblogs.bean.Singleton@7852e922
       s1 == s2? false

    观察一下就可以发现两种方式获得的单例模式不一样,违反了单例模式。

    破解方式:

      我们观察反射获取单例的代码,发现它还是调用了私有的构造方法获取对象【声明为私有的构造方法就是为了不让类外直接new对象】。如果只让私有的构造器只能调用一次就可以避免反射。

    package cnblogs.bean;
    
    public class Singleton {
    	private static Singleton sl;
    	
    	
    	private Singleton() {
    		//如果sl不为空即这不是第一次调用该构造器
    		if(sl != null)
    			throw new RuntimeException();
    	}
    	
    	public static Singleton getInstance() {
    		if(sl == null) {
    			sl = new Singleton();
    		}
    		return sl;
    	}
    }
    

     再次调用上面的反射测试类看控制台输出:

    cnblogs.bean.Singleton@6d06d69c
    Exception in thread "main" java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
        at java.lang.reflect.Constructor.newInstance(Unknown Source)
        at cnblogs.test.Test2.main(Test2.java:20)
    Caused by: java.lang.RuntimeException
        at cnblogs.bean.Singleton.<init>(Singleton.java:10)
        ... 5 more

    三:反序列化获得单例模式对象

       单例类:

    package cnblogs.bean;
    
    import java.io.Serializable;
    /**
     * 序列化必须实现Serializable接口,否则序列化时会报错
     *
     */
    public class Singleton implements Serializable{
    	
    	private static final long serialVersionUID = 1L;
    
    	private static Singleton sl;
    	
    	
    	private Singleton() {
    		//如果sl不为空即这不是第一次调用该构造器
    		if(sl != null)
    			throw new RuntimeException();
    	}
    	
    	public static Singleton getInstance() {
    		if(sl == null) {
    			sl = new Singleton();
    		}
    		return sl;
    	}
    }
    

       测试类:

    package cnblogs.test;
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    
    
    import cnblogs.bean.Singleton;
    
    public class Test3 {
    
    	public static void main(String[] args) {
    		Singleton s1 = Singleton.getInstance();
    		System.out.println(s1);
    		//先序列化后反序列化
    		try {
    			ByteArrayOutputStream os = new ByteArrayOutputStream();
    			ObjectOutputStream oos = new ObjectOutputStream(os);
    			oos.writeObject(s1);
    			
    			InputStream is = new ByteArrayInputStream(os.toByteArray());
    			ObjectInputStream ois = new ObjectInputStream(is);
    			Singleton s2 = (Singleton) ois.readObject();
    			
    			System.out.println(s2);
    			System.out.println("s1 == s2? " + (s1 == s2));
    		} catch (IOException e) {
    			e.printStackTrace();
    		} catch (ClassNotFoundException e) {
    			e.printStackTrace();
    		}
    
    	}
    
    }
    

     控制台输出:

      cnblogs.bean.Singleton@6d06d69c
      cnblogs.bean.Singleton@42a57993
      
    s1 == s2? false

    解决方法:

      

    package cnblogs.bean;
    
    import java.io.Serializable;
    /**
     * 序列化必须实现Serializable接口,否则序列化时会报错
     *
     */
    public class Singleton implements Serializable{
    	
    	private static final long serialVersionUID = 1L;
    
    	private static Singleton sl;
    	
    	
    	private Singleton() {
    		//如果sl不为空即这不是第一次调用该构造器
    		if(sl != null)
    			throw new RuntimeException();
    	}
    	
    	public static Singleton getInstance() {
    		if(sl == null) {
    			sl = new Singleton();
    		}
    		return sl;
    	}
    	
    	/**
    	 * 反序列化时,如果定义了readResolve()则直接返回此方法指定的对象,而不需要在创建新的对象!
    	 * @return
    	 */
    	private Object readResolve() {  
    	    return getInstance();  
    	}  
    }
    

     反序列化时,如果定义了readResolve()则直接返回此方法指定的对象,而不需要在创建新的对象!

    运行效果:

      cnblogs.bean.Singleton@6d06d69c
      cnblogs.bean.Singleton@6d06d69c
      s1 == s2? true


     

  • 相关阅读:
    TDSCDMA手机(WM系统)信号的采集?
    vc2008 + libpq + postgresql 8.4 配置
    code complete 2阅读笔记(第二章)
    Python 学习笔记(一)语句,变量,函数
    CS通用模型设计,socket,tcp实现()
    VS2005,VS2008编辑器设置
    设计模式之个人理解单例模式
    请教:C#网络编程相关的知识,建立socket服务器时向客户端连接,就建立不了了?
    服务器开发
    年终总结
  • 原文地址:https://www.cnblogs.com/yangji0202/p/10573895.html
Copyright © 2011-2022 走看看