1. Lắng Nghe Sự Kiện Dịch Vụ (Service Event Listening)
Tương tự như việc lắng nghe vòng đời của Bundle, OSGi Framework cung cấp cơ chế phát ra các sự kiện khi một dịch vụ được đăng ký, hủy đăng ký hoặc cập nhật thuộc tính. Các thành phần đã đăng ký ServiceListener sẽ tiếp nhận và xử lý những thay đổi này.
1.1. Các loại sự kiện dịch vụ
| Tên sự kiện | Mô tả | Giá trị |
|---|---|---|
| REGISTERED | Dịch vụ được đăng ký thành công vào registry | 1 |
| MODIFIED | Thuộc tính của dịch vụ bị thay đổi | 2 |
| UNREGISTERING | Dịch vụ đang trong quá trình bị gỡ bỏ | 4 |
| MODIFIED_ENDMATCH | Thuộc tính thay đổi khiến dịch vụ không còn khớp với bộ lọc (filter) của listener | 8 |
API đăng ký và hủy lắng nghe:
bundleContext.addServiceListener(listener)bundleContext.addServiceListener(listener, filter)bundleContext.removeServiceListener(listener)
1.2. Ví dụ minh họa
Giả sử chúng ta có một module notification-client cần theo dõi trạng thái của dịch vụ sms-provider-viettel.
Code trong BundleActivator của notification-client:
@Override
public void start(BundleContext bundleCtx) throws Exception {
// Tìm kiếm dịch vụ SMS với nhà mạng Viettel
ServiceReference<?>[] references = bundleCtx.getServiceReferences(
SmsService.class.getName(), "(provider=Viettel)"
);
if (references != null) {
for (ServiceReference<?> ref : references) {
SmsService smsSvc = (SmsService) bundleCtx.getService(ref);
System.out.println("Found SMS Service: " + smsSvc);
}
}
// Đăng ký listener để theo dõi mọi thay đổi
bundleCtx.addServiceListener(event -> {
System.out.println("Service Event Triggered: " + event.getSource() + " | Type: " + event.getType());
});
}
Cập nhật thuộc tính dịch vụ trong sms-provider-viettel:
@Override
public void start(BundleContext bundleCtx) throws Exception {
Hashtable<String, Object> props = new Hashtable<>();
props.put("provider", "Viettel");
ServiceRegistration<SmsService> registration = bundleCtx.registerService(
SmsService.class, new SmsServiceImpl(), props
);
// Giả lập việc thay đổi thuộc tính sau 5 giây
new Thread(() -> {
try {
Thread.sleep(5000);
Hashtable<String, Object> newProps = new Hashtable<>();
newProps.put("provider", "Mobifone");
registration.setProperties(newProps);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
Nếu chúng ta áp dụng bộ lọc (provider=Viettel) khi đăng ký listener ở client, sự kiện MODIFIED_ENDMATCH sẽ được kích hoạt khi provider đổi sang Mobifone.
2. Theo Dõi Dịch Vụ (Service Tracker)
Khi consumer cần quản lý chặt chẽ vòng đời của service (như khởi tạo tài nguyên khi service xuất hiện, giải phóng khi service biến mất), ServiceTracker là công cụ tối ưu. Nó theo dõi ba trạng thái chính: thêm, sửa đổi và xóa.
public interface ServiceTrackerCustomizer<S, T> {
T addingService(ServiceReference<S> reference);
void modifiedService(ServiceReference<S> reference, T service);
void removedService(ServiceReference<S> reference, T service);
}
Các bước triển khai
Bước 1: Tạo lớp thực thi ServiceTrackerCustomizer.
Bước 2: Khởi tạo và mở Tracker trong Activator.
public class DatabaseActivator implements BundleActivator {
private ServiceTracker<DataSource, DataSource> dbTracker;
@Override
public void start(BundleContext context) throws Exception {
ServiceTrackerCustomizer<DataSource, DataSource> customizer = new DataSourceTrackerCustomizer(context);
dbTracker = new ServiceTracker<>(context, DataSource.class.getName(), customizer);
dbTracker.open(); // Bắt đầu theo dõi
}
@Override
public void stop(BundleContext context) throws Exception {
dbTracker.close(); // Dừng theo dõi và giải phóng tài nguyên
}
}
3. Service Hooks Trong OSGi
Service Hook cho phép các bundle can thiệp vào các hoạt động nội bộ của OSGi Framework liên quan đến dịch vụ. Có ba loại hook chính:
- EventListenerHook: Can thiệp trước khi sự kiện dịch vụ được gửi đến các listener.
- ListenerHook: Theo dõi việc thêm/bớt các service listener trong framework.
- FindHook: Lọc và ẩn các service reference khi consumer thực hiện tìm kiếm.
Ứng dụng của FindHook
FindHook rất hữu ích để implement cơ chế bảo mật hoặc phân quyền, ngăn không cho một số bundle nhìn thấy các dịch vụ nhạy cảm.
public class SecurityHookActivator implements BundleActivator {
private ServiceRegistration<FindHook> hookReg;
@Override
public void start(BundleContext ctx) throws Exception {
hookReg = ctx.registerService(FindHook.class, new VisibilityFindHook(), null);
}
@Override
public void stop(BundleContext ctx) throws Exception {
hookReg.unregister();
}
}
class VisibilityFindHook implements FindHook {
@Override
public void find(BundleContext context, String name, String filter, boolean allServices,
Collection<ServiceReference<?>> references) {
// Ẩn các dịch vụ có đánh dấu là internal đối với các bundle không phải system bundle
if (context.getBundle().getBundleId() != 0) {
references.removeIf(ref -> "internal".equals(ref.getProperty("visibility")));
}
}
}
Trong đoạn code trên, bất kỳ bundle nào (trừ system bundle) gọi getServiceReference sẽ không thể nhìn thấy các dịch vụ có thuộc tính visibility=internal.
4. Declarative Services (DS)
4.1. Hạn chế của cách tiếp cận truyền thống
Việc đăng ký và tiêu thụ dịch vụ thủ công qua BundleActivator và BundleContext tồn tại nhiều nhược điểm:
- Code rườm rà: Phải viết nhiều logic để xử lý các trường hợp service chưa sẵn sàng hoặc bị gỡ bỏ đột ngột.
- Ảnh hưởng hiệu suất khởi động: Phương thức
start()chạy đồng bộ, việc khởi tạo toàn bộ service ngay lúc đầu sẽ làm chậm thời gian boot của ứng dụng. - Tốn kém bộ nhớ: Các service object được instantiate ngay cả khi chưa có consumer nào sử dụng.
- Coupling chặt chẽ: Business logic bị phụ thuộc nặng nề vào OSGi API, khó tái sử dụng ở các môi trường khác.
4.2. Giải pháp Declarative Services
Declarative Services (DS) giải quyết các vấn đề trên bằng mô hình component hướng dịch vụ. DS sử dụng XML để mô tả component, hỗ trợ lazy-loading (khởi tạo lười) và tự động quản lý vòng đời, giúp giảm thiểu code boilerplate và tách biệt logic nghiệp vụ khỏi OSGi API.
4.3. Triển khai thực tế
Chúng ta sẽ xây dựng một CacheService và một DataProcessor tiêu thụ service đó.
Bước 1: Định nghĩa và triển khai CacheService
package com.example.cache;
public class RedisCacheManager implements CacheService {
@Override
public void store(String key, Object value) {
System.out.println("Storing data in Redis: " + key);
}
}
Bước 2: Cấu hình XML cho CacheService (component-cache.xml)
<?xml version="1.0" encoding="UTF-8"?>
<component name="cache.manager" immediate="false"
xmlns="http://www.osgi.org/xmlns/scr/v1.3.0">
<implementation class="com.example.cache.RedisCacheManager" />
<property name="cache.type" value="Redis" />
<service>
<provide interface="com.example.cache.CacheService" />
</service>
</component>
Bước 3: Khai báo trong MANIFEST.MF của bundle cache
Service-Component: OSGI-INF/component-cache.xml
Bước 4: Viết DataProcessor (Consumer)
package com.example.processor;
import com.example.cache.CacheService;
import java.util.Map;
public class DataProcessor {
// Phương thức bind được gọi khi CacheService sẵn sàng
public void attachCache(CacheService cache, Map<String, Object> props) {
System.out.println("Cache service bound: " + props.get("cache.type"));
cache.store("init_key", "system_started");
}
// Phương thức unbind được gọi khi CacheService bị gỡ
public void detachCache(CacheService cache, Map<String, Object> props) {
System.out.println("Cache service unbound.");
}
public void processData() {
// Logic xử lý dữ liệu
}
}
Bước 5: Cấu hình XML cho DataProcessor (component-processor.xml)
<?xml version="1.0" encoding="UTF-8"?>
<component name="data.processor" immediate="true"
xmlns="http://www.osgi.org/xmlns/scr/v1.3.0">
<implementation class="com.example.processor.DataProcessor" />
<reference interface="com.example.cache.CacheService"
bind="attachCache"
unbind="detachCache"
cardinality="1..1"
policy="dynamic" />
</component>
Bước 6: Khai báo trong MANIFEST.MF của bundle processor
Service-Component: OSGI-INF/component-processor.xml
Để chạy được mô hình Declarative Services, bundle org.apache.felix.scr (Service Component Runtime) bắt buộc phải được cài đặt và active trong OSGi container.