采用共享jar包部署struts2+spring集成项目会遇到的问题

来源:互联网 发布:邮政储蓄银行软件 编辑:程序博客网 时间:2024/06/05 07:46

比如tomcat下边有个lib,放着我们需要的struts2 + spring 集成jar包(一定要struts2和spring集成),即共享给所有webapp使用,如图:


此时tomcat启动时,会先加载a项目,再加载b项目(一定要是这个顺序才会出现问题,比如tomcat是通过list file顺序部署的)。(关于tomcat的classloader请参考:http://tomcat.apache.org/tomcat-6.0-doc/class-loader-howto.html)

 

tomcat/lib下的jar包:

请参考附件 

 

a项目(WEB-INF/lib下没有jar包):

Java代码  收藏代码
  1. package cn.javass.a;  
  2. public class Project {  
  3.     private Integer id;  
  4.     //省略getter/setter  
  5. }  
  6.   
  7. package cn.javass.a;  
  8. public class Deploment {  
  9.     private Project project;  
  10.     //省略getter/setter  
  11. }  

 Deploment中包含了Project

 

struts2的action

Java代码  收藏代码
  1. package cn.javass.a;  
  2. import org.springframework.context.annotation.Scope;  
  3. import org.springframework.stereotype.Controller;  
  4.   
  5. @Controller("myAction")  
  6. @Scope("prototype")  
  7. public class MyAction {  
  8.   
  9.     private Deploment deploment;  
  10.     //省略getter/setter  
  11.      
  12.     public String execute() {  
  13.         System.out.println("===a::::" + deploment.getProject().getClass());  
  14.         return "success";  
  15.     }  
  16. }  

 

spring-config.xml只有一句,用来扫描注解bean:

Java代码  收藏代码
  1. <context:component-scan base-package="cn.javass"/>      

 

struts.xml配置一个action(因为和spring集成了,所以会问spring要):

Java代码  收藏代码
  1. <action name="myaction" class="myAction">  
  2.         <result>/WEB-INF/jsp/success.jsp</result>  
  3. </action>  

 

当我们请求url,如:localhost/a/myaction?deploment.project.id=1时,struts2会创建deploment,又因为project为null,所以默认也会创建project。

 

b项目(WEB-INF/lib下没有jar包):

和a项目类似,但是不同的是,Project同时也是一个bean才会出问题。

Java代码  收藏代码
  1. package cn.javass.b;  
  2. import org.springframework.context.annotation.Scope;  
  3. import org.springframework.stereotype.Component;  
  4.    
  5. @Component  
  6. @Scope("prototype")  
  7. public class Project {  
  8.     private Integer id;  
  9.     //省略getter/setter  
  10. }  

 

即Project同时也是一个bean。其他的和a项目完全一样。

 

此时启动如tomcat服务器,一定注意要让tomcat先加载a项目,再加载b项目(目前测试默认就是),并开启如log4j日志,观察日志。

此时在地址栏输入:http://localhost/a/myaction?deploment.project.id=1  ,会得到

java.lang.NullPointerExceptioncn.javass.a.MyAction.execute(MyAction.java:27)sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method…………

即deploment对象为null。然后看控制台输出日志,会看到

写道
org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'cn.javass.b.Project' to required type 'cn.javass.a.Project' for property 'project'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [cn.javass.b.Project] to required type [cn.javass.a.Project] for property 'project': no matching editors or conversion strategy found
at org.springframework.beans.BeanWrapperImpl.convertIfNecessary(BeanWrapperImpl.java:463)
at org.springframework.beans.BeanWrapperImpl.convertForProperty(BeanWrapperImpl.java:494)
at org.springframework.beans.BeanWrapperImpl.convertForProperty(BeanWrapperImpl.java:488)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.convertForProperty(AbstractAutowireCapableBeanFactory.java:1433)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1392)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1128)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:376)
at com.opensymphony.xwork2.spring.SpringObjectFactory.autoWireBean(SpringObjectFactory.java:203)
at com.opensymphony.xwork2.spring.SpringObjectFactory.buildBean(SpringObjectFactory.java:183)
at com.opensymphony.xwork2.conversion.impl.InstantiatingNullHandler.createObject(InstantiatingNullHandler.java:161)
at com.opensymphony.xwork2.conversion.impl.InstantiatingNullHandler.nullPropertyValue(InstantiatingNullHandler.java:137)
at com.opensymphony.xwork2.ognl.OgnlNullHandlerWrapper.nullPropertyValue(OgnlNullHandlerWrapper.java:21)
at ognl.ASTProperty.getValueBody(ASTProperty.java:118)
at ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212)
at ognl.SimpleNode.getValue(SimpleNode.java:258)
at ognl.ASTChain.setValueBody(ASTChain.java:222)
at ognl.SimpleNode.evaluateSetValueBody(SimpleNode.java:220)
at ognl.SimpleNode.setValue(SimpleNode.java:301)
at ognl.Ognl.setValue(Ognl.java:737)
at com.opensymphony.xwork2.ognl.OgnlUtil.setValue(OgnlUtil.java:234)
at com.opensymphony.xwork2.ognl.OgnlValueStack.trySetValue(OgnlValueStack.java:183)
at com.opensymphony.xwork2.ognl.OgnlValueStack.setValue(OgnlValueStack.java:170)
at com.opensymphony.xwork2.ognl.OgnlValueStack.setParameter(OgnlValueStack.java:148)  
at com.opensymphony.xwork2.interceptor.ParametersInterceptor.setParameters(ParametersInterceptor.java:318)
at com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:231)
at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246)
at com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:239)


…………………………

at java.lang.Thread.run(Thread.java:619)
Caused by: java.lang.IllegalStateException: Cannot convert value of type [cn.javass.b.Project] to required type [cn.javass.a.Project] for property 'project': no matching editors or conversion strategy found
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:264)
at org.springframework.beans.BeanWrapperImpl.convertIfNecessary(BeanWrapperImpl.java:448)
... 74 more
WARN-2013-11-28 22:58:03,656 - Error setting expression 'deploment.project.id' with value '[Ljava.lang.String;@8feeb'
ognl.OgnlException: source is null for getProperty(null, "project")
at ognl.OgnlRuntime.getProperty(OgnlRuntime.java:2310)
at ognl.ASTProperty.getValueBody(ASTProperty.java:114)
at ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212)
at ognl.SimpleNode.getValue(SimpleNode.java:258)
at ognl.ASTChain.setValueBody(ASTChain.java:222)
at ognl.SimpleNode.evaluateSetValueBody(SimpleNode.java:220)
at ognl.SimpleNode.setValue(SimpleNode.java:301)
at ognl.Ognl.setValue(Ognl.java:737)
at com.opensymphony.xwork2.ognl.OgnlUtil.setValue(OgnlUtil.java:234)

……………………

ERROR-2013-11-28 22:58:03,656 - Exception occurred during processing request: null
java.lang.NullPointerException
at cn.javass.a.MyAction.execute(MyAction.java:27)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.opensymphony.xwork2.DefaultActionInvocation.invokeAction(DefaultActionInvocation.java:450)
at com.opensymphony.xwork2.DefaultActionInvocation.invokeActionOnly(DefaultActionInvocation.java:289)
at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:252)

 

1、struts2默认情况下发现project为null,会委托给相应的Null处理器处理,此处就是com.opensymphony.xwork2.conversion.impl.InstantiatingNullHandler,请看上边红色部分,此时会发现,它是这样处理的:

1.1、先委托给SpringObjectFactory,其又委托给spring容器去获取同名(project)的bean,但是此时会看到 获取了b项目中的Project,但是转型到a项目中的Project出错了。

1.2、如果从spring容器找不到同名的bean,会通过反射new一个(即如果不合spring容器集成也不会有问题,或者spring容器中没有名字为project的也没问题)。

 

怎么会找到b项目中的spring容器呢?接着分析。

 

分析:

1、InstantiatingNullHandler是由 ognl.ASTProperty.getValueBody(ASTProperty.java:118)处调用的,去跟踪下代码:

 

Java代码  收藏代码
  1. result = OgnlRuntime.getNullHandler(OgnlRuntime.getTargetClass(source)).nullPropertyValue(context, source, property);  

 

 

2、此处先调用OgnlRuntime.getNullHandler(OgnlRuntime.getTargetClass(source))得到一个NullHandler;发现没有静态方法调用;

 

3、接着去看看OgnlRuntime,即根据传入的class类型,去到_nullHandlers中匹配合适的handler:

Java代码  收藏代码
  1. public static NullHandler getNullHandler(Class cls)  
  2.            throws OgnlException  
  3.    {  
  4.        NullHandler answer = (NullHandler) getHandler(cls, _nullHandlers);  
  5.        if (answer != null)  
  6.            return answer;  
  7.        throw new OgnlException("No null handler for class " + cls);  
  8.    }  

 

 

 4、_nullHandlers何时注册的呢?这个是问题的关键,通过查看源代码,会发现:

Java代码  收藏代码
  1. public static void setNullHandler(Class cls, NullHandler handler)  
  2.    {  
  3.        synchronized (_nullHandlers) {  
  4.            _nullHandlers.put(cls, handler);  
  5.        }  
  6.    }  

 

此处有一个setNullHandler方法,用于注册null handler的,而且一定注意是静态的。

 

5、谁调用这个注册的呢?debug跟踪代码会发现是struts2在启动时的那个Filter(StrutsPrepareAndExecuteFilter)调用的。

 

6、分析:

6.1、类变量(静态变量)是一个类装载器一份,此处因为我们把jar包放在tomcat/lib下,所以整个web应用(a项目和b项目都能拿到这个,且俩人拿到的都是同一个)只有一份

6.2、因为a项目启动时调用会调用setNullHandler注册null handler,而接着b项目也会启动注册,又因为6.1所以呢,只有最后一个项目注册的有效(后边的覆盖前面的)

6.3、启动时debug的图:

6.3.1、a项目启动时注册的handler,该handler有a项目的spring容器引用(里边没有project bean的):


 

6.3.2、b项目启动时注册的handler,该handler有b项目的spring容器引用(里边有project bean的):

 

6.3.4、所以呢最后一个项目的null handler有效。就造成了之前的问题。

 

所以解决办法就是不能共享这些jar包。或者保证spring容器中没有同名的bean。。

 

0 0