疾风步行者

V1

2022/07/01阅读:22主题:兰青

Java实现热插拨功能总结

OSGI

OSGI介绍

OSGI全称为Open Service Gateway Initiative(开放服务网关规范),有两个层面的含义,一方面它指OSGi Alliance组织;另一方面指该组织制定的一个基于Java语言的服务(业务)规范——OSGi服务平台(Service Platform)。

一般Java开发人员所说的OSGI是指由OSGi Alliance组织制定的Java模块化规范,该规范的核心部分是一个框架,其中定义了应用程序的生命周期模式和服务注册。基于这个框架定义了大量的OSGi服务:日志、配置管理,HTTP服务(运行Servlet)、XML解析、设备访问、软件包管理、许可管理、用户管理、IO连接、Jini和UPnP等。

OSGI优缺点

使用OSGI构建Java应用优点比较明显,主要体现在以下几个方面:

1、基于OSGI的应用程序可动态更改运行状态和行为。在OSGI框架中,每一个Bundle实际上都是可热插拔的,因此,对一个特定的Bundle进行修改不会影响到容器中的所有应用,运行的大部分应用还是可以照常工作。当你将修改后的Bundle再部署上去的时候,容器从来没有重新启过。这种可动态更改状态的特性在一些及时性很强的系统中比较重要,尤其是在Java Web项目中,无需重启应用服务器就可以做到应用的更新。

2、OSGI是一个微核的系统,所谓微核是指其核心只有为数不多的几个jar包。基于OSGI框架的系统可分可合,其结构的优势性导致具体的Bundle不至于影响到全局,不会因为局部的错误导致全局系统的崩溃。例如Java EE项目中可能会因为某个Bean的定义或注入有问题,而导致整个应用跑不起来,而使用OSGI则不会有这种问题,顶多相关的几个Bundle无法启动。

3、可复用性强,OSGI框架本身可复用性极强,很容易构建真正面向接口的程序架构,每一个Bundle 都是一个独立可复用的单元。

使用OSGI的缺点如下: 1、每个Bundle都由单独的类加载器加载,与一些Java EE项目中使用比较多的框架整合比较困难,如Spring MVC、Struts2等,例如笔者尝试在OSGI应用中整合Spring MVC时,通过DispatcherServlet启动的Bean与OSGI Bundle启动的Bean无法相互依赖,需要做特殊处理,后面文章中会有介绍。

2、目前OSGI框架提供的管理端不够强大,现在的管理端中仅提供了基本的Bundle状态管理、日志查看等功能,像动态修改系统级别的配置(config.ini)、动态修改Bundle的配置(Manifest.mf)、启动级别等功能都尚未提供,而这些在实际的项目或产品中都是非常有必要的。

3、采用OSGI作为规范的模块开发、部署方式自然给现有开发人员提出了新的要求,需要学习新的基于OSGI的开发方式。

OSGI实例

在这里插入图片描述 PS: Target Platform选择 standard,标准模式,这样可运行的平台就多了 在这里插入图片描述 在这里插入图片描述 这里我用模版创建,选择Hello OSGI Bundle 在这里插入图片描述 输入s查看所有的Bundle 我这边有很多,只贴出了几个自定义的Bundle

id State       Bundle
0 ACTIVE      org.eclipse.osgi_3.17.100.v20211104-1730
             Fragments=139, 146
1 RESOLVED    ServiceOSGI_1.0.0.qualifier
2 RESOLVED    TestService_1.0.0.qualifier
3 RESOLVED    HelloOSGI_1.0.0.qualifier

输入start 3 启动HelloOSGI

osgi> start 3
Hello World!!

现在修改输出为"Hello OSGI!!!"

public class Activator implements BundleActivator {
 @Override
 public void start(BundleContext context) throws Exception {
  System.out.println("Hello OSGI!!");
 }
 
 @Override
 public void stop(BundleContext context) throws Exception {
  System.out.println("Goodbye OSGI!!");
 }
}

再启动一下

osgi> stop 3
Goodbye OSGI!!
osgi> start 3
Hello OSGI!!

Groovy脚本

ScriptEngineManager

按照JSR223,使用标准接口ScriptEngineManager调用。

ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("groovy");// 每次生成一个engine实例
Bindings binding = engine.createBindings();
binding.put("date"new Date()); // 入参
engine.eval("def getTime(){return date.getTime();}", binding);
// 如果script文本来自文件,请首先获取文件内容
//engine.eval(new FileReader(new File("/Users/chenjujun/java-projects/Java-Test/src/test/java/script/test.groovy")));
engine.eval("def sayHello(name,age){return 'Hello,I am ' + name + ',age' + age;}");
Long time = (Long) ((Invocable) engine).invokeFunction("getTime"null);// 反射到方法
System.out.println(time);
String message = (String) ((Invocable) engine).invokeFunction("sayHello""zhangsan"12);
System.out.println(message);

GroovyShell

Groovy官方提供GroovyShell,执行Groovy脚本片段,GroovyShell每一次执行时代码时会动态将代码编译成Java Class,然后生成Java对象在Java虚拟机上执行,所以如果使用GroovyShell会造成Class太多,性能较差。

final String script = "Runtime.getRuntime().availableProcessors()";
Binding intBinding = new Binding();
GroovyShell shell = new GroovyShell(intBinding);
final Object eval = shell.evaluate(script);
System.out.println(eval);

GroovyClassLoader

Groovy官方提供GroovyClassLoader类,支持从文件、url或字符串中加载解析Groovy Class,实例化对象,反射调用指定方法。

GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
  String helloScript = "package com.vivo.groovy.util" +  // 可以是纯Java代码
          "class Hello {" +
            "String say(String name) {" +
              "System.out.println(\"hello, \" + name)" +
              " return name;"
            "}" +
          "}";
Class helloClass = groovyClassLoader.parseClass(helloScript);
GroovyObject object = (GroovyObject) helloClass.newInstance();
Object ret = object.invokeMethod("say""zhangshan"); 
System.out.println(ret.toString()); 

Spring

Spring常规方法

DeployUtil 工具类

@Slf4j
public class DeployUtils {
 /**
  * 读取jar包中所有类文件
  */

 public static Set<String> readJarFile(String jarAddress) throws IOException {
     Set<String> classNameSet = new HashSet<>();
     
     try(JarFile jarFile = new JarFile(jarAddress)) {
      Enumeration<JarEntry> entries = jarFile.entries();//遍历整个jar文件
      while (entries.hasMoreElements()) {
          JarEntry jarEntry = entries.nextElement();
          String name = jarEntry.getName();
          if (name.endsWith(".class")) {
              String className = name.replace(".class""").replaceAll("/"".");
              classNameSet.add(className);
          }
      }
  } catch (Exception e) {
   log.warn("加载jar包失败", e);
  }
     return classNameSet;
 }
 /**
  * 方法描述 判断class对象是否带有spring的注解
  */

 public static boolean isSpringBeanClass(Class<?> cls) {
     if (cls == null) {
         return false;
     }
     //是否是接口
     if (cls.isInterface()) {
         return false;
     }
     //是否是抽象类
     if (Modifier.isAbstract(cls.getModifiers())) {
         return false;
     }
     //自定义注解
     if (cls.getAnnotation(Supplier.class) !null) {
         return true;
     }
     if (cls.getAnnotation(Component.class) !null) {
         return true;
     }
     if (cls.getAnnotation(Mapper.class) !null) {
         return true;
     }
     if (cls.getAnnotation(Service.class) !null) {
         return true;
     }
     return false;
 }
 
 
 public static boolean isController(Class<?> cls) {
  if (cls.getAnnotation(Controller.class) !null) {
   return true;
  }
  if (cls.getAnnotation(RestController.class) !null) {
   return true;
  }
  return false;
 }
 
 /**
  * 类名首字母小写 作为spring容器beanMap的key
  */

 public static String transformName(String className) {
     String tmpstr = className.substring(className.lastIndexOf(".") + 1);
     return tmpstr.substring(01).toLowerCase() + tmpstr.substring(1);
 }
}

注册bean

public void register(String pluginId) {

  Method detectHandlerMethods = null;
  Method unregisterMapping = null;
  
  try {
   detectHandlerMethods = ReflectUtils.findDeclaredMethod(requestMappingHandlerMapping.getClass(),
     "detectHandlerMethods"new Class[] { Object.class });
   unregisterMapping = ReflectUtils.findDeclaredMethod(requestMappingHandlerMapping.getClass(),
     "unregisterMapping"new Class[] { Object.class });
   
   String jarPath = "/Users/chenjujun/greentown-projects/aio-edge/aio-edge-third-plugins/device/plugin-device-md/target/plugin-device-md-1.0.4.jar";
   Set<String> classNames = DeployUtils.readJarFile(jarPath);

            URL jarURL = new File(jarPath).toURI().toURL();
   
   URLClassLoader classLoader = new URLClassLoader(new URL[] { jarURL }, Thread.currentThread().getContextClassLoader());

   for (String className : classNames) {
    Class clazz = classLoader.loadClass(className);
    if (DeployUtils.isSpringBeanClass(clazz)) {
     ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
     BeanDefinitionRegistry beanDefinitonRegistry = (BeanDefinitionRegistry) configurableApplicationContext
       .getBeanFactory();

     BeanDefinitionBuilder usersBeanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
     usersBeanDefinitionBuilder.setScope("singleton");
     beanDefinitonRegistry.registerBeanDefinition(DeployUtils.transformName(className),
       usersBeanDefinitionBuilder.getRawBeanDefinition());
     
     //注册接口
     if (Boolean.TRUE.equals(DeployUtils.isController(clazz))) {
      try {
       detectHandlerMethods.setAccessible(true);
       detectHandlerMethods.invoke(requestMappingHandlerMapping, new Object[] { DeployUtils.transformName(className) });
      } catch (Exception e) {
       //移除bean
       beanDefinitonRegistry.removeBeanDefinition(DeployUtils.transformName(className));
      }
     }
    
    } 
   }

  } catch (Exception e) {
   e.printStackTrace();
  }
 }

Spring Brick

介绍

"Spring-brick",是一个可以动态扩展系统的框架,最早在2019年开始开发,该框架可以在SpringBoot项目上开发插件功能,开发插件就像开发独立应用一样,根据网站的介绍,使用该框架可以实现如下需求:

  • 在插件中,您可以当成一个微型的SpringBoot项目来开发,简单易用。
  • 在插件中扩展出系统各种功能点,用于系统灵活扩展,再也不用使用分支来交付不同需求的项目了。
  • 在插件中可以集成各种框架及其各种spring-boot-xxx-starter。
  • 在插件中可以定义独立依赖包了,再也不用在主程序中定义依赖包了。
  • 可以完美解决插件包与插件包、插件包与主程序因为同一框架的不同版本冲突问题了。各个插件可以定义同一依赖的不同版本框架。
  • 无需重启主程序,可以自由实现插件包的动态安装部署,来动态扩展系统的功能。
  • 插件也可以不依赖主程序独立集成微服务模块。

项目更新蛮活跃的:

文档比较丰富:

快速入门

示例地址:
https://gitee.com/starblues/springboot-plugin-framework-example

作者提供了一个项目示例,我们来跑一下看看,跑之前看下文档的快速入门:

1、将sql/plugin-test-example.sql文件导入到mysql数据库
2、修改各个模块对数据库连接配置信息,插件目录地址
3、打包插件: mvn clean package
4、进入主程序example-main 启动 Application

运行日志:


  .   ____   _     __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::  (v2.5.6)

 [Thread-1] c.g.s.l.launcher.SpringMainBootstrap     : Starting SpringMainBootstrap using Java 1.8.0_311 on MacBookAir.local with PID 21618 (/Users/chenjujun/java-projects/springboot-plugin-framework-parent/spring-brick-loader/target/classes started by apple in /Users/chenjujun/java-projects/springboot-plugin-framework-example/example-main)
 [Thread-1] c.g.s.l.launcher.SpringMainBootstrap     : The following profiles are active: dev
 [Thread-1] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
 [Thread-1] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
 [Thread-1] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.54]
 [Thread-1] o.a.c.c.C.[Tomcat].[localhost].[/]: Initializing Spring embedded WebApplicationContext
 [Thread-1] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1571 ms
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
Parsed mapper file: 'file [/Users/chenjujun/java-projects/springboot-plugin-framework-example/example-main/target/classes/mapper/MainUserMapper.xml]'
 _ _   |_  _ _|_. ___ _ |    _ 
| | |\/|_)(_| | |_\  |_)||_|_\ 
     / |  
   3.4.1 
 [Thread-1] pertySourcedRequestMappingHandlerMapping : Mapped URL path [/v2/api-docs] onto method [springfox.documentation.swagger2.web.Swagger2ControllerWebMvc#getDocumentation(String, HttpServletRequest)]
 [Thread-1] ion$DefaultTemplateResolverConfiguration : Cannot find template location: classpath:/templates/ (please add some templates or check your Thymeleaf configuration)
 [Thread-1] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
 [Thread-1] d.s.w.p.DocumentationPluginsBootstrapper : Documentation plugins bootstrapped
 [Thread-1] d.s.w.p.DocumentationPluginsBootstrapper : Found 1 custom documentation plugin(s)
 [Thread-1] s.d.s.w.s.ApiListingReferenceScanner     : Scanning for api listing references
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getBeanUsingGET_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getBeanByInterfaceUsingGET_1
 [Thread-1] c.g.s.l.launcher.SpringMainBootstrap     : Started SpringMainBootstrap in 4.107 seconds (JVM running for 6.445)
 [Thread-1] c.g.s.i.operator.DefaultPluginOperator   : 插件加载环境: dev
 [Thread-1] c.g.s.i.operator.DefaultPluginOperator   : 开始加载插件, 插件根路径为: 
/Users/chenjujun/java-projects/springboot-plugin-framework-example/example-plugins-basic
 [Thread-1] c.g.s.s.web.PluginStaticResourceConfig   : 插件静态资源访问前缀配置为: /static-plugin/{pluginId}
 [Thread-1] c.g.s.core.PluginLauncherManager  : 插件[example-basic-2@1.0.0-SNAPSHOT]加载成功
 [Thread-1] c.g.s.example.listener.MyPluginListener  : 插件[example-basic-2]加载成功.
 [Thread-1] c.g.s.core.PluginLauncherManager  : 插件[example-basic-1@1.0.0]加载成功
 [Thread-1] c.g.s.example.listener.MyPluginListener  : 插件[example-basic-1]加载成功.
 [Thread-1] c.g.s.e.l.MyPluginInitializerListener    : 初始化之前
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-2]注册接口: {GET [/plugins/example-basic-2/hello/config]}
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-2]注册接口: {GET [/plugins/example-basic-2/hello]}
 [Thread-1] c.g.s.core.PluginLauncherManager  : 插件[example-basic-2@1.0.0-SNAPSHOT]启动成功
 [Thread-1] c.g.s.example.listener.MyPluginListener  : 插件[example-basic-2]启动成功
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-1]注册接口: {GET [/plugins/example-basic-1/caller/test-param-map]}
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-1]注册接口: {GET [/plugins/example-basic-1/caller/test]}
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-1]注册接口: {GET [/plugins/example-basic-1/caller/test-param-list]}
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-1]注册接口: {GET [/plugins/example-basic-1/caller/test-param]}
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-1]注册接口: {GET [/plugins/example-basic-1/hello/name]}
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-1]注册接口: {GET [/plugins/example-basic-1/hello]}
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-1]注册接口: {GET [/plugins/example-basic-1/hello/config]}
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-1]注册接口: {GET [/plugins/example-basic-1/main/main-user]}
 [Thread-1] c.g.s.b.p.web.PluginControllerProcessor  : 插件[example-basic-1]注册接口: {GET [/plugins/example-basic-1/thy]}
 [Thread-1] c.g.s.s.w.PluginStaticResourceResolver   : 插件[example-basic-1@1.0.0]配置的静态资源: classpath[[static/]], file[[]]
 [Thread-1] c.g.s.core.PluginLauncherManager  : 插件[example-basic-1@1.0.0]启动成功
 [Thread-1] c.g.s.example.listener.MyPluginListener  : 插件[example-basic-1]启动成功
 [Thread-1] d.s.w.p.DocumentationPluginsBootstrapper : Documentation plugins bootstrapped
 [Thread-1] d.s.w.p.DocumentationPluginsBootstrapper : Found 1 custom documentation plugin(s)
 [Thread-1] s.d.s.w.s.ApiListingReferenceScanner     : Scanning for api listing references
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getExtractImplUsingGET_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getExtractByInterClassUsingGET_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getExtractByInterClassOfMainUsingGET_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: helloUsingGET_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: helloUsingGET_2
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getConfigUsingGET_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: helloUsingGET_3
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: parseUsingPOST_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: backupPluginUsingPOST_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: installUsingPOST_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: startUsingPOST_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: stopUsingPOST_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: uninstallUsingPOST_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: uploadUsingPOST_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: verifyUsingPOST_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getPluginInfoUsingGET_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getBeanByInterfaceUsingGET_2
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getObjectWithAnnotationUsingGET_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getBeanNameUsingGET_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getBeanUsingGET_2
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getBeanUsingGET_3
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getBeanByInterfaceErrorUsingGET_1
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getBeanByInterfaceUsingGET_3
 [Thread-1] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: getObjectWithAnnotationByPluginIdUsingGET_1
 [Thread-1] c.g.s.e.l.MyPluginInitializerListener    : 初始化完成
 [Thread-1] c.g.s.i.operator.DefaultPluginOperator   : 插件初始化完成

这是已经加载完成了。

试试怎么动态部署。
动态部署需要使用prod模式启动,毕竟是要模拟线上环境。
插件目录没有插件的时候是这样的:

[Thread-1] c.g.s.i.operator.DefaultPluginOperator   : 插件加载环境: prod
[Thread-1] c.g.s.i.operator.DefaultPluginOperator   : 开始加载插件, 插件根路径为: 
../plugins
[Thread-1] c.g.s.s.web.PluginStaticResourceConfig   : 插件静态资源访问前缀配置为: /static-plugin/{pluginId}
[Thread-1] c.g.s.core.PluginLauncherManager         : 以下路径未发现插件: 
../plugins
请检查路径是否合适.
请检查配置[plugin.runMode]是否合适.
请检查插件是否合法.

调用下插件上传安装接口:

返回成功了:

看下日志,已经安装成功了:

后面就可以跟调用插件的HTTP接口了。

调用流程

为了能够更好的理解,我整理了一个调用流程图

扩展

xxl-job动态定时任务

我们主要看Java相关的部分

public class ExecutorBizImpl implements ExecutorBiz {
    //省略....

    @Override
    public ReturnT<String> run(TriggerParam triggerParam) {
        //省略....

  //使用Groovy加载的Java代码
        } else if (GlueTypeEnum.GLUE_GROOVY == glueTypeEnum) {

            // valid old jobThread
            if (jobThread != null &&
                    !(jobThread.getHandler() instanceof GlueJobHandler
                        && ((GlueJobHandler) jobThread.getHandler()).getGlueUpdatetime()==triggerParam.getGlueUpdatetime() )) {
                // change handler or gluesource updated, need kill old thread
                removeOldReason = "change job source or glue type, and terminate the old job thread.";

                jobThread = null;
                jobHandler = null;
            }

            // valid handler
            if (jobHandler == null) {
                try {
                 //载入源码
                    IJobHandler originJobHandler = GlueFactory.getInstance().loadNewInstance(triggerParam.getGlueSource());
                    jobHandler = new GlueJobHandler(originJobHandler, triggerParam.getGlueUpdatetime());
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                    return new ReturnT<String>(ReturnT.FAIL_CODE, e.getMessage());
                }
            }
        } 
  //省略....

        // replace thread (new or exists invalid)
        if (jobThread == null) {
            jobThread = XxlJobExecutor.registJobThread(triggerParam.getJobId(), jobHandler, removeOldReason);
        }

        // push data to queue
        ReturnT<String> pushResult = jobThread.pushTriggerQueue(triggerParam);
        return pushResult;
    }
    //省略....

}

Glue工厂GlueFactory

 public IJobHandler loadNewInstance(String codeSource) throws Exception{
  if (codeSource!=null && codeSource.trim().length()>0) {
   Class<?> clazz = getCodeSourceClass(codeSource);
   if (clazz != null) {
    //实例化对象
    Object instance = clazz.newInstance();
    if (instance!=null) {
     if (instance instanceof IJobHandler) {
      //注入bean
      this.injectService(instance);
      return (IJobHandler) instance;
     } else {
      throw new IllegalArgumentException(">>>>>>>>>>> xxl-glue, loadNewInstance error, "
        + "cannot convert from instance["+ instance.getClass() +"] to IJobHandler");
     }
    }
   }
  }
  throw new IllegalArgumentException(">>>>>>>>>>> xxl-glue, loadNewInstance error, instance is null");
 }

SpringGlueFactory处理工厂

public void injectService(Object instance){
        if (instance==null) {
            return;
        }

        if (XxlJobSpringExecutor.getApplicationContext() == null) {
            return;
        }

        Field[] fields = instance.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (Modifier.isStatic(field.getModifiers())) {
                continue;
            }

            Object fieldBean = null;
            // with bean-id, bean could be found by both @Resource and @Autowired, or bean could only be found by @Autowired

            if (AnnotationUtils.getAnnotation(field, Resource.class) !null) {
                try {
                //根据指定bean名称获取
                    Resource resource = AnnotationUtils.getAnnotation(field, Resource.class);
                    if (resource.name()!=null && resource.name().length()>0){
                        fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(resource.name());
                    } else {
                    //根据字段名获取,默认bean名称
                        fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(field.getName());
                    }
                } catch (Exception e) {
                }
                if (fieldBean==null ) {
                    //根据字段类型获取
                    fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(field.getType());
                }
            } else if (AnnotationUtils.getAnnotation(field, Autowired.class) !null) {
                Qualifier qualifier = AnnotationUtils.getAnnotation(field, Qualifier.class);
                if (qualifier!=null && qualifier.value()!=null && qualifier.value().length()>0) {
                    fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(qualifier.value());
                } else {
                    fieldBean = XxlJobSpringExecutor.getApplicationContext().getBean(field.getType());
                }
            }

            if (fieldBean!=null) {
                field.setAccessible(true);
                try {
                //注入spring bean
                    field.set(instance, fieldBean);
                } catch (IllegalArgumentException e) {
                    logger.error(e.getMessage(), e);
                } catch (IllegalAccessException e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

关注我,给大家更多精彩

分类:

后端

标签:

Java

作者介绍

疾风步行者
V1

还在奔跑的老程序员