JModuleLink使用文档

版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/jianggujin/article/details/81877684

JModuleLink是一个基于JAVA的模块化开发框架,它提供在运行时动态加载模块(一个或一组JAR包)、卸载模块的API,使开发者更加关注业务本身。

第一部分 JModuleLink简介

1.1 需求背景

  • 应用拆分的多或少都有问题。多则维护成本高,每次发布一堆应用。少则拆分成本高,无用功能很难下线。
  • 故障不隔离。当一个系统由多人同时参与开发时,修改A功能,可能会影响B功能,引发故障。
  • 多分支开发引发冲突。多分支开发完之后合并会产生冲突。
  • 牵一发动全身。一处核心代码的改动,或一个基础Jar的升级需要回归整个系统。
  • 升级和迁移成本高。中间件升级每个应用都有升级成本。

1.2 模块化开发的好处

  • 可插拔,一个应用由多个模块组成,应用里的模块可拆和合,模块可快速在多个系统中迁移和部署。
  • 模块化开发,模块之间互相隔离,实现故障隔离。
  • 一个模块一个分支,不会引发代码冲突。
  • 在模块中增加或修改功能,只会影响当前模块,不会影响整个应用。
  • 动态部署,在运行时把模块部署到应用中,快速修复故障,提高发布效率。
  • 多版本部署,可以在运行时同时部署某个模块的新旧版本,进行AB TEST。
  • 减少资源消耗,通过部署模块的方式减少应用数量和机器数量。

1.3 特性

1.3.1 隔离性

  • 类隔离:框架为每个模块的Class使用单独的ClassLoader来加载,每个模块可以依赖同一种框架的不同的版本。
  • 实例隔离:框架为每个模块创建了一个独立的Spring上下文,来加载模块中的BEAN,实例化失败不会影响其他模块(Spring环境)。

1.3.2 动态性

  • 动态发布:模块能在运行时动态加载到系统中,实现不需要重启和发布系统新增功能。支持突破双亲委派机制,在运行时加载父加载器已经加载过的类,实现模块升级依赖包不需要系统发布。
  • 动态卸载:模块能在运行时被动态卸载干净,实现快速下线不需要功能。

1.3.3 易用性

  • 提供了通用灵活的API让系统和模块进行交互。

第二部分 开始使用

可以从码云获取代码。

Maven:

<!-- https://mvnrepository.com/artifact/com.jianggujin/JModuleLink -->
<dependency>
    <groupId>com.jianggujin</groupId>
    <artifactId>JModuleLink</artifactId>
    <version>最新版本</version>
</dependency>
graph LR
A[开始] -->B(初始化JModuleManager)
B --> C(加载模块)
C --> D(扫描并装配JAction)
D --> E(销毁)
E --> F[结束]

2.1 编写模块

对于模块而言,简单的来说就是Action的合集,所以我们需要掌握如何开发Action,编写一个Action我们只需要创建一个普通的类,然后让其实现JAction接口即可。

package com.jianggujin.modulelink.test;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.jianggujin.modulelink.JAction;
import com.jianggujin.modulelink.JModuleLinkException;

public class UserAction implements JAction {

   @Override
   public Object execute(Object in)
         throws JModuleException {
      // 真正的业务逻辑
      return null;
   }

   @Override
   public String getActionName() {
      return "user";
   }

   @Override
   public boolean isDefault(String moduleName) {
      return false;
   }
}

JAction中包含三个方法:

  • execute(Object in) - 真正的业务逻辑处理方法
  • getActionName() - Action的名称,在同一个模块中,该值唯一
  • isDefault(String moduleName) - 该方法用于判断Action在模块中是否为默认的Action,用于找不到指定Action的时候的默认处理(人为控制是否使用)

最后只需要将编写好的模块打包即可。建议一个模块就是一个FAT JAR,当然了,这并不是强制的,JModuleLink也支持一个模块有多个资源或jar

2.2 初始化 JModuleManager

模块的加载运行需要JModuleLink的核心支持,首先我们需要初始化JModuleManager,使用该类可以完成模块的加载、卸载、查找等操作,建议使用单例模式初始化该类的对象,使用全局唯一的模块管理器管理模块。

JModuleManager moduleManager = new JModuleManagerImpl();

除此之外,还可以使用更加简单的方式来获得JModuleManager的实例对象:

JModuleManager moduleManager = JModuleManagerImpl.getInstance();

该种方式获得的JModuleManager对象为单例对象,且只有第一次调用该方法的时候才会真正初始化。

2.3 加载模块

我们可以使用JModuleConfig来定义一个模块信息。配置说明如下:

名称 说明
name 模块名,建议用英文命名,全局唯一
desc 模块描述
version 模块版本
overridePackages 模块指定需要覆盖的Class的包名,不遵循双亲委派,模块的类加载器加载这些包, 如果子模块中加载不到那么仍然会到父容器中加载
moduleUrls 模块的资源地址,比如jar文件的位置
scanPackages 需要扫描的包,不为空时会启动扫描。默认的实现会扫描包下面的JAction实现类;如果使用spring, 将自动扫描注解形式的bean ,并装配扫描到的JAction
active 是否激活,仅当模块有多个版本的时候有效

使用示例:

JModuleConfig moduleConfig = new JModuleConfig("moduleName", "moduleVersion");
moduleConfig.setDesc(moduleDesc);
moduleConfig.addModuleUrl(new File(modulePath).toURI().toURL());
moduleConfig.addOverridePackage(overridePackage);
moduleConfig.addScanPackage(scanPackage);
moduleManager.load(moduleConfig);

这样就完成了一个模块的加载。

2.4 使用模块

模块正常加载后,我们就可以通过模块管理器查找已经加载的模块并执行指定的Action。一种比较推荐的使用方式如下:

protected void service(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
   String moduleName = request.getParameter("moduleName");
   String actionName = request.getParameter("actionName");
   JModule module = moduleManager.findModule(moduleName);
   if (module != null) {
      if (actionName == null) {
         if (module.hasDefaultAction()) {
            module.doDefaultAction(null);
         }
      } else {
         if (module.hasAction(actionName)) {
            module.doAction(actionName, null);
         }
      }
   }
}

建议直接使用JModule提供的doAction方法执行指定的Action,或者使用JModuleUtils提供的doActionWithinModuleClassLoader方法执行(这两者本质相同),否则可能会导致模块的类加载器无法正常使用。

Action的参数按照实际情况传递

2.5 AB测试

如果我们希望对一个模块的不同版本进行AB TEST,我们只需要加载指定模块的不同版本,然后使用模块管理器的activeVersion(String name, String version)可以直接切换模块默认使用的版本。

2.6 模块卸载销毁

当一个模块或模块的版本不再使用的时候,我们可以动态的将其卸载,JModuleManager提供了相应的卸载方法:

   /**
    * 卸载一个模块
    */
   void unload(String name);

   /**
    * 卸载一个模块的指定版本
    */
   void unload(String name, String version);

需要注意的是如果仅仅是卸载模块的指定版本,那么被卸载的版本不能是当前默认使用的版本,否则无法卸载。在程序的生命周期结束或者不需要使用JModuleLink的时候,我们可以调用destroy ()方法卸载所有已加载的模块。

卸载模块的时候必须调用JModuleManagerunload方法实现,虽然该接口提供了查找JModule的方法,并且JModule存在destroy()方法,但是我们依然不建议直接调用模块的的销毁方法,避免管理器模块状态不一致出现错误。

2.7 spring

JModuleLink除了默认的模块管理器实现,也针对spring提供了spring的模块管理器实现。使用spring的模块管理器,我们仅需要将JModuleManagerImpl换成JSpringModuleManager,一般情况下,我们会将该对象实例交给spring管理。JSpringModuleManager已经实现了DisposableBean,所以不需要主动调用destroy()方法来销毁加载的模块。

另外,与默认的通用实现不同的地方还有,在加载模块的时候,建议使用JSpringModuleConfig作为模块信息的配置类。JSpringModuleConfigJModuleConfig的基础上增加了3个配置项。配置说明如下:

名称 说明
properties 模块里的bean需要的配置信息,集成了spring properties
xmlPatterns 需要加载的 XML配置
exclusions 需要排除的配置文件

需要注意的是,如果配置中我们添加了扫描的包,在初始化spring模块的上下文的时候将使用注解的形式扫描,否则使用XML方式。

第三部分 日志

因为JModuleLink的大部分常用功能不需要依赖任何三方包,所以提供了一种轻量的日志方式,默认会在控制台输出日志内容,我们可以按照实际集成的日志三方包,编写日志的实现类,然后调用JLogFactorysetImplementation方法设置日志实现类,该类需要实现JLog接口。下面提供几种常用的日志的实现类供大家选择。

3.1 commons-logging

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.jianggujin.dynamicmodule.util.JLogFactory.JLog;

public class JJakartaCommonsLoggingImpl implements JLog {

   private Log log;

   public JJakartaCommonsLoggingImpl(String clazz) {
      log = LogFactory.getLog(clazz);
   }

   public boolean isDebugEnabled() {
      return log.isDebugEnabled();
   }

   public boolean isTraceEnabled() {
      return log.isTraceEnabled();
   }

   public void error(String s, Throwable e) {
      log.error(s, e);
   }

   public void error(String s) {
      log.error(s);
   }

   public void debug(String s) {
      log.debug(s);
   }

   public void trace(String s) {
      log.trace(s);
   }

   public void warn(String s) {
      log.warn(s);
   }

   public void info(String s) {
      log.info(s);
   }

   public void debug(String s, Throwable e) {
      log.debug(s, e);
   }

   public void trace(String s, Throwable e) {
      log.trace(s, e);
   }

   public void warn(String s, Throwable e) {
      log.warn(s, e);
   }

   public void info(String s, Throwable e) {
      log.info(s, e);
   }

   public boolean isErrorEnabled() {
      return log.isErrorEnabled();
   }

   public boolean isInfoEnabled() {
      return log.isInfoEnabled();
   }

   public boolean isWarnEnabled() {
      return log.isWarnEnabled();
   }
}

3.2 jdk14

package com.jianggujin.logging.jdk14;

import java.util.logging.Level;
import java.util.logging.Logger;

import com.jianggujin.dynamicmodule.util.JLogFactory.JLog;

public class JJdk14LoggingImpl implements JLog {

   private Logger log;

   public JJdk14LoggingImpl(String clazz) {
      log = Logger.getLogger(clazz);
   }

   public boolean isDebugEnabled() {
      return log.isLoggable(Level.FINE);
   }

   public boolean isTraceEnabled() {
      return log.isLoggable(Level.FINER);
   }

   public void error(String s, Throwable e) {
      log.log(Level.SEVERE, s, e);
   }

   public void error(String s) {
      log.log(Level.SEVERE, s);
   }

   public void debug(String s) {
      log.log(Level.FINE, s);
   }

   public void trace(String s) {
      log.log(Level.FINER, s);
   }

   public void warn(String s) {
      log.log(Level.WARNING, s);
   }

   public void info(String s) {
      log.log(Level.INFO, s);
   }

   public void debug(String s, Throwable e) {
      log.log(Level.FINE, s, e);
   }

   public void trace(String s, Throwable e) {
      log.log(Level.FINER, s, e);
   }

   public void warn(String s, Throwable e) {
      log.log(Level.WARNING, s, e);
   }

   public void info(String s, Throwable e) {
      log.log(Level.INFO, s, e);
   }

   public boolean isErrorEnabled() {
      return log.isLoggable(Level.SEVERE);
   }

   public boolean isInfoEnabled() {
      return log.isLoggable(Level.INFO);
   }

   public boolean isWarnEnabled() {
      return log.isLoggable(Level.WARNING);
   }
}

3.3 log4j

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import com.jianggujin.dynamicmodule.util.JLogFactory.JLog;

public class JLog4jImpl implements JLog {
   private static final String FQCN = JLog4jImpl.class.getName();

   private Logger log;

   public JLog4jImpl(String clazz) {
      log = Logger.getLogger(clazz);
   }

   public boolean isDebugEnabled() {
      return log.isDebugEnabled();
   }

   public boolean isTraceEnabled() {
      return log.isTraceEnabled();
   }

   public void error(String s, Throwable e) {
      log.log(FQCN, Level.ERROR, s, e);
   }

   public void error(String s) {
      log.log(FQCN, Level.ERROR, s, null);
   }

   public void debug(String s) {
      log.log(FQCN, Level.DEBUG, s, null);
   }

   public void trace(String s) {
      log.log(FQCN, Level.TRACE, s, null);
   }

   public void warn(String s) {
      log.log(FQCN, Level.WARN, s, null);
   }

   public void info(String s) {
      log.log(FQCN, Level.INFO, s, null);
   }

   public void debug(String s, Throwable e) {
      log.log(FQCN, Level.DEBUG, s, e);
   }

   public void trace(String s, Throwable e) {
      log.log(FQCN, Level.TRACE, s, e);
   }

   public void warn(String s, Throwable e) {
      log.log(FQCN, Level.WARN, s, e);
   }

   public void info(String s, Throwable e) {
      log.log(FQCN, Level.INFO, s, e);
   }

   public boolean isErrorEnabled() {
      if (log.getLoggerRepository().isDisabled(Level.ERROR_INT)) {
         return false;
      }
      return Level.ERROR.isGreaterOrEqual(log.getEffectiveLevel());
   }

   public boolean isInfoEnabled() {
      if (log.getLoggerRepository().isDisabled(Level.INFO_INT)) {
         return false;
      }
      return Level.INFO.isGreaterOrEqual(log.getEffectiveLevel());
   }

   public boolean isWarnEnabled() {
      if (log.getLoggerRepository().isDisabled(Level.WARN_INT)) {
         return false;
      }
      return Level.WARN.isGreaterOrEqual(log.getEffectiveLevel());
   }
}

3.4 nologging

import com.jianggujin.dynamicmodule.util.JLogFactory.JLog;

public class JNoLoggingImpl implements JLog {

   public JNoLoggingImpl(String clazz) {
   }

   public boolean isDebugEnabled() {
      return false;
   }

   public boolean isTraceEnabled() {
      return false;
   }

   public void error(String s, Throwable e) {
   }

   public void error(String s) {
   }

   public void debug(String s) {
   }

   public void trace(String s) {
   }

   public void warn(String s) {
   }

   public void info(String s) {
   }

   public void debug(String s, Throwable e) {
   }

   public void trace(String s, Throwable e) {
   }

   public void warn(String s, Throwable e) {
   }

   public void info(String s, Throwable e) {
   }

   public boolean isErrorEnabled() {
      return false;
   }

   public boolean isInfoEnabled() {
      return false;
   }

   public boolean isWarnEnabled() {
      return false;
   }
}

3.5 slf4j

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.spi.LocationAwareLogger;

import com.jianggujin.dynamicmodule.util.JLogFactory.JLog;

public class JSlf4jImpl implements JLog {

   private JLog log;

   public JSlf4jImpl(String clazz) {
      Logger logger = LoggerFactory.getLogger(clazz);

      if (logger instanceof LocationAwareLogger) {
         try {
            // check for slf4j >= 1.6 method signature
            logger.getClass().getMethod("log", Marker.class, String.class, int.class, String.class, Object[].class,
                  Throwable.class);
            log = new JSlf4jLocationAwareLoggerImpl((LocationAwareLogger) logger);
            return;
         } catch (SecurityException e) {
            // fail-back to Slf4jLoggerImpl
         } catch (NoSuchMethodException e) {
            // fail-back to Slf4jLoggerImpl
         }
      }

      // Logger is not LocationAwareLogger or slf4j version < 1.6
      log = new JSlf4jLoggerImpl(logger);
   }

   public boolean isDebugEnabled() {
      return log.isDebugEnabled();
   }

   public boolean isTraceEnabled() {
      return log.isTraceEnabled();
   }

   public void error(String s, Throwable e) {
      log.error(s, e);
   }

   public void error(String s) {
      log.error(s);
   }

   public void debug(String s) {
      log.debug(s);
   }

   public void trace(String s) {
      log.trace(s);
   }

   public void warn(String s) {
      log.warn(s);
   }

   public void info(String s) {
      log.info(s);
   }

   public void debug(String s, Throwable e) {
      log.debug(s, e);
   }

   public void trace(String s, Throwable e) {
      log.trace(s, e);
   }

   public void warn(String s, Throwable e) {
      log.warn(s, e);
   }

   public void info(String s, Throwable e) {
      log.info(s, e);
   }

   public boolean isErrorEnabled() {
      return log.isErrorEnabled();
   }

   public boolean isInfoEnabled() {
      return log.isInfoEnabled();
   }

   public boolean isWarnEnabled() {
      return log.isWarnEnabled();
   }
}
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import org.slf4j.spi.LocationAwareLogger;

import com.jianggujin.dynamicmodule.util.JLogFactory.JLog;
import com.jianggujin.dynamicmodule.util.JLogFactory;

public class JSlf4jLocationAwareLoggerImpl implements JLog {

   private static Marker MARKER = MarkerFactory.getMarker(JLogFactory.MARKER);

   private static final String FQCN = JSlf4jImpl.class.getName();

   private LocationAwareLogger logger;

   JSlf4jLocationAwareLoggerImpl(LocationAwareLogger logger) {
      this.logger = logger;
   }

   public boolean isDebugEnabled() {
      return logger.isDebugEnabled();
   }

   public boolean isTraceEnabled() {
      return logger.isTraceEnabled();
   }

   public boolean isErrorEnabled() {
      return logger.isErrorEnabled();
   }

   public boolean isInfoEnabled() {
      return logger.isInfoEnabled();
   }

   public boolean isWarnEnabled() {
      return logger.isWarnEnabled();
   }

   public void error(String s, Throwable e) {
      logger.log(MARKER, FQCN, LocationAwareLogger.ERROR_INT, s, null, e);
   }

   public void error(String s) {
      logger.log(MARKER, FQCN, LocationAwareLogger.ERROR_INT, s, null, null);
   }

   public void debug(String s) {
      logger.log(MARKER, FQCN, LocationAwareLogger.DEBUG_INT, s, null, null);
   }

   public void trace(String s) {
      logger.log(MARKER, FQCN, LocationAwareLogger.TRACE_INT, s, null, null);
   }

   public void warn(String s) {
      logger.log(MARKER, FQCN, LocationAwareLogger.WARN_INT, s, null, null);
   }

   public void info(String s) {
      logger.log(MARKER, FQCN, LocationAwareLogger.INFO_INT, s, null, null);
   }

   public void debug(String s, Throwable e) {
      logger.log(MARKER, FQCN, LocationAwareLogger.DEBUG_INT, s, null, e);
   }

   public void trace(String s, Throwable e) {
      logger.log(MARKER, FQCN, LocationAwareLogger.TRACE_INT, s, null, e);
   }

   public void warn(String s, Throwable e) {
      logger.log(MARKER, FQCN, LocationAwareLogger.WARN_INT, s, null, e);
   }

   public void info(String s, Throwable e) {
      logger.log(MARKER, FQCN, LocationAwareLogger.INFO_INT, s, null, e);
   }
}
import org.slf4j.Logger;

import com.jianggujin.dynamicmodule.util.JLogFactory.JLog;

public class JSlf4jLoggerImpl implements JLog {

   private Logger log;

   public JSlf4jLoggerImpl(Logger logger) {
      log = logger;
   }

   public boolean isDebugEnabled() {
      return log.isDebugEnabled();
   }

   public boolean isTraceEnabled() {
      return log.isTraceEnabled();
   }

   public void error(String s, Throwable e) {
      log.error(s, e);
   }

   public void error(String s) {
      log.error(s);
   }

   public void debug(String s) {
      log.debug(s);
   }

   public void trace(String s) {
      log.trace(s);
   }

   public void warn(String s) {
      log.warn(s);
   }

   public void info(String s) {
      log.info(s);
   }

   public void debug(String s, Throwable e) {
      log.debug(s, e);
   }

   public void trace(String s, Throwable e) {
      log.trace(s, e);
   }

   public void warn(String s, Throwable e) {
      log.warn(s, e);
   }

   public void info(String s, Throwable e) {
      log.info(s, e);
   }

   public boolean isErrorEnabled() {
      return log.isErrorEnabled();
   }

   public boolean isInfoEnabled() {
      return log.isInfoEnabled();
   }

   public boolean isWarnEnabled() {
      return log.isWarnEnabled();
   }
}
阅读更多

扫码向博主提问

蒋固金

非学,无以致疑;非问,无以广识
  • 擅长领域:
  • java
  • oracle
  • js
去开通我的Chat快问
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页