欢迎来到:clap: 阿提说说:clap:的博客。很高兴,您能看到我的文章。
在这篇文章中,我对Java如何实现热插拔技术进行了总结并编写了示例,对现有项目进行了分析,希望对您有帮助。
@[toc]
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(0, 1).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);
}
}
}
}