2016-05-15 24 views
21

Tôi đang cố gắng xác định cách tốt nhất để tạo một cá thể mới của một lớp dựa trên các lớp nào có sẵn trên classpath khi chạy.Tạo triển khai lớp Java động dựa trên các phụ thuộc được cung cấp trong thời gian chạy

Ví dụ: tôi có thư viện yêu cầu phản hồi JSON phải được phân tích cú pháp trong nhiều lớp. Thư viện có giao diện như sau:

JsonParser.java:

public interface JsonParser { 
    <T> T fromJson(String json, Class<T> type); 
    <T> String toJson(T object); 
} 

Lớp này có nhiều hiện thực, tức là GsonJsonParser, JacksonJsonParser, Jackson2JsonParser, và hiện nay, người sử dụng của thư viện là cần thiết để "nhặt" thực hiện của họ để được sử dụng dựa trên thư viện nào họ đã đưa vào dự án của họ. Ví dụ:

JsonParser parser = new GsonJsonParser(); 
SomeService service = new SomeService(parser); 

Điều tôi muốn làm là tự động nhận thư viện nào trên đường dẫn lớp và tạo cá thể thích hợp để người dùng thư viện không phải nghĩ về nó (hoặc thậm chí phải biết việc thực thi nội bộ của một lớp phân tích cú pháp JSON).

tôi đang xem xét một cái gì đó tương tự như sau:

try { 
    Class.forName("com.google.gson.Gson"); 
    return new GsonJsonParser(); 
} catch (ClassNotFoundException e) { 
    // Gson isn't on classpath, try next implementation 
} 

try { 
    Class.forName("com.fasterxml.jackson.databind.ObjectMapper"); 
    return new Jackson2JsonParser(); 
} catch (ClassNotFoundException e) { 
    // Jackson 2 was not found, try next implementation 
} 

// repeated for all implementations 

throw new IllegalStateException("You must include either Gson or Jackson on your classpath to utilize this library"); 

này sẽ là một giải pháp thích hợp? Nó có vẻ giống như một hack, cũng như sử dụng các ngoại lệ để kiểm soát dòng chảy.

Có cách nào tốt hơn để thực hiện việc này không?

+3

Tôi đồng ý, đó là * hack *. Chọn một phụ thuộc trình phân tích cú pháp JSON và sử dụng nó? Cách tiếp cận của bạn cũng * có thể * trở nên bất ngờ ** không xác định **; tùy thuộc vào các JAR (và có thể thứ tự của các JAR trong đường dẫn lớp) hành vi của chương trình có thể thay đổi. –

+0

@ElliottFrisch Tôi muốn làm điều đó, nhưng hiện tại các ứng dụng sẽ sử dụng nó có bất kỳ một trong những trình phân tích cú pháp đó, và tôi đang cố gắng tránh việc thứ hai trở thành phụ thuộc nếu nó khác với thứ tôi quyết định để gói (hoặc cùng một thư viện, phiên bản khác nhau). – Casey

+0

@ElliottFrisch Dường như nó không phải là hack như vậy, xem câu trả lời của tôi, nơi tôi đã cung cấp liên kết đến việc thực hiện cùng một mùa xuân của cùng một vấn đề. – Andremoniy

Trả lời

10

Thực chất bạn muốn tạo JsonParserFactory của riêng mình. Chúng ta có thể xem như thế nào nó thực hiện trong Spring Boot framework:

public static JsonParser getJsonParser() { 
    if (ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", null)) { 
     return new JacksonJsonParser(); 
    } 
    if (ClassUtils.isPresent("com.google.gson.Gson", null)) { 
     return new GsonJsonParser(); 
    } 
    if (ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) { 
     return new YamlJsonParser(); 
    } 

    return new BasicJsonParser(); 
} 

Vì vậy, cách tiếp cận của bạn là gần giống như thế này, ngoại trừ việc sử dụng các ClassUtils.isPresent method.

+2

Cảm ơn câu trả lời của bạn. Có vẻ như [đó chính là điều mà ClassUtils đang làm] (https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/util/ClassUtils. java # L325-L334). Vì vậy, có lẽ tôi đã không _completely_ off-base. – Casey

+0

@Casey bạn hoàn toàn đúng, tôi đã bỏ lỡ thực tế là 'ClassUtils' là một phần của Spring, vì vậy tôi đã chỉnh sửa câu trả lời của mình. – Andremoniy

5

Nếu chỉ một trong các triển khai (GsonJsonParser, JacksonJsonParser, Jackson2JsonParser) sẽ có trong thời gian chạy và không có tùy chọn nào khác, thì bạn phải sử dụng Class.forName().

Mặc dù bạn có thể xử lý thông minh hơn. Ví dụ, bạn có thể đặt tất cả các lớp vào một Set<String> và sau đó lặp lại chúng. Nếu bất kỳ một trong số họ ném ngoại lệ, bạn chỉ có thể tiếp tục, và một trong đó không, bạn có thể làm các hoạt động của bạn.

Vâng, đó là một bản hack và mã của bạn sẽ trở thành phụ thuộc vào thư viện. Nếu có thể có bất kỳ cơ hội nào mà bạn có thể bao gồm cả ba việc triển khai các JsonParsers của bạn trong classpath của bạn và sử dụng một logic để xác định việc thực hiện nào bạn phải sử dụng; đó sẽ là một cách tiếp cận tốt hơn nhiều.

Nếu điều này là không thể, bạn có thể tiếp tục ở trên.


Ngoài ra, thay vì sử dụng đơn giản Class.forName(String name), bạn có thể sử dụng một lựa chọn tốt hơn Class.forName(String name, boolean initialize, ClassLoader loader) đó sẽ không chạy bất kỳ initializers tĩnh (nếu có mặt tại lớp học của bạn).

đâu initialize = falseloader = [class].getClass().getClassLoader()

4

Cách tiếp cận đơn giản là SLF4J sử dụng: tạo thư viện trình bao bọc riêng biệt cho mỗi thư viện JSON cơ bản (GSON, Jackson, v.v.) với lớp com.mypackage.JsonParserImpl ủy nhiệm thư viện cơ bản. Đặt trình bao bọc thích hợp trong đường dẫn lớp cùng với thư viện cơ bản. Sau đó, bạn có thể thực hiện hiện tại như:

public JsonParser getJsonParser() { 
    // needs try block 
    // also, you probably want to cache 
    return Class.forName("com.mypackage.JsonParserImpl").newInstance() 
} 

Phương pháp này sử dụng trình tải lớp để định vị trình phân tích cú pháp JSON. Đây là đơn giản nhất và không yêu cầu phụ thuộc hoặc khung công tác của bên thứ ba. Tôi không thấy có nhược điểm nào liên quan đến Spring, Service Provider hoặc bất kỳ phương pháp định vị tài nguyên nào khác.


Sử dụng luân phiên API nhà cung cấp dịch vụ thay thế, như Daniel Pryden đề xuất. Để làm điều này, bạn vẫn tạo một thư viện trình bao bọc riêng biệt cho mỗi thư viện JSON cơ bản. Mỗi thư viện bao gồm một tệp văn bản tại vị trí "META-INF/services/com.mypackage.JsonParser" có nội dung là tên đủ điều kiện triển khai JsonParser trong thư viện đó. Sau đó, phương pháp getJsonParser của bạn sẽ trông như thế:

public JsonParser getJsonParser() { 
    return ServiceLoader.load(JsonParser.class).iterator().next(); 
} 

IMO phương pháp này là không cần thiết hơn phức tạp hơn trước.

+0

Giải pháp thú vị, chắc chắn là âm thanh như một lựa chọn tốt hơn và dễ bảo trì hơn so với cái tôi đã đề xuất. Tôi đã kiểm tra API của nhà cung cấp dịch vụ và dường như không phức tạp lắm, chỉ cần một chút phiền toái khi phải thêm tệp đó vào 'META-INF' của mỗi trình bao bọc. Tôi làm như vậy nó cho phép các lớp giữ một tên khác, tức là 'GsonJsonParser' và' JacksonJsonParser' thay vì 'JsonParserImpl', nhưng đó hoàn toàn là ngữ nghĩa mà tôi cho là ... – Casey

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