2011-10-27 26 views
23

Tôi có một singleton như thế này.java Singleton - ngăn chặn nhiều sự sáng tạo thông qua sự phản ánh

public class BookingFactory { 

    private final static BookingFactory instance; 

    static { 
     instance = new BookingFactory(); 
    } 

    public static BookingFactory getInstance() { 
     return instance; 
    } 

    private BookingFactory() { 
     System.out.println("Object is created."); 
    } 
} 

public class Test { 
    BookingFactory instance = BookingFactory.getInstance(); 
    instance = BookingFactory.getInstance(); 

    Class<?> clazz = Class.forName("com.test.BookingFactory"); 

    Constructor pvtConstructor = clazz.getDeclaredConstructors()[0]; 

    // Set its access control 
    pvtConstructor.setAccessible(true); 

    // Invoke Private Constructor 
    BookingFactory notSingleton = (BookingFactory) pvtConstructor.newInstance(null); 
} 

Khi tôi chạy điều này, tôi thấy nhiều hơn một bản in. Có cách nào để ngăn chặn singleton này được instantiated nhiều hơn một lần từ sự phản ánh này?

Cảm ơn.

+3

Câu hỏi thực sự là lý do tại sao bạn sẽ rất quan tâm đến việc ngăn chặn nó? Nếu một lập trình viên đang gặp phải vấn đề đó thì điều đó có nghĩa là họ có ý định đảo ngược kỹ sư singleton theo một cách nào đó để đưa ra một số trường hợp đặc biệt không được xem xét trong quá trình thiết kế. Không nói nó sẽ là ý tưởng tuyệt vời nhất, nhưng đồng thời, đây là tất cả các giả thuyết. –

+1

+1 Câu hỏi thực sự được trình bày tốt, đặc biệt là [SSCCE] (http://pscode.org/sscce.html) – Bohemian

Trả lời

13

Tận dụng tối khẳng định trong constructor:

private BookingFactory() { 
    if (instance != null) 
     throw new IllegalStateException("Only one instance may be created"); 
    System.out.println("Object is created."); 
} 
+0

Đây có phải là một chủ đề an toàn không? – Kaunteya

+1

@Kaunteya yes, khi được sử dụng với mã trong câu hỏi, nó là threadsafe, vì 'instance' được tạo ra trong một khối tĩnh nó thực hiện tại thời gian tải lớp (không lười biếng khởi tạo) và trình nạp lớp có an toàn thread riêng của nó được xây dựng trong. JVM đảm bảo rằng tất cả khởi tạo tĩnh sẽ hoàn thành trước khi lớp có thể được sử dụng, bao gồm các hàm tạo gọi. – Bohemian

16

Thử sử dụng enum. enums làm cho Singletons tốt.

public static enum BookingFactory { 
    INSTANCE; 
    public static BookingFactory getInstance() { 
     return INSTANCE; 
    } 
} 

Bạn không thể tạo enum thông qua phản ánh.

Phương pháp getInstance() là không cần thiết nhưng làm cho nó dễ dàng hơn để chạy thử nghiệm của bạn, ném ngoại lệ sau đây:

java.lang.IllegalArgumentException: Cannot reflectively create enum objects 
    at java.lang.reflect.Constructor.newInstance(Constructor.java:530) 
    at MultiSingletonTest.main(MultiSingletonTest.java:40) 

Oh nhìn, ai đó đã đưa ra câu trả lời enum. Vẫn đăng bài để có đầy đủ hơn.

+0

Tôi đã đi qua lại trên chỉ bỏ phiếu cho cloase như là một bản sao. Nó có lẽ nên được. –

+0

chúng có thể tự động gần như trùng lặp với bất kỳ câu hỏi Java nào có chứa từ singleton nhưng không phải là enum;) – laher

+0

enum không thể kế thừa. Đôi khi chúng tôi muốn một Singleton kế thừa từ một lớp khác, ví dụ java.util.Observable. – simpatico

1

Nếu singleton của bạn không thực sự lưu trữ trạng thái, thì lựa chọn tốt nhất của bạn là không sử dụng singleton. Thay vào đó, hãy triển khai nhà máy như một phương thức không có trạng thái tĩnh.

8

Phỏng theo Making the Java Singleton Reflection Proof when using Lazy Loading:

package server; 

import java.lang.reflect.ReflectPermission; 
import java.security.*; 


public class JavaSingleton { 

    private static JavaSingleton INSTANCE = null; 

    private JavaSingleton() { 
    ReflectPermission perm = new ReflectPermission("suppressAccessChecks", ""); 
    AccessController.checkPermission(perm); 
    } 


    synchronized public static final JavaSingleton getInstance() { 
    if (INSTANCE == null) { 
     AccessController.doPrivileged(new PrivilegedAction<Object>() { 
     public Object run() { 
      INSTANCE= new JavaSingleton(); 
      return null; 
     } 
     }); 
    } 
    return INSTANCE; 
    } 

Các nhà xây dựng có một kiểm tra xem nếu người gọi có quyền truy cập vào nó. Khi liên kết giải thích, một tệp chính sách cho phép chính lớp Singleton gọi hàm tạo sẽ cần được tạo.

Phương thức ném ngoại lệ của Bohemian không ngăn cản khách hàng gọi điện cho nhà xây dựng trước khi getInstance() được gọi. Mặc dù nó đảm bảo rằng chỉ có một cá thể được tạo ra, không có gì đảm bảo rằng điều này được thực hiện bởi phương thức getInstance() của lớp Singleton.

Kiểm tra kiểm soát truy cập sẽ ngăn chặn việc khởi tạo không mong muốn này.

1
import java.io.Serializable; 

public class Singleton implements Serializable,Cloneable{ 

private static final long serialVersionUID = 1L; 
private static Singleton singleton=null; 
//private static volatile Singleton singleton=null; 
private Singleton() { 
    if(singleton!=null){ 
     throw new RuntimeException("Its Singleton Class use getInstance method for object creation"); 
    } 
} 

public static Singleton getInstance(){ 
    return Holder.singleton; 

} 

/**** 
* good way for getting the instance. No need to worry about 
* BillPughSingleton 
*/ 
private static class Holder{ 
    private static final Singleton singleton=new Singleton(); 
} 

/*** 
/* 
* Use this code for preventing Singleton breakage in multi threading scenario and comment above getInstance method 
* As this is the efficient way 
* If we put synchronized at method level level then will impact performance and will executed every time when getInstance is called 
* But if once the instance is created then there is no need for synchronized. 
*/ 

/* public static Singleton getInstance(){ 
    if(singleton==null){ 
     synchronized (Singleton.class) { 
      if(singleton==null){ 
       singleton=new Singleton(); 
      } 
     } 

    } 
    return singleton; 

}*/ 

@Override 
public Object clone() throws CloneNotSupportedException{ 
    /*** 
    * We can place below check OR we can remove the exception thrown check and return singleton instead of super.clone() 
    * Use any one way 
    */ 
    if(singleton!=null){ 
     throw new RuntimeException("Its Singleton Class use getInstance method for object creation"); 
    } 
    return super.clone(); 
} 
/*** 
* 
* To Prevent breaking of singleton pattern by using serilization/de serilization 
*/ 
private Object readResolve(){ 
    System.out.println("Read Resolve executed"); 
    return singleton; 
} 
} 

** Kiểm tra singleton **

import java.io.FileInputStream; 
import java.io.FileNotFoundException; 
import java.io.FileOutputStream; 
import java.io.IOException; 
import java.io.ObjectInputStream; 
import java.io.ObjectOutputStream; 
import java.lang.reflect.Constructor; 
import java.lang.reflect.InvocationTargetException; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 

/*** 
* 
* Ways to prevent break Singleton 
*/ 
public class Main { 

private static ObjectInputStream inputStream; 

public static void main(String[] args) throws Exception { 
    Singleton orginalSingletonObject = Singleton.getInstance(); 

    /*** 
    * Singleton is broken by using Reflection 
    * We can prevent that by putting a check in private constructor of Singleton.java 
    * 
    */ 
    breakSingletonByReflection(orginalSingletonObject); 

    /*** 
    * By Serialization/De-Serialization break Singleton We need 
    * Serialization interface in a class needs to be serialized like 
    * Singleton.java 
    * 
    * To prevent breaking of singleton we can add readResolve method in Singleton.java 
    * readResolve is the method which returns the instance of the class when a serialized class is de serialized. 
    * So implement the readResolve method to return the same object. 
    * Hence prevent breaking of Singleton design pattern. 
    * Refer this link for more information on readResolve 
    * https://docs.oracle.com/javase/6/docs/platform/serialization/spec/input.html#5903 
    */ 
    breakSingletonByserialization(orginalSingletonObject); 

    /*** 
    * By Cloning break Singleton 
    * We need to implement Cloneable interface 
    * We can prevent that by putting a check in clone method of Singleton.java 
    */ 
    breakSingletonByCloning(orginalSingletonObject); 


    /*** 
    * Break Singleton By thread 
    * This scenario is related to multi-threading environment 
    * We can do this by putting double lock mechanism in Singleton.java and its good practice to use Volatile 
    * We can also prevent this scenario of breaking by creating object eagerly but its not good to create object eagerly 
    */ 

    breakSingletonByThreading(orginalSingletonObject); 
} 

private static void breakSingletonByThreading(Singleton orginalSingletonObject) { 

    ExecutorService executorService=Executors.newFixedThreadPool(2); 
    /** 
    * Run this code snippet after commenting the other code for better understanding 
    * Run it repeatly to create a condition when 2 threads enter the method getInstance() of Singleton class at a same time 
    * When 2 threads enter the getInstance method at same time they will get the singleton object as null (private static Singleton singleton in Singleton.java) 
    * Then they will create two different objects (have different hashcode) in this case singleton pattern will break. 
    */ 
    executorService.submit(Main::useSingleton); // JAVA 8 syntax it will get the singleton instance 
    executorService.submit(Main::useSingleton); 
    executorService.shutdown(); 
} 

public static void useSingleton(){ 
    Singleton singleton=Singleton.getInstance(); 
    printSingletonData("By Threading", singleton); 

} 




private static void breakSingletonByCloning(Singleton orginalSingletonObject) throws CloneNotSupportedException { 
    Singleton clonedSingletonObject=(Singleton) orginalSingletonObject.clone(); 
    printSingletonData("By Cloning", orginalSingletonObject, clonedSingletonObject); 
} 

private static void breakSingletonByReflection(Singleton orginalsingleton) 
     throws ClassNotFoundException, NoSuchMethodException, 
     InstantiationException, IllegalAccessException, 
     InvocationTargetException { 

    Class<?> singletonClass = Class.forName("SingletonTest.Singleton"); 
    @SuppressWarnings("unchecked") 
    Constructor<Singleton> constructor = (Constructor<Singleton>) singletonClass 
      .getDeclaredConstructor(); 
    constructor.setAccessible(true); 
    Singleton s = constructor.newInstance(); 
    printSingletonData("By Reflection", orginalsingleton, s); 
} 

private static void breakSingletonByserialization(Singleton orginalsingleton) 
     throws FileNotFoundException, IOException, ClassNotFoundException { 

    /** 
    * Serialization 
    */ 
    ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("E:\\Singleton.ser")); 
    outputStream.writeObject(orginalsingleton); 
    outputStream.close(); 

    /** 
    * DeSerialization 
    */ 
    inputStream = new ObjectInputStream(new FileInputStream("E:\\Singleton.ser")); 

    Singleton deserializeObject = (Singleton) inputStream.readObject(); 
    deserializeObject.hashCode(); 
    printSingletonData("By Serialization", orginalsingleton, deserializeObject); 


} 

public static void printSingletonData(String operationName, 
     Singleton orginalsingleton, Singleton reflectionSigletonObject) { 

    System.out.println("------------------------------------------"); 
    System.out.println("New Operation"); 
    System.out.println(operationName); 
    System.out.println("orginal Hashcode=" + orginalsingleton.hashCode()); 
    System.out.println("New Object hashcode=" 
      + reflectionSigletonObject.hashCode()); 
    Boolean value = orginalsingleton.hashCode() != reflectionSigletonObject.hashCode(); 
    System.out.println("These Object have different hascode. They are two different object Right = " 
        + value); 
    System.out.println("As these are different Object this means Singleton Pattern is broken"); 
} 


private static void printSingletonData(String operationName,Singleton singleton) { 


    System.out.println("------------------------------------------"); 
    System.out.println("New Operation"); 
    System.out.println(operationName); 
    System.out.println("Object hashcode=" + singleton.hashCode()); 
    //System.out.println("As these are different Object this means Singleton Pattern is broken"); 

} 

} 
Các vấn đề liên quan