Posts Tagged ‘spring’

在Tomcat的多个WAR之间共享Spring

August 24th, 2010

由于非技术原因,公司的专门成立的支付部,招一批Java程序员,做了支付系统;又由于非技术原因,两个月内,这个部门从领导到程序员全走光了。然后这个支付系统就移交给我们这帮受苦受难的兄弟们维护了。在看他们代码的时候发现了很有趣的东西,就是在tomcat内共享Spring context。

1,实现方法

1.1 建立一个全局的jndi资源

这个全局的jndi资源代表了一个共享的spring上下文,每次通过jndi查找资源时,都返回同一个spring上下文实例。修改tomcat/conf/context.xml文件,加入以下代码:

    <Resource auth="Container"
       contextConfigLocation="/com/test/application-context.xml"
       factory="com.test.TomcatWebApplicationContextResourceFactory"
       name="bean/RootApplicationContextFactory"
       type="org.springframework.context.ApplicationContext"/>

上面这段配置的意思就是,当有人通过jndi名称”bean/RootApplicationContextFactory”来查找对象时,容器就新建一个TomcatWebApplicationContextResourceFactory类实例,并调用它的getObjectInstance方法来获得资源对象。这个工厂类要是想javax.naming.spi.ObjectFactory接口。代码如下:

package com.test;

import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.Name;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TomcatWebApplicationContextResourceFactory implements
        ObjectFactory {
    private static final String PARAM = "contextConfigLocation";
        private static final String DEFAULT = "application-context.xml";

    private static ApplicationContext context; //被共享的spring上下文

    private void init(String confFile) {
        context = new ClassPathXmlApplicationContext(confFile);
    }

    public Object getObjectInstance(Object obj,
                        Name name, Context nameCtx,
            Hashtable<?, ?> environment) throws Exception {
        if (null == context) {
            // Customize the bean properties from our attributes
            Reference ref = (Reference) obj;

                        //从xml配置文件里取得contextConfigLocation元素的值
            RefAddr addr = ref.get(PARAM);

            if (null != addr) {
                String value = (String) addr.getContent();
                init(value);
            } else {
                init(DEFAULT);
            }
        }
        return context;
    }
}

1.2 在web应用中引用上面定义的资源

修改tomcat/conf/web.xml,添加以下的内容,这样在这个tomcat下的所有的web应用就都可以访问共享的spring上下文了。

    <resource-env-ref>
        <description>Object factory for Root applicationcontext</description>
        <resource-env-ref-name> bean/RootApplicationContextFactory
        </resource-env-ref-name>
        <resource-env-ref-type> org.springframework.context.ApplicationContext
        </resource-env-ref-type>
    </resource-env-ref>

1.3 加入依赖的包

从第一步可以看出,tomcat容器需要访问spring的一些类,需要初始化上下文,所以要把初始化上下文时用到的类共享给tomcat。最简单的方法是把所有相关的类和jar包复制到tomcat/lib目录。还可以修改catalina.properties实现,如下:

shared.loader=/usr/local/jars/*.jar

上面的代码让tomcat载入自定目录里面所有的jar文件。

2 我的看法

一个庞大的spring上下文是要占用很多内存的,所有应用共享一个上下文,而不是每个web应用一份,就可以节省不少的内存。但是由此却带来了部署和配置的麻烦。如果有人利用这种机制,让多个web应用通过共享spring bean来实现一些奇怪的功能,结果导致这些应用间相互耦合,那么以后想拆分开也不容易了。总体来说,我不喜欢这样方案,但是如果真的很穷很缺内存的话,这也不失一个好方法。

  • Share/Bookmark

groovy+spring全动态web开发方案

July 14th, 2009

为什么java开发没有php容易?,我在那篇文章了提出了三点原因,利用groovy,我们能在一定程度上做到那三点,从而加快了java web的开发。jsp本身以具有不需要重启就可以生效的能力,但是没有好用的map和动态类型的能力,用在前文 用groovyServlet和TemplateServlet快速开发网页中的方法,可以通过groovy让页面具有好用的map和动态类型的能力,但是 TemplateServlet的局限性 还是很难让我们单凭在页面里编程来构建规模稍大些的应用。引入spring,利用spring对动态语言的支持,我们可以将可重用的逻辑封装到由groovy编写的spring的bean里,这样,就可以最终实现从页面到后台全groovy的效果了,让java的web开发可以达到近似于php的易用度。

看看详细的做法。
把最新版的groovy-all.jar和spring.jar以及spring所依赖的相关包放到/WEB-INF/lib/目录下。

首先,在web.xml里配置好对groovyServlet, TemplateServlet和spring的支持

代码如下:

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml</param-value>
  </context-param>
   
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
 
  <servlet>
    <servlet-name>groovlet</servlet-name>
    <servlet-class>groovy.servlet.GroovyServlet</servlet-class>
  </servlet>
 
  <servlet>
    <servlet-name>gsp</servlet-name>
    <servlet-class>groovy.servlet.TemplateServlet</servlet-class>
  </servlet>
 
  <servlet-mapping>
    <servlet-name>groovlet</servlet-name>
    <url-pattern>*.groovy</url-pattern>
  </servlet-mapping>  
 
  <servlet-mapping>
    <servlet-name>gsp</servlet-name>
    <url-pattern>*.gsp</url-pattern>
  </servlet-mapping>

然后,配置用groovy写spring的bean

如果你要写一个id为foo的bean, 那么要先用java写一个接口,然后用groovy写Bean实现接口,这样不但可以让你以后可以更容易用java重写groovy的bean,也可以避免和spring aop的冲突。

interface Foo.java 代码:

public interface Foo {
    public String shout(String name);
}

groovy实现类 FooImpl.groovy代码:

public class FooImpl implements Foo {
    def sampleProp
    public String shout(String name) {
        def c = {"Groovy : ${sampleProp} ${it}"}
        return c(name)
    }
}

然后在applicationContext中配置

    <lang:groovy id="foo" refresh-check-delay="5000" script-source="classpath:FooImpl.groovy">
        <lang:property name="sampleProp" value="Hello" />
    </lang:groovy>

注意上面的refresh-check-delay=”5000″是让spring每过五秒钟就刷新一下groovy代码,重新编译,当最终部署的时候,可以把它去掉,这样就不会重复刷新了。但是在开发时,我们因此获得了不重启就生效能里。

最后,可以在页面groovy文件中使用这个bean了

使用这个bean和使用其他的spring bean没有什么区别。

import org.springframework.web.context.support.WebApplicationContextUtils;

def spring = WebApplicationContextUtils.getWebApplicationContext(context)
Foo foo = spring.getBean("foo")
foo.shout("sulong")

只要不修改applicationConfig.xml,修改其他的部分都不需要重启或重新部署,这样,你可以通过groovy和spring来达到类似于php那种快速开发的目的。并且在后期,如果有必要,你还可以通过用java重写的bean的方式,来做性能优化。

  • Share/Bookmark

在seam中使用spring

July 17th, 2008

选择seam并不代表要放弃spring, 两者并不互斥,相反,可以很好的协作。seam对spring提供了很好的兼容性。那么在seam里如何使用spring里的代码呢?

POJO代码的重用

正如spring管理的bean是POJO一样,seam管理的组件,也是POJO的,而且连ejb3也是POJO的,所以直接的重用以前用在 spring里的代码的方法,就是直接重用POJO了。以前我们就applicationContext.xml来配置bean,现在只需要换一个语法就 能把spring的POJO bean变成seam的component。例如,对于spring的bean:

<bean id=”testBean” class=”org.test.TestBean”>
<property name=”stringProperty” value=”this is a string” />
<property name=”intProperty” value=”3″ />
<property name=”anotherBean” ref=”anotherBean” />
</bean>

对应到seam里就是:

<component name=”testBean” class=”org.test.TestBean”>
<property name=”stringProperty”>this is a string</property>
<property name=”intProperty”>3</property>
<property name=”anotherBean”>#{anotherBean}</property>
</component>

大部分的POJO可能这就可以搞定了。但是要注意:
假如这个POJO依赖于spring容器,比如它实现了spring的bean生命周期回调方法,类似于  afterPropertiesSet() , 那么你也得让seam去模仿spring容器,在适应的时候,去调用这些回调方法。比如:

public class TestBeanAdapter extends TestBean {
@Create
public void afterPropertiesSet() {
super.afterPropertiesSet();
}
}


通JSF EL Resolver来桥接

只要实现了EL Resolver, 并在faces-config.xml里注册它,那么在JSF里使用EL表达式时,就会自动地调用EL Resolver来寻找这个EL表达式对应的对象等。seam当然实现了它自己的Resolver,而spring也实了这个Resolver,如果把它 们都注册上,那么JSF EL 将变成seam与spring的一座桥梁。用下面代码注册spring EL Resolver:

<faces-config>
<application>         
<el-resolver>
org.springframework.web.jsf.el.SpringBeanFacesELResolver 
</el-resolver>
</application>
</faces-config>

如果你定义了spring bean testBean, 那么在seam组件里,你就可以通过 @In(“#{testBean}”) 来注入spring的bean。但是要记住,这种方法获得的对象,完全是由spring负责组装的地地道道的spring的bean, 所以它不具有seam component具有功能,比如双向注入,和seam的上下文完美一体,延长的持久化上下文(extended persistence context)。

seam里的spring

最完美的方案,我想你也想到了,就是让seam来管理spring容器,并加入seam的增强功能。这是可以做得到的,并且已经做到了,感谢spring的灵活性,和seam开发者们的努力。像下面那样在conponents.xml里加入spring名称空间:

<components xmlns=”http://jboss.com/products/seam/components”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:spring=”http://jboss.com/products/seam/spring”
xsi:schemaLocation=”
http://jboss.com/products/seam/components
http://jboss.com/products/seam/components-2.0.xsd
http://jboss.com/products/seam/spring
http://jboss.com/products/seam/spring-2.0.xsd”>
<!– component declarations –>
</components>

现在你可以在components.xml里使用<spring:context-loader />了:

<spring:context-loader>
<spring:config-locations>
<value>classpath:spring-beans-persistence.xml</value>
<value>classpath:spring-beans-service.xml</value>
</spring:config-locations>
</spring:context-loader>

好了,现在当你启动seam时,seam便会到你指定的位置去寻找spring的配置,并起动一个spring容器。为了能让seam知道要如何增强 spring里的bean,我们需要在spring那边做一些手脚。在spring的配置文件里加入seam的名称空间如下:

<beans xmlns=”http://www.springframework.org/schema/beans”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:seam=”http://jboss.com/products/seam/spring-seam”
xsi:schemaLocation=”
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://jboss.com/products/seam/spring-seam
http://jboss.com/products/seam/spring-seam-2.0.xsd”>
<!– bean definitions –>
</bean>

这样,你就可以在spring的配置文件里使用以下三种元素来定义如何增强spring bean了:

  1. <seam:conponent />  把一个spring bean变成seam component
  2. <seam:instance> 向一个spring bean里注入seam component
  3. <seam:configure-scopes> 定义一些默认的配置

看看下面的使用举例,我想你根本不需要解释就明白了:

<bean id=”aBean” class=”org.test.TestBean” scope=”prototype”>
<property name=”aString” value=”a string” />
<seam:component name=”testBean” scope=”CONVERSATION” auto-create=”true” />
</bean>

<bean id=”anotherBean” class=”org.test.AnotherBean” scope=”prototype”>
<property name=”aBean”>
<seam:instance name=”testBean” />
</property>
</bean>

<seam:configure-scopes default-auto-create=”true”/>

spring的 aBean在seam里被叫做 testBean, 并且它在 conversation scope里,spring的anotherBean在创建它时,会把testBean注入进去。非常简单吧?

但是,在使用过程中,要注意,seam不能对spring通过jdk 动态代理增强过的类进行再增强,所以如果要让一个spring的bean参予到seam里来,就不要对它使用动态代理的方式来实现AOP,你需要在spring的配置文件里做如下配置:

<aop:config proxy-target-class=”true”>
……
</aop:config>

好了,至此,在seam中使用spring的方法就介绍完了。

  • Share/Bookmark

震惊!微软收购SpringSource!

April 1st, 2008

突然看到消息,SpringSource宣布被微软收购,非常震惊! 这是真的吗?
今天是个西方特殊的日子,目前还不能肯定是不是真的。但是,微软完全有动机这样做。微软提出.net已有数年,但是仍旧不无法动摇java在企业开发界的地位。我对.net没有深入的研究,但是.net界远没有像java界那样百家争鸣,思想活跃是不争的事实。当用java开发有无数的方案可选时,.net世界里基本上只有MS自己的解决方案一家。sping是Java世界里最丰硕的果实,虽然spring也有.net的版本,但是一直没有被足够支持和发扬。微软收购Spring,一下子就把java世界的果实摘了去。而且,微软借此收购,还可以把java界的牛人Rod Johnson收入麾下,这颗脑袋比spring的源代码还要重要。
我很不希望这是真的。我担心johnson被拉入微软后,以后spring在java界的发展变慢,甚至于停滞。但愿这不是真的!

  • Share/Bookmark