如何编写更好的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.mzph.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…