webservice发布使用spring的SpringBeanAutowiringSupport自动装配不了属性

当在Tomcat7环境中使用SpringBeanAutowiringSupport进行webservice发布时,遇到服务类属性注入为空的问题。分析发现,由于ServletContainerInitializer的onStartup方法在Spring容器初始化之前执行,导致属性装配失败。解决方案包括删除jar包中的ServletContainerInitializer文件或配置Tomcat扫描跳过指定jar。
摘要由CSDN通过智能技术生成

    同事将开发好的webservice服务发布到测试环境后,使用客户端去访问时发现,服务提供类中使用spring容器注入的属性都为空,配置片段如下:

 

@javax.jws.WebService(endpointInterface = "com.mipo.webservice.service.impl.URInterfaceServletmplDelegate", targetNamespace = "http://impl.service.webservice.mipo.com/", serviceName = "URInterfaceServletService", portName = "URInterfaceServletPort")
public class URInterfaceServletPortImpl extends SpringBeanAutowiringSupport{
	@Autowired
	private URInventoryInService  urInventoryInService;

 服务提供类URInterfaceServletPortImpl继承了spring的SpringBeanAutowiringSupport实现自动装配,web.xml中配置片段如下:

<listener>
  	<listener-class>
  		com.sun.xml.ws.transport.http.servlet.WSServletContextListener
  	</listener-class>
  </listener>

 

<servlet>
  	<description>JAX-WS endpoint - URInterfaceServlet</description>
  	<display-name>URInterfaceServlet</display-name>
  	<servlet-name>URInterfaceServlet</servlet-name>
  	<servlet-class>
  		com.sun.xml.ws.transport.http.servlet.WSServlet
  	</servlet-class>
  	<load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
  	<servlet-name>URInterfaceServlet</servlet-name>
  	<url-pattern>/URInterfaceServletPort</url-pattern>
  </servlet-mapping>

WEB-INF目录下有一个sun-jaxws.xml文件,表面上看没啥问题,启动过程也没有报错,但是使用webservice客户端访问的时候,就报出上面服务类中注入的属性urInventoryInService为空,抛出空指针异常。

    既然为空,说明spring没有装配进这个属性,才会导致执行的时候属性为空,查看下spring处理这个类的过程,看到这个服务类URInterfaceServletPortImpl,以往我们使用spring时,会把这个bean先交给spring容器管理(类上使用注解@Service等,并使用自动扫描component-scan指定包,或者是在xml文件中配置bean),然后在使用@Autowired等注解注入依赖的属性。但是这个类URInterfaceServletPortImpl上面即没有@Service等注解,也没在xml中配置,只是继承了SpringBeanAutowiringSupport,然后就直接使用@Autowired注入依赖的属性了,spring怎么知道这个类需要注入属性呢?按照我的想法,肯定得在配置文件中说明,这个类需要依赖注入,然后spring容器启动的时候读取到这个类,然后通过反射取得类的属性,检测属性是否需要注入,然后去容器中获取指定的bean,注入进来。

    但是全局搜索了一下,并没有看到在配置文件中有配置URInterfaceServletPortImpl这个bean,看了下它集成的SpringBeanAutowiringSupport这个类的源代码,只有一个无参的构造函数

	public SpringBeanAutowiringSupport() {
		processInjectionBasedOnCurrentContext(this);
	}

 

public static void processInjectionBasedOnCurrentContext(Object target) {
		Assert.notNull(target, "Target object must not be null");
		WebApplicationContext cc = ContextLoader.getCurrentWebApplicationContext();
		if (cc != null) {
			AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
			bpp.setBeanFactory(cc.getAutowireCapableBeanFactory());
			bpp.processInjection(target);
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("Current WebApplicationContext is not available for processing of " +
						ClassUtils.getShortName(target.getClass()) + ": " +
						"Make sure this class gets constructed in a Spring web application. Proceeding without injection.");
			}
		}
	}

 在这个地方去注入了URInterfaceServletPortImpl依赖的bean,在java中,子类创建时会先创建父类,执行父类默认的构造函数,而在SpringBeanAutowiringSupport的构造函数中处理了子类属性的注入,唉 ,master is master,思想比我高明多了!!在这个类中打了几个断点,调试了一下,发现都是OK的,并没有出什么错,开源软件就是好啊,一言不合就可以关联源代码调试。生成webservice客户端,访问了一下,也是OK的,说明注入没问题,但是在同时环境中还是不行。

    问了同事是不是在服务没启动完就访问的,没启动完就访问有可能spring容器中还没有依赖的bean,同事说不是,那就奇怪了,理论上注入是没问题的,而且在我环境上是OK的。在同事环境中,在其他类中new了一个服务类URInterfaceServletPortImpl的实例,然后访问,发现这回依赖的属性urInventoryInService是有值的,说明注入是没问题的,很奇怪,猜测是webservice在启动初始化URInterfaceServletPortImpl的时候有问题。怀疑是启动顺序的原因,调整了下listener、servlet在web.xml中的顺序,修改了servlet的load-on-startup参数,发现在同事的环境中都不行,奇了怪了。

    跟同事统一了下环境,我的环境是6版本的tomcat6.0.39,同事使用的是tomcat7版本的7.0.69,但是不应该跟服务器有关啊,同事切换到tomcat6版本,发现竟然OK了,好神奇啊。难道tomcat7解析web.xml的顺序改了?查了下,发现并没有变,是在没辙,我的环境切换到tomcat7后也报空指针了,tomcat7上的确有问题,来看看URInterfaceServletPortImpl是在什么时候初始化的。在web的xml中把相关的listener、servlet注释掉,启动了下,发现在tomcat6中URInterfaceServletPortImpl没有被初始化,但是在tomcat7中,这个类居然还是被初始化了,明明web.xml中已经注释了相关配置,把WEB-INF目录下的sun-jaxws.xml文件也删了,发现在tomcat6和tomcat7下都不初始化URInterfaceServletPortImpl了,难道tomcat7会自动读取WEB-INF目录下的sun-jaxws.xml?应该不可能。

    没办法,还是来看源代码,com.sun.xml.ws.transport.http.servlet.WSServletContextListener的方法中

 

void parseAdaptersAndCreateDelegate(ServletContext context){
        //The same class can be invoked via @WebListener discovery or explicit configuration in deployment descriptor
        // avoid redoing the processing of web services.
        String alreadyInvoked = (String) context.getAttribute(WSSERVLET_CONTEXT_LISTENER_INVOKED);
        if(Boolean.valueOf(alreadyInvoked)) {
            return;
        }
        context.setAttribute(WSSERVLET_CONTEXT_LISTENER_INVOKED, "true");
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        if (classLoader == null) {
            classLoader = getClass().getClassLoader();
        }
        try {
            URL sunJaxWsXml = context.getResource(JAXWS_RI_RUNTIME);
            if(sunJaxWsXml==null) {
                    throw new WebServiceException(WsservletMessages.NO_SUNJAXWS_XML(JAXWS_RI_RUNTIME));                 
            }

            // Parse the descriptor file and build endpoint infos
            DeploymentDescriptorParser<ServletAdapter> parser = new DeploymentDescriptorParser<ServletAdapter>(
                classLoader,new ServletResourceLoader(context), createContainer(context), new ServletAdapterList(context));
            adapters = parser.parse(sunJaxWsXml.toExternalForm(), sunJaxWsXml.openStream());
            registerWSServlet(adapters, context);
            delegate = createDelegate(adapters, context);

            context.setAttribute(WSServlet.JAXWS_RI_RUNTIME_INFO,delegate);

        } catch (Throwable e) {
            logger.log(Level.SEVERE,
                WsservletMessages.LISTENER_PARSING_FAILED(e),e);
            context.removeAttribute(WSServlet.JAXWS_RI_RUNTIME_INFO);
            throw new WSServletException("listener.parsingFailed", e);
        }

    }

 读取了WEB-INF下的sun-jaxws.xml,打了几个断点,debug一下,发现在tomcat7下,会两次走进这个方法,查看了一下,这个方法被两个地方调用:



 一个是listener本身,一个是
WSServletContainerInitializer的onStartup方法,listener已经被注释了,应该不会执行,那应该是WSServletContainerInitializer的onStartup方法导致URInterfaceServletPortImpl被初始化了,WSServletContainerInitializer代码如下:

@HandlesTypes({WebService.class, WebServiceProvider.class})
public class WSServletContainerInitializer implements ServletContainerInitializer {
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
        //Called with null, when there are no matching classes as per Servlet 3.0 spec
        try {
            if (c != null && !c.isEmpty()) {
                URL sunJaxWsXml = ctx.getResource(WSServletContextListener.JAXWS_RI_RUNTIME);
                //Don't register a listener, when there is no sun-jaxws.xml, let 109 impl  handle it.
                if (sunJaxWsXml != null) {
                    WSServletContextListener listener = new WSServletContextListener();
                    listener.parseAdaptersAndCreateDelegate(ctx);
                    ctx.addListener(listener);
                }
            }
        } catch (Exception e) {
            throw new ServletException(e);
        }
    }
}

 这边创建了WSServletContextListener,也读取了WEB-INF目录下的sun-jaxws.xml,断点打了一下,的确是执行了,那这个类的onStartup方法是谁来执行的,什么时候执行的,查看了相关资料,这个类实现了ServletContainerInitializer,是servlet3.0中新增的功能,支持servlet3.0的容器在启动的时候会自动执行实现了ServletContainerInitializer接口的类的onStartup方法,但是有个前提,就是要执行的实现了ServletContainerInitializer接口的类必须在META-INF/services目录下配置javax.servlet.ServletContainerInitializer文件,查看了下WSServletContainerInitializer类所在包的META-INF目录,的确是有这么一个文件



 把这个包中的这个文件删除,在tomcat7下终于也OK 了。那看来就是这个原因导致的,tomcat容器在启动的时候会扫描jar包的META-INF目录,执行那些配置了的,并实现了ServletContainerInitializer接口的类的onStartup,所以这时候会执行WSServletContextListener的parseAdaptersAndCreateDelegate,然后web.xml中配置了com.sun.xml.ws.transport.http.servlet.WSServletContextListener,导致parseAdaptersAndCreateDelegate方法又执行了一遍,看下parseAdaptersAndCreateDelegate方法

void parseAdaptersAndCreateDelegate(ServletContext context){
        //The same class can be invoked via @WebListener discovery or explicit configuration in deployment descriptor
        // avoid redoing the processing of web services.
        String alreadyInvoked = (String) context.getAttribute(WSSERVLET_CONTEXT_LISTENER_INVOKED);
        if(Boolean.valueOf(alreadyInvoked)) {
            return;
        }
        context.setAttribute(WSSERVLET_CONTEXT_LISTENER_INVOKED, "true");
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        if (classLoader == null) {
            classLoader = getClass().getClassLoader();
        }
        try {
            URL sunJaxWsXml = context.getResource(JAXWS_RI_RUNTIME);
            if(sunJaxWsXml==null) {
                    throw new WebServiceException(WsservletMessages.NO_SUNJAXWS_XML(JAXWS_RI_RUNTIME));                 
            }

            // Parse the descriptor file and build endpoint infos
            DeploymentDescriptorParser<ServletAdapter> parser = new DeploymentDescriptorParser<ServletAdapter>(
                classLoader,new ServletResourceLoader(context), createContainer(context), new ServletAdapterList(context));
            adapters = parser.parse(sunJaxWsXml.toExternalForm(), sunJaxWsXml.openStream());
            registerWSServlet(adapters, context);
            delegate = createDelegate(adapters, context);

            context.setAttribute(WSServlet.JAXWS_RI_RUNTIME_INFO,delegate);

        } catch (Throwable e) {
            logger.log(Level.SEVERE,
                WsservletMessages.LISTENER_PARSING_FAILED(e),e);
            context.removeAttribute(WSServlet.JAXWS_RI_RUNTIME_INFO);
            throw new WSServletException("listener.parsingFailed", e);
        }

    }

 第一次执行后alreadyInvoked已经为true,因此第二次由listener初始化是不在后续处理,查了下,说tomcat初始化ServletContainerInitializer并执行onStartup还是比较靠前,先于listener初始化,因此WSServletContainerInitializer的onStartup执行时spring容器还没有初始化,因此URInterfaceServletPortImpl中的属性装配进来都为空,而在第二次listener初始化时,由于第一次已经初始化过了,虽然这时候spring容器已经初始化了,但是URInterfaceServletPortImpl不再被初始化,因此后续访问的时候提示注入的属性为空。

    解决办法,一个就是把jar包中META-INF目录下的javax.servlet.ServletContainerInitializer文件删除,虽然凑效,但未免有点暴力,往上看到有说使用web.xml中配置metadata-complete="true",这个是在web.xml中配置让servlet容不扫描servlet3.0规范中定义的类,需要在web.xml中使用servlet3.0规范的申明,但是我在测试中发现并没有卵用,还一个方法是在tomcat的conf/catalina.properties文件中,配置tomcat.util.scan.DefaultJarScanner.jarsToSkip属性,在其值最后加上,*



 其目的是指示tomcat扫描是跳过指定jar,这里最后跳过所有jar,原文链接如下(http://blog.sina.com.cn/s/blog_4d0855690102whto.html)

但是个人感觉虽然解决了问题,但不是很优雅。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值