本部分演示如何用模型持久层和基于CDI注解的UI创建一个小的应用。
1 blueprint-maven-plugin
编写blueprint xml文件是很繁琐的,太大的blueprint xml文件很难与代码修改保持同步,尤其是代码重构。所以很多人喜欢使用注解来进行声明。理想情况下,这些注解应该被标准化,这样就很清晰地定义注解的功能。maven-blueprint-plugin允许使用注解配置blueprint。它会扫描一个或多个路径下的注解类,然后target/generated-resources创建blueprint.xml文件。请参阅maven-blueprint-plugin文档。
2 示例tasklist-blueprint-cdi
本部分演示如何用模型持久层和UI创建一个小的应用,而完全不用手动编写blueprint xml文件。
Github源代码地址:Karaf-Tutorial/tasklist-cdi-blueprint
2.1 工程结构
- features
- model
- persistence
- ui
2.2 创建bundle
这些bundle都是使用maven-bundle-plugin创建的。这个创建只在父工程使用,它会用<_include>osgi.bnd</_include>抽取OSGi配置到单独的文件。所以每一个bundle工程只需要空的、可包含额外配置的osgi.bnd文件。
由于bnd会自动计算出大多部分配置,因此osgi.bnd文件一般都比较小。
2.3 Features
定义karaf feature,安装示例以及必要的依赖。
2.4 模型
Model工程定义了Task为一个jpa实体,定义了服务接口TaskService。由于model工程不需要任何依赖注入,所以不涉及到blueprint-maven-plugin。
Task JPA Entity
@Entity public class Task { @Id Integer id; String title; String description; Date dueDate; boolean finished; // Getters and setters omitted } |
TaskService (Task的CRUD操作)
public interface TaskService { Task getTask(Integer id); void addTask(Task task); void updateTask(Task task); void deleteTask(Integer id); Collection<Task> getTasks(); } |
persistence.xml
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistencehttp://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="tasklist" transaction-type="JTA"> <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> <jta-data-source>osgi:service/tasklist</jta-data-source> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/> <property name="hibernate.hbm2ddl.auto" value="create-drop"/> </properties> </persistence-unit>
</persistence> |
Persistence.xml文件定义了持久单元的名字为"tasklist",使用JTA事务。jta-data-source元素指向一个DataSource服务"tasklist"的jndi名字。所以除了JTA DataSource名字,它是一个标准的hibernate 4.3风格的持久化定义,自动创建schema。
另一个比较重要的事情是maven-bundle-plugin插件的配置。
配置maven bundle plugin
<Meta-Persistence>META-INF/persistence.xml</Meta-Persistence> <Import-Package>*, org.hibernate.proxy, javassist.util.proxy</Import-Package> |
Meta-Persistence指向一个persistence.xml文件, 它用于触发aries jpa创建这个Bundle的EntityManagerFactory。Import-Package配置导入了两个package,hibernate在运行时优化处理时需要。由于在编译时,这个优化是未知的,所以需要给maven-bundle-plugin一些提示。
2.5 持久化
tasklist-cdi-persistence bundle是这个示例当中第一个使用blueprint-maven-plugin的模块。在pom文件中,我们设置搜索路径为"net.lr.tasklist.persistence.impl"。所以所有在这个包或者子包中的类都会被扫描。
在pom文件中,我们需要一个maven-bundle-plugin的特殊配置:
<Import-Package>!javax.transaction, *, javax.transaction;version="[1.1,2)"</Import-Package>
在依赖中,我们使用事务API1.2作为包含@Transactional注解的第一个规范版本。在运行时,虽然我们不需要这个注解,且karaf只提供事务API的1.1版本。所以我们调整import使用karaf提供的版本。一旦事务API 1.2版本可用,这一行就不再需要了。
TaskServiceImpl
@OsgiServiceProvider(classes = {TaskService.class}) @Singleton @Transactional public class TaskServiceImpl implements TaskService { @PersistenceContext(unitName="tasklist") EntityManager em; @Override public Task getTask(Integer id) { return em.find(Task.class, id); } @Override public void addTask(Task task) { em.persist(task); em.flush(); } // Other methods omitted } |
TaskServiceImpl使用了很多注解。使用注解@Singleton标记这个类为blueprint bean。使用注解@OsgiServiceProvider标记这个类为接口TaskService的OSGi服务。使用注解@Transactional标记这个类支持事务操作。
所有的方法都在jta事务中执行。这意味着如果没有事务,那就会创建出来一个事务。如果已经有一个事务,那么方法就会参与其中。在事务的边界,事务要么被提交,要么因为异常而被回滚。
EntityManager用于持久单元"tasklist",被注入到字段em。它为每一个方法透明地提供一个EntityManager,按需创建,并在事务边界关闭。
InitHelper
@Singleton public class InitHelper { Logger LOG = LoggerFactory.getLogger(InitHelper.class); @Inject TaskService taskService; @PostConstruct public void 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); } } } |
InitHelper类不是严格必须的。它只是简单滴创建和持久化第一个任何,这样UI就可以显示一些东西了。
@Singleton注解标记这个类作为一个blueprint bean创建。
@Inject TaskService taskService注入在blueprint上下文中找到的第一个类型为TaskService的bean,并赋给字段taskService。
@PostConstruct注解确保在这个bean的所有字段都被注入后,调用addDemoTasks()方法。
另一件有趣的事情是模块中的测试类TaskServiceImplTest。它在OSGi之外运行,使用特殊的persistence.xml用于测试时创建EntityManagerFactory,而不需要jndi DataSource,因为这个很难提供。它也使用了RESOURCE_LOCAL事务,这样我们就不需要创建事务管理。测试注入了普通的EntityManger对象到TaskServiceImpl类中。所以我们必须手动开启事务和提交事务。这个例子说明了用普通的java测试JPA代码,测试简单快速。
2.6 Servlet UI
tasklist-ui模块使用OSGi service TaskService。Pax-web whiteboard bundle会检测导出的servlet,并使用HttpService发布它。在pom文件中,这个模块需要blueprint-maven-plugin有适当的scanPath。
TasklistServlet
@OsgiServiceProvider(classes={Servlet.class}) @Properties({@Property(name="alias", value="/tasklist")}) @Singleton public class TaskListServlet extends HttpServlet { @Inject @OsgiService TaskService taskService; protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // Actual code omitted } } |
TaskListServlet用接口javax.servlet.Servlet导出,服务属性别名为"/tasklist"。这样,通过http://localhost:8181/tasklist就可以访问到。
@Inject @OsgiService TaskService taskService这条语句会创建一个blueprint的引用元素,导入TaskService接口定义的OSGI服务。然后,这个服务就被注入到上面这个类的taskService字段。如果这个接口存在多个服务实例,那么filter属性可用于选择使用哪一个。