我们有一个应用平台需要和某特殊服务器对接,实现对特殊服务器的一些操作以及状态信息的读取。但是该服务器由多个厂商完成,且每个厂商的实现方式不同,因此不能通过应用平台逐一实现每个厂商的接口调用。我们考虑在特殊服务器端部署一个通用操作接口服务,应用平台仅仅与该接口通信,而具体的命令下发则由该通用操作接口服务实施。此时,仅需在接口服务项目中实现不同厂商的命令实现即可。该接口服务的需求如下:
我们计划将后端接口服务设计为REST风格的微服务,因此采用Spring boot作为框架基础。同时考虑到前后解耦的需求,我们将项目拆分为前后端两个模块。如下所示:
nmi为项目名:network management interface
--> nmi (父项目)
|-->nmi-front(前端模块)
|-->nmi-core(后端模块)
如上所示,整体项目由MAVEN管理,将项目拆分后,nmi-core以jar包形式引入nmi-front中。我们在nmi-core中制定好接口interface,不同厂商的则可以在该模块模板的基础上开发各自对应的nmi-core.jar。这样在最终部署时即可通过替换nmi-core.jar实现对不同设备的定制。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>org.wsygroupId>
<artifactId>nmiartifactId>
<version>0.0.1-SNAPSHOTversion>
<packaging>pompackaging>
<name>nminame>
<description>network management interfacedescription>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.wsygroupId>
<artifactId>nmi-coreartifactId>
<type>jartype>
<scope>compilescope>
dependency>
dependencies>
dependencyManagement>
<modules>
<module>nmi-coremodule>
<module>nmi-frontmodule>
modules>
project>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.wsygroupId>
<artifactId>nmiartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<artifactId>nmi-frontartifactId>
<name>nmi-frontname>
<url>http://maven.apache.orgurl>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>1.3.3.RELEASEversion>
dependency>
<dependency>
<groupId>org.wsygroupId>
<artifactId>nmi-coreartifactId>
<version>0.0.1-SNAPSHOTversion>
<scope>systemscope>
<systemPath>${project.basedir}/lib/nmi-core.jarsystemPath>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.wsygroupId>
<artifactId>nmiartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<artifactId>nmi-coreartifactId>
<name>nmi-corename>
<url>http://maven.apache.orgurl>
<packaging>jarpackaging>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
<build>
<finalName>nmi-corefinalName>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
plugin>
plugins>
build>
project>
core仅需负责硬件命令的下发和设备认证,如有需要添加日志功能。因此包结构如下:
|>org.wsy.nmi.core
|--|>org.wsy.nmi.core.command
|--|--|>ICommand.java
|--|--|>ACommand.java
|--|>org.wsy.nmi.core.device
|--|--|>IDevice.java
|--|--|>ADevice.java
|--|>org.wsy.nmi.core.log
|--|--|>logger.java
其中ICommand.java
接口定义了所有设备应该实现的操作。IDevice.java
接口定义了设备认证相关的操作。
ACommand.java
以及 ADevice.java
为某厂商(如:A)的具体接口实现。
nmi-front 模块带有主要的Spring boot特性,有Application启动器,controller等模块。结构如下
|>org.wsy.nmi.front
|--|>org.wsy.nmi.front.controllers
|--|--|>RequestController.java
|--|>org.wsy.nmi.front.module
|--|--|>org.wsy.nmi.front.module.config
|--|--|--|>Config.java
|--|--|>org.wsy.nmi.front.module.factory
|--|--|--|>CommandServiceFactory.java
|--|--|--|>DeviceAuthFactory.java
|--|--|>org.wsy.nmi.front.module.task
|--|--|--|>InitTask.java
|--|--|>org.wsy.nmi.front.module.cache
|--|--|--|>GlobalCache.java
|--|>Application.java
@SpringBootApplication
//需要支持外部配置properties文件
@EnableConfigurationProperties({ DeviceConfig.class })
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
//读取指定项目
@ConfigurationProperties(prefix="device")
public class DeviceConfig {
private String mac;
private String infname;
private String deviceauth;
private String commandservice;
//....以下省略
附:其中的GlobalCache,DeviceAuthFactory,CommandServiceFactory的具体实现在后边标出。
//这部分代码主要负责项目启动完毕后的设备认证等工作
@Component
public class PostTask {
DeviceAuth auth = null;
@Autowired
DeviceConfig config;
@PostConstruct
public void init() {
System.out.println("init finished");
try {
//从properties文件读取具体实现的Class路径,在后边通过反射加载
String deviceAuthClass = config.getDeviceauth();
String commandService = config.getCommandservice();
//根据配置加载模块
System.out.println("\n******** registering module ********");
try {
//加载设备认证模块
auth = DeviceAuthFactory.getInstance().build(deviceAuthClass).getAuth();
GlobalCache.getInstance().setDeviceAuthState(auth.deviceAuth());
if (GlobalCache.getInstance().isDeviceAuthState()) {
//如果认证通过则继续加载命令执行模块
System.out.println("device auth success!");
System.out.println("loading command service ...");
GlobalCache.getInstance().setCommandService(
CommandServiceFactory.getInstance().build(commandService).getCommandService());
} else {
//如果认证过程失败则标记为认证失败
GlobalCache.getInstance().setDeviceAuthState(false);
System.out.println("device auth fail!");
}
} catch (Exception e) {
//如果设备认证模块加载失败则标记为认证失败
System.out.println("init fail, error:"+e);
GlobalCache.getInstance().setDeviceAuthState(false);
}
} catch (Exception e) {
System.out.println("auth err");
}
}
}
通过单例模式实现,仅需一次加载即可,DeviceAuthFactory的加载机制相同,这里就不赘述了。
public class CommandServiceFactory {
private volatile static CommandServiceFactory instance;
private String className;
private CommandService service;
public static CommandServiceFactory getInstance(){
if (instance== null) {
synchronized(CommandServiceFactory.class){
if(instance == null){
instance = new CommandServiceFactory();
}
}
}
return instance;
}
private CommandServiceFactory(){ }
//通过反射动态加载
public CommandServiceFactory build(String className) throws Exception{
this.className = className;
service = (CommandService) Class.forName(className).newInstance();
return this;
}
public CommandService getCommandService() {
return service;
}
public String getClassName(){
return className;
}
}
同样以单例形式实现,用于存储认证结果,设备信息等。
具体实现略。
假设nmi-core.jar中确实包含配置文件中的实现类,则顺利加载模块并执行认证过程。
以下为认证通过的启动过程截图:
可以看到模块顺利载入,并认证成功。
模块加载成功同时认证成功后,可以正常调用接口。其中返回的”this is a A command service”即为假设A设备厂商的自定义服务返回的串。
图上可以看出,已经顺利加载模块(XXDevice),但是设备认证失败了。
比如配置文件错误,厂商并未实现接口,则无法顺利载入模块,也会造成认证失败。
可以看出,模块并未顺利加载(class not found err),因此无法认证设备。
和上文的认证失败是相同的,因此不在列举。