JModuleLink使用文档

/ JModuleLink / 没有评论 / 7浏览

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

第一部分 JModuleLink简介

1.1 需求背景

1.2 模块化开发的好处

1.3 特性

1.3.1 隔离性

1.3.2 动态性

1.3.3 易用性

第二部分 开始使用

可以从码云获取代码。

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中包含三个方法:

最后只需要将编写好的模块打包即可。建议一个模块就是一个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();
   }
}