如何编写更好的POJO服务
什么是服務(wù)?
今天,“ 服務(wù) ”一詞已被過度使用,對不同的人可能意味著很多事情。 當(dāng)我說Service時 ,我的定義是一個具有最小生命周期(例如init , start , stop和destroy )的軟件組件。 在編寫的每個服務(wù)中,您可能不需要生命周期的所有這些階段,但是您可以忽略那些不適用的服務(wù)。 在編寫旨在長期運(yùn)行的大型應(yīng)用程序(例如服務(wù)器組件)時,定義這些生命周期并確保按正確的順序執(zhí)行它們是至關(guān)重要的!
我將引導(dǎo)您完成我準(zhǔn)備的Java演示項(xiàng)目。 這是非常基礎(chǔ)的,應(yīng)該獨(dú)立運(yùn)行。 它具有的唯一依賴性是SLF4J記錄器。 如果您不知道如何使用記錄器,則只需將它們替換為System.out.println 。 但是,我強(qiáng)烈建議您學(xué)習(xí)在應(yīng)用程序開發(fā)期間如何有效使用記錄器。 另外,如果您想嘗試與Spring相關(guān)的演示,那么顯然您也將需要其jar。
編寫基本的POJO服務(wù)
您可以在界面中如下所示快速定義具有生命周期的服務(wù)合同。
package servicedemo;public interface Service {void init();void start();void stop();void destroy();boolean isInited();boolean isStarted(); }開發(fā)人員可以自由地在Service實(shí)現(xiàn)中做他們想做的事情,但是您可能想給他們一個適配器類,這樣他們就不必在每個Service上重寫相同的基本邏輯。 我將提供這樣的抽象服務(wù):
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() {} }這個抽象類提供大多數(shù)服務(wù)需求的基礎(chǔ)。 它有一個記錄器,并指出了生命周期。 然后,它委托新的生命周期方法集,以便子類可以選擇重寫。 注意, start()方法正在檢查是否自動調(diào)用init() 。 在destroy()方法和stop()方法中也是如此。 如果我們要在只有兩個階段生命周期調(diào)用的容器中使用它,則這一點(diǎn)很重要。 在這種情況下,我們可以簡單地調(diào)用start()和destroy()來匹配我們服務(wù)的生命周期。
一些框架可能會再進(jìn)一步,對于生命周期,如每個階段創(chuàng)建獨(dú)立的接口InitableService或StartableService等,但我認(rèn)為這將是太多了在一個典型的應(yīng)用。 在大多數(shù)情況下,您想要簡單的東西,所以我只喜歡一個界面。 用戶可以選擇忽略不需要的方法,或僅使用適配器類。
在結(jié)束本節(jié)之前,我將提供一個愚蠢的Hello world服務(wù),以后可以在我們的演示中使用它。
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服務(wù)
現(xiàn)在我們已經(jīng)定義了服務(wù)定義的基礎(chǔ),您的開發(fā)團(tuán)隊(duì)可能會開始編寫業(yè)務(wù)邏輯代碼! 不久之后,您將擁有自己的服務(wù)庫以供重新使用。 為了能夠有效地分組和控制這些服務(wù),我們還希望提供一個容器來管理它們。 這個想法是,我們通常希望通過容器作為更高級別的組來控制和管理多個服務(wù)。 這是一個入門的簡單實(shí)現(xiàn):
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.');} }從上面的代碼中,您將注意到一些重要的事情:
上面的容器實(shí)現(xiàn)很簡單,并且以同步方式運(yùn)行。 這意味著,您啟動容器,然后所有服務(wù)將按照您添加它們的順序啟動。 停止應(yīng)該相同,但順序相反。
我也希望您能夠看到有足夠的空間來改進(jìn)此容器。 例如,您可以添加線程池以異步方式控制服務(wù)的執(zhí)行。
運(yùn)行POJO服務(wù) 通過一個簡單的運(yùn)行程序運(yùn)行服務(wù)。
以最簡單的形式,我們可以自己運(yùn)行POJO服務(wù),而無需任何高級服務(wù)器或框架。 Java程序是從靜態(tài)main方法開始的,因此我們肯定可以在其中調(diào)用init并start我們的服務(wù)。 但是,當(dāng)用戶關(guān)閉程序時(通常通過按CTRL+C ),我們還需要解決stop和destroy生命周期的問題。為此,Java具有java.lang.Runtime#addShutdownHook()功能。 您可以創(chuàng)建一個簡單的獨(dú)立服務(wù)器來引導(dǎo)服務(wù),如下所示:
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.');}});} }使用優(yōu)于跑步者,您應(yīng)該可以使用以下命令運(yùn)行它:
$ java demo.ServiceRunner servicedemo.HelloService仔細(xì)查看,您會發(fā)現(xiàn)您可以使用上述運(yùn)行器有很多選擇來運(yùn)行多個服務(wù)。 讓我突出幾個:
您能想到其他改進(jìn)此跑步者的方法嗎?
使用Spring運(yùn)行服務(wù)
Spring框架是一個IoC容器,眾所周知,它易于使用POJO,而Spring可讓您將應(yīng)用程序連接在一起。 這將非常適合在我們的POJO服務(wù)中使用。 但是,由于Spring提供了所有功能,因此錯過了易于使用的現(xiàn)成的主程序來引導(dǎo)spring config xml上下文文件。 但是就目前為止構(gòu)建的內(nèi)容而言,這實(shí)際上是一件容易的事。 讓我們編寫一個POJO 服務(wù)來引導(dǎo)一個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您可以運(yùn)行和加載任何spring xml文件。 例如,嘗試以下操作:
$ java -DspringContext=config/service-demo-spring.xml demo.ServiceRunner servicedemo.SpringService在config/service-demo-spring.xml文件中,您可以輕松地創(chuàng)建我們的容器,該容器在Spring Bean中承載一項(xiàng)或多項(xiàng)服務(wù)。
<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上設(shè)置一次init-method和destroy-method 。 然后,您可以根據(jù)需要添加一個或多個其他服務(wù),例如helloService 。 關(guān)閉Spring上下文時,它們將全部啟動,管理,然后關(guān)閉。
請注意,Spring上下文容器沒有明確地具有與我們的服務(wù)相同的生命周期。 Spring上下文將自動實(shí)例化您的所有依賴項(xiàng)Bean,然后調(diào)用所有設(shè)置了init-method Bean。 所有這些操作都在FileSystemXmlApplicationContext的構(gòu)造函數(shù)中完成。 用戶未調(diào)用任何顯式的init方法。 但是最后,在服務(wù)停止期間,Spring提供了container#close()來清理內(nèi)容。 同樣,它們不區(qū)分stop destroy 。 因此,我們必須合并我們的init并start進(jìn)入Spring的init狀態(tài),然后合并stop和destroy進(jìn)入Spring的close狀態(tài)。 回想一下我們的AbstractService#destory會自動調(diào)用stop如果尚未執(zhí)行的話)。 因此,為了有效使用Spring,我們需要了解這一技巧。
使用JEE應(yīng)用服務(wù)器運(yùn)行服務(wù)
在公司環(huán)境中,我們通常沒有自由運(yùn)行獨(dú)立程序所需的內(nèi)容。 相反,它們通常已經(jīng)具有一些基礎(chǔ)設(shè)施和更嚴(yán)格的標(biāo)準(zhǔn)技術(shù)堆棧,例如使用JEE應(yīng)用程序服務(wù)器。 在這種情況下,運(yùn)行POJO服務(wù)最可移植的是war Web應(yīng)用程序。 在Servlet Web應(yīng)用程序中,您可以編寫一個實(shí)現(xiàn)javax.servlet.ServletContextListener的類,這將通過contextInitialized和contextDestroyed為您提供生命周期掛鉤。 在其中,您可以實(shí)例化ServiceContainer對象,并相應(yīng)地調(diào)用start和destroy方法。
您可以瀏覽以下示例:
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>該演示提供了一個占位符,您必須在代碼中添加服務(wù)。 但是您可以使用web.xml作為上下文參數(shù)輕松地將其配置為可配置的。
如果要在Servlet容器中使用Spring,則可以直接使用其org.springframework.web.context.ContextLoaderListener類,該類與上述功能大致相同,不同之處在于它們允許您使用contextConfigLocation上下文參數(shù)指定其xml配置文件。 這就是典型的基于Spring MVC的應(yīng)用程序的配置方式。 設(shè)置完成后,您可以像上面給出的Spring xml示例一樣嘗試我們的POJO服務(wù)以進(jìn)行測試。 您應(yīng)該在記錄器的輸出中看到我們的服務(wù)正在運(yùn)行。
PS:實(shí)際上,我們在此描述的只是與Servlet Web應(yīng)用程序相關(guān),而不與JEE有關(guān)。 因此,您也可以使用Tomcat服務(wù)器。
服務(wù)生命周期的重要性及其在現(xiàn)實(shí)世界中的使用
我在這里提供的所有信息都不是新穎的,也不是殺手級的設(shè)計(jì)模式。 實(shí)際上,它們已在許多流行的開源項(xiàng)目中使用。 但是,根據(jù)我過去的工作經(jīng)驗(yàn),人們總是設(shè)法使這些變得極為復(fù)雜,更糟糕的情況是,他們在編寫服務(wù)時完全無視生命周期的重要性。 的確,并非您要編寫的所有內(nèi)容都需要安裝到服務(wù)中,但是,如果發(fā)現(xiàn)需要,請務(wù)必注意它們,并請務(wù)必小心它們的正確調(diào)用。 您想要的最后一件事是退出JVM,而不清理您為其分配了寶貴資源的服務(wù)。 如果您允許在部署過程中動態(tài)地重新加載應(yīng)用程序而不退出JVM,這些操作將變得更加災(zāi)難性,這將導(dǎo)致系統(tǒng)資源泄漏。
以上服務(wù)實(shí)踐已在TimeMachine項(xiàng)目中使用。 實(shí)際上,如果您查看timemachine.scheduler.service.SchedulerEngine ,它將只是許多運(yùn)行在一起的服務(wù)的容器。 這就是用戶可以通過編寫Service來擴(kuò)展調(diào)度程序功能的方式。 您可以通過一個簡單的屬性文件動態(tài)加載這些服務(wù)。
參考: 如何在A Programmer's Journal博客上從我們的JCG合作伙伴 Zemian Deng 編寫更好的POJO服務(wù) 。
翻譯自: https://www.javacodegeeks.com/2012/09/how-to-write-better-pojo-services.html
總結(jié)
以上是生活随笔為你收集整理的如何编写更好的POJO服务的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓平板杀毒软件哪个好用(安卓平板杀毒软
- 下一篇: 在DelayQueue中更改延迟,从而更