Karaf教程第9部分基於註解的blueprint和JPA
本部分演示如何用模型持久層和基於CDI註解的UI建立一個小的應用。
1 blueprint-maven-plugin
編寫blueprint xml檔案是很繁瑣的,太大的blueprint xml檔案很難與程式碼修改保持同步,尤其是程式碼重構。所以很多人喜歡使用註解來進行宣告。理想情況下,這些註解應該被標準化,這樣就很清晰地定義註解的功能。允許使用註解配置blueprint。它會掃描一個或多個路徑下的註解類,然後target/generated-resources建立blueprint.xml檔案。請參閱maven-blueprint-plugin文件。
2示例tasklist-blueprint-cdi
本部分演示如何用模型持久層和UI建立一個小的應用,而完全不用手動編寫blueprint xml檔案。
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-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檔案中,我們設定搜尋路徑為".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 classInitHelper { LoggerLOG= LoggerFactory.getLogger(InitHelper.class); @InjectTaskService taskService; @PostConstruct public voidaddDemoTasks() { try{ Task task =newTask(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"。這樣,通過就可以訪問到。
@Inject @OsgiService TaskService taskService這條語句會建立一個blueprint的引用元素,匯入TaskService介面定義的OSGI服務。然後,這個服務就被注入到上面這個類的taskService欄位。如果這個介面存在多個服務例項,那麼filter屬性可用於選擇使用哪一個。