如何編寫更好的POJO服務

在Java中,您可以輕松地在Plain Old Java Object(POJO)類中實現一些業務邏輯,并且可以在高級服務器或框架中輕松運行它們。 有許多服務器/框架,例如JBossAS,Spring或Camel等,它們使您可以部署POJO甚至不對其API進行硬編碼。 顯然,如果愿意耦合到它們的API細節,您將獲得高級功能,但是即使您這樣做,也可以通過將自己的POJO和他們的API封裝在包裝器中來使這些特性降至最低。 通過盡可能簡單的POJO編寫和設計自己的應用程序,您將擁有最靈活的方式來選擇框架或服務器來部署和運行應用程序。 在這些環境中編寫業務邏輯的一種有效方法是使用服務組件。 在本文中,我將分享在編寫Services方面學到的一些知識。

什么是服務?

今天,“ 服務 ”一詞已被過度使用,對不同的人可能意味著很多事情。 當我說Service時 ,我的定義是一個具有最小生命周期(例如initstartstopdestroy )的軟件組件。 在編寫的每個服務中,您可能不需要生命周期的所有這些階段,但是您可以忽略那些不適用的服務。 在編寫旨在長期運行的大型應用程序(例如服務器組件)時,定義這些生命周期并確保按正確的順序執行它們是至關重要的!

我將引導您完成我準備的Java演示項目。 這是非常基礎的,應該獨立運行。 它具有的唯一依賴性是SLF4J記錄器。 如果您不知道如何使用記錄器,則只需將它們替換為System.out.println 。 但是,我強烈建議您學習在應用程序開發期間如何有效使用記錄器。 另外,如果您想嘗試與Spring相關的演示,那么顯然您也將需要其jar。

編寫基本的POJO服務

您可以在界面中如下所示快速定義具有生命周期的服務合同。

package servicedemo;public interface Service {void init();void start();void stop();void destroy();boolean isInited();boolean isStarted();
}

開發人員可以自由地在Service實現中做他們想做的事情,但是您可能想給他們一個適配器類,這樣他們就不必在每個Service上重寫相同的基本邏輯。 我將提供這樣的抽象服務:

package servicedemo;import java.util.concurrent.atomic.*;
import org.slf4j.*;
public abstract class AbstractService implements Service {protected Logger logger = LoggerFactory.getLogger(getClass());protected AtomicBoolean started = new AtomicBoolean(false);protected AtomicBoolean inited = new AtomicBoolean(false);public void init() {if (!inited.get()) {initService();inited.set(true);logger.debug('{} initialized.', this);}}public void start() {// Init service if it has not done so.if (!inited.get()) {init();}// Start service now.if (!started.get()) {startService();started.set(true);logger.debug('{} started.', this);}}public void stop() {if (started.get()) {stopService();started.set(false);logger.debug('{} stopped.', this);}}public void destroy() {// Stop service if it is still running.if (started.get()) {stop();}// Destroy service now.if (inited.get()) {destroyService();inited.set(false);logger.debug('{} destroyed.', this);}}public boolean isStarted() {return started.get();}public boolean isInited() {return inited.get();}@Overridepublic String toString() {return getClass().getSimpleName() + '[id=' + System.identityHashCode(this) + ']';}protected void initService() {}protected void startService() {}protected void stopService() {}protected void destroyService() {}
}

這個抽象類提供大多數服務需求的基礎。 它有一個記錄器,并指出了生命周期。 然后,它委托新的生命周期方法集,以便子類可以選擇重寫。 注意, start()方法正在檢查是否自動調用init() 。 在destroy()方法和stop()方法中也是如此。 如果我們要在只有兩個階段生命周期調用的容器中使用它,則這一點很重要。 在這種情況下,我們可以簡單地調用start()destroy()來匹配我們服務的生命周期。

一些框架可能會再進一步,對于生命周期,如每個階段創建獨立的接口InitableServiceStartableService等,但我認為這將是太多了在一個典型的應用。 在大多數情況下,您想要簡單的東西,所以我只喜歡一個界面。 用戶可以選擇忽略不需要的方法,或僅使用適配器類。

在結束本節之前,我將提供一個愚蠢的Hello world服務,以后可以在我們的演示中使用它。

package servicedemo;public class HelloService extends AbstractService {public void initService() {logger.info(this + ' inited.');}public void startService() {logger.info(this + ' started.');}public void stopService() {logger.info(this + ' stopped.');}public void destroyService() {logger.info(this + ' destroyed.');}
}


使用容器管理多個POJO服務

現在我們已經定義了服務定義的基礎,您的開發團隊可能會開始編寫業務邏輯代碼! 不久之后,您將擁有自己的服務庫以供重新使用。 為了能夠有效地分組和控制這些服務,我們還希望提供一個容器來管理它們。 這個想法是,我們通常希望通過容器作為更高級別的組來控制和管理多個服務。 這是一個入門的簡單實現:

package servicedemo;import java.util.*;
public class ServiceContainer extends AbstractService {private List<Service> services = new ArrayList<Service>();public void setServices(List<Service> services) {this.services = services;}public void addService(Service service) {this.services.add(service);}public void initService() {logger.debug('Initializing ' + this + ' with ' + services.size() + ' services.');for (Service service : services) {logger.debug('Initializing ' + service);service.init();}logger.info(this + ' inited.');}public void startService() {logger.debug('Starting ' + this + ' with ' + services.size() + ' services.');for (Service service : services) {logger.debug('Starting ' + service);service.start();}logger.info(this + ' started.');}public void stopService() {int size = services.size();logger.debug('Stopping ' + this + ' with ' + size + ' services in reverse order.');for (int i = size - 1; i >= 0; i--) {Service service = services.get(i);logger.debug('Stopping ' + service);service.stop();}logger.info(this + ' stopped.');}public void destroyService() {int size = services.size();logger.debug('Destroying ' + this + ' with ' + size + ' services in reverse order.');for (int i = size - 1; i >= 0; i--) {Service service = services.get(i);logger.debug('Destroying ' + service);service.destroy();}logger.info(this + ' destroyed.');}
}

從上面的代碼中,您將注意到一些重要的事情:

  1. 我們擴展了AbstractService,因此容器本身就是服務。
  2. 在進入下一個服務之前,我們將調用所有服務的生命周期。 除非啟動所有其他服務,否則不會啟動任何服務。
  3. 對于大多數一般用例,我們應該以相反的順序停止和銷毀服務。

上面的容器實現很簡單,并且以同步方式運行。 這意味著,您啟動容器,然后所有服務將按照您添加它們的順序啟動。 停止應該相同,但順序相反。

我也希望您能夠看到有足夠的空間來改進此容器。 例如,您可以添加線程池以異步方式控制服務的執行。

運行POJO服務 通過一個簡單的運行程序運行服務。

以最簡單的形式,我們可以自己運行POJO服務,而無需任何高級服務器或框架。 Java程序是從靜態main方法開始的,因此我們肯定可以在其中調用initstart我們的服務。 但是,當用戶關閉程序時(通常通過按CTRL+C ),我們還需要解決stopdestroy生命周期的問題。為此,Java具有java.lang.Runtime#addShutdownHook()功能。 您可以創建一個簡單的獨立服務器來引導服務,如下所示:

package servicedemo;import org.slf4j.*;
public class ServiceRunner {private static Logger logger = LoggerFactory.getLogger(ServiceRunner.class);public static void main(String[] args) {ServiceRunner main = new ServiceRunner();main.run(args);}public void run(String[] args) {if (args.length < 1)throw new RuntimeException('Missing service class name as argument.');String serviceClassName = args[0];try {logger.debug('Creating ' + serviceClassName);Class<?> serviceClass = Class.forName(serviceClassName);if (!Service.class.isAssignableFrom(serviceClass)) {throw new RuntimeException('Service class ' + serviceClassName + ' did not implements ' + Service.class.getName());}Object serviceObject = serviceClass.newInstance();Service service = (Service)serviceObject;registerShutdownHook(service);logger.debug('Starting service ' + service);service.init();service.start();logger.info(service + ' started.');synchronized(this) {this.wait();}} catch (Exception e) {throw new RuntimeException('Failed to create and run ' + serviceClassName, e);}}private void registerShutdownHook(final Service service) {Runtime.getRuntime().addShutdownHook(new Thread() {public void run() {logger.debug('Stopping service ' + service);service.stop();service.destroy();logger.info(service + ' stopped.');}});}
}

使用優于跑步者,您應該可以使用以下命令運行它:

$ java demo.ServiceRunner servicedemo.HelloService

仔細查看,您會發現您可以使用上述運行器有很多選擇來運行多個服務。 讓我突出幾個:

  1. 直接改善上述運行器,并為每個新服務類名稱(而不是僅第一個元素)使用所有args
  2. 或編寫一個MultiLoaderService來加載所需的多個服務。 您可以使用“系統屬性”控制參數傳遞。

您能想到其他改進此跑步者的方法嗎?

使用Spring運行服務

Spring框架是一個IoC容器,眾所周知,它易于使用POJO,而Spring可讓您將應用程序連接在一起。 這將非常適合在我們的POJO服務中使用。 但是,由于Spring提供了所有功能,因此錯過了易于使用的現成的主程序來引導spring config xml上下文文件。 但是就目前為止構建的內容而言,這實際上是一件容易的事。 讓我們編寫一個POJO 服務來引導一個Spring上下文文件。

package servicedemo;import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;public class SpringService extends AbstractService {private ConfigurableApplicationContext springContext;public void startService() {String springConfig = System.getProperty('springContext', 'spring.xml);springContext = new FileSystemXmlApplicationContext(springConfig);logger.info(this + ' started.');}public void stopService() {springContext.close();logger.info(this + ' stopped.');}
}

使用該簡單的SpringService您可以運行和加載任何spring xml文件。 例如,嘗試以下操作:

$ java -DspringContext=config/service-demo-spring.xml demo.ServiceRunner servicedemo.SpringService

config/service-demo-spring.xml文件中,您可以輕松地創建我們的容器,該容器在Spring Bean中承載一項或多項服務。

<beans xmlns='http://www.springframework.org/schema/beans'xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'xsi:schemaLocation='http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd'><bean id='helloService' class='servicedemo.HelloService'></bean><bean id='serviceContainer' class='servicedemo.ServiceContainer' init-method='start' destroy-method='destroy'><property name='services'><list><ref bean='helloService'/></list></property></bean></beans>

注意,我只需要在serviceContainer bean上設置一次init-methoddestroy-method 。 然后,您可以根據需要添加一個或多個其他服務,例如helloService 。 關閉Spring上下文時,它們將全部啟動,管理,然后關閉。

請注意,Spring上下文容器沒有明確地具有與我們的服務相同的生命周期。 Spring上下文將自動實例化您的所有依賴項Bean,然后調用所有設置了init-method Bean。 所有這些操作都在FileSystemXmlApplicationContext的構造函數中完成。 用戶未調用任何顯式的init方法。 但是最后,在服務停止期間,Spring提供了container#close()來清理內容。 同樣,它們不區分stop destroy 。 因此,我們必須合并我們的initstart進入Spring的init狀態,然后合并stopdestroy進入Spring的close狀態。 回想一下我們的AbstractService#destory會自動調用stop如果尚未執行的話)。 因此,為了有效使用Spring,我們需要了解這一技巧。

使用JEE應用服務器運行服務

在公司環境中,我們通常沒有自由運行獨立程序所需的內容。 相反,它們通常已經具有一些基礎設施和更嚴格的標準技術堆棧,例如使用JEE應用程序服務器。 在這種情況下,運行POJO服務最可移植的是war Web應用程序。 在Servlet Web應用程序中,您可以編寫一個實現javax.servlet.ServletContextListener的類,這將通過contextInitializedcontextDestroyed為您提供生命周期掛鉤。 在其中,您可以實例化ServiceContainer對象,并相應地調用startdestroy方法。

您可以瀏覽以下示例:

package servicedemo;
import java.util.*;
import javax.servlet.*;
public class ServiceContainerListener implements ServletContextListener {private static Logger logger = LoggerFactory.getLogger(ServiceContainerListener.class);private ServiceContainer serviceContainer;public void contextInitialized(ServletContextEvent sce) {serviceContainer = new ServiceContainer();List<Service> services = createServices();serviceContainer.setServices(services);serviceContainer.start();logger.info(serviceContainer + ' started in web application.');}public void contextDestroyed(ServletContextEvent sce) {serviceContainer.destroy();logger.info(serviceContainer + ' destroyed in web application.');}private List<Service> createServices() {List<Service> result = new ArrayList<Service>();// populate services here.return result;}
}

您可以像上面這樣在WEB-INF/web.xml配置:

<listener><listener-class>servicedemo.ServiceContainerListener</listener-class></listener></web-app>

該演示提供了一個占位符,您必須在代碼中添加服務。 但是您可以使用web.xml作為上下文參數輕松地將其配置為可配置的。

如果要在Servlet容器中使用Spring,則可以直接使用其org.springframework.web.context.ContextLoaderListener類,該類與上述功能大致相同,不同之處在于它們允許您使用contextConfigLocation上下文參數指定其xml配置文件。 這就是典型的基于Spring MVC的應用程序的配置方式。 設置完成后,您可以像上面給出的Spring xml示例一樣嘗試我們的POJO服務以進行測試。 您應該在記錄器的輸出中看到我們的服務正在運行。

PS:實際上,我們在此描述的只是與Servlet Web應用程序相關,而不與JEE有關。 因此,您也可以使用Tomcat服務器。

服務生命周期的重要性及其在現實世界中的使用

我在這里提供的所有信息都不是新穎的,也不是殺手級的設計模式。 實際上,它們已在許多流行的開源項目中使用。 但是,根據我過去的工作經驗,人們總是設法使這些變得極為復雜,更糟糕的情況是,他們在編寫服務時完全無視生命周期的重要性。 的確,并非您要編寫的所有內容都需要安裝到服務中,但是,如果發現需要,請務必注意它們,并請務必小心它們的正確調用。 您想要的最后一件事是退出JVM,而不清理您為其分配了寶貴資源的服務。 如果您允許在部署過程中動態地重新加載應用程序而不退出JVM,這些操作將變得更加災難性,這將導致系統資源泄漏。

以上服務實踐已在TimeMachine項目中使用。 實際上,如果您查看timemachine.scheduler.service.SchedulerEngine ,它將只是許多運行在一起的服務的容器。 這就是用戶可以通過編寫Service來擴展調度程序功能的方式。 您可以通過一個簡單的屬性文件動態加載這些服務。

參考: 如何在A Programmer's Journal博客上從我們的JCG合作伙伴 Zemian Deng 編寫更好的POJO服務 。


翻譯自: https://www.javacodegeeks.com/2012/09/how-to-write-better-pojo-services.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/371397.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/371397.shtml
英文地址,請注明出處:http://en.pswp.cn/news/371397.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

mongo 唯一約束索引_快速掌握mongoDB(三)——mongoDB的索引詳解

1 mongoDB索引的管理本節介紹mongoDB中的索引&#xff0c;熟悉mysql/sqlserver等關系型數據庫的小伙伴應該都知道索引對優化數據查詢的重要性。我們先簡單了解一下索引&#xff1a;索引的本質就是一個排序的列表&#xff0c;在這個列表中存儲著索引的值和包含這個值的數據(數據…

HBase MapReduce

1. HBase to HBase Mapper 繼承 TableMapper&#xff0c;輸入為Rowkey和Result. public abstract class TableMapper<KEYOUT, VALUEOUT> extends Mapper<ImmutableBytesWritable, Result, KEYOUT, VALUEOUT> { public TableMapper() { }} package com.scb.ja…

第六期的知識點

1.volatile詳解 在應用程序中&#xff0c;volatile主要是被設計用來修飾被不同線程訪問和修改的變量 .volatile的本意是“易變的” 因為訪問寄存器要比訪問內存單元快的多,所以編譯器一般都會作減少存取內存的優化&#xff0c;但有可能會讀臟數據。當要求使用volatile聲明變量值…

在DelayQueue中更改延遲,從而更改順序

因此&#xff0c;我正在研究構建一個簡單的對象緩存&#xff0c;該緩存在給定時間后會使對象過期。 顯而易見的機制是使用Java并發包中的DelayedQueue類。 但我想知道是否有可能在將對象添加到隊列后更新延遲。 看一下Delayed接口&#xff0c;似乎沒有充分的理由不在文檔中&…

vi編輯器服務器維護,vi編輯器有哪幾種工作模式及如何轉換_網站服務器運行維護,vi編輯器,工作模式...

整理分享一些 Linux思維導圖(值得收藏)_網站服務器運行維護本篇文章整理分享了一些 Linux思維導圖(值得收藏)。有一定的參考價值&#xff0c;有需要的朋友可以參考一下&#xff0c;希望對大家有所幫助。vi編輯器有三種基本的工作模式&#xff0c;分別是&#xff1a;指令行模式、…

(八)cmockery中的calculator和run_tests函數的注釋代碼

所分析的calculator.c和calculator_test.c文件位于 工程中的 cmockery/src/example/ 目錄下&#xff0c;是一個相對而言比較全面的樣例程序&#xff0c;用到了cmockery項目中的大多數單元測試方法。基本上涵蓋了之前所有的樣例程序中的用法&#xff0c;還有兩組測試是database操…

家用雙wan口路由器推薦_請推薦雙WAN口的有線千兆硬路由器?

利益相關&#xff1a;TP-LINK一線銷售人員(來看看會不會有推薦我司產品的2333 )路由器&#xff1a;TL-ER3220G&#xff0c;帶機量300終端&#xff0c;可管理50個AP&#xff0c;最大支持四條寬帶接入POE交換機&#xff1a;TL-SF1005P(5口百兆) TL-SG1005P(5口千兆) TL-SF1009PH(…

第一章魔獸窗口

開始顯示第一個窗體 用戶直接點登陸的話就會提示用戶名不能為空密碼不能為空 沒有賬號的話只能先注冊&#xff0c;點擊藍色摁鈕進入下一個窗體 這里有判斷是否為空&#xff0c;注冊成功后利用窗體傳值&#xff0c;并且打開第一個窗口 把注冊的用戶名和密碼寫上去就可以的登陸到…

Apache Digester示例–輕松配置

解決問題–硬編碼&#xff0c;需要為您的應用程序創建自定義配置&#xff0c;例如struts配置文件&#xff0c;以僅通過更改文件來改變應用程序行為。 Apache Digester可以輕松為您完成此任務。 使用Apache Digester相當容易將XML文檔轉換為相應的Java bean對象層次結構。 請參閱…

騰訊云搭svn服務器,騰訊云使用筆記二: 安裝svn服務器及web同步

A01&#xff1a;安裝subversionsudo apt-get install subversionA02:創建倉庫很多目錄可以放subversion文件倉庫&#xff0c;最常見的是/usr/local/svn和/home/svnsudo mkdir -p /home/svn/youshengyousesudo svnadmin create /home/svn/youshengyouse//說明&#xff1a;先創建…

python將圖像轉換為8位單通道_使用Python將圖片轉換為單通道黑白圖片

本文介紹如何使用python將圖片轉換為純黑白的單通道圖片。文中用到的腳本支持彩色、灰度、帶alpha通道的輸入圖片以及SVG矢量圖&#xff0c;支持調整輸出圖片大小以及設置灰度閾值。最后介紹如何輸出SSD1306 OLED顯示屏可用的XBM文件&#xff0c;并利用輸出的XBM數據在0.96寸的…

Java FlameGraph 火焰圖

上周一個偶然的機會聽同事提到了Java FlameGraph&#xff0c;剛實驗了一下&#xff0c;效果非常好。 一、什么是FlameGraph 直接看圖說話。FlameGraph 是 SVG格式&#xff0c;矢量圖&#xff0c;可以隨意擴大縮小&#xff0c;看不清的信息可以放大看。圖中&#xff0c;各種紅橙…

ADB 常用命令

獲取Android設備號 adb shell getprop ro.serialno 獲取系統版本 adb shell getprop ro.build.version.release>4.2.2 獲取系統api版本 adb shell getprop ro.build.version.sdk>17 獲取設備分辨率&#xff08;SDK4.3&#xff09; adb shell wm size獲取設備屏幕密度&am…

哪個Java線程消耗了我的CPU?

當您的Java應用程序占用100&#xff05;的CPU時&#xff0c;您該怎么辦&#xff1f; 事實證明&#xff0c;您可以使用內置的UNIX和JDK工具輕松找到有問題的線程。 不需要探查器或代理。 為了進行測試&#xff0c;我們將使用以下簡單程序&#xff1a; public class Main {publi…

煙草局計算機筆試,2020年廣西南寧煙草局什么時候筆試?

最近廣西煙草局各地市社招通知頻發&#xff0c;南寧煙草局報名截止至今都無任何消息&#xff0c;根據往年的考情&#xff0c;通知近期很大可能會發布&#xff0c;將于6月底完成筆面!你備考好了嗎&#xff1f;今天廣西中公國企小編來給大家說一下南寧煙草局社招的筆試內容及備考…

JAVA Swing 組件演示***

下面是Swing組件的演示&#xff1a; package a_swing;import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.…

Spring 3.1緩存和@CacheEvict

我的上一個博客演示了Spring 3.1的Cacheable批注的應用&#xff0c; Cacheable批注用于標記返回值將存儲在緩存中的方法。 但是&#xff0c; Cacheable只是Spring的Guy為緩存而設計的一對注釋??中的一個&#xff0c;另一個是CacheEvict 。 像Cacheable一樣&#xff0c; Cache…

centos 獲取硬件序列號_如何在 Linux 上查找硬件規格

在 Linux 系統上有許多工具可用于查找硬件規格。-- Sk&#xff08;作者&#xff09;在 Linux 系統上有許多工具可用于查找硬件規格。在這里&#xff0c;我列出了四種最常用的工具&#xff0c;可以獲取 Linux 系統的幾乎所有硬件&#xff08;和軟件&#xff09;細節。好在是這些…

位置服務器管理器,查看 DIMM 位置

鍵入&#xff1a;-> show /System/Memory/DIMMs -t locationTarget | Property | Value-----------------------------------------------------------------------/System/Memory/DIMMs/ | location | CMIOU0/CM/CMP/BOB00/CH0/DIMM (CPU MemoryDIMM_0 | | IO Unit 0 Memor…

Spring –持久層–編寫實體并配置Hibernate

歡迎來到本教程的第二部分。 當您看到本文有多長時間時&#xff0c;請不要驚慌–我向您保證&#xff0c;這主要是簡單的POJO和一些生成的代碼。 在開始之前&#xff0c;我們需要更新我們的Maven依賴項&#xff0c;因為我們現在將使用Hibernate和Spring。 將以下依賴項添加到pom…