Springboot Application 集成 OSGI 框架开发

内容来源:https://www.ibm.com/developerworks/cn/java/j-springboot-application-integrated-osgi-framework-development/index.html

Springboot Application 集成 OSGI 框架开发

张 莹莹
2018 年 4 月 02 日发布

WeiboGoogle+用电子邮件发送本页面

0

Java 类加载器

启动类加载器 (Bootstrap ClassLoader)

是 Java 类加载层次中最顶层的类加载器,负责加载 JDK 中的核心类库,如:rt.jar、resources.jar、charsets.jar 等

扩展类加载器(Extension ClassLoader)

负责加载 Java 的扩展类库,默认加载 JAVA_HOME/jre/lib/ext/目下的所有 jar

应用类加载器(Application ClassLoader)

负责加载应用程序 classpath 目录下的所有 jar 和 class 文件。

ClassLoader 使用的是双亲委托模型来搜索类的,每个 ClassLoader 实例都有一个父类加载器的引用(不是继承的关系,是一个包含的关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它 ClassLoader 实例的的父类加载器。当一个 ClassLoader 实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器 Bootstrap ClassLoader 试图加载,如果没加载到,则把任务转交给 Extension ClassLoader 试图加载,如果也没加载到,则转交给 App ClassLoader进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等 URL 中加载该类。如果它们都没有加载到这个类时,则抛出 ClassNotFoundException 异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的 Class 实例对象。

判别两个类是否相同,除了是相同的 class 字节码,还必须由同一类加载器加载。比如类 Example,javac 编译之后生成字节码文件 Example.class,ClassLoaderA 和 ClassLoaderB 这两个类加载器并读取了 Example.class 文件,并分别定义出了 java.lang.Class 实例来表示这个类,对于 JVM 来说,它们是两个不同的实例对象,但它们确实是同一份字节码文件,如果试图将这个 Class 实例生成具体的对象进行转换时,就会抛运行时异常 java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.Class

OSGI 类加载器

OSGI 类加载器并不遵循 Java 的双亲委派模型,OSGi 为每个 bundle 提供一个类加载器,该加载器能够加载 bundle 内部的类和资源,bundle 之间的交互是从一个 bundle 类加载器委托到另一个 bundle 类加载器,所有 bundle 都有一个父类加载器。

Fragment bundle 是一种特殊的 bundle,不是独立的 bundle,必须依附于其他 bundle 来使用。通过 Fragment-Host 来指定宿主 bundle,同时也可以通过这种方式使用宿主的类加载器。

图 1.OSGI 类加载器

OSGI 框架根据 Bundle 的 MANIFEST.MF 文件中描述的数据信息进行解析处理 Bundle 间的依赖关系。Fragment Bundle 的宿主 bundle 的检查在 bundle 解析之前已经完成,所以 Fragement Bundle 可以获取到宿主 bundle 的加载器信息。

Equinox OSGI ServletBridge 实现原理及源码解析

BridgeServlet 与 OSGI 容器

Equinox 提供了 servletbridge.jar 将 OSGI framework 和 servlet container 桥接起来,并且提供了一系列的 bundle 可以将 Equinox OSGI 应用嵌入到现有的 web 服务器中(eg. Tomcat)。servletbridge.jar 包含如下两个文件 (package: org.eclipse.equinox.servletbridge)

BridgeServlet – 负责请求处理

FrameworkLauncher – 负责 OSGI bundle 启动管理

Web 工程被加载到 web 容器中,比如 Tomcat,容器读取 web 工程 WEB-INF 目录下的 web.xml 文件,通过 servlet mapping 指定相应的类处理请求,如下所示:

清单 1.BridgeServlet 配置


1

2

<servlet-name>equinoxbridgeservlet</servlet-name>

<servlet-class>org.eclipse.equinox.servletbridge.BridgeServlet</servlet-class>

Web 容器自动加载 org.eclipse.equinox.servletbridge.BridgeServlet 这个 Servlet,所有发向 Web 容器的请求都被这个 Servlet 处理。

在 BridgeServlet 中的 init()方法中完成了对 OSGI 容器的启动。如下所示:

清单 2.BridgeServlet 初始化


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

public void init() throws ServletException {

super.init();

setInstance(this);

String enableFrameworkControlsParameter = getServletConfig().getInitParameter("enableFrameworkControls");

this.enableFrameworkControls = ((enableFrameworkControlsParameter != null)

&& (enableFrameworkControlsParameter.equals("true")));

String frameworkLauncherClassParameter = getServletConfig().getInitParameter("frameworkLauncherClass");

if (frameworkLauncherClassParameter != null) {

try {

Class frameworkLauncherClass = getClass().getClassLoader().loadClass(frameworkLauncherClassParameter);

this.framework = ((FrameworkLauncher) frameworkLauncherClass.newInstance());

} catch (Exception e) {

throw new ServletException(e);

}

} else {

this.framework = new FrameworkLauncher();

}

this.framework.init(getServletConfig());

this.framework.deploy();

this.framework.start();

}

初始化所用到的参数都是从 web.xml 中获取,如果指定了 frameworkLauncherClass 参数即启动器的实现类,则用该启动类作为框架启动器,如果没有指定,默认采用 org.eclipse.equinox.servletbridge. FrameworkLauncher 作为框架启动器。

OSGI 启动包括 init(ServletConfig),deploy(),start()三个方法,其中 init()完成了 config 和 context 等的一系列初始化工作,deploy 完成了相应的 osgi bundle 等的拷贝,以及相应的目录建立和桥接扩展器 bundle 的创建,start 完成了类加载器的切换和通过反射调用 EclipseStart 的 startup 方法对于 osgi 的启动。具体看源码分析。

清单 3.osgi 框架初始化


1

2

3

4

5

6

7

void init(ServletConfig servletConfig) {

this.config = servletConfig;

this.context = servletConfig.getServletContext();

init();

}

String commandLine = this.config.getInitParameter("commandline");

String extendedExports = this.config.getInitParameter("extendedFrameworkExports");

Init 完成初始化所有资源配置的工作。

清单 4.osgi 框架部署


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

public synchronized void deploy() {

if (this.platformDirectory != null) {

this.context.log("Framework is already deployed");

return;

}

File servletTemp = (File) this.context.getAttribute("javax.servlet.context.tempdir");

this.platformDirectory = new File(servletTemp, "eclipse");

if (!this.platformDirectory.exists()) {

this.platformDirectory.mkdirs();

}

copyResource("/WEB-INF/eclipse/configuration/", new File(this.platformDirectory, "configuration"));

copyResource("/WEB-INF/eclipse/features/", new File(this.platformDirectory, "features"));

File plugins = new File(this.platformDirectory, "plugins");

copyResource("/WEB-INF/eclipse/plugins/", plugins);

deployExtensionBundle(plugins);

copyResource("/WEB-INF/eclipse/.eclipseproduct", new File(this.platformDirectory, ".eclipseproduct"));

}

deploy

首先将 configuration,features,plugins 等资源拷贝到临时目录下,然后部署一个 extension bundle,这一步非常重要,这个 bundle 会作为一个 fragment 附加到系统 bundle 之上,同时导出 org.eclipse.equinox.servletbridge 和其他一些 package。这一步的操作其实是进行了一个 classcloader 的切换操作,使得 package 名为 org.eclipse.equinox.servletbridge 的 bundle 可以获取到 system bundle 的 classloader,下面会进行程序演示。

清单 5.osgi 框架启动


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

public synchronized void start(){

if (this.platformDirectory == null) {

throw new IllegalStateException("Could not start the Framework - (not deployed)");

}

if (this.frameworkClassLoader != null) {

this.context.log("Framework is already started");

return;

}

Map initalPropertyMap = buildInitialPropertyMap();

String[] args = buildCommandLineArguments();

ClassLoader original = Thread.currentThread().getContextClassLoader();

try {

System.setProperty("osgi.framework.useSystemProperties", "false");

URL[] osgiURLArray = { new URL((String)initalPropertyMap.get("osgi.framework")) };

this.frameworkClassLoader = new ChildFirstURLClassLoader(osgiURLArray, getClass().getClassLoader());

Class clazz = this.frameworkClassLoader.loadClass("org.eclipse.core.runtime.adaptor.EclipseStarter");

Method setInitialProperties = clazz.getMethod("setInitialProperties", new Class[] {Map.class });

setInitialProperties.invoke(null, new Object[] { initalPropertyMap });

Method runMethod = clazz.getMethod("startup", new Class[] { [Ljava.lang.String.class, Runnable.class });

runMethod.invoke(null, new Object[] { args });

this.frameworkContextClassLoader = Thread.currentThread().getContextClassLoader();

} catch (InvocationTargetException ite) {

Throwable t = ite.getTargetException();

if (t == null)

t = ite;

this.context.log("Error while starting Framework", t);

throw new RuntimeException(t.getMessage());

} catch (Exception e) {

this.context.log("Error while starting Framework", e);

throw new RuntimeException(e.getMessage());

} finally {

Thread.currentThread().setContextClassLoader(original);

}

}

其中 buildInitialPropertyMap 完成了 osgi 容器的配置参数初始化,osgi.install.area,osgi.configuration.area,osgi.framework 等,osgi 启动的时候会自动读取这些参数的路径。args 参数是容器配置的 commandline 参数,是 osgi 命令行启动所需的参数。

ClassLoader original = Thread.currentThread().getContextClassLoader();

获取当前线程的 contextClassLoader(AppClassLoader)接下来等完成反射调用之后,还需要把 contextClassLoader 切换回去。

通过 ChildFirstURLClassLoader 加载 EclipseStarter,反射调用 setInitialProperties 和 startup 方法完成 osgi 启动。

此时 frameworkContextClassLoader 应为 org.eclipse.core.runtime.internal.adaptor.ContextFinder

完成 osgi 启动之后,finally 应该将当前线程的 contextClassLoader 切换回去。

BridgeServlet 请求处理

BridgeServlet 作为 web 容器和 OSGI 的桥接方法即是 BridgeServlet 接收所有的 HTTP 请求同时将所有经过他的请求转发给 DelegateServlet,DelegateServlet 作为 OSGI 里面的 bundle,bundle 之前是可以互相沟通的,此时就完成了桥接工作,具体源码分析如下所示:

所有 HTTP 请求都发给 ServletBridge 的 service 方法处理。

清单 6.BridgeServlet 请求处理


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

protected void service(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException{

if (req.getAttribute("javax.servlet.include.request_uri") == null) {

String pathInfo = req.getPathInfo();

if ((pathInfo == null) && (isExtensionMapping(req.getServletPath()))) {

req = new ExtensionMappingRequest(req);

}

if ((!this.enableFrameworkControls) ||

(pathInfo == null) || (!pathInfo.startsWith("/sp_")) ||

(!serviceFrameworkControls(req, resp))) {}

}

else

{

String pathInfo = (String)req.getAttribute("javax.servlet.include.path_info");

if ((pathInfo == null) || (pathInfo.length() == )) {

String servletPath = (String)req.getAttribute("javax.servlet.include.servlet_path");

if (isExtensionMapping(servletPath)) {

req = new IncludedExtensionMappingRequest(req);

}

}

}

ClassLoader original = Thread.currentThread().getContextClassLoader();

HttpServlet servletReference = acquireDelegateReference();

if (servletReference == null) {

resp.sendError(404, "BridgeServlet: " + req.getRequestURI());

return;

}

try {   Thread.currentThread().setContextClassLoader(this.framework.getFrameworkContextClassLoader());

servletReference.service(req, resp);

} finally {

releaseDelegateReference();

Thread.currentThread().setContextClassLoader(original);

}

}

首先获取通过 acquireDelegateReference 获取 delegate,这个 delegate 是如何初始化的呢?

清单 7.BridgeServlet 注册接口


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

public static synchronized void registerServletDelegate(HttpServlet servletDelegate) {

if (instance == null) {

return;

}

if (servletDelegate == null) {

throw new NullPointerException("cannot register a null servlet delegate");

}

synchronized (instance) {

if (instance.delegate != null) {

throw new IllegalStateException("A Servlet Proxy is already registered");

}

try {

servletDelegate.init(instance.getServletConfig());

} catch (ServletException e) {

instance.getServletContext().log("Error initializing servlet delegate", e);

return;

}

instance.delegate = servletDelegate;

}

}

我们注意到有这样一个方法对 delegate 进行了初始化,这个方法是在另外一个 osgi bundle 中被调用,在 org.eclipse.equinox.http.servletbridge 的 Activator 的 start 方法中对 delegate 进行了初始化

清单 8.注册委托对象


1

2

3

4

5

public void start(BundleContext context)throws Exception

{

this.httpServiceServlet = new HttpServiceServlet();

BridgeServlet.registerServletDelegate(this.httpServiceServlet);

}

但我们注意到在 org.eclipse.equinox.http.servletbridge 的 bundle 中并没有对与 BridgeServlet 的 jar 包引用,那他是如何调用到 BridgeServlet 的 registerServletDelegate 的方法的呢?但是在其 MANIFEST 描述文件中有如下:

Import-Package: org.eclipse.equinox.servletbridge;version="1.0.0"

并且 Equinox 还提供了一个 bundle 名为 org.eclipse.equinox.servletbridge,那么这个 bundle 跟我们加入到 classpath 的 servletbridge.jar 是什么关系呢?实际上这个 bundle 只是将 servletbridge.jar 给包装起来,并将其导出

Export-Package: org.eclipse.equinox.servletbridge;version="1.1.0"

由此我们了解了 ServletBridge 的工作原理,因此我们可以实现 OSGI 直接嵌入到 Springboot Application 中。

Spring boot 启动 OSGI bundle

根据上面的分析,接下来我们完成在 Springboot 中 OSGI 的启动。

首先创建一个 maven 工程

图 2.Springboot 工程创建

pom.xml 添加对 spring-boot-starter-web 的依赖

图 3. Springboot 工程目录

SpringbootApplication 作为启动类,MyConfig 和 MyContext 完成 OSGI 启动的配置工作,分别实现 ServletConfig 和 ServletContext 接口,MyFrameworkLauncher 继承 org.eclipse.equinox.servletbridge.FrameworkLauncher 来完成 OSGI 的启动工作。

我们将需要启动的 OSGI bundle 放到当前工程的 temp 目录下,目录结构如下所示:

图 4. OSGI 插件目录

SpringbootApplication 实现如下所示:

清单 9.SpringbootApplication 实现


1

2

3

4

5

6

7

8

9

10

11

@SpringBootApplication(scanBasePackages = { "com.test.springboot" })

public class SpringbootApplication {

@Autowired

MyFrameworkLauncher framework;

public static void main(String[] args) {

MyBridge bridge = new MyBridge();

bridge.init();

System.out.println("init classloader"+ SpringbootApplication.class.getClassLoader());

SpringApplication.run(SpringbootApplication.class, args);

}

}

MyContext 需要实现 getResource 和 getAttribute 方法 ,分别来指定 osgi bundle 的的读取路径和存储路径,为简单操作,这里我都指定当前工程下的 temp 路径下,分别实现 MyContext 类中 getAttribute 和 getResources 方法。

MyFrameworkLauncher 需要初始化父类的 config 和 context 变量,并且调用父类的 deploy 和 start 方法来启动 osgi bundle。

清单 10.OSGI 框架实现


1

2

3

4

5

6

7

8

9

10

11

12

13

@Service("framework")

public class MyFrameworkLauncher extends FrameworkLauncher{

@PostConstruct

public void initialize() {

this.config = new MyConfig();

this.context = config.getServletContext();

File servletTemp = (File) this.context.getAttribute("javax.servlet.context.tempdir");

File platformDirectory = new File(servletTemp, "eclipse");

File plugins = new File(platformDirectory, "plugins");

deployBridgeExtensionBundle(plugins);

this.deploy();

this.start();

}

运行 SpringbootApplication 如下所示:

图 5. Springboot 运行结果

图 6. OSGI 命令行

我们看到 OSGI bundle 已经在 Springboot Application 中 run 起来了。

Spring boot 与 OSGI bundle 交互

想要完成 springboot 与 OSGI bundle 的交互,我们按照 servletbridge 的原理来实现类似的机制,我们也为我们的桥接插件部署一个扩展的插件使得桥接插件可以获取到系统的类加载器。

首先我们建立一个 Bridge 项目 SpringbootBridge ,分别提供 registerDelegat 和 acquireDelegateReference 方法。

清单 11. registerDelegate 接口


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

public static synchronized void registerDelegate(Object delegate) {

if (instance == null) {

System.out.println("intance is null");

return;

}

if (delegate == null) {

System.out.println("delegate is null");

throw new NullPointerException("Cannot register a null delegate");

}

synchronized (instance) {

if (instance.delegate != null) {

System.out.println("A delegate is already registered");

throw new IllegalStateException("A delegate is already registered");

}

instance.delegate = delegate;

}

}

acquireDelegateReference 方法


1

2

3

4

5

public synchronized Object acquireDelegateReference() {

if (this.delegate != null)

this.delegateReferenceCount += 1;

return this.delegate;

}

将这个 Java 项目导出 jar 包 com.test.bridge.jar

图 7. 导出 JAR 包

接下来在 Eclipse for RCP and RAP Developers 中创建一个 bundle 将我们的 com.test.bridge.jar 封装起来,并且导出。

图 8. Bundle 创建

清单 12. MANIFEST.MF 文件


1

2

3

4

5

6

7

8

9

10

11

12

13

Manifest-Version: 1.0

Bundle-ManifestVersion: 2

Bundle-Name: Bridge

Bundle-SymbolicName: com.test.bridge.wrapper

Bundle-Version: 1.0.0.qualifier

Bundle-Activator: com.test.bridge.Activator

Bundle-Vendor: TEST

Require-Bundle: org.eclipse.core.runtime

Bundle-RequiredExecutionEnvironment: JavaSE-1.6

Bundle-ActivationPolicy: lazy

Export-Package: com.test.bridge

Bundle-ClassPath: com.test.bridge.jar,

.

接下来需要创建一个 bundle 来初始化委托的对象 delegate(com.test.registry) 首先需要导入 com.test.bridge 添加到 classpath,然后调用桥接类的注册方法来注册 delegate 对象。

清单 13. 注册委托对象


1

2

3

4

public void start(BundleContext bundleContext) throws Exception {

Activator.context = bundleContext;

MyBridge.registerDelegate(new Integer(123456));

}

清单 14. MANIFEST.MF 文件


1

2

3

4

5

6

7

8

9

10

11

Manifest-Version: 1.0

Bundle-ManifestVersion: 2

Bundle-Name: Registry

Bundle-SymbolicName: com.test.registry

Bundle-Version: 1.0.0.qualifier

Bundle-Activator: com.test.registry.Activator

Bundle-Vendor: TEST

Require-Bundle: org.eclipse.core.runtime

Bundle-RequiredExecutionEnvironment: JavaSE-1.6

Bundle-ActivationPolicy: lazy

Import-Package: com.test.bridge

将这两个 bundle 导出跟其他 osgi bundle 放在一起(本文目录为${工程目录}/temp/eclipse/plugins)

图 9. 导出 plugins

此时我们再回归到 SpringbootOSGI 项目,初始化 MyBridge 对象然后调用 acquireDelegateReference,读者可能疑问这里,在 SpringbootOSGI 项目和注册插件中我们同时添加了对于 com.test.bridge.jar 的依赖,在插件注册时获取的是哪一个呢?首先添加 jar 包的依赖解决了两边的编译问题,在运行时,我们在 SpringbootOSGI 项目一启动就对 Mybridge 类进行了初始化,此时注册类已经获取了系统的类加载器也就是 SpringbootOSGI 中的 App 类加载器,所以注册类插件可以获取到 Mybridge 对象,同时注册类插件注册了委托对象,那么自然可以通过 acquireDelegateReference 获取到注册的委托对象,下面我们看程序演示:

获取 delegateReference。

清单 15. SpringbootApplication 实现


1

2

3

4

5

6

7

8

9

10

11

@SpringBootApplication(scanBasePackages = { "com.test.springboot" })

public class SpringbootApplication {

@Autowired

MyFrameworkLauncher framework;

public static void main(String[] args) {

MyBridge bridge = new MyBridge();

bridge.init();

System.out.println("init classloader"+ SpringbootApplication.class.getClassLoader());

SpringApplication.run(SpringbootApplication.class, args);

}

}

清单 16. Springboot REST API

然后提供 REST 获取 delegate


1

2

3

4

5

6

7

8

9

10

11

@RestController

@RequestMapping("/test")

public class TestRestController {

@RequestMapping(value = "/getObj", method = RequestMethod.GET)

@ResponseBody

public Object test(){

Object obj = MyBridge.getInstance().acquireDelegateReference();

System.out.println(obj);

return obj;

}

}

run SpringbootApplication 启动 OSGI bundle

图 10. 启动 bundle

可以看到 bundle 已经以 lazy 方式启动了,但是启动 registry 的时候发现 instance is null

我们已经在 SpringbootApplication 中对 MyBridge 进行了实例化,但是在 OSGI bundle (com.test.registry)去注册 delegate 的时候,MyBridge 的实例却是 null,这是因为 SpringbootApplication 和 com.test.registry 的类加载器是不相同的,SpringbootApplication 是 AppClassLoader, com.test.registry 是默认 bundle 加载器,那么解决方案就是按照上述说的,我们需要创建一个 fragment bundle,将其挂在到系统 bundle 上面,那么就能将加载器切换为 AppClassLoader.具体 fragment bundle 的创建如下所示。并再次 run SpringbootApplication 启动 OSGI bundle。

清单 17. 部署 fragement 插件


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

private void deployBridgeExtensionBundle(File plugins){

File extensionBundle = new File(plugins, "com.test.bridge.extensionbundle.jar");

File extensionBundleDir = new File(plugins, "com.test.bridge.extensionbundle");

if ((extensionBundle.exists()) || ((extensionBundleDir.exists()) && (extensionBundleDir.isDirectory()))) {

return;

}

Manifest mf = new Manifest();

Attributes attribs = mf.getMainAttributes();

attribs.putValue("Manifest-Version", "1.0");

attribs.putValue("Bundle-ManifestVersion", "2");

attribs.putValue("Bundle-Name", "bridge Extension Bundle");

attribs.putValue("Bundle-SymbolicName", "com.test.bridge.extensionbundle");

attribs.putValue("Bundle-Version", "1.0.0");

attribs.putValue("Fragment-Host", "system.bundle; extension:=framework");

String packageExports = "com.test.bridge";

attribs.putValue("Export-Package", packageExports);

try {

JarOutputStream jos = null;

try {

jos = new JarOutputStream(new FileOutputStream(extensionBundle), mf);

jos.finish();

} finally {

if (jos != null) {

jos.close();

}

}

} catch (IOException e) {

System.out.println("Error generating extension bundle" + e);

}

}

图 11. Bundle 状态

发送 GET 请求

http://localhost:8080/test/getObj

图 12. 发送 GET 请求

如果需要 osgi bunld 默认直接启动,只需要在 temp/eclipse/configuration/config.ini 中添加相应的启动配置。

[email protected]

在我们上述例子中,我们只是传递了一个 Integer 对象,如果此时我们需要传递我们自定义对象,并且需要调用相应的方法,我们需要怎么做呢?首先我们应该定义一个接口工程,在 Springboot 项目和注册 bundle 中分别添加对于接口的依赖,同时在注册 bundle 中对该接口进行实现,并在注册 bundle 的启动方法中将实现了的对象传递给桥接对象,这样在 Springboot 项目中我们就可以获取到该对象,并且进行方法的调用。

首先我们定义一个 Java 项目 spring-boot-interface

定义接口 APIInterface,添加接口方法 testFun

清单 18. 自定义接口


1

2

3

4

package com.test;

public interface APIInterface {

String testFun(int a1,String a2);

}

将这个 Java 项目导出 jar 包 com.test.interface.jar

接下来在 Eclipse for RCP and RAP Developers 中创建一个 bundle 将我们的 com.test.interface.jar 封装起来,并且将该接口 package 进行导出。

清单 19. MANIFEST.MF 文件


1

2

3

4

5

6

7

8

9

10

11

12

13

Manifest-Version: 1.0

Bundle-ManifestVersion: 2

Bundle-Name: Wrapper

Bundle-SymbolicName: com.test.inter.wrapper

Bundle-Version: 1.0.0.qualifier

Bundle-Activator: com.test.inter.wrapper.Activator

Bundle-Vendor: TEST

Bundle-RequiredExecutionEnvironment: JavaSE-1.8

Import-Package: org.osgi.framework;version="1.3.0"

Bundle-ActivationPolicy: lazy

Bundle-ClassPath: com.test.inter.jar,

.

Export-Package: com.test.inter

接下来需要在 com.test.registry 中添加对 APIInterface 的实现,并且作为 delegate 对象注册以供使用。

清单 20. 自定义接口实现


1

2

3

4

5

6

7

public class MyInterfaceImpl implements APIInterface {

@Override

public String testFun(int arg0, String arg1) {

String message = "show the para passsing: arg1 :" + arg1 +", arg0: "+ arg0;

return message;

}

}

接下来我们将实现的这个对象作为委托对象注册到桥接类中。

清单 21. 注册实现类


1

2

3

4

5

public void start(BundleContext bundleContext) throws Exception {

Activator.context = bundleContext;

MyInterfaceImpl inImpl = new MyInterfaceImpl();

MyBridge.registerDelegate(inImpl);

}

下面我们将 com.test.registry,com.test.bridge.wrapper,com.test.api.inter.wrapper 这三个插件导出 bundle 的 jar 包,与 OSGI 其他 bundle 放到同一目录下,在本文中为${SpringbootOSGI}/temp/eclipse/plugins

接下来我们在 SpringbootOSGI 中添加相应包的依赖和方法的调用,如下所示:

清单 22. Springboot REST API


1

2

3

4

5

6

7

8

9

10

11

@RestController

@RequestMapping("/test")

public class TestRestController {

@RequestMapping(value = "/getObj", method = RequestMethod.GET)

@ResponseBody

public Object test(){

APIInterface obj = (APIInterface) MyBridge.getInstance().acquireDelegateReference();

String result = obj.testFun(2018, "happy new year!");

return result;

}

}

如果此时我们运行 springbootOSGI 项目,同时把相应的 bundle 启动会发现报错

java.lang.ClassCastException: com.test.api.inter.impl.MyInterfaceImpl cannot be cast to com.test.api.inter.APIInterface

这是因为不同的 classloader 导致的,因为我们缺少了将 interface 的 bundle 挂载到系统的 bundle 上面。

清单 23. 部署 fragement bundle


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

private void deployInterfaceExtensionBundle(File plugins){

File extensionBundle = new File(plugins, "com.test.api.inter.extensionbundle.jar");

File extensionBundleDir = new File(plugins, "com.test.api.inter.extensionbundle");

if ((extensionBundle.exists()) || ((extensionBundleDir.exists()) && (extensionBundleDir.isDirectory()))) {

return;

}

Manifest mf = new Manifest();

Attributes attribs = mf.getMainAttributes();

attribs.putValue("Manifest-Version", "1.0");

attribs.putValue("Bundle-ManifestVersion", "2");

attribs.putValue("Bundle-Name", "interface Extension Bundle");

attribs.putValue("Bundle-SymbolicName", "com.test.api.inter");

attribs.putValue("Bundle-Version", "1.0.0");

attribs.putValue("Fragment-Host", "system.bundle; extension:=framework");

String packageExports = "com.test.api.inter";

attribs.putValue("Export-Package", packageExports);

try {

JarOutputStream jos = null;

try {

jos = new JarOutputStream(new FileOutputStream(extensionBundle), mf);

jos.finish();

} finally {

if (jos != null) {

jos.close();

}

}

} catch (IOException e) {

System.out.println("Error generating extension bundle" + e);

}

}

测试 API 启动情况

发送 GET 请求

http://localhost:8080/test/getObj

图 13. 发送 GET 请求

常见错误

java.lang.ClassCastException/ Loader constraint violation :loader

不同的 classloader 即使是相同的 class 文件也不是同一实例。解决方案即为使用同一 classloader,在这个项目中,报出此错是因为 bundle 和 springboot application 不是同一个 classloader 来加载的,需要为 bundle 建立一个 Fragement bundle 并且挂在到 system bundle 下。

NullPointerException

报出此错一般是因为桥接的 instance 没有被初始化,或者调用注册的 bundle 没有启动注册。

Load Class Circle Error

在使用 Springboot 打包 jar 包之后,如果我们使用的 OSGI 的 plugins 是 3.4 版本的话,启动失败,ChildFirstClassLoader 与 URLClassloader 互相调用时出现了死循环,这个问题在 osgi 更高版本已经得到了解决。

总结

本文从 Java 类加载器说起,探讨了 OSGI 的类加载器原理并对 Equinox 中的 Servletbridge 原理实现进行了详细的研究,同时扩展到使用这一原理如何在 Spring boot 应用中嵌入 OSGI 开发和 Spring boot 应用如何与 OSGI 插件之间进行相互调用。使用一个例子来对这一系列的使用做了进一步的讲解。并对它的实现方法做了进一步的探讨,这些探讨对于将 OSGI 应用嵌入到任何其他的系统中是一个启发和帮助,希望有兴趣的读者可以做进一步的了解和实现。

原文地址:https://www.cnblogs.com/itcabb/p/9283409.html

时间: 2024-08-28 10:49:28

Springboot Application 集成 OSGI 框架开发的相关文章

springboot+vue集成mavon-editor,开发在线文档知识库

先睹为快,来看下效果: 技术选型 SpringBoot.Spring Security.Oauth2.Vue-element-admin 集成mavon-editor编辑器 安装 mavon-editor npm install mavon-editor --save 引入mavon-editor 后台使用 js文件:index.js // 全局注册 import Vue from 'vue' import mavonEditor from 'mavon-editor' import 'mavo

iOS平台软件开发工具(一)-新建的工程使用CocoaPods工具集成第三方框架

CocoaPods是一款集合了上千个第三方开源库的开发工具,能够大幅度的提升团队项目的开发效率,降低时间成本. 那么就看一下CocoaPods这个工具在项目中的使用体现吧. 我们马上用ASIHTTPRequest第三方开源框架,说明如何在我们的项目中使用CocoaPods工具快速集成第三方开源框架. 首先,创建一个iOS平台下的空应用程序工程EmptySample. 然后,在终端中进入新建的工程目录,如图所示 结果如图所示. 在新建的工程根目录下,使用vi命令新建Podfile文件.(注意,不用

OSGi原理与最佳实践:第一章 OSGi框架简介(2)

OSGi原理与最佳实践:第一章 OSGi框架简介(2) 由  ValRay 发布 已被浏览4884次 共有3条评论 已被3个人收藏 2013-08-16 21:23 顶(0) 踩(0) osgi原理与最佳实践 1.1.4 开发传统类型的应用 1.1.4.1 B/S 我们首先来看一下,如何基于 OSGi 来开发 B/S 结构的应用.B/S 结构应用程序的开发,可有两个选择:一个是在 OSGi 的框架中嵌入 Http 服务器,另外一个是在 Servlet 容器中嵌入 OSGi 框架.下面分别介绍这两

OSGi原理与最佳实践:第一章 OSGi框架简介(5)Spring-DM

OSGi原理与最佳实践:第一章 OSGi框架简介(5)Spring-DM 由  ValRay 发布 已被浏览8409次 共有3条评论 已被2个人收藏 2013-08-16 21:29 顶(1) 踩(0) osgi原理与最佳实践 1.3 Spring-DM 1.3.1 简介 Spring-DM 指的是 Spring Dynamic Modules.Spring-DM 的主要目的是能够方便地将 Spring 框架 和OSGi框架结合在一起,使得使用Spring的应用程序可以方便简单地部署在OSGi环

使用SpringBoot快速构建Spring框架应用

从SpringBoot项目名称中的 Boot 可以看出来,SpringBoot的作用在于创建和启动新的基于Spring框架的项目.它的目的是帮助开发人员很容易的创建出独立运行和产品级别的基于 Spring框架的应用.SpringBoot会选择最适合的Spring子项目和第三方开源库进行整合.大部分SpringBoot应用只需要非常少的 配置就可以快速运行起来. SpringBoot包含的特性如下: 创建可以独立运行的Spring应用. 直接嵌入 Tomcat 或 Jetty 服务器,不需要部署

springboot elasticsearch 集成注意事项

文章来源: http://www.cnblogs.com/guozp/p/8686904.html 一 elasticsearch基础 这里假设各位已经简单了解过elasticsearch,并不对es进入更多的,更深层次的解释,如有必要,会在写文章专门进行es讲解. Elasticsearch是一个基于Apache Lucene(TM)的开源搜索引擎.无论在开源还是专有领域,Lucene可以被认为是迄今为止最先进.性能最好的.功能最全的搜索引擎库. 但是,Lucene只是一个库.想要使用它,你必

thinkPHP 框架开发极速快三网站开发环境部署运行调试

环境要求 1.极速快三网站开发(Q2222168869) 下载 2.thinkPHP 框架开发各地快三玩法 下载 3.Apache Maven 3.3+ 下载 4.MySql 5.7+ 下载 导入到Eclipse 1.检出JeeSite4源代码: 2.拷贝web文件夹,到你的工作目录(不包含中文和空格的目录)下,重命名为你的工程名,如:jeesite-demo 3.打开pom.xml文件,修改第13行,artifactId为你的工程名,如:<artifactId>jeesite-demo<

避免重复造轮子的UI自动化测试框架开发

一懒起来就好久没更新文章了,其实懒也还是因为忙,今年上半年的加班赶上了去年一年的加班,加班不息啊,好了吐槽完就写写一直打算继续的自动化开发 目前各种UI测试框架层出不穷,但是万变不离其宗,驱动PC浏览器的基本上底层都是selenium,驱动无线app和浏览器基本是appium.monkey之类的,底层都是基于官方支持的自动化测试框架开发而来,然后上层又做了各种封装 首先在开始计划开发自动化时,第一步是了解目前已有的自动化开发技术,上面说了,最底层的就那几种,根据实际要去测试的业务需求选择合适的自

OSGI框架学习

OSGI框架三个重要概念 OSGi框架是根据OSGi规范中定义的三个概念层设计的:模块.模块生命周期.服务. 模块层定义了OSGi模块的概念(bundle,即包含一个元数据MANIFEST.MF的JAR文件). bundle比标准JAR文件更强大,它可以声明哪些包对外可见(Export-Package):所以说它扩展了Java的访问修饰符. bundle还可以明确声明依赖哪些外部包(Import-Package),这样就可以自动地管理和验证依赖包的一致性(这个过程称为bundle解析),确保了b