Dịch vụ là phần rất quan trọng của OSGi. Dịch vụ cho phép các bundle chia sẻ các đối tượng một cách linh hoạt, không cứng nhắc (loosely couple) [5]. Hình 2.15 mô tả một OSGi service đơn giản, nó được biểu diễn bởi một tam giác. Đỉnh của tam giác trỏ tới đối tượng cung cấp dịch vụ (service provider) chứ không phải đối tượng sử dụng (service consumer). Nguyên nhân là dịch vụ chỉ có thể có một nhà cung cấp (provider), nhưng nó có thể có nhiều đối tượng sử dụng (consumer).
Hình 2.15: Một dịch vụ được cung cấp bởi một bundle và được sử dụng bởi một bundle khác. Hình tam giác có đỉnh trỏ về bên cung cấp dịch vụ
Service Registry
Việc quản lý và tìm ra các dịch vụ được xử lý bởi một Service Registry đã được chia sẻ. Dịch vụ được đăng ký (register) và lấy ra sử dụng tên của giao diện và các tham số tùy chọn (Chúng ta sẽ xem xét các bộ lọc dịch vụ - service filter trong phần sau).
Các dịch vụ được đăng ký trên bundle context. Các dịch vụ chỉ có thể được cung cấp bởi các bundle đã khởi động. Khi một dịch vụ được đăng ký bởi một bundle, nó cung cấp một hoặc nhiều tên lớp để đánh dấu API của dịch vụ, và chính đối tượng dịch vụ (service object). Các thuộc tính tùy chọn có thể cung cấp thêm các thông tin mở rộng về dịch vụ, và chúng có thể được sử dụng để lọc ra các dịch vụ được trả về khi tìm kiếm dịch vụ. Các thuộc tính (property) của dịch vụ không nhằm mục đích cho chính dịch vụ đó sử dụng.
Việc đăng ký dịch vụ có thể được làm bởi hai dòng mã nguồn như sau: Hashtable<String, String> props = new
Hashtable<String, String>();
props.put("check.type", "slow");
ctx.registerService(SpecialOffer.class.getName(), props);
Bằng cách sử dụng tên của giao diện và các thuộc tính có dạng đơn giản, OSGi Service Registry cho phép các OSGi bundle ngăn ngừa những lo lắng về không gian lớp của chúng. Nếu hai phiên bản của một giao diện dịch vụ cùng tồn tại, thì nó chỉ có thể sử dụng một cài đặt mà ta đã khai báo cùng với giao diện đó. Nếu ta có gắng sử dụng một dịch vụ mà nằm trong không gian lớp khác với bundle của ta, thì sẽ tạo ra một ClassCastException. Điều này có vẻ kỳ cục, nhưng trong Java, một lớp không chỉ được nhận dạng bằng tên, mà còn bằng Classloader đã nạp nó.
Truy cập các dịch vụ
Trong quy ước của OSGi, các dịch vụ được tìm kiếm (look up) sử dụng bundle context. Nếu sử dụng bundle context thông thường ta sẽ phải làm như sau:
Ở đây ta chưa thêm vào phần xử lý lỗi. Sau khi thêm vào phần xử lý lỗi, dọn dẹp, tính toán đến tính động của OSGi thì mã nguồn để tìm kiếm một dịch vụ sẽ dài hơn nhiều. Hầu như mỗi dòng đều cần kiểm tra null. Đây là lý do tại sao ta thường sử dụng các công cụ Dependancy Injection để làm điều này như Dependancy Manager của Apache Felix ta sẽ dùng trong ứng dụng phần sau. Đó là một trong những cách tốt nhất để làm điều này.
Khi ta sử dụng phương pháp tìm kiếm chuẩn, OSGi framework đảm bảo rằng ta chỉ có thể tìm ra những dịch vụ mà đã chia sẻ các API của nó với không gian lớp của ta. Điều này đảm bảo rằng ta luôn có thể sử dụng được bất kỳ những dịch vụ mà ta tìm thấy bằng phương pháp đó. Mặc dù đôi khi nó cũng khiến ta thắc mắc khi mà tìm kiếm dường như đã bỏ qua một số dịch vụ tốt.
Sử dụng nhiều dịch vụ
Điều gì xảy ra khi nhiều provider đăng ký cùng một dịch vụ tương tự? Service consumer có một lựa chọn giữa một hoặc lấy ra một danh sách bao gồm những dịch vụ đó (hình 2.16)
String interfaceName = SpecialOffer.class.getName(); ServiceReference ref =
ctx.getServiceReference(interfaceName); SpecialOffer lister = (SpecialOffer) ctx.getService(ref);
Hình 2.16: Một service consumer có thể sử dụng nhiều instance của một dịch vụ Nếu dịch vụ là cái gì đó giống dịch vụ thẻ tín dụng thì nó chỉ cần sử dụng trong một lần thanh toán. Trong trường hợp này một service provider là đủ, và hẳn sẽ không gặp phải tình huống có quá nhiều provider được chọn. Trong trường hợp một dịch vụ log, message đã được log nên được gửi tới tất cả các logger có sẵn thay vì một như sau:
Vòng đời của service
Dịch vụ trong OSGi có tính động hơn nhiều như ta thường thấy. Chúng có thể xuất hiện và biến mất bất cứ lúc nào. Điều này cho phép các ứng dụng thú vị hơn, nhưng nó cũng tạo ra một số thử thách khi xử lý dịch vụ.
ServiceReference[] refs =
ctx.getServiceReferences(Logger.class.getName()); if (refs != null){
for (ServiceReference ref: refs) {
Logger logger = (Logger) ctx.getService(ref); logger.doSomeLogging();
} }
Nếu một bundle bị dừng, bất kỳ dịch vụ nào của bundle đã được đăng ký đều tự động unregister. Các consumer của các dịch vụ này cần có khả năng đối phó với khả năng những dịch vụ này không còn nữa. Ví dụ, một dịch vụ log có thể đang ghi tới một ổ đĩa từ xa mà ổ đĩa này bị mất kết nối. Thay vì dịch vụ vẫn có sẵn thì toàn bộ dịch vụ sẽ được unregister.
Điều gì xảy ra với những consumer của dịch vụ nếu bundle sở hữu dịch vụ bị dừng? Dịch vụ sẽ bị unregister. Nhưng các instance đã được tạo sẽ không biến mất. Nhưng chúng sẽ bị cũ. Hành vi của các dịch vụ là không xác định; chúng có thể vẫn tiếp tục làm việc tốt, hoặc cũng có thể tạo ra một ngoại lệ. Để ngăn ngừa việc để lại những dịch vụ cũ, sử dụng đối tượng ServiceTracker để xác định khi nào dịch vụ bị unregister là một phương pháp rất hay trong OSGi. Nhưng nó cũng thêm vào nhiều mã nguồn để sử dụng dịch vụ. Công nghệ OSGi enterprise của Declarative Service và Blueprint hay Denpendancy Manager của Apache Felix đều cung cấp cơ chế xử lý dịch vụ động một cách gọn gàng hơn.
Sử dụng ServiceTracker
Mặc dù ServiceReference là cách rõ ràng nhất để truy cập dịch vụ bằng lập trình, API ở mức khá thấp và tiềm tàng một số vấn đề. Do đó thậm chí những lập trình viên không sử dụng Declartive Services hoặc Blueprint cũng hướng tới một API tốt hơn ở mức cao hơn đó là ServiceTracker. Mã nguồn sau đây sử dụng để gọi ra một dịch vụ sử dụng ServiceTracker, nhưng đã bỏ đi phần xử lý lỗi:
String name = SpecialOffer.class.getName();
ServiceTracker<SpecialOffer, SpecialOffer> tracker; tracker = new ServiceTracker<SpecialOffer,
SpecialOffer>(ctx, name, null); tracker.open();
SpecialOffer offer =
(SpecialOffer)tracker.getService(); if (offer == null) {
throw new RuntimeException(
"TheSpecialOffer service is not available."); }
Như tên đã đề cập, ServiceTracker cho phép ta lần theo các dịch vụ khi chúng được register hoặc unregister, và thực hiện các hành động tương ứng. Nhưng chúng cũng đơn giản hóa việc truy cập dịch vụ thông thường. Sử dụng ServiceTracker thay vì Service Reference được xem như một cách tốt nhất.
ServiceTracker cũng cho phép ta truy cập dịch vụ theo cách nhẹ và không phụ thuộc vào thứ tự khởi động; bởi vì các bundle có thể khởi động trong bất cứ thứ tự nào trong một OSGi framework, do đó các dịch vụ cũng có thể được đăng ký theo bất cớ thứ tự nào. Mã nguồn mong muốn các dịch vụ luôn luôn hiện diện và có thể làm việc ở mọi thời điểm, nhưng sau đó nó làm ta phát điên với những lỗi liên quan đến thời gian. Như hầu hết những thứ để làm việc với truy cập dịch vụ, Blueprint hay Denpendency Manager ngăn chặn những thứ phức tạp này. Nếu ta không sử dụng Blueprint, thì sẽ là một ý kiến hay nếu đợi một dịch vụ theo cách sau, hơn là gọi nó ra và chờ đợi nó hiện diện:
Filter
Filter được sử dụng rộng dãi trong OSGi. Đôi khi chúng được sử dụng trong bundle manifest, nhưng mục đích sử dụng chính của chúng là để tìm kiếm (look up) dịch vụ. Cú pháp của OSGi filter sẽ khá quen thuộc với những ai đã từng viết LDAP (Lightweight Directory Access Protocol) filter. Mỗi biểu thức filter có định dạng chung sau đây:
( Attribute Operator Value )
(Không nên có bất cứ khoảng trống nào giữa các thành phần trong filter). Attribute là tên của thuộc tính được chọn, Operator là một toán tử Boolean, và Value là giá trị được khớp. Các toán tử “=”, “~=”, “<=”, “>=”, và “!” được hỗ trợ. Các ký tự “\”, “*”, “(” và “)” cần phải sử dụng chuỗi thoát nếu bao gồm trong giá trị.
Cho ví dụ,
(gift=cup) khớp thuộc tính gift với giá trị là cup.
Các cấu trúc phức tạp hơn cũng có thể được sử dụng như and (“&”) và or (“|”) đằng trước của biểu thức và bao quanh là dấu ngoặc đơn. Ví dụ,
(&(gift=cup) (colour=blue))
Sẽ khớp thuộc tính món quà là cái cốc và màu của nó là xanh. Chuỗi con có thể được khớp với toán tử *; ví dụ, (colour=b*)
SpecialOffer offer = (SpecialOffer) tracker.waitForService(TIMEOUT);
Sẽ khớp màu bắt đầu với chữ “b”-“blue”, hoặc cũng có thể là “brown”. Filter được tạo sử dụng BundleContext, như sau: