Java Web应用集成OSGI
对OSGI的简单理解
就像Java Web应用程序需要运行在Tomcat、Weblogic这样的容器中一样。程序员开发的OSGI程序包也需要运行在OSGI容器中。目前主流的OSGI容器包括:Apache Felix以及Eclipse Equinox。OSGI程序包在OSGI中称作Bundle
。
Bundle
的整个生命周期都交与OSGI容器进行管理。可以在不停止服务的情况下,对Bundle
进行加载和卸载,实现热部署。
Bundle
对于外部程序来说就是一个黑盒。他只是向OSGI容器中注册了供外部调用的服务接口,至于实现则对外部不可见。不同的Bundle
之间的调用,也需要通过OSGI容器来实现。
Bundle如何引入jar
刚才说到Bundle
是一个黑盒,他所有实现都包装到了自己这个“盒子”中。在开发Bundle
时,避免不了引用一些比如Spring、Apache commons等开源包。在为Bundle
打包时,可以将当前Bundle
依赖jar与Bundle
的源码都打包成一个包(all-in-one)。这种打包结果就是打出的包过大,经常要几兆或者十几兆,这样当然我们是不可接受的。下面就介绍一种更优的做法。
Bundle与OSGI容器的契约
Bundle
可以在MANIFEST.MF
配置文件中声明他要想运行起来所要的包以及这些包的版本 !!!而OSGI容器在加载Bundle
时会为Bundle
提供Bundle
所需要的包 !!!在启动OSGI容器时,需要在OSGI配置文件中定义org.osgi.framework.system.packages.extra
,属性。这个属性定义了 OSGI容器能提供的包以及包的版本。OSGI在加载Bundle
时,会将他自己能提供的包以及版本与Bundle所需要的包以及版本列表进行匹配。如果匹配不成功则直接抛出异常:
Unable to execute command on bundle 248: Unresolved constraint in bundle
com.osgi.demo2 [248]: Unable to resolve 248.0: missing requirement [248.0] osgi
.wiring.package; (&(osgi.wiring.package=org.osgi.framework)(version>=1.8.0)(!(version>=2.0.0)))
也可能加载Bundle
通过,但是运行Bundle
时报ClassNotFoundException
。这些异常都由于配置文件没配置造成的。理解了配置文件的配置方法,就能解决60%的异常。
Import-Package
在Bundle
的Import-Package
属性中通过以下格式配置:
<!--pom.xml-->
<Import-Package>
javax.servlet,
javax.servlet.http,
org.xml.sax.*,
org.springframework.beans.factory.xml;org.springframework.beans.factory.config;version=4.1.1.RELEASE,
org.springframework.util.*;version="[2.5,5.0]"
</Import-Package>
- 包与包之间通过逗号分隔
- 可以使用*这类的通配符,表示这个包下的所有包。如果不想使用通配符,则同一个包下的其他包彼此之间可以使用
;
分隔。 - 如果需要指定包的版本则在包后面增加
;version="[最低版本,最高版本]"
。其中[
表示大于等于、]
表示小于等于、)
表示小于。
org.osgi.framework.system.packages.extra
语法与Impirt-Package
基本一致,只是org.osgi.framework.system.packages.extra
不支持通配符。
错误的方式
org.springframework.beans.factory.*;version=4.1.1.RELEASE
正确的方式:
org.springframework.beans.factory.xml;org.springframework.beans.factory.config;version=4.1.1.RELEASE,
Class文件加载
在我们平时开发中有些情况下加载一个Class会使用this.getClassLoader().loadClass
。但是通过这种方法加载Bundle
中所书写的类的class
会失败,会报ClassNotFoundException
。在Bundle
需要使用下面的方式来替换classLoader.loadClass
方法
public void start(BundleContext context) throws Exception {
Class classType = context.loadClass(name);
}
Bundle中加载Spring配置文件时的问题
由于Bundle
加载Class
的特性,会导致在加载Spring配置文件时报错。所以需要将Spring启动所需要的ClassLoader进行更改,使其调用BundleContext.loadClass
来加载Class。
String xmlPath = "";
ClassLoader classLoader = new ClassLoader(ClassUtils.getDefaultClassLoader()) { @Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
return currentBundle.loadClass(name);
} catch (ClassNotFoundException e) {
return super.loadClass(name);
}
}
};
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.setBeanClassLoader(classLoader);
GenericApplicationContext ctx = new GenericApplicationContext(beanFactory);
ctx.setClassLoader(classLoader);
DefaultResourceLoader resourceLoader = new DefaultResourceLoader(classLoader) {
@Override
public void setClassLoader(ClassLoader classLoader) {
if (this.getClassLoader() == null) {
super.setClassLoader(classLoader);
}
}
};
ctx.setResourceLoader(resourceLoader);
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(ctx);
reader.loadBeanDefinitions(xmlPath);
ctx.refresh();
Web应用集成OSGI
这里选用了Apache Felix
来开发,主要是因为Apache Felix
是Apache的顶级项目。社区活跃,对OSGI功能支持比较完备,并且文档例子比较全面。
其实OSGI支持两种方式来部署Bundle
。
- 单独部署OSGI容器,通过OSGI自带的Web中间件(目前只有jetty)来对外提供Web服务
- 将OSGI容器嵌入到Web应用中,然后就可以使用Weblogic等中间件来运行Web应用
从项目的整体考虑,我们选用了第二种方案。
BundleActivator开发
开发Bundle
时,首先需要开发一个BundleActivator
。OSGI在加载Bundle
时,首先调用BundleActivator
的start
方法,对Bundle
进行初始化。在卸载Bundle
时,会调用stop
方法来对资源进行释放。
public void start(BundleContext context) throws Exception;
public void stop(BundleContext context) throws Exception;
在start
方法中调用context.registerService
来完成对外服务的注册。
Hashtable props = new Hashtable();
props.put("servlet-pattern", new String[]{"/login","/logout"})
ServiceRegistration servlet = context.registerService(Servlet.class, new DispatcherServlet(), props);
- context.registerService方法的第一个参数表示服务的类型,由于我们提供的是Web请求服务,所以这里的服务类型是一个
javax.servlet.Servlet
,所以需要将javax.servlet.Servlet
传入到方法中 - 第二个参数为服务处理类,这里配置了一个路由Servlet,其后会有相应的程序来处理具体的请求。
- 第三个参数为
Bundle
对外提供服务的属性。在例子中,在Hashtable
中定义了Bundle
所支持的servlet-pattern
。OSGI容器所在Web应用通过Bundle
定义的servlet-pattern
判断是否将客户请求分发到这个Bundle
。servlet-pattern
这个名称是随意起的,并不是OSGI框架要求的名称。
应用服务集成OSGI容器
- 首先工程需要添加如下依赖
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.framework</artifactId>
<version>5.6.10</version>
</dependency> <dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.http.bundle</artifactId>
<version>3.0.0</version>
</dependency> <dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.http.bridge</artifactId>
<version>3.0.18</version>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.http.proxy</artifactId>
<version>3.0.0</version>
</dependency>
- 然后在
web.xml
中添加
<listener>
<listener-class>org.apache.felix.http.proxy.ProxyListener</listener-class>
</listener>
- 开发
ServletContextListener
用以初始化并启动OSGI容器
请参考Apache Felix
提供的例子程序。例子中提供的ProvisionActivator
会扫描/WEB-INF/bundles/
,加载其中的Bundle
包。(当然例子中提供的ProvisionActivator并不带有Bundle
自动发现注册等机制,这些逻辑需要自行增加。请参照后续的Bundle自动加载章节)
路由开发
通过上面的配置,只是将OSGI容器加载到了Web应用中。还需要修改Web应用程序路由的代码。
- 在
Bundle
加载到OSGI容器中后,可以通过bundleContext.getBundles()
方法获取到OSGI容器中的所有已经加载的Bundle
。 - 可以调用
Bundle
的bundle.getRegisteredServices()
方法获取到该Bundle
对外提供的所有服务服务。getRegisteredServices
方法返回ServiceReference
的数组。前文中我们调用context.registerService(Servlet.class, new DispatcherServlet(), props)
我们已经注册了一个服务,getRegisteredServices
返回的数据只有一个ServiceReference
对象。 - 获取
Bundle
所能提供的服务
可以通过ServiceReference
对象的getProperty
方法获取context.registerService
中传入的props
中的值。这样我们就能通过调用ServiceReference.getProperty
方法获取到该Bundle
所能提供的服务。 - 通过上面提供的接口,我们可以将
Bundle
对应ServiceReference
以及Bundle
对应的servlet-pattern
进行缓存。当用户请求进入到应用服务器后,通过缓存的servlet-pattern
可以判断Bundle
是否能提供用户所请求的服务,如果可以提供通过下面的方式,来调用Bundle
所提供的服务。
ServiceReference sr = cache.get(bundleName);
HttpServlet servlet = (HttpServlet) this.bundleContext.getService(sr);
servlet.service(request, response);
Bundle自动加载
在Apache Felix
例子中提供的ProvisionActivator
,只会在系统启动时加载/WEB-INF/bundles/
目录下的Bundle
。当文件夹下的Bundle
文件有更新时,并不会自动更新OSGI容器中的Bundle
。所以Bundle
自动加载的逻辑,需要我们自己增加。下面提供实现的思路:
- 在第一次加载文件夹下的
Bundle
时,记录Bundle
包所对应的最后的更新时间。 - 在程序中创建一个独立线程,用以扫描
/WEB-INF/bundles/
目录,逐个的比较Bundle
的更新时间。如果与内存中的不相符合,则从OSGI中获取Bundle
对象然后调用其stop
以及uninstall
方法,将其从OSGI容器中卸载。 - 卸载后,再调用
bundleContext.installBundle
以及bundle.start
将最新的Bundle
加载到OSGI容器中
BundleListener
最后一个问题,通过上面的方式,可以实现Bundle
的自动加载。但是刚才我们介绍了,在路由程序中,我们会缓存OSGI容器中所有的Bundle
所对应的ServiceReference
以及所有Bundle
所对应的servlet-pattern
。所以Bundle
自动更新后,我们还需要将路由程序中的缓存同步的进行更新。
可以通过向bundleContext
中注册BundleListener
,当OSGI容器中的Bundle
状态更新后,会调用BundleListener
的bundleChanged
回调方法。然后我们可以在bundleChanged
回调方法中书写更新路由缓存的逻辑
this.bundleContext.addBundleListener(new BundleListener() {
@Override
public void bundleChanged(BundleEvent event) {
if (event.getType() == BundleEvent.STARTED) {
initBundle(event.getBundle());
} else if (event.getType() == BundleEvent.UNINSTALLED) {
String name = event.getBundle().getSymbolicName();
indexes.remove(name);
}
}
});
*:first-child {
margin-top: 0 !important;
}
.markdown-body>*:last-child {
margin-bottom: 0 !important;
}
.markdown-body .headeranchor-link {
position: absolute;
top: 0;
bottom: 0;
left: 0;
display: block;
padding-right: 6px;
padding-left: 30px;
margin-left: -30px;
}
.markdown-body .headeranchor-link:focus {
outline: none;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
position: relative;
margin-top: 1em;
margin-bottom: 16px;
font-weight: bold;
line-height: 1.4;
}
.markdown-body h1 .headeranchor,
.markdown-body h2 .headeranchor,
.markdown-body h3 .headeranchor,
.markdown-body h4 .headeranchor,
.markdown-body h5 .headeranchor,
.markdown-body h6 .headeranchor {
display: none;
color: #000;
vertical-align: middle;
}
.markdown-body h1:hover .headeranchor-link,
.markdown-body h2:hover .headeranchor-link,
.markdown-body h3:hover .headeranchor-link,
.markdown-body h4:hover .headeranchor-link,
.markdown-body h5:hover .headeranchor-link,
.markdown-body h6:hover .headeranchor-link {
height: 1em;
padding-left: 8px;
margin-left: -30px;
line-height: 1;
text-decoration: none;
}
.markdown-body h1:hover .headeranchor-link .headeranchor,
.markdown-body h2:hover .headeranchor-link .headeranchor,
.markdown-body h3:hover .headeranchor-link .headeranchor,
.markdown-body h4:hover .headeranchor-link .headeranchor,
.markdown-body h5:hover .headeranchor-link .headeranchor,
.markdown-body h6:hover .headeranchor-link .headeranchor {
display: inline-block;
}
.markdown-body h1 {
padding-bottom: 0.3em;
font-size: 2.25em;
line-height: 1.2;
border-bottom: 1px solid #eee;
}
.markdown-body h2 {
padding-bottom: 0.3em;
font-size: 1.75em;
line-height: 1.225;
border-bottom: 1px solid #eee;
}
.markdown-body h3 {
font-size: 1.5em;
line-height: 1.43;
}
.markdown-body h4 {
font-size: 1.25em;
}
.markdown-body h5 {
font-size: 1em;
}
.markdown-body h6 {
font-size: 1em;
color: #777;
}
.markdown-body p,
.markdown-body blockquote,
.markdown-body ul,
.markdown-body ol,
.markdown-body dl,
.markdown-body table,
.markdown-body pre,
.markdown-body .admonition {
margin-top: 0;
margin-bottom: 16px;
}
.markdown-body hr {
height: 4px;
padding: 0;
margin: 16px 0;
background-color: #e7e7e7;
border: 0 none;
}
.markdown-body ul,
.markdown-body ol {
padding-left: 2em;
}
.markdown-body ul ul,
.markdown-body ul ol,
.markdown-body ol ol,
.markdown-body ol ul {
margin-top: 0;
margin-bottom: 0;
}
.markdown-body li>p {
margin-top: 16px;
}
.markdown-body dl {
padding: 0;
}
.markdown-body dl dt {
padding: 0;
margin-top: 16px;
font-size: 1em;
font-style: italic;
font-weight: bold;
}
.markdown-body dl dd {
padding: 0 16px;
margin-bottom: 16px;
}
.markdown-body blockquote {
padding: 0 15px;
color: #777;
border-left: 4px solid #ddd;
}
.markdown-body blockquote>:first-child {
margin-top: 0;
}
.markdown-body blockquote>:last-child {
margin-bottom: 0;
}
.markdown-body table {
display: block;
width: 100%;
overflow: auto;
word-break: normal;
word-break: keep-all;
}
.markdown-body table th {
font-weight: bold;
}
.markdown-body table th,
.markdown-body table td {
padding: 6px 13px;
border: 1px solid #ddd;
}
.markdown-body table tr {
background-color: #fff;
border-top: 1px solid #ccc;
}
.markdown-body table tr:nth-child(2n) {
background-color: #f8f8f8;
}
.markdown-body img {
max-width: 100%;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.markdown-body code,
.markdown-body samp {
padding: 0;
padding-top: 0.2em;
padding-bottom: 0.2em;
margin: 0;
font-size: 85%;
background-color: rgba(0,0,0,0.04);
border-radius: 3px;
}
.markdown-body code:before,
.markdown-body code:after {
letter-spacing: -0.2em;
content: "\00a0";
}
.markdown-body pre>code {
padding: 0;
margin: 0;
font-size: 100%;
word-break: normal;
white-space: pre;
background: transparent;
border: 0;
}
.markdown-body .codehilite {
margin-bottom: 16px;
}
.markdown-body .codehilite pre,
.markdown-body pre {
padding: 16px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: #f7f7f7;
border-radius: 3px;
}
.markdown-body .codehilite pre {
margin-bottom: 0;
word-break: normal;
}
.markdown-body pre {
word-wrap: normal;
}
.markdown-body pre code {
display: inline;
max-width: initial;
padding: 0;
margin: 0;
overflow: initial;
line-height: inherit;
word-wrap: normal;
background-color: transparent;
border: 0;
}
.markdown-body pre code:before,
.markdown-body pre code:after {
content: normal;
}
/* Admonition */
.markdown-body .admonition {
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
position: relative;
border-radius: 3px;
border: 1px solid #e0e0e0;
border-left: 6px solid #333;
padding: 10px 10px 10px 30px;
}
.markdown-body .admonition table {
color: #333;
}
.markdown-body .admonition p {
padding: 0;
}
.markdown-body .admonition-title {
font-weight: bold;
margin: 0;
}
.markdown-body .admonition>.admonition-title {
color: #333;
}
.markdown-body .attention>.admonition-title {
color: #a6d796;
}
.markdown-body .caution>.admonition-title {
color: #d7a796;
}
.markdown-body .hint>.admonition-title {
color: #96c6d7;
}
.markdown-body .danger>.admonition-title {
color: #c25f77;
}
.markdown-body .question>.admonition-title {
color: #96a6d7;
}
.markdown-body .note>.admonition-title {
color: #d7c896;
}
.markdown-body .admonition:before,
.markdown-body .attention:before,
.markdown-body .caution:before,
.markdown-body .hint:before,
.markdown-body .danger:before,
.markdown-body .question:before,
.markdown-body .note:before {
font: normal normal 16px fontawesome-mini;
-moz-osx-font-smoothing: grayscale;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
line-height: 1.5;
color: #333;
position: absolute;
left: 0;
top: 0;
padding-top: 10px;
padding-left: 10px;
}
.markdown-body .admonition:before {
content: "\f056\00a0";
color: 333;
}
.markdown-body .attention:before {
content: "\f058\00a0";
color: #a6d796;
}
.markdown-body .caution:before {
content: "\f06a\00a0";
color: #d7a796;
}
.markdown-body .hint:before {
content: "\f05a\00a0";
color: #96c6d7;
}
.markdown-body .danger:before {
content: "\f057\00a0";
color: #c25f77;
}
.markdown-body .question:before {
content: "\f059\00a0";
color: #96a6d7;
}
.markdown-body .note:before {
content: "\f040\00a0";
color: #d7c896;
}
.markdown-body .admonition::after {
content: normal;
}
.markdown-body .attention {
border-left: 6px solid #a6d796;
}
.markdown-body .caution {
border-left: 6px solid #d7a796;
}
.markdown-body .hint {
border-left: 6px solid #96c6d7;
}
.markdown-body .danger {
border-left: 6px solid #c25f77;
}
.markdown-body .question {
border-left: 6px solid #96a6d7;
}
.markdown-body .note {
border-left: 6px solid #d7c896;
}
.markdown-body .admonition>*:first-child {
margin-top: 0 !important;
}
.markdown-body .admonition>*:last-child {
margin-bottom: 0 !important;
}
/* progress bar*/
.markdown-body .progress {
display: block;
width: 300px;
margin: 10px 0;
height: 24px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
background-color: #ededed;
position: relative;
box-shadow: inset -1px 1px 3px rgba(0, 0, 0, .1);
}
.markdown-body .progress-label {
position: absolute;
text-align: center;
font-weight: bold;
width: 100%; margin: 0;
line-height: 24px;
color: #333;
text-shadow: 1px 1px 0 #fefefe, -1px -1px 0 #fefefe, -1px 1px 0 #fefefe, 1px -1px 0 #fefefe, 0 1px 0 #fefefe, 0 -1px 0 #fefefe, 1px 0 0 #fefefe, -1px 0 0 #fefefe, 1px 1px 2px #000;
-webkit-font-smoothing: antialiased !important;
white-space: nowrap;
overflow: hidden;
}
.markdown-body .progress-bar {
height: 24px;
float: left;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
background-color: #96c6d7;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .5), inset 0 -1px 0 rgba(0, 0, 0, .1);
background-size: 30px 30px;
background-image: -webkit-linear-gradient(
135deg, rgba(255, 255, 255, .4) 27%,
transparent 27%,
transparent 52%, rgba(255, 255, 255, .4) 52%,
rgba(255, 255, 255, .4) 77%,
transparent 77%, transparent
);
background-image: -moz-linear-gradient(
135deg,
rgba(255, 255, 255, .4) 27%, transparent 27%,
transparent 52%, rgba(255, 255, 255, .4) 52%,
rgba(255, 255, 255, .4) 77%, transparent 77%,
transparent
);
background-image: -ms-linear-gradient(
135deg,
rgba(255, 255, 255, .4) 27%, transparent 27%,
transparent 52%, rgba(255, 255, 255, .4) 52%,
rgba(255, 255, 255, .4) 77%, transparent 77%,
transparent
);
background-image: -o-linear-gradient(
135deg,
rgba(255, 255, 255, .4) 27%, transparent 27%,
transparent 52%, rgba(255, 255, 255, .4) 52%,
rgba(255, 255, 255, .4) 77%, transparent 77%,
transparent
);
background-image: linear-gradient(
135deg,
rgba(255, 255, 255, .4) 27%, transparent 27%,
transparent 52%, rgba(255, 255, 255, .4) 52%,
rgba(255, 255, 255, .4) 77%, transparent 77%,
transparent
);
}
.markdown-body .progress-100plus .progress-bar {
background-color: #a6d796;
}
.markdown-body .progress-80plus .progress-bar {
background-color: #c6d796;
}
.markdown-body .progress-60plus .progress-bar {
background-color: #d7c896;
}
.markdown-body .progress-40plus .progress-bar {
background-color: #d7a796;
}
.markdown-body .progress-20plus .progress-bar {
background-color: #d796a6;
}
.markdown-body .progress-0plus .progress-bar {
background-color: #c25f77;
}
.markdown-body .candystripe-animate .progress-bar{
-webkit-animation: animate-stripes 3s linear infinite;
-moz-animation: animate-stripes 3s linear infinite;
animation: animate-stripes 3s linear infinite;
}
@-webkit-keyframes animate-stripes {
0% {
background-position: 0 0;
}
100% {
background-position: 60px 0;
}
}
@-moz-keyframes animate-stripes {
0% {
background-position: 0 0;
}
100% {
background-position: 60px 0;
}
}
@keyframes animate-stripes {
0% {
background-position: 0 0;
}
100% {
background-position: 60px 0;
}
}
.markdown-body .gloss .progress-bar {
box-shadow:
inset 0 4px 12px rgba(255, 255, 255, .7),
inset 0 -12px 0 rgba(0, 0, 0, .05);
}
/* Multimarkdown Critic Blocks */
.markdown-body .critic_mark {
background: #ff0;
}
.markdown-body .critic_delete {
color: #c82829;
text-decoration: line-through;
}
.markdown-body .critic_insert {
color: #718c00 ;
text-decoration: underline;
}
.markdown-body .critic_comment {
color: #8e908c;
font-style: italic;
}
.markdown-body .headeranchor {
font: normal normal 16px octicons-anchor;
line-height: 1;
display: inline-block;
text-decoration: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.headeranchor:before {
content: '\f05c';
}
.markdown-body .task-list-item {
list-style-type: none;
}
.markdown-body .task-list-item+.task-list-item {
margin-top: 3px;
}
.markdown-body .task-list-item input {
margin: 0 4px 0.25em -20px;
vertical-align: middle;
}
/* Media */
@media only screen and (min-width: 480px) {
.markdown-body {
font-size:14px;
}
}
@media only screen and (min-width: 768px) {
.markdown-body {
font-size:16px;
}
}
@media print {
.markdown-body * {
background: transparent !important;
color: black !important;
filter:none !important;
-ms-filter: none !important;
}
.markdown-body {
font-size:12pt;
max-width:100%;
outline:none;
border: 0;
}
.markdown-body a,
.markdown-body a:visited {
text-decoration: underline;
}
.markdown-body .headeranchor-link {
display: none;
}
.markdown-body a[href]:after {
content: " (" attr(href) ")";
}
.markdown-body abbr[title]:after {
content: " (" attr(title) ")";
}
.markdown-body .ir a:after,
.markdown-body a[href^="javascript:"]:after,
.markdown-body a[href^="#"]:after {
content: "";
}
.markdown-body pre {
white-space: pre;
white-space: pre-wrap;
word-wrap: break-word;
}
.markdown-body pre,
.markdown-body blockquote {
border: 1px solid #999;
padding-right: 1em;
page-break-inside: avoid;
}
.markdown-body .progress,
.markdown-body .progress-bar {
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
}
.markdown-body .progress {
border: 1px solid #ddd;
}
.markdown-body .progress-bar {
height: 22px;
border-right: 1px solid #ddd;
}
.markdown-body tr,
.markdown-body img {
page-break-inside: avoid;
}
.markdown-body img {
max-width: 100% !important;
}
.markdown-body p,
.markdown-body h2,
.markdown-body h3 {
orphans: 3;
widows: 3;
}
.markdown-body h2,
.markdown-body h3 {
page-break-after: avoid;
}
}
-->
Java Web应用集成OSGI的更多相关文章
- Java Web项目实战第1篇之环境搭建
写在前面的话 从今天开始一个Java Web实战项目,参考自 http://blog.csdn.net/eson_15/article/details/51277324 这个博客(非常感谢博主的分享精 ...
- IntelliJ IDEA 14.x 与 Tomcat 集成,创建并运行Java Web项目
转自:http://www.php-note.com/article/detail/854 IntelliJ IDEA 14.x 与 Tomcat 集成,创建并运行Java Web项目 作者:php- ...
- Java Web学习系列——Maven Web项目中集成使用Spring、MyBatis实现对MySQL的数据访问
本篇内容还是建立在上一篇Java Web学习系列——Maven Web项目中集成使用Spring基础之上,对之前的Maven Web项目进行升级改造,实现对MySQL的数据访问. 添加依赖Jar包 这 ...
- 使用Eclipse+Maven+Jetty构建Java Web开发环境(几个教程综合集成2014发行)
工作需要使用Jetty由于web集装箱,得知Eclipse+Maven+Jetty该组合是非常好的,因此,要在网上找了很多教程,但不写或多或少特定的或过时的内容而导致最终的配置失败,易于配置为未来的同 ...
- 珠联壁合地设天造|M1 Mac os(Apple Silicon)基于vscode(arm64)配置搭建Java开发环境(集成web框架Springboot)
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_194 也许有人从未听说过Python,但是不会有人没听说过Java,它作为一个拥有悠久历史的老牌编程语言,常年雄踞TIOBE编程语 ...
- Java Web学习系列——Maven Web项目中集成使用Spring
参考Java Web学习系列——创建基于Maven的Web项目一文,创建一个名为LockMIS的Maven Web项目. 添加依赖Jar包 推荐在http://mvnrepository.com/.h ...
- Docker学习笔记五:Docker生成jenkins容器,支持Java Web项目持续集成、持续部署
一.创建jenkins容器: 1.拉取jeknin镜像 sudo docker pull jenkins 2.创建一个jenkins目录 sudo mkdir /jenkins 3.在jenkins目 ...
- Springboot Application 集成 OSGI 框架开发
内容来源:https://www.ibm.com/developerworks/cn/java/j-springboot-application-integrated-osgi-framework-d ...
- JAVA web 框架集合
“框架”犹如滔滔江水连绵不绝, 知道有它就好,先掌握自己工作和主流的框架: 在研究好用和新框架. 主流框架教程分享在Java帮帮-免费资源网 其他教程需要时间制作,会陆续分享!!! 152款框架,你还 ...
随机推荐
- Intellij Idea配置MapReduce编程环境
原文参考地址:http://www点w2bc点com/article/229178 增加内容:question1: Hadoop2以上版本时,在Hadoop2的bin目录下没有winutils.exe ...
- JDBC开源框架:DBUtils使用入门
在单元测试过程中,只涉及到数据库的直接操作来验证业务逻辑是否正确的情况,DBUtils非常适合使用.它结构简单,包小,友好处理掉那些jdbc异常,让你更专注于业务代码,而非底层的操作.官网对它的定义: ...
- HTML基础教程-简介
关于html5笔记前言 之前有在W3school学习过html5以及javascript.为了和大家一块学习,为了回顾这些遗忘的基础,现在我把之前自己整理的笔记共享给大家.希望大家共同进步. HTML ...
- OC学习12——字符串、日期、日历
前面主要学习了OC的基础知识,接下来将主要学习Foundation框架的一些常用类的常用方法.Foubdation框架是Cocoa编程.IOS编程的基础框架,包括代表字符串的NSString(代表字符 ...
- 遍历map的几种方式
1,平时开发中对map的使用很多,然后发现了很多map可能存在的各种问题:如HashMap 需要放置 1024 个元素,由于没有设置容量初始大小,随着元素不断增加,容量 7 次被迫扩大,resize ...
- ABP .Net Core API和Angular前端APP独立部署跨域问题(No Access-Control-Allow-Origin)
前言: 通过ABP官网(https://aspnetboilerplate.com)下载ASP.NET Core 2.x + Angular模板项目是按ReStful风格架构Web API和angul ...
- ToolStrip和MenuStrip控件簡介及常用屬性(转)
ToolStrip和MenuStrip實際上是相同的控件,因為MenuStrip直接派生於ToolStrip.也就是說ToolStrip可以做的工作,MenuStrip也能完成. ToolStrip( ...
- bzoj 4817: [Sdoi2017]树点涂色
Description Bob有一棵n个点的有根树,其中1号点是根节点.Bob在每个点上涂了颜色,并且每个点上的颜色不同.定义一条路 径的权值是:这条路径上的点(包括起点和终点)共有多少种不同的颜色. ...
- 在亚马逊linux环境上装mysql+添加启动项
安装mysql sudo yum install mysql sudo yum install mysql-server sudo yum install mysql-devel 添加到系统启动项su ...
- K:java中序列化的两种方式—Serializable或Externalizable
在java中,对一个对象进行序列化操作,其有如下两种方式: 第一种: 通过实现java.io.Serializable接口,该接口是一个标志接口,其没有任何抽象方法需要进行重写,实现了Serializ ...