zoukankan      html  css  js  c++  java
  • maven-bundle-plugin插件, 用maven构建基于osgi的web应用

     

    maven-bundle-plugin 2.4.0以下版本导出META-INF中的内容到MANIFEST.MF中  

    今天终于把maven-bundle-plugin不能导出META-INF中的内容到Export-Package中的问题解决了,因为产品用的是OSGI框架,用到的第三方JAR包需要加载META-INF/XX/XX.xml这个内容,但在运行的时候getResource返回null。

    经一番调查发现META-XX.XX这个包名没有导出,手动去修改MANIFEST.MF可以解决问题。但产品的源码中这个依赖是以maven-bundle-plugin进行打包的,在添加Export-Package:META-INF.xx的时候报错:说找不到META-INF.XX这个包名(显然这样子的包名是不合法的)。

    想来想去没有其它好的解决方案,后来发现maven-bundle-plugin 2.4以上的版本可以导出这样子的包名。

    可问题又来了,产品目前都是2.3.7的插件版本。。。。又搜索了一番,最后发现用<_failok>true</_failok>可以强制导出一些名甚至一些并不存在的包名。如下:

    <Export-Package>META-INF.xxx</Export-Package>
    <_failok>true</_failok>

    之前的Opendoc中没有涉及过此部分的内容,maven又是现在非常流行的java的工具,再加上到目前为止搭建OSGi Maven开发和部署的环境还是比较的麻烦,觉得有必要写篇这样的blog,:),在这篇blog中来看下如何搭建一个比较好用的OSGi Maven开发和部署环境,看看我在搭建一个这样的环境中的痛苦历程。
    首先说下我期望的OSGi Maven开发/部署的环境:
    1、META-INF中的manifest.mf文件可以自己控制;
          Eclipse对插件工程的开发支持的很好了,在IDE中可以很方便的去修改这个manifest.mf,所以还是自己控制更爽,当然,打包的时候需要打入自己控制的这个manifest.mf。
    2、在mvn eclipse:eclipse生成的.classpath中,能够不把所依赖的bundle的jar包放进去;
          因为在OSGi环境中,已经不再通过直接在project的classpath中依赖其他bundle的jar了来调用其他bundle中的 package,而是通过在manifest.mf中增加import-package这样的方式,所以不能再把依赖的bundle的jar打到 classpath里了,否则会很奇怪,当然,这也源于eclipse有个很好的插件开发环境,让你可以在不依赖bundle jar的情况下直接写依赖其他bundle的package的代码。
    3、在mvn clean package的时候能够把需要依赖的jar打到bundle jar中,并和META-INF/Manifest.mf文件中的Bundle-Classpath是匹配的;
          在某些bundle中可能会依赖一些jar,在META-INF中通常会去指定依赖的这些jar,放入bundle-classpath中,因此要求在打包的时候能够把这些依赖的jar打入相应的路径下。
    说完想法后,首先想到的是在OSGi界中支持maven环境的大名鼎鼎的maven-bundle-plugin(http://felix.apache.org/site/apache-felix-maven-bundle-plugin-bnd.html),maven-bundle-plugin基于Peter写的bnd实现,不说废话了,按照自己期望的环境来使用maven-bundle-plugin进行搭建:

    步骤一
    按照上面页面的指导,在pom.xml中增加maven-bundle-plugin先,接着按照自己的想法,要自己控制manifest.mf,于是在plugin的configuration中增加:
    <_include>META-INF/MANIFEST.MF</_include>
    满心欢喜的等待着完美的结果,可惜....不如人意呀,打包出来的jar里面的MANIFEST.MF已经物是人非了,完全不是自己控制的那个,插件给你 自动的加上了一堆的import-package、private-package、export-package,我知道这个插件是基于bnd来写的, 但没想到竟然连自己控制的权力都不给我了,完全仍然是通过bnd来计算出import-package、private-package什么的;

    步骤二
    好,在伤心过后接着仔细看,还好,在plugin的configuration中可以自己指定export-package、private- package这些,于是继续欣喜的使用,这两个倒是控制住了,但....import-package自己是不能控制的,这个是不行的,这样就导致了必 须同时自己维护pom.xml以及project中的META-INF/MANIFEST.MF,让它们保持一致,否则可能导致打出来的包和你在 project中运行的表现不一致,并且bnd计算出来的import-package并不是我想要的,有点太复杂了,还是自己控制比较好;

    步骤三
    伤心到极点了,其实到目前为止,已经可以确定maven-bundle-plugin,也是OSGi maven中唯一的插件,不能满足我的需求,不过还是继续看看这个插件其他方面的表现,惊喜的发现有一点倒是做的不错的,它支持一个<Embed-Dependency>*;scope=compile|runtime</Embed-Dependency>,有了这个标签后,它可以直接把依赖的jar打入bundle jar包中,并且相应的自动在bundle-classpath中加上了,这点倒是不错的,看起来与我期望的环境的第3点是比较匹配的,可惜了。
    还有就是,很当然的,它没法做到控制mvn eclipse:eclipse时生成的.classpath不包含bundle jar的引用。

    按照上面的三个步骤,总结下,有些时候智能是好事,但maven-bundle-plugin就是过于智能了,为什么不给点权力给使用者呢,因此这个插件 要提升到完全可用的情况的话,还需要提供下让使用者自己控制MANIFEST.MF的权力,相信这点要做到并不困难,而且做到这点后基本也就可以使用了。

    继续寻找,于是静心分析了下自己的需求,貌似可以自己通过maven现有的几个插件来达成自己的愿望,于是开始了组合拳:
    1、MANIFEST.MF文件自己控制
         不就是要自己控制这个文件嘛,OK,干脆,就只用maven-jar-plugin,这个插件允许指定所使用的MANIFEST.MF文件,于是,尝试着在这个plugin的configuration中增加:
         <archive>
             <manifestFile>META-INF/MANIFEST.MF</manifestFile>
         </archive>
         恩,很顺利,开门红呀,打出来的jar包中的MANIFEST.MF文件就是自己的那个。
    2、mvn eclipse:eclipse生成的.classpath中要去掉bundle jar的依赖
          对于我这么一个对maven不是那么熟悉的人来讲,这个有点复杂,于是不断的google,甚至是翻看了maven-eclipse-plugin的源码...
          最终终于功夫不负有心人,找到一个简单的办法:
          首先将工程方式指定为pde,也就是eclipse插件工程,在maven-eclipse-plugin的configuration配置中增加<pde>true</pde>;
          然后在pom.xml中将不希望生成到.classpath中依赖的scope指定为provided;
          心惊胆战的开始运行mvn eclipse:eclipse,OH YEAH!,成功!
          ps: 另外也可以通过在maven-eclipse-plugin的configuration中增加exclude配置,来将某些依赖从.classpath中去掉,当然,这方法没有上面的易用。
    3、在mvn clean package的时候能够把需要依赖的jar打到bundle jar中,并和META-INF/Manifest.mf文件中的Bundle-Classpath是匹配的;
          恩,这点,印象中貌似maven是有支持的,于是继续开始找,终于找到了maven- dependency-plugin(之前还找到了一个maven-shade-plugin,也很帅,不过不满足需求),通过这个插件可以把需要的依赖 的jar都复制到某个指定的目录中去,但记得把这个指定的目录加入到maven-jar-plugin的resources目录里面去,否则这些jar文 件是不会出现在你的bundle jar里的。

    OK,通过上面这套组合拳,终于达成了目的,看来有必要找个时间写个好用点的maven的OSGi插件,否则真的忒折腾了,上面这个方法仍然有几个痛苦的地方:
    1、如果你的bundle中需要export其中依赖的lib的package的话;
         mvn eclipse:eclipse之后你会发现其他bundle即使import了这个package,也会调用不到,这里的原因在于生成 的.classpath中所依赖的那个lib的exported属性没有设置为true,google了maven eclipse插件,貌似这是它的一个缺失的功能,因此在目前只能是mvn eclipse:eclipse后,再到build path里把这些lib exported出去。
    2、还是得在插件的pom.xml中配置所依赖的其他的bundle;
         这是为了让你在mvn clean package的时候能通过,这点还是挺郁闷的,如果能自己去找到的话就好了(Felix构建bundle repo是有潜质做到这点的),:),这样导致了在每次在import package后,还得记得去修改了pom,否则的话在eclipse compile什么都正常,到了maven里就挂了。
    因此,如果能解决上面两点的话,那将更加完美。

    用maven构建基于osgi的web应用

    •   如何用maven创建osgi项目
    •   如何启动osgi框架
    •   如何在osgi外部与osgi框架通信
    •   如何应用jndi配置资源引用
    •   如何发布osgi服务
    •   如何创建基于osgi的web应用项目
    1.用maven创建osgi项目
       用maven的目的是使开发相率更高,而且不需要自己修改manifest.mf文件,用maven插件即可自动完成创建manifest并打包。创建这种项目的要求如下:
       项目包类型为:<packaging>bundle</packaging>
       要使用maven自动创建manifest.mf文件,需要插件maven-bundle-plugin,配置如下:
      <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.felix</groupId>
                    <artifactId>maven-bundle-plugin</artifactId>
                    <version>1.4.0</version>
                    <extensions>true</extensions>
                    <configuration>
                        <instructions>
                            <Bundle-SymbolicName>${pom.groupId}.${pom.artifactId}</Bundle-SymbolicName>
                            <Bundle-Name>${pom.name}</Bundle-Name>
                            <Bundle-Version>${pom.version}</Bundle-Version>
                            <Bundle-Activator>com.javaworld.sample.tomcat.webActivator</Bundle-Activator>
                            <Export-Package>com.javaworld.sample.tomcat</Export-Package>
                            <Import-Package>
                                org.osgi.framework
                            </Import-Package>
                        </instructions>
                    </configuration>
                </plugin>
            </plugins>
        </build>
      使用maven-bundle-plugin请参考:http://wso2.org/library/tutorials/develop-osgi-bundles-using-maven-bundle-plugin ,配置好后在项目目录下执行mvn install即可
    同时还要引用osgi框架支持依赖包:
        <dependencies>
                <dependency>
                <groupId>org.eclipse.osgi</groupId>
                <artifactId>framework</artifactId>
                <version>3.4.2.R34x_v20080826</version>
                <type>jar</type>
                <scope>provided</scope>
            </dependency>
        </dependencies>
     注意这里的type和scope,这种用法不仅表名bundel项目可以引用第三方bundle项目,普通项目也可以。下面将会看到。

    2.启动osgi框架,并在外部与osgi框架通信
     这里用Equinox OSGi框架,让EquinoxContainer 实现接口OsgiServices 并实现OsgiContainable 的start和stop方法。对于框架的启动和停止,我们放在tomcat启动和停止时触发。
    首先用maven创建普通的项目,在pom中添加依赖
        <dependencies>
            <dependency>
                <groupId>apache-tomcat</groupId>
                <artifactId>catalina</artifactId>
                <version>5.5.12</version>
                <type>jar</type>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.eclipse.osgi</groupId>
                <artifactId>services</artifactId>
                <version>3.1.200.v20071203</version>
                <type>jar</type>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.eclipse.osgi</groupId>
                <artifactId>framework</artifactId>
                <version>3.4.2.R34x_v20080826</version>
                <type>jar</type>
                <scope>provided</scope>
            </dependency>
        </dependencies>

    public interface OsgiServices {

        public Object getOSGiService(String serviceName);

        public Class<?> getBundleClass(String bundleName, String className)
                throws ClassNotFoundException;
    }

    //OsgiContainable 扩展OsgiServices 并提供start和stop接口
    public class EquinoxContainer implements OsgiContainable {
        private static Logger log = Logger
                .getLogger(EquinoxContainer.class.getName());

        private static BundleContext context;

        private static String equinoxHome;

        private static EquinoxContainer instance;

        private EquinoxContainer() {
        }

        public static EquinoxContainer getInstance() {
            if (instance == null) {
                instance = new EquinoxContainer();
            }
            return instance;
        }

        public void start() throws Exception {

            configFramework();

            EclipseStarter.run(new String[] { "-console" }, null);
            // get BundleContext
            context = EclipseStarter.getSystemBundleContext();
        }

        private void configFramework() {
            String catalinaHome = System.getProperty("catalina.home");
            log.info("The catalina home is " + catalinaHome);
            File filePath = new File(catalinaHome, "/osgi/equinox");
            if (!filePath.exists()) {
                throw new IllegalStateException(
                        "Can not find Equinox runtime on the specified the directory. "
                                + catalinaHome + "/osgi/equinox");
            }

            equinoxHome = filePath.getAbsolutePath();
            log.info("The osgi home is " + equinoxHome);

            filePath = new File(filePath, "plugins");
            String bundlePath = filePath.getAbsolutePath();
            log.info("The bundle home is " + bundlePath);
            // set the bundle home
            FrameworkProperties.setProperty("osgi.syspath", bundlePath);

            filePath = new File(equinoxHome, "configuration");
            String confHome = filePath.getAbsolutePath();
            // set the configuration home
            FrameworkProperties.setProperty("osgi.configuration.area", confHome);
            log.info("The configuration home is " + confHome);
            //config.ini中至少需要配置osgi.bundles需要启动的bundles
            Properties prop = loadConfigProperties(confHome, "/config.ini");
            setSystemProperties(prop);

            // set the framework properties
            FrameworkProperties.setProperty("osgi.noShutdown", "true");
            FrameworkProperties.setProperty("eclipse.ignoreApp", "true");
            FrameworkProperties.setProperty("osgi.bundles.defaultStartLevel", "4");
        }
        。。。
    }
    为了让tomcat等启动时执行EquinoxContainer 的start和stop,需要实现LifecycleListener接口,并在tomcat server.xml文件中配置Listener :
    <Listener className="com.javaworld.sample.tomcat.osgi.OsgiLifecycleListener" osgiType="Equinox"/>

    public class OsgiLifecycleListener implements LifecycleListener {

        private static Logger log = Logger.getLogger(OsgiLifecycleListener.class
                .getName());

        private static OsgiLifecycleListener listener;

       
        private String osgiType = "Equinox";

        private OsgiContainable osgiContent = null;

        public OsgiLifecycleListener() {
        }

        public static OsgiLifecycleListener getInstance() {
            return listener;
        }

        @Override
        public void lifecycleEvent(LifecycleEvent event) {
            try {
                if (Lifecycle.INIT_EVENT.equals(event.getType())) {
                    log.info("Initializing osgi content. osgi type is " + osgiType);
                    initContent();
                    log.info("The osgi content initialized. osgi type is "
                            + osgiType);
                } else if (Lifecycle.START_EVENT.equals(event.getType())) {
                    log.info("Starting osgi service.");
                    osgiContent.start();
                    log.info("The osgi service started.");
                } else if (Lifecycle.STOP_EVENT.equals(event.getType())) {
                    log.info("Stopping osgi service.");
                    osgiContent.stop();
                    log.info("The osgi service stopped.");
                }
            } catch (Exception e) {
                log.info(e.getMessage());
                System.exit(-1);
            }
        }

        private void initContent() throws Exception {
            listener=this;
            log.info("listener"+listener.toString());
            osgiContent = OsgiContainerFactory.getInstance().getOsgiContent(osgiType);
        }
        。。。。
    }
    随着tomcat的启动将执行OsgiLifecycleListener,并通过OsgiLifecycleListener启动osgi框架,后者将完成bundles的安装和服务的注册等。

    3。在osgi外部与osgi框架通信
    OSGi通过BundleContext来获取OSGi服务,因此想在OSGi容器外获取OSGi服务,首要的问题就是要在OSGi容器外获取到BundleContext,EclipseStarter 中 提供了一个getSystemBundle- Context的方法,通过这个方法可以轻松拿到BundleContext,而通过BundleContext则可以轻易拿到OSGi服务的实例。不过 这个时候要注意的是,如果想执行这个OSGi 服务实例的方法,还是不太好做的,因为容器外的classloader和OSGi服务实例的class所在的classloader并不相同,因此不太好 按照java对象的方式直接去调用,更靠谱的是通过反射去调用。 如果想在容器外获取到OSGi容器里插件的class,一个可选的做法是通过BundleContext获取到Bundle,然后通过Bundle 来加载 class,采用这样的方法加载的class就可以保证是相同的。
       
        public  static Object getOSGiService(String serviceName) {
            ServiceReference serviceRef = context.getServiceReference(serviceName);
            if (serviceRef == null)
                return null;
            return context.getService(serviceRef);
        }
       
        public  static Class<?> getBundleClass(String bundleName, String className)
                throws Exception {
            Bundle[] bundles = context.getBundles();
            for (int i = 0; i < bundles.length; i++) {
                if (bundleName.equalsIgnoreCase(bundles[i].getSymbolicName())) {
                    return bundles[i].loadClass(className);
                }
            }
            return null;
        }
    在实现了OSGi容器外与OSGi交互之后,通常会同时产生一个需求,就是在OSGi容器内的插件要加载OSGi容器外的类,例如OSGi容器内提 供了一个mvc框架,而Action类则在OSGi容器外由其他的容器负责加载,那么这个时候就会产生这个需求了,为了做到这点,有一个比较简单的解决方 法,就是编写一个Bundle,在该Bundle中放置一个允许设置外部ClassLoader的OSGi服务,例如:

    Java代码  
    1. public   class  ClassLoaderService{    
    2.     public   void  setClassLoader(ClassLoader classloader);    
    3. }   
     

          基于上面的方法,在外部启动Equinox 的类中去反射执行ClassLoaderService这个OSGi服务的setClassLoader方法,将外部的classloader设置进来, 然后在OSGi容器的插件中要加载OSGi容器外的类的时候就调用下面这个ClassLoaderService去完成类的加载。


     4.应用jndi配置资源引用
     上面知道要在osgi外面引用osgi服务或者osgi类,唯一可行的办法是获取BundleContext,这点是可以在osgi框架获取,这样一来BundleContext就是我们与osgi通信的桥梁了,OsgiServices 也是通过此桥梁与osgi框架通信,为方便对OsgiServices 的获取,现在将OsgiServices配置到jndi。同样把Resource添加到server.xml的Host标签中:
        <Resource name="osgi/services" auth="Container"
           type="com.javaworld.sample.tomcat.osgi.OsgiServices"
           factory="com.javaworld.sample.tomcat.osgi.OsgiServicesObjectFactory" />
    在tomcat 的web.xml中添加Resource引用:
        <resource-env-ref>
           <description>osgi services</description>
           <resource-env-ref-name>osgi/services</resource-env-ref-name>
           <resource-env-ref-type>
               com.javaworld.sample.tomcat.osgi.OsgiServices
           </resource-env-ref-type>
        </resource-env-ref>

    public class OsgiServicesObjectFactory implements ObjectFactory {

        private static Logger log = Logger.getLogger(OsgiServicesObjectFactory.class
                .getName());

        private OsgiServices osgiServices;

        @Override
        public Object getObjectInstance(Object obj, Name name, Context nameCtx,
                Hashtable<?, ?> environment) throws Exception {

            Map<String, String> attMap = new HashMap<String, String>();
            // Customize the bean properties from our attributes
            Reference ref = (Reference) obj;
            Enumeration<RefAddr> addrs = ref.getAll();
            while (addrs.hasMoreElements()) {
                RefAddr addr = addrs.nextElement();
                String attrName = addr.getType();
                String value = (String) addr.getContent();
                log.info("the attribute is (" + attrName + " == " + value + ")");
                attMap.put(attrName, value);
            }
            log.info("getObjectInstance called.");
            osgiServices=OsgiLifecycleListener.getInstance().getOsgiServices();
            return osgiServices;
        }
    }
     5. 发布osgi服务
    public interface HelloService {
        public String sayHello();
    }

    public class webActivator implements BundleActivator {
        ServiceRegistration serviceRegistration;

       
        public void start(BundleContext bundleContext) {
            serviceRegistration = bundleContext.registerService(
                    HelloService.class.getName(), new HelloServiceImpl(),
                    new Properties());
        }

       
        public void stop(BundleContext bundleContext) {
            serviceRegistration.unregister();
        }
    }

     6.创建基于osgi的web应用项目
    创建<packaging>war</packaging>web项目,添以下类:
    public class TomcatWebCall {
        public static String invokeService() {
            try {
                HelloService service = OsgiServiceFactory.getOsgiService(
                        "osgi/services", HelloService.class);
                return service.sayHello();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
                e.printStackTrace();
            }
            return null;
        }
    }

    public class OsgiServiceFactory {
        public static <T> T getOsgiService(String jndiName,Class<T> clazz) {
            OsgiServices services = (OsgiServices) Utils.lookup(jndiName);
            OsgiServiceInvocationHandler handler = new OsgiServiceInvocationHandler(
                    services, clazz.getName());
            return Utils.getProxyObject(clazz,handler);
        }

        public class OsgiServiceInvocationHandler implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            try {
                Object service = services.getOSGiService(servicName);
                if (service == null) {
                    throw new IllegalServiceException("Cann't find out osgi service:"
                            + servicName);
                }
               
                Method[] methods = service.getClass().getDeclaredMethods();
                for (Method meth : methods) {
                    if (meth.getName().equals(method.getName())) {
                        return meth.invoke(service, args);
                    }
                }
                。。。
             }
        }
    }

    public class Utils {
       
        private static final String CONTAINER_PREFIX = "java:comp/env/";
       
        public static <T> T getProxyObject(Class<T> clazz, InvocationHandler handler) {
            Object o = Proxy.newProxyInstance(clazz.getClassLoader(),
                    new Class[] { clazz }, handler);
            return clazz.cast(o);
        }
       
        private static String convertJndiName(String jndiName) {
            if (!jndiName.startsWith(CONTAINER_PREFIX)
                    && jndiName.indexOf(':') == -1) {
                jndiName = CONTAINER_PREFIX + jndiName;
            }
            return jndiName;
        }
       
        public static Object lookup(String jndiName) {
            String convertedName = convertJndiName(jndiName);
            Object jndiObject = null;
            try {
                Context context = new InitialContext();
                jndiObject = context.lookup(convertedName);
            } catch (NamingException e) {
                throw new IllegalServiceException(
                        "The JNDI OSGi services name is error.", e);
            } catch (Exception e) {
                throw new IllegalServiceException(
                        "The JNDI OSGi services can not be initialized.", e);
            }

            return jndiObject;
        }
    }
    简单的jsp测试:
    <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="ISO-8859-1"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <%@page import="com.javaworld.sample.tomcat.TomcatWebCall"%>
    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
                <title>Demo invoke HelloService</title>
        </head>
        <body>
            <%=TomcatWebCall.invokeService() %>
        </body>
    </html>
  • 相关阅读:
    centos7部署jmeter+grafana+telegraf+prometheus/influxdb,构建性能测试可视化实时监控(三)
    python+uvicorn+fastapi (二)
    python+uvicorn+fastapi (一)
    vue系列 <router-link>属性
    vue系列 vue-router安装配置呈现DEMO
    vue系列 url的hash和HTML5的history
    vue系列 前后端渲染+前后端路由
    python框架Django实战商城项目之工程搭建
    Socket--selecct的用法
    迭代器与生成器的区别
  • 原文地址:https://www.cnblogs.com/timssd/p/5779499.html
Copyright © 2011-2022 走看看