OSGi声明式服务引用能否使用原型组件的target过滤器
问题1:需求的实现方式
你的需求可以通过OSGi声明式服务的原型服务工厂(PrototypeServiceFactory)配合服务属性过滤实现,之前尝试失败是因为普通原型组件只能在注册时固定服务属性,无法动态根据引用方的target参数生成对应实例,也无需依赖ConfigAdmin的configurationPID,具体实现步骤如下:
- 实现并注册PrototypeServiceFactory,在获取服务实例时动态解析引用方的target过滤器参数:
import org.osgi.framework.*; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import java.util.Hashtable; @Component public class LoggerPrototypeFactory implements PrototypeServiceFactory<Logger> { @Activate public void activate(BundleContext context) { Hashtable<String, Object> props = new Hashtable<>(); // 注册Logger服务,不固定name属性 context.registerService(Logger.class, this, props); } @Override public Logger getService(Bundle bundle, ServiceRegistration<Logger> registration, PrototypeServiceObjects<Logger> objects) { String name = null; // 解析target过滤器中的name参数 String filter = (String) registration.getReference().getProperty("service.filter"); if (filter != null) { try { Filter f = FrameworkUtil.createFilter(filter); name = (String) f.getProperties().get("name"); } catch (InvalidSyntaxException ignored) {} } if (name == null) { throw new IllegalArgumentException("Logger引用需指定name属性"); } // 返回自定义Logger实现 return new CustomLogger(name); } @Override public void ungetService(Bundle bundle, ServiceRegistration<Logger> registration, PrototypeServiceObjects<Logger> objects, Logger service) { // 按需实现Logger资源清理逻辑 } }
- 引用方即可直接通过target过滤器获取对应名称的Logger,和你预期的写法完全一致:
@Component public class User { @Reference(target = "(name=user)") private Logger logger; }
问题2:替代方案
如果不想自己实现PrototypeServiceFactory,有两种成熟的替代方案:
- 方案1:使用OSGi R7及以上版本内置的日志注入注解,不需要自定义工厂逻辑,注解默认使用组件名称作为Logger名称,也可手动指定名称:
import org.osgi.service.log.Logger; import org.osgi.service.log.annotations.LogServiceLogger; @Component public class User { // 手动指定名称 @LogServiceLogger(name = "user") private Logger logger; // 不指定名称则默认用组件名作为Logger名 // @LogServiceLogger // private Logger defaultLogger; }
- 方案2:保留原有LoggerFactory,在激活方法中自动获取组件名作为Logger名称,不需要手动传入字符串:
@Component public class User { private Logger logger; @Activate public void activate(@Reference LoggerFactory factory, ComponentContext ctx) { this.logger = factory.create(ctx.getComponentName()); } }
问题3:可选target过滤器的实现
可以实现,只需要调整PrototypeServiceFactory的getService逻辑,当没有指定target过滤器时自动读取引用方的组件名称作为默认Logger名即可:
@Override public Logger getService(Bundle bundle, ServiceRegistration<Logger> registration, PrototypeServiceObjects<Logger> objects) { String name = null; // 优先读取target过滤器中的name属性 String filter = (String) registration.getReference().getProperty("service.filter"); if (filter != null) { try { Filter f = FrameworkUtil.createFilter(filter); name = (String) f.getProperties().get("name"); } catch (InvalidSyntaxException ignored) {} } // 未指定name则用引用方的组件名称作为默认值 if (name == null) { name = (String) objects.getServiceReference().getProperty("component.name"); } return new CustomLogger(name); }
调整后两种引用写法都支持:
- 指定target:
@Reference(target = "(name=customName)") private Logger logger;获得名称为customName的Logger - 不指定target:
@Reference private Logger logger;自动获得和当前组件同名的Logger
内容的提问来源于stack exchange,提问作者F.L
相关产品推荐
相关产品推荐

