您的位置:首页 > 其它

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

2017-10-08 09:21 1176 查看
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
DS和Blueprint比较
让我们比较一下DS和blueprint。主要有几个不同点:

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

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

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

所以,如果DS是否适合你的工程,依赖于你需要的服务的动态性,依赖于是否DS可以集成到其他的工程。
JEE和JPA
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的代码风格,可以使用特殊的jpa、tx命名空间、处理事务、em管理的拦截器实现。
DS和JPA
在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.xml。model的详细描述请参见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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: