Karaf教程第10部分 –声明式服务

Karaf教程第10部分 –声明服务

这个教程演示如何使用Aries JPA2.0的声明式服务。

你可以在github上找到完整的源代码github Karaf-Tutorial/tasklist-ds

声明式服务

声明式服务(DS)是对Blueprint的最大的扩展。它是OSGi轻量的服务注入框架。 DS允许你提供和消费OSGi服务,与配置一起工作。

DS的核心部分是使用xml文件定义scr组件和它们的依赖。 它们通常位于 OSGI-INF目录,在Manifest中使用"Service-Component"头声明组件描述文件的路径。幸运地是,没必要直接使用这个xml文件,因为DS已经支持注解了。这些是由maven-bundle-plugin处理的。唯一的前置条件是必须在maven-bundle-plugin插件的configuration instruction中使能:<_dsannotations>*</_dsannotations>

更多细节请参阅 http://www.aqute.biz/Bnd/Components

DSBlueprint比较

让我们比较一下DSblueprint。主要有几个不同点:

  1. Blueprint总是工作在一个完整的blueprint上下文上。所以当所有的强制服务代表都存在的时候,上下文才会启动。然后它会发布所有提供的服务。 因此,blueprint上下文自己不能依赖于任何它提供的服务。DS工作在组件上。一个组件就是一个提供服务的类,它依赖于其他的服务和配置。在DS中,你可以分开管理每一个组件,如启动和停止组件。 一个bundle有可能提供了两个组件,但是只有一个组件启动了,而另一个组件的依赖还没有。    
  2. blueprint相比,DS更好地支持OSGi动态服务。 让我们看一个例子:你有一个DSblueprint模块组件,提供了服务A,强依赖于服务BBlueprint会等待强制服务第一次启动。如果服务B启动失败了,那么服务A在超时后也会失败,并且不能恢复。一旦blueprint上下文启动了,它就会一直启动,即使强制的服务没有了。这被称为服务阻尼。目的就是避免blueprint上下文频繁重启。服务作为动态代理被注入到blueprint bean中。内部,代理处理服务的替换和不可用。这会引起的问题是调用一个不可用的服务会阻塞当前线程,知道超时或者抛出RuntimeException

另一个方面,在DS中组件的生命周期直接绑定到依赖的服务上。所以组件只会在所有的强制服务存在时才会激活,只要有一个强制服务消失了,那么组件就会失效。这样的优势在于注入到组件中的服务不必被代理,调用服务总是有效的。

  1. 每一个DS组件必须是一个服务。Blueprint可以有内部bean,只是用于互相连接内部类,这在DS中是不可能的。所以DS不是一个完全的依赖注入框架,从这个角度看DS缺少很多blueprint提供的特性。
  2. DS不支持扩展命名空间。Aries blueprint使用扩展的命名空间支持很多其他的Apache的工程。例如Aries jpaAries transactionsAries authzCXF Camel。所以在DS中使用这些技术有点困难。
  3. DS不支持拦截器。在blueprint中,扩展命名空间可以在bean前后引入和拦截。例如,用于事务处理的安全性。因此,DS不支持JPA,一般的用法都是托管都有拦截器。下面会说明jpa如何在DS中工作。

所以,如果DS是否适合你的工程,依赖于你需要的服务的动态性,依赖于是否DS可以集成到其他的工程。

JEEJPA

JPA规范基于JEE,它拥有非常特殊的线程和拦截器模型。在JEE中,你在EntityManager管理的容器中使用会话bean。为了操作JPA实体,它看起来是这样的:

@Stateless

class TaskServiceImpl implements TaskService {

 

  @PersistenceContext(unitName="tasklist")

  private EntityManager em;

 

  public Task getTask(Integer id) {

    return em.find(Task.class, id);

  }

}

JEE中,调用getTask方法默认参与或者启动一个事务。如果方法调用成功了,那么事务就会被提交。如果方法发生了异常,那么事务就会回滚。

这些调用会进入到TaskServiceImpl 实例池子中。每一个实例每次只能被一个线程使用。因此,EntityManager接口不是线程安全的!

所以这个模型的优势是它看来其很简单,且允许相当少的代码。另一个方面,在容器外面,测试这样的代码有点困难,所以你必须模拟容器的行为来测试这个类。它也很难访问,例如访问em,因为它是私有的并且没有set方法。

Blueprint支持类似于JEE的代码风格,可以使用特殊的jpatx命名空间、处理事务、em管理的拦截器实现。

DSJPA

DS中,每一个组件都是单例的。所有只有唯一一个组件实例需要处理多线程访问。所以对于JPA与普通的JEE概念工作在DS中是不可能的。

当前,可能会注入EntityManagerFactory,处理EntityManager声明周期和事务,但是这将导致相当冗长且错误百出的代码。

Aries JPA 2.0.0为框架(如不提供拦截器的DS)提供特殊支持的第一个版本。这里的解决方案是JPATemplate的概念, 同时支持Java 8中的闭包。要想知道代码长什么样,请看看persistence 的章节。

不是注入EntityManager,我们注入了一个线程安全的JpaTemplate到代码中。我们需要将jpa代码放在闭包中,用jpa.txEpr()jpa.tx()方法运行它。 JPATemplate会保证在闭包中具有像JEE一样的环境。由于每一个闭包都在自己的实例中运行,每一个线程都有一个em。这个代码也会分享或创建事务,事务也会提交或回滚,就像JEE一样。

所以,这个需要更多的代码,但优势是你不需要集成一个特殊的框架。代码有容易测试。参见下面的TaskServiceImplTest

工程结构

  • features
  • model
  • persistence
  • ui

Features

定义karaf feature,安装示例和所有的必要的依赖。

Model

这个模块定义了Task JPA实体,TaskService接口和persistence.xmlmodel的详细描述请参见tasklist-blueprint示例。模型与这里的完全一样。

Persistence

TaskServiceImpl

@Component

publicclass TaskServiceImpl implements TaskService {

 

    private JpaTemplate jpa;

 

    public Task getTask(Integer id) {

        return jpa.txExpr(em -> em.find(Task.class, id));

    }

 

    @Reference(target = "(osgi.unit.name=tasklist)")

    publicvoid setJpa(JpaTemplate jpa) {

        this.jpa = jpa;

    }

}

我们定义了需要一个OSGi服务TaskService和具有属性值“tasklist”的属性“osgi.unit.name”。

InitHelper

@Component

publicclass InitHelper {

    Logger LOG = LoggerFactory.getLogger(InitHelper.class);

    TaskService taskService;

     

    @Activate

    publicvoid addDemoTasks() {

        try {

            Task task = new Task(1, "Just a sample task", "Some more info");

            taskService.addTask(task);

        } catch (Exception e) {

            LOG.warn(e.getMessage(), e);

        }

    }

    @Reference

    publicvoid setTaskService(TaskService taskService) {

        this.taskService = taskService;

    }

}

InitHelper类创建和持久化第一个task,所以UI有东西可以显示。这个例子也说明了业务代码如何使用TaskService

@Reference TaskService taskService注入TaskService到字段field taskService@Activate确保addDemoTasks()方法在注入这个组件后被调用

另一个有趣的地方是测试类 TaskServiceImplTest。它运行在OSGi的外面,使用特殊的persistence.xml来为测试创建EntityManagerFactory。它也演示了如何实例化ResourceLocalJpaTemplate,来避免为测试安装JTA事务管理。测试代码演示了TaskServiceImpl确实可以被用于普通Java代码,而不需要任何特殊的技巧。

UI

tasklist-ui模块用TaskService作为OSGi服务,将一个Servlet作为OSGi service发布。Pax-web whiteboard bundle会获得这个导出的Servlet,并用HttpService服务发布它,所以可以通过http访问它。

TaskListServlet

@Component(immediate = true, service = { Servlet.class },

property = { "alias:String=/tasklist" })

publicclass TaskListServlet extends HttpServlet {

    private TaskService taskService;

    protectedvoid doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,

        IOException {

        // Actual code omitted

    }

 

    @Reference

    publicvoid setTaskService(TaskService taskService) {

        this.taskService = taskService;

    }

}

上面的代码片段演示了当到处服务时,如何指定使用哪个接口及定义服务属性。

TaskListServlet用接口javax.servlet.Servlet和服务属性alias="/tasklist"导出。所以可以通过 http://localhost:8181/tasklist访问。

构建

确保使用JDK8,运行mvn clean install

安装

确保使用JDK 8,下载并解压Karaf 4.0.0,启动karaf,执行下面的命令。

创建DataSource配置,安装示例工程

cat https://raw.githubusercontent.com/cschneider/Karaf-Tutorial/master/tasklist-blueprint-cdi/org.ops4j.datasource-tasklist.cfg | tac -f etc/org.ops4j.datasource-tasklist.cfg

feature:repo-add mvn:net.lr.tasklist.ds/tasklist/1.0.0-SNAPSHOT/xml/features

feature:install example-tasklist-ds-persistence example-tasklist-ds-ui

验证安装

首先,我们要未持久化单元检查JpaTemplate服务已经存在。

 

service:list JpaTemplate

 

[org.apache.aries.jpa.template.JpaTemplate]

-------------------------------------------

 osgi.unit.name = tasklist

 transaction.type = JTA

 service.id = 164

 service.bundleid = 57

 service.scope = singleton

Provided by :

 tasklist-model (57)

Used by:

 tasklist-persistence (58)

 

Aries JPA应该已经从我们的model bundle创建了这个服务。如果这个服务没有工作,那么检查来自Aries JPA的日志消息。它应该打印它尝试的东西和等待的东西。你也可以检查EntityManagerFactory的存在和JpaTemplate 使用的EmSupplier服务是否存在。

可能的问题是丢失了DataSource,所以让我们检查一下它:

 

service:list DataSource

 

[javax.sql.DataSource]

----------------------

 dataSourceName = tasklist

 felix.fileinstall.filename = file:/home/cschneider/java/apache-karaf-4.0.0/etc/org.ops4j.datasource-tasklist.cfg

 osgi.jdbc.driver.name = H2-pool-xa

 osgi.jndi.service.name = tasklist

 service.factoryPid = org.ops4j.datasource

 service.pid = org.ops4j.datasource.cdc87e75-f024-4b8c-a318-687ff83257cf

 url = jdbc:h2:mem:test

 service.id = 156

 service.bundleid = 113

 service.scope = singleton

Provided by :

 OPS4J Pax JDBC Config (113)

Used by:

 Apache Aries JPA container (62)

 

这就是它应该的样子。Pax-jdbc-config根据配置"etc/org.ops4j.datasource-tasklist.cfg"创建了DataSource 通过使用DataSourceFactory wit 属性"osgi.jdbc.driver.name=H2-pool-xa",所以 DataSource应该是成池了,完全准备好了XA事务。

下一步就是检查启动的DS组件:

 

scr:list

 

ID | State  | Component Name

--------------------------------------------------------------

1  | ACTIVE | net.lr.tasklist.persistence.impl.InitHelper

2  | ACTIVE | net.lr.tasklist.persistence.impl.TaskServiceImpl

3  | ACTIVE | net.lr.tasklist.ui.TaskListServlet

 

如果任何组件没有活动,你都可以像下面这样检查它:

 

scr:details net.lr.tasklist.persistence.impl.TaskServiceImpl

 

Component Details

  Name                : net.lr.tasklist.persistence.impl.TaskServiceImpl

  State               : ACTIVE

  Properties          :

    component.name=net.lr.tasklist.persistence.impl.TaskServiceImpl

    component.id=2

    Jpa.target=(osgi.unit.name=tasklist)

References

  Reference           : Jpa

    State             : satisfied

    Multiple          : single

    Optional          : mandatory

    Policy            : static

    Service Reference : Bound Service ID 164

测试

在浏览器中访问http://localhost:8181/tasklist

你可以看到一个task的列表

 http://localhost:8181/tasklist?add&taskId=2&title=Another Task

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值