• 使用Spring提供远程和WEB服务

    作者:xiuson, 原文发表于并发编程网:http://ifeve.com/docs-spring-remotin/

    24.1 介绍

    Spring提供了使用多种技术实现远程访问支持的集成类。远程访问支持使得具有远程访问功能的服务开发变得相当简单,而这些服务由普通的 (Spring) POJO实现。目前,Spring支持以下几种远程技术:

    • 远程方法调用(RMI)。通过使用RmiProxyFactoryBean和RmiServiceExporter,Spring同时支持传统的RMI(与java.rmi.Remote接口和java.rmi.RemoteException配合使用)和通过RMI调用器的透明远程调用(透明远程调用可以使用任何Java接口)。
    • Spring的HTTP调用器。Spring提供了一个特殊的远程处理策略,允许通过HTTP进行Java序列化,支持任何Java接口(就像RMI调用器)。相应的支持类是HttpInvokerProxyFactoryBean和HttpInvokerServiceExporter。
    • Hessian。通过HessianProxyFactoryBean和HessianServiceExporter,可以使用Caucho提供的基于HTTP的轻量级二进制协议来透明地暴露服务。
    • JAX-WS。Spring通过JAX-WS为web服务提供远程访问支持。(JAX-WS: 从Java EE 5 和 Java 6开始引入,作为JAX-RPC的继承者)
    • JMS。通过JmsInvokerServiceExporter和JmsInvokerProxyFacotryBean类,使用JMS作为底层协议来提供远程服务。
    • AMQP。Spring AMQP项目支持AMQP作为底层协议来提供远程服务。

    在讨论Spring的远程服务功能时,我们将使用以下的域模型和对应的服务:

    public class Account implements Serializable{
        private String name;
    
        public String getName(){
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
    }
    
    public interface AccountService {
    
        public void insertAccount(Account account);
    
        public List<Account> getAccounts(String name);
    
    }
    
    // the implementation doing nothing at the moment
    public class AccountServiceImpl implements AccountService {
    
        public void insertAccount(Account acc) {
            // do something...
        }
    
        public List<Account> getAccounts(String name) {
            // do something...
        }
    
    }

    我们将从使用RMI把服务暴露给远程客户端开始,同时讨论使用RMI的一些缺点。然后我们将继续演示一个使用Hessian的例子。

    24.2 使用RMI暴露服务

    使用Spring的RMI支持,你可以通过RMI基础架构透明地暴露你的服务。完成Spring的RMI设置后,你基本上具有类似于远程EJB配 置,除了没有对安全上下文传递和远程事务传递的标准支持。当使用RMI调用器时,Spring对这些额外的调用上下文提供了钩子,你可以在此插入安全框架 或者自定义的安全凭证。

    24.2.1 使用RmiServiceExporter导出服务

    使用RmiServiceExporter,我们可以把AccountService对象的接口暴露成RMI对象。可以使用RmiProxyFactoryBean或者在传统RMI服务中使用普通RMI来访问该接口。RmiServiceExporter明确支持使用RMI调用器暴露任何非RMI的服务。

    当然,我们首先需要在Spring容器中设置我们的服务:

    <bean id="accountService" class="example.AccountServiceImpl">
        <!-- any additional properties, maybe a DAO? -->
    </bean>

    下一步我们需要使用RmiServiceExporter来暴露我们的服务:

    <bean class="org.springframework.remoting.rmi.RmiServiceExporter">
        <!-- does not necessarily have to be the same name as the bean to be exported -->
        <property name="serviceName" value="AccountService"/>
        <property name="service" ref="accountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
        <!-- defaults to 1099 -->
        <property name="registryPort" value="1199"/>
    </bean>

    正如你所见,我们覆盖了RMI注册的端口号。通常你的应用服务器还维护一个RMI注册表,明智的做法是不要和它冲突。此外,服务名是用来绑定服务的。现在服务绑定在‘rmi://HOST:1199/AccountService’。我们将在客户端使用这个URL来链接到服务。

    Note:servicePort属性被省略了(默认值为0).这表示在与服务通信时将使用匿名端口.

    24.2.2 在客户端链接服务

    我们的客户端是一个使用AccountService来管理account的简单对象:

    public class SimpleObject {
    
        private AccountService accountService;
    
        public void setAccountService(AccountService accountService) {
            this.accountService = accountService;
        }
    
        // additional methods using the accountService
    
    }

    为了把服务链接到客户端上,我们将创建一个单独的Spring容器,包含这个简单对象和链接配置位的服务:

    <bean class="example.SimpleObject">
        <property name="accountService" ref="accountService"/>
    </bean>
    
    <bean id="accountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
        <property name="serviceUrl" value="rmi://HOST:1199/AccountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>

    这就是我们为支持远程account服务在客户端所需要做的。Spring将透明地创建一个调用器并且通过RmiServiceExporter使得account服务支持远程服务。在客户端,我们用RmiProxyFactoryBean连接它。

    24.3 使用Hessian通过HTTP远程调用服务

    Hessian提供一种基于HTTP的二进制远程协议。它由Caucho开发的,可以在 http://www.caucho.com 找到更多有关Hessian的信息。

    24.3.1 为Hessian和co.配置DispatcherServlet

    Hessian使用一个自定义Servlet通过HTTP进行通讯。使用Spring的DispatcherServlet原理,从Spring Web MVC使用中可以看出,可以很容易的配置这样一个Servlet来暴露你的服务。首先我们要在你的应用里创建一个新的Servlet(以下摘录自web.xml):

    <servlet>
        <servlet-name>remoting</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>remoting</servlet-name>
        <url-pattern>/remoting/*</url-pattern>
    </servlet-mapping>

    你可能对Spring的DispatcherServlet很熟悉,这样你将需要在’WEB-INF’目录中创建一个名为’remoting-servlet.xml'(在你的servlet名称后) 的Spring容器配置上下文。这个应用上下文将在下一节中里使用。

    或者,可以考虑使用Spring中更简单的HttpRequestHandlerServlet。这允许你在根应用上下文(默认是’WEB-INF/applicationContext.xml’)中嵌入远程exporter定义。每个servlet定义指向特定的exporter bean。在这种情况下,每个servlet的名称需要和目标exporter bean的名称相匹配。

    24.3.2 使用HessianServiceExporter暴露你的bean

    在新创建的remoting-servlet.xml应用上下文里,我们将创建一个HessianServiceExporter来暴露你的服务:

    <bean id="accountService" class="example.AccountServiceImpl">
        <!-- any additional properties, maybe a DAO? -->
    </bean>
    
    <bean name="/AccountService" class="org.springframework.remoting.caucho.HessianServiceExporter">
        <property name="service" ref="accountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>

    现在我们准备好在客户端连接服务了。不必显示指定处理器的映射,所以使用BeanNameUrlHandlerMapping把URL请求映射到服务上:因此,服务将通过其包含的bean名称指定的URL导出 DispatcherServlet’s mapping (as defined above): ’http://HOST:8080/remoting/AccountService’ 或者, 在你的根应用上下文中创建一个HessianServiceExporter(比如在’WEB-INF/applicationContext.xml’中):

    <bean name="accountExporter" class="org.springframework.remoting.caucho.HessianServiceExporter">
        <property name="service" ref="accountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>

    在后一情况下, 在’web.xml’中为这个导出器定义一个相应的servlet,也能得到同样的结果:这个导出器映射到request路径/remoting/AccountService。注意这个servlet名称需要与目标导出器bean的名称相匹配。

    <servlet>
        <servlet-name>accountExporter</servlet-name>
        <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>accountExporter</servlet-name>
        <url-pattern>/remoting/AccountService</url-pattern>
    </servlet-mapping>

    24.3.3 在客户端上链接服务

    使用HessianProxyFactoryBean,我们可以在客户端链接服务。与RMI示例一样也适用相同的原理。我们将创建一个单独的bean工厂或者应用上下文,并指明SimpleObject使用AccountService来管理accounts的以下bean:

    <bean class="example.SimpleObject">
        <property name="accountService" ref="accountService"/>
    </bean>
    
    <bean id="accountService" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
        <property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>

    24.3.4 对通过Hessian暴露的服务使用HTTP基本认证

    Hessian的优点之一是,我们可以轻松应用HTTP基本身份验证,因为这两种协议都是基于HTTP的。你的正常HTTP 服务器安全机制可以通过使用web.xml安全功能来应用。通常,你不会为每个用户都建立不同的安全证书,而是在Hessian/BurlapProxyFactoryBean级别共享安全证书(类似一个JDBCDataSource)。

    <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
        <property name="interceptors" ref="authorizationInterceptor"/>
    </bean>
    
    <bean id="authorizationInterceptor"
            class="org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor">
        <property name="authorizedRoles" value="administrator,operator"/>
    </bean>

    这个是我们显式使用了BeanNameUrlHandlerMapping的例子,并设置了一个拦截器,只允许管理员和操作员调用这个应用上下文中提及的bean。

    Note: 当然,这个例子并不表现出灵活的安全架构。有关安全性方面的更多选项,请查看Spring Security项目http://projects.spring.io/spring-security/。

    24.4 使用HTTP调用器暴露服务

    与使用自身序列化机制的轻量级协议Hessian相反,Spring HTTP调用器使用标准Java序列化机制通过HTTP暴露业务。如果你的参数或返回值是复杂类型,并且不能通过Hessian的序列化机制进行序列化,HTTP调用器就很有优势(请参阅下一节,以便在选择远程处理技术时进行更多考虑)。

    在底层,Spring使用JDK提供的标准工具或Commons的HttpComponents来实现HTTP调用。如果你需要更先进和更易用的功能,请使用后者。你可以参考 hc.apache.org/httpcomponents-client-ga/ 以获取更多信息。

    24.4.1 暴露服务对象

    为服务对象设置HTTP调用器基础架构类似于使用Hessian进行相同操作的方式。就象为Hessian支持提供的HessianServiceExporter,Spring的HTTP调用器提供了org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter。 为了在Spring Web MVC的DispatcherServlet中暴露AccountService(之前章节提及过), 需要在调度程序的应用程序上下文中使用以下配置:

    <bean name="/AccountService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
        <property name="service" ref="accountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>

    如Hessian章节部分所述,这个导出器定义将通过DispatcherServlet的标准映射工具暴露出来。 或者, 在你的根应用上下文中(比如’WEB-INF/applicationContext.xml’)创建一个HttpInvokerServiceExporter:

    <bean name="accountExporter" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
        <property name="service" ref="accountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>

    此外,在’web.xml’中为该导出器定义相应的servlet ,其中servlet名称与目标导出器的bean名称相匹配:

    <servlet>
        <servlet-name>accountExporter</servlet-name>
        <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>accountExporter</servlet-name>
        <url-pattern>/remoting/AccountService</url-pattern>
    </servlet-mapping>

    如果你在一个servlet容器之外运行程序和使用Oracle的Java6, 那么你可以使用内置的HTTP服务器实现。你可以配置SimpleHttpServerFactoryBean和SimpleHttpInvokerServiceExporter在一起,像下面这个例子一样:

    <bean name="accountExporter"
            class="org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter">
        <property name="service" ref="accountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>
    
    <bean id="httpServer"
            class="org.springframework.remoting.support.SimpleHttpServerFactoryBean">
        <property name="contexts">
            <util:map>
                <entry key="/remoting/AccountService" value-ref="accountExporter"/>
            </util:map>
        </property>
        <property name="port" value="8080" />
    </bean>

    24.4.2 在客户端连接服务

    同样,从客户端连接业务与你使用Hessian所做的很相似。使用代理,Spring可以将你的HTTP POST调用请求转换成被暴露服务的URL。

    <bean id="httpInvokerProxy" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
        <property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/>
        <property name="serviceInterface" value="example.AccountService"/>
    </bean>

    如前所述,你可以选择要使用的HTTP客户端。默认情况下,HttpInvokerProxy使用JDK的HTTP功能,但你也可以通过设置httpInvokerRequestExecutor属性来使用ApacheHttpComponents客户端:

    <property name="httpInvokerRequestExecutor">
        <bean class="org.springframework.remoting.httpinvoker.HttpComponentsHttpInvokerRequestExecutor"/>
    </property>

    24.5 Web 服务

    Spring提供了对标准Java Web服务API的全面支持:

    • 使用JAX-WS暴露Web服务
    • 使用JAX-WS访问Web服务

    除了在Spring Core中支持 JAX-WS,Spring portfolio也提供了一种特性Spring Web Services,一种为契约优先和文档驱动的web服务所提供的方案,强烈建议用来创建现代化的,面向未来的web服务。

    24.5.1使用JAX- WS暴露基于servlet的web服务

    Spring为JAX-WS servlet的端点实现提供了一个方便的基类 – SpringBeanAutowiringSupport. 为了暴露我们的AccountService,我们扩展Spring的SpringBeanAutowiringSupport类并在这里实现了我们的业务逻辑,通常委派调用业务层。我们在Spring管理的bean里面简单地使用Spring的@Autowired 注解来表达这样的依赖关系。

    /**
     * JAX-WS compliant AccountService implementation that simply delegates
     * to the AccountService implementation in the root web application context.
     *
     * This wrapper class is necessary because JAX-WS requires working with dedicated
     * endpoint classes. If an existing service needs to be exported, a wrapper that
     * extends SpringBeanAutowiringSupport for simple Spring bean autowiring (through
     * the @Autowired annotation) is the simplest JAX-WS compliant way.
     *
     * This is the class registered with the server-side JAX-WS implementation.
     * In the case of a Java EE 5 server, this would simply be defined as a servlet
     * in web.xml, with the server detecting that this is a JAX-WS endpoint and reacting
     * accordingly. The servlet name usually needs to match the specified WS service name.
     *
     * The web service engine manages the lifecycle of instances of this class.
     * Spring bean references will just be wired in here.
     */
    import org.springframework.web.context.support.SpringBeanAutowiringSupport;
    
    @WebService(serviceName="AccountService")
    public class AccountServiceEndpoint extends SpringBeanAutowiringSupport {
    
        @Autowired
        private AccountService biz;
    
        @WebMethod
        public void insertAccount(Account acc) {
            biz.insertAccount(acc);
        }
    
        @WebMethod
        public Account[] getAccounts(String name) {
            return biz.getAccounts(name);
        }
    
    }

    我们的AccountServletEndpoint需要和Spring在同一个上下文的web应用里运行,以允许访问Spring的功能。为JAX-WS servlet端点部署使用标准规约是Java EE 5 环境下的默认情况。

    24.5.2 使用JAX-WS暴露单独web服务

    Oracle JDK 1.6附带的内置JAX-WS provider 使用内置的HTTP服务器来暴露web服务。Spring的SimpleJaxWsServiceExporter类检测所有在Spring应用上下文中配置有@WebService注解的bean,然后通过默认的JAX-WS服务器(JDK 1.6 HTTP服务器)导出。

    在这种场景下,端点实例将被作为Spring bean来定义和管理。它们将使用JAX-WS引擎来注册,但其生命周期将由Spring应用程序上下文决定。这意味着Spring的显示依赖注入可用于端点实例。当然通过@Autowired来进行注解驱动的注入也会起作用。

    <bean class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter">
        <property name="baseAddress" value="http://localhost:8080/"/>
    </bean>
    
    <bean id="accountServiceEndpoint" class="example.AccountServiceEndpoint">
        ...
    </bean>
    
    ...

    AccountServiceEndpoint可能来自于Spring的SpringBeanAutowiringSupport,也可能不是。因为这里的端点是由Spring完全管理的bean。这意味着端点实现可能像下面这样没有任何父类定义 – 而且Spring的@Autowired配置注解仍然能够使用:

    @WebService(serviceName="AccountService")
    public class AccountServiceEndpoint {
    
        @Autowired
        private AccountService biz;
    
        @WebMethod
        public void insertAccount(Account acc) {
            biz.insertAccount(acc);
        }
    
        @WebMethod
        public List<Account> getAccounts(String name) {
            return biz.getAccounts(name);
        }
    
    }

    24.5.3 使用JAX-WS RI的Spring支持来暴露服务

    Oracle的JAX-WS RI被作为GlassFish项目的一部分来开发,它使用了Spring支持来作为JAX-WS Commons项目的一部分。这允许把JAX-WS端点作为Spring管理的bean来定义。这与前面章节讨论的单独模式类似 – 但这次是在Servlet环境中。注意这在Java EE 5环境中是不可迁移的,建议在没有EE的web应用环境如Tomcat中嵌入JAX-WS RI。 与标准的暴露基于servlet的端点方式不同之处在于端点实例的生命周期将被Spring管理。这里在web.xml将只有一个JAX-WS servlet定义。在标准的Java EE 5风格中(如上所示),你将对每个服务端点定义一个servlet,每个服务端点都代理到Spring bean (通过使用@Autowired,如上所示)。 关于安装和使用详情请查阅https://jax-ws-commons.dev.java.net/spring/

    24.5.4 使用JAX-WS访问web服务

    Spring提供了2个工厂bean来创建JAX-WS web服务代理,它们是LocalJaxWsServiceFactoryBean和JaxWsPortProxyFactoryBean。前一个只能返回一个JAX-WS服务对象来让我们使用。后面的是可以返回我们业务服务接口的代理实现的完整版本。这个例子中我们使用后者来为AccountService端点再创建一个代理:

    <bean id="accountWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
        <property name="serviceInterface" value="example.AccountService"/>
        <property name="wsdlDocumentUrl" value="http://localhost:8888/AccountServiceEndpoint?WSDL"/>
        <property name="namespaceUri" value="http://example/"/>
        <property name="serviceName" value="AccountService"/>
        <property name="portName" value="AccountServiceEndpointPort"/>
    </bean>

    serviceInterface是我们客户端将使用的远程业务接口。wsdlDocumentUrl是WSDL文件的URL. Spring需要用它作为启动点来创建JAX-WS服务。namespaceUri对应.wsdl文件中的targetNamespace。serviceName对应.wsdl文件中的服务名。portName对应.wsdl文件中的端口号。 现在我们可以很方便的访问web服务,因为我们有一个可以将它暴露为AccountService接口的bean工厂。我们可以在Spring中这样使用:

    <bean id="client" class="example.AccountClientImpl">
        ...
        <property name="service" ref="accountWebService"/>
    </bean>

    从客户端代码上我们可以把这个web服务当成一个普通的类进行访问:

    public class AccountClientImpl {
    
        private AccountService service;
    
        public void setService(AccountService service) {
            this.service = service;
        }
    
        public void foo() {
            service.insertAccount(...);
        }
    }

    .

    Note: 上面例子被稍微简化了,因为JAX-WS需要端点接口及实现类来使用@WebService,@SOAPBinding等注解。 这意味着你不能简单地使用普通的Java接口和实现来作为JAX-WS端点,你需要首先对它们进行相应的注解。这些需求详情请查阅JAX-WS文档。

    24.6 JMS

    使用JMS来作为底层的通信协议透明暴露服务也是可能的。Spring框架中对JMS的远程支持也很基础 – 它在同一线程和同一个非事务 Session上发送和接收,这些吞吐量将非常依赖于实现。需要注意的是这些单线程和非事务的约束仅适用于Spring的JMS远程处理支持。请参见 第26章, JMS (Java消息服务),Spring对基于JMS的消息的丰富支持。 下面的接口可同时用在服务端和客户端。

    package com.foo;
    
    public interface CheckingAccountService {
    
        public void cancelAccount(Long accountId);
    
    }

    对于上面接口的使用在服务的端简单实现如下:

    package com.foo;
    
    public class SimpleCheckingAccountService implements CheckingAccountService {
    
        public void cancelAccount(Long accountId) {
            System.out.println("Cancelling account [" + accountId + "]");
        }
    
    }

    这个包含JMS设施的bean的配置文件可同时用在客户端和服务端: <?xml version=”1.0″ encoding=”UTF-8″?>

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
            <property name="brokerURL" value="tcp://ep-t43:61616"/>
        </bean>
    
        <bean id="queue" class="org.apache.activemq.command.ActiveMQQueue">
            <constructor-arg value="mmm"/>
        </bean>
    
    </beans>

    24.6.1 服务端配置

    在服务端你只需要使用JmsInvokerServiceExporter来暴露服务对象。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="checkingAccountService"
                class="org.springframework.jms.remoting.JmsInvokerServiceExporter">
            <property name="serviceInterface" value="com.foo.CheckingAccountService"/>
            <property name="service">
                <bean class="com.foo.SimpleCheckingAccountService"/>
            </property>
        </bean>
    
        <bean class="org.springframework.jms.listener.SimpleMessageListenerContainer">
            <property name="connectionFactory" ref="connectionFactory"/>
            <property name="destination" ref="queue"/>
            <property name="concurrentConsumers" value="3"/>
            <property name="messageListener" ref="checkingAccountService"/>
        </bean>
    
    </beans>

    .

    package com.foo;
    
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class Server {
    
        public static void main(String[] args) throws Exception {
            new ClassPathXmlApplicationContext(new String[]{"com/foo/server.xml", "com/foo/jms.xml"});
        }
    
    }

    24.6.2 客户端配置

    客户端只需要创建一个客户端代理来实现上面的接口(CheckingAccountService)。根据后面的bean定义创建的结果对象可以被注入到其它客户端对象中,而这个代理会负责通过JMS将调用转发到服务端。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="checkingAccountService"
                class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean">
            <property name="serviceInterface" value="com.foo.CheckingAccountService"/>
            <property name="connectionFactory" ref="connectionFactory"/>
            <property name="queue" ref="queue"/>
        </bean>
    
    </beans>

    .

    package com.foo;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class Client {
    
        public static void main(String[] args) throws Exception {
            ApplicationContext ctx = new ClassPathXmlApplicationContext(
                    new String[] {"com/foo/client.xml", "com/foo/jms.xml"});
            CheckingAccountService service = (CheckingAccountService) ctx.getBean("checkingAccountService");
            service.cancelAccount(new Long(10));
        }
    
    }

    24.7 AMQP

    有关更多信息,请参考 Spring AMQP Reference Document ‘Spring Remoting with AMQP’ section 。

    24.8 不实现远程接口自动检测

    对远程接口不实现自动探测的主要原因是为了避免向远程调用者打开了太多的大门。目标对象有可能实现的是类似InitializingBean或者DisposableBean这样的内部回调接口,而这些是不希望暴露给调用者的。

    提供一个所有接口都被目标实现的代理通常和本地情况无关。但是当暴露一个远程服务时,你应该只暴露特定的用于远程使用的服务接口。除了内部回调接口,目标有可能实现了多个业务接口,而往往只有一个是用于远程调用的。出于这些原因,我们要求指定这样的服务接口。

    这是在配置方便性和意外暴露内部方法的危险性之间作的权衡。始终指定一个服务接口并不需要花太大代价,并可以令控制具体方法暴露更加安全。

    24.9 选择技术时的注意事项

    这里提到的每种技术都有它的缺点。你在选择一种技术时,应该仔细考虑你的需要和所暴露的服务及你在远程访问时传送的对象。

    当使用RMI时,通过HTTP协议访问对象是不可能的,除非你正在HTTP通道传输RMI流量。RMI是一种重量级协议,因为它支持整个对象的序列化,当要求网络上传输复杂数据结构时这是非常重要的。然而,RMI-JRMP与Java客户端相关:它是一种Java-to-Java的远程访问解决方案。

    如果你需要基于HTTP的远程访问而且还要求使用Java序列化,Spring的HTTP调用器是一个不错的选择。它和RMI调用器共享相同的基础设施,只需使用HTTP作为传输。注意HTTP调用器不仅限于Java-to-Java的远程访问,而且还限于使用Spring的客户端和服务器端。(后者也适用于Spring的RMI调用器,用于非RMI接口。)

    Hessian可以在异构环境中运行时提供重要的价值,因为它们明确允许非Java客户端。然而,非Java支持仍然有限。已知问题包括将Hibernate对象与延迟初始化的集合相结合的序列化。如果您有这样的数据模型,请考虑使用RMI或HTTP调用者而不是Hessian。

    在使用服务集群和需要JMS代理(JMS broker)来处理负载均衡及发现和自动-失败恢复服务时JMS是很有用的。缺省情况下,在使用JMS远程服务时使用Java序列化,但是JMS提供者也可以使用不同的机制例如XStream来让服务器用其他技术。

    最后但并非最不重要的是,EJB比RMI具有优势,因为它支持标准的基于角色的身份认证和授权,以及远程事务传递。用RMI调用器或HTTP调用器来支持安全上下文的传递是可能的,虽然这不由核心core Spring提供:Spring提供了合适的钩子来插入第三方或定制的解决方案。

    24.10 在客户端访问RESTful服务

    RestTemplate是客户端访问RESTful服务的核心类。它在概念上类似于Spring中的其他模板类,例如JdbcTemplate、 JmsTemplate和其他Spring组合项目中发现的其他模板类。

    RestTemplate’s behavior is customized by providing callback methods and configuring the `HttpMessageConverter用于将对象打包到HTTP请求体中,并将任何响应解包成一个对象。通常使用XML作为消息格式,Spring提供了MarshallingHttpMessageConverter,它使用了的Object-to-XML框架,也是org.springframework.oxm包的一部分。这为你提供了各种各样的XML到对象映射技术的选择。

    本节介绍如何使用RestTemplate它及其关联 的HttpMessageConverters。

    24.10.1 RestTemplate

    在Java中调用RESTful服务通常使用助手类(如Apache HttpComponents)完成HttpClient。对于常见的REST操作,此方法的级别太低,如下所示。

    String uri = "http://example.com/hotels/1/bookings";
    
    PostMethod post = new PostMethod(uri);
    String request = // create booking request content
    post.setRequestEntity(new StringRequestEntity(request));
    
    httpClient.executeMethod(post);
    
    if (HttpStatus.SC_CREATED == post.getStatusCode()) {
        Header location = post.getRequestHeader("Location");
        if (location != null) {
            System.out.println("Created new booking at :" + location.getValue());
        }
    }

    RestTemplate提供了更高级别的方法,这些方法与六种主要的HTTP方法中的每一种相对应,这些方法使得调用许多RESTful服务成为一个单行和执行REST的最佳实践。

    Note: RestTemplate具有异步计数器部分:请参见第24.10.3节“异步RestTemplate”

    Table 24.1. RestTemplate方法概述

    RestTemplate方法名称遵循命名约定,第一部分指出正在调用什么HTTP方法,第二部分指出返回的内容。例如,该方法getForObject()将执行GET,将HTTP响应转换为你选择的对象类型并返回该对象。方法postForLocation() 将执行POST,将给定对象转换为HTTP请求,并返回可以找到新创建的对象的响应HTTP Location头。在异常处理HTTP请求的情况下,RestClientException类型的异常将被抛出; 这个行为可以在RestTemplate通过插入另一个ResponseErrorHandler实现来改变。

    exchange和execute方法是上面列出的更具体的方法的广义版本,并且可以支持额外的组合和方法,例如HTTP PATCH。但是,请注意,底层HTTP库还必须支持所需的组合。JDK HttpURLConnection不支持该PATCH方法,但Apache HttpComponents HttpClient4.2或更高版本支持。他们还能够通过使用一个能够捕获和传递通用类型信息的新类ParameterizedTypeReference来使得RestTemplate能够读取通用类型的HTTP响应信息(例如List)。

    对象通过HttpMessageConverter实例传递给这些方法并从这些方法返回被转换为HTTP消息。主要mime类型的转换器默认注册,但你也可以编写自己的转换器并通过messageConverters()实体属性注册它 。模板默认注册的转换器实例是ByteArrayHttpMessageConverter,StringHttpMessageConverter,FormHttpMessageConverter和SourceHttpMessageConverter。如果使用MarshallingHttpMessageConverter或者MappingJackson2HttpMessageConverter,你可以使用messageConverters()实体属性覆盖这些默认值。

    每个方法以两种形式使用URI模板参数,作为String可变长度参数或Map<String,String>。例如,使用可变长参数如下:

    String result = restTemplate.getForObject(
        "http://example.com/hotels/{hotel}/bookings/{booking}", String.class,"42", "21");

    使用一个Map<String,String>如下:

    Map<String, String> vars = Collections.singletonMap("hotel", "42");
    String result = restTemplate.getForObject(
            "http://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);

    要创建一个实例,RestTemplate可以简单地调用默认的无参数构造函数。这将使用java.net包中的标准Java类作为底层实现来创建HTTP请求。这可以通过指定实现来覆盖ClientHttpRequestFactory。Spring提供了HttpComponentsClientHttpRequestFactory使用Apache HttpComponents HttpClient创建请求的实现。HttpComponentsClientHttpRequestFactory通过使用一个可以配置凭证信息或连接池功能的org.apache.http.client.HttpClient实例来配置。

    Note: HTTP请求的java.net实现可能会在访问表示错误的响应状态(例如401)时引发异常。如果这是一个问题,请切换到HttpComponentsClientHttpRequestFactory。

    前面使用Apache HttpCOmponentsHttpClientdirectly的例子用RestTemplate重写如下:

    uri = "http://example.com/hotels/{id}/bookings";
    
    RestTemplate template = new RestTemplate();
    
    Booking booking = // create booking object
    
    URI location = template.postForLocation(uri, booking, "1");

    使用Apache HttpComponents, 而不是原生的java.net功能,构造RestTemplate如下:

    RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());

    .

    Note: Apache HttpClient 支持gzip编码,要使用这个功能,构造HttpCOmponentsClientHttpRequestFactory如下:
    HttpClient httpClient = HttpClientBuilder.create().build();
    ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
    RestTemplate restTemplate = new RestTemplate(requestFactory);

    当execute方法被调用,通用的回调接口是RequestCallback并且会被调用。

    public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback,
            ResponseExtractor<T> responseExtractor, String... uriVariables)
    
    // also has an overload with uriVariables as a Map<String, String>.

    RequestCallback接口定义如下:

    public interface RequestCallback {
     void doWithRequest(ClientHttpRequest request) throws IOException;
    }

    允许您操作请求标头并写入请求主体。当使用execute方法时,你不必担心任何资源管理,模板将始终关闭请求并处理任何错误。有关使用execute方法及它的其他方法参数的含义的更多信息,请参阅API文档。

    使用URI

    对于每个主要的HTTP方法,RestTemplate提供的变体使用String URI或java.net.URI作为第一个参数。
    String URI变体将模板参数视为String变长参数或者一个Map<String,String>。他们还假定URL字符串不被编码且需要编码。例如:

    restTemplate.getForObject("http://example.com/hotel list", String.class);

    将在 http://example.com/hotel%20list执行一个GET请求。这意味着如果输入的URL字符串已被编码,它将被编码两次 – 即将 http://example.com/hotel%20list变为http://example.com/hotel%2520list。如果这不是预期的效果,则使用java.net.URI方法变体,假设URL已经被编码,如果要重复使用单个(完全扩展)URI多次,通常也是有用的。

    UriComponentsBuilder类可用于构建和编码URI包括URI模板的支持。例如,你可以从URL字符串开始:

    UriComponents uriComponents = UriComponentsBuilder.fromUriString( "http://example.com/hotels/{hotel}/bookings/{booking}").build()
            .expand("42", "21")
            .encode();
    
    URI uri = uriComponents.toUri();

    或者分别制定每个URI组件:

    UriComponents uriComponents = UriComponentsBuilder.newInstance()
            .scheme("http").host("example.com").path("/hotels/{hotel}/bookings/{booking}").build()
            .expand("42", "21")
            .encode();
    
    URI uri = uriComponents.toUri();

    处理请求和响应头

    除了上述方法之外,RestTemplate还具有exchange() 方法,可以用于基于HttpEntity 类的任意HTTP方法执行。
    也许最重要的是,该exchange()方法可以用来添加请求头和读响应头。例如:

    HttpHeaders requestHeaders = new HttpHeaders();
    requestHeaders.set("MyRequestHeader", "MyValue");
    HttpEntity<?> requestEntity = new HttpEntity(requestHeaders);
    
    HttpEntity<String> response = template.exchange( "http://example.com/hotels/{hotel}",
            HttpMethod.GET, requestEntity, String.class, "42");
    
    String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
    String body = response.getBody();

    在上面的例子,我们首先准备了一个包含MyRequestHeader 头的请求实体。然后我们检索返回和读取MyResponseHeader和消息体。

    Jackson JSON 视图支持

    可以指定一个 Jackson JSON视图来系列化对象属性的一部分,例如:

    MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
    value.setSerializationView(User.WithoutPasswordView.class);
    HttpEntity<MappingJacksonValue> entity = new HttpEntity<MappingJacksonValue>(value);
    String s = template.postForObject("http://example.com/user", entity, String.class);

    24.10.2 HTTP 消息转换

    通过HttpMessageConverters,对象传递到和从getForObject(),postForLocation(),和put()这些方法返回被转换成HTTP请求和HTTP相应。HttpMessageConverter接口如下所示,让你更好地感受它的功能:

    public interface HttpMessageConverter<T> {
    
        // Indicate whether the given class and media type can be read by this converter.
        boolean canRead(Class<?> clazz, MediaType mediaType);
    
        // Indicate whether the given class and media type can be written by this converter.
        boolean canWrite(Class<?> clazz, MediaType mediaType);
    
        // Return the list of MediaType objects supported by this converter.
        List<MediaType> getSupportedMediaTypes();
    
        // Read an object of the given type from the given input message, and returns it.
        T read(Class<T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
    
        // Write an given object to the given output message.
        void write(T t, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
    
    }

    框架中提供主要媒体(mime)类型的具体实现,默认情况下,通过RestTemplate在客户端和 RequestMethodHandlerAdapter在服务器端注册。

    HttpMessageConverter的实现下面章节中描述。对于所有转换器,使用默认媒体类型,但可以通过设置supportedMediaTypesbean属性来覆盖。

    StringHttpMessageConverter

    一个HttpMessageConverter的实现,实现从HTTP请求和相应中读和写Strings。默认情况下,该转换器支持所有的文本媒体类型(text/*),并用text/plain的Content-Type来写。

    FormHttpMessageConverter

    一个HttpMessageConverter的实现,实现从HTTP请求和响应读写表单数据。默认情况下,该转换器读写application/x-www-form-urlencoded媒体类型。表单数据被读取并写入MultiValueMap<String, String>。

    ByteArrayHttpMessageConverter

    一个HttpMessageConverter的实现,实现从HTTP请求和响应中读取和写入字节数组。默认情况下,此转换器支持所有媒体类型(/),并使用其中的一种Content-Type进行写入application/octet-stream。这可以通过设置supportedMediaTypes属性和覆盖getContentType(byte[])来重写。

    MarshallingHttpMessageConverter

    一个HttpMessageConverter的实现,从org.springframework.oxm包中使用Spring的Marshaller和Unmarshaller抽象实现读取和写入XML。该转换器需要Marshaller和Unmarshaller才能使用它。这些可以通过构造函数或bean属性注入。默认情况下,此转换器支持( text/xml)和(application/xml)。

    MappingJackson2HttpMessageConverter

    一个HttpMessageConverter的实现,使用Jackson XML扩展的ObjectMapper实现读写JSON。可以根据需要通过使用JAXB或Jackson提供的注释来定制XML映射。当需要进一步控制时,XmlMapper 可以通过ObjectMapper属性注入自定义,以便需要为特定类型提供自定义XML序列化器/反序列化器。默认情况下,此转换器支持(application/xml)。

    MappingJackson2XmlHttpMessageConverter

    一个HttpMessageConverter的实现,可以使用Jackson XML扩展的XmlMapper读取和写入XML。可以根据需要通过使用JAXB或Jackson提供的注释来定制XML映射。当需要进一步控制时,XmlMapper 可以通过ObjectMapper属性注入自定义,以便需要为特定类型提供自定义XML序列化器/反序列化器。默认情况下,此转换器支持(application/xml)。

    SourceHttpMessageConverter

    一个HttpMessageConverter的实现,从HTTP请求和响应中读写 javax.xml.transform.Source。仅支持DOMSource、SAXSource和StreamSource。默认情况下,此转换器支持(text/xml)和(application/xml)。

    BufferedImageHttpMessageConverter

    一个HttpMessageConverter的实现,从HTTP请求和响应中读写java.awt.image.BufferedImage。此转换器读写Java I/O API支持的媒体类型。

    24.10.3 异步RestTemplate

    Web应用程序通常需要查询外部REST服务。当为这些需求扩张应用程序时,HTTP和同步调用的性质带来挑战:可能会阻塞多个线程,等待远程HTTP响应。

    AsyncRestTemplate和第24.10.1节“RestTemplate”的API非常相似; 请 参见表24.1“RestTemplate方法概述”。这些API之间的主要区别是AsyncRestTemplate返回ListenableFuture 封装器而不是具体的结果。

    前面的RestTemplate例子翻译成:

    // async call
    Future<ResponseEntity<String>> futureEntity = template.getForEntity(
        "http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");
    
    // get the concrete result - synchronous call
    ResponseEntity<String> entity = futureEntity.get();

    ListenableFuture 接受完成回调:

    ListenableFuture<ResponseEntity<String>> futureEntity = template.getForEntity(
        "http://example.com/hotels/{hotel}/bookings/{booking}", String.class, "42", "21");
    
    // register a callback
    futureEntity.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
        @Override
        public void onSuccess(ResponseEntity<String> entity) {
            //...
        }
    
        @Override
        public void onFailure(Throwable t) {
            //...
        }
    });

    .

    Note: 默认AsyncRestTemplate构造函数为执行HTTP请求注册一个SimpleAsyncTaskExecutor 。当处理大量短命令请求时,线程池的TaskExecutor实现ThreadPoolTaskExecutor 可能是一个不错的选择。

    有关更多详细信息,参考ListenableFuture的javadoc and AsyncTestTmeplate的javadoc.

  • JVM调优之:JDK自带工具大全——工欲善其事,必先利其器!

    本文主要讲解JDK1.8自带工具用途和用法,因为绝大部分Java程序都是部署在Linux平台,所以讲解Linux工具(Windows平台工具可以参考Oracle文档https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html)。 

    按用途分为12类:基础工具、安全工具、国际化工具、远程方法调用(RMI)工具、Java IDL and RMI-IIOP工具、Java部署工具、Java Web启动工具、Java诊断分析监控管理工具、Java Web服务工具、监控工具、分析工具和脚本工具。
    JDK1.8u20下的工具目录图: 

    java_tools_full

    (迟些时候再写一篇文章说明常用工具用法和技巧)

  • jstack: Java占用高CPU分析之- C2 CompilerThread

    1. 现象

    应用刚上线时发现Java进程占用了大量的CPU份额,但过了几分钟后会降下来(流量没变的情况下),因为已经做了负载均衡,于是拿一台实例重新部署代码上线来分析。具体分析步骤参考另外一篇文章《jstack: Java占用高CPU分析之- GC task thread》。这里简单说一下步骤,重点是分析结果后的解决方法,不过强调一点:当发现Java应用占用高CPU的时候,先把Java的线程号和JVM的堆栈信息记录到文件(这个可以用脚本实现),因为堆栈信息随时在变

     

    2.定位

    top | grep java

    cpu2_1

    cpu2_2

    cpu2_3

    cpu2_4

     

    3.原因和解释

    定位到 C2 CompilerThread0这个线程占用了比较高的CPU。C2 Compiler 是JVM在server模式下字节码编译器,JVM启动的时候所有代码都处于解释执行模式,当某些代码被执行到一定阈值次数,这些代码(称为热点代码)就会被 C2 Compiler编译成机器码,编译成机器码后执行效率会得到大幅提升。

    流量进来后,大部分代码成为热点代码,这个过程中C2 Compiler需要频繁占用CPU来运行,当大部分热点代码被编译成机器代码后,C2 Compiler就不再长期占用CPU了,这个过程也可以看作抖动。

    4.解决方案

    (1)最直接有效的方法是“预热(warm up)”:可以使用Jmeter等压测工具模拟线上访问流量,让C2 Compiler预先将热点代码编译成机器码, 减少对正式环境流量的影响。

    warmup

    (2) 设置JVM启动参数:-XX:CICompilerCount=threads

    默认是2, 可以设置4或6。在默认值下抖动时CPU已经满载,设置成更多的线程也不一定起作用,但对于CPU“高而不满”的情况会有用,能减少抖动时间。

    CICompilerCount

     

    参考文章:

    http://www.javaworld.com/article/2078635/enterprise-middleware/jvm-performance-optimization-part-2-compilers.html

    https://answers.atlassian.com/questions/22651310/c1c2-compiler-thread-eats-a-lot-of-cpu

    http://www.cnblogs.com/LBSer/p/3703967.html

    http://qa.blog.163.com/blog/static/190147002201392221426372/

    https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html

     

  • Java8 集合框架 collections framework

    一.概述

    Java8发布已经有两年半了(2014年03月),是Java5以来改动最大的一次版本发布(具体新特性http://openjdk.java.net/projects/jdk8/features )。公司项目也已经用了两年(不得不佩服国外同事在新技术使用方面的先进性),不过国内大部分公司还是在使用Java7,甚至一些传统ERP公司还在使用Java6。Dzone上面有一篇ZK关于开发者使用JDK版本使用情况调查的文章https://dzone.com/articles/current-development-trends-of-java-web-programmers

                                                                        jdk_version

    • 注:开发使用的JDK版本情况和生产上会有差别,开发中新版本比例要比生产高,一般建议开发和生产使用同版本号(包括update的小版本号)。

    本文主要介绍Java8集合框架的架构、常用集合特点用法和工具类,因为集合框架是Java中被使用最多的类库。Java8对集合框架进行了大改进,支持lambda表达式、streams 和聚合操作,以提升对Collection对象功能的增强(其中streams支持List、Queue和Set, 不支持Map)。直接使用Java集合框架的类,而不是自己编写类和方法,有以下几个好处:

    • 减少编程量
    • 提高编程速度和质量
    • 减少学习和使用新APIs的工作量
    • 促进软件重用

    一个例子就是用BlockingQueue实现生产消费者模型(BlockingQueue的javadoc里有例子),而不是自己写wait和notifyAll,因为容易出错,当然其中的原理还是要懂。

    二.集合分类和实现

    Java 集合框架中Collection是最基础的接口,另一个基础接口是Map。Collection主要由Set、Queue和List这三种接口继承,并形成三种基本的数据结构。

    1.实现了集合接口的类通常具有<Implementation-style><Interface>形式的名称,下表总结了常用的实现:

    Interface Hash Table Resizable Array Balanced Tree Linked List Hash Table + Linked List
    Set HashSet TreeSet LinkedHashSet
    List ArrayList LinkedList
    Deque ArrayDeque LinkedList
    Map HashMap TreeMap LinkedHashMap

     

    2. Collection接口及子类子接口继承实现图:

    java_collections_framework

    3.Map接口及子类子接口继承实现图:

    map  

    三.集合类在一些流行框架里面的使用情况

    虽然有些集合类(如 WeakHashMap)我们自己写代码可能会比较少用,但在一些流行框架里面使用却非常广泛,了解这些集合的特点和适用场景对于学习这些框架的设计思想和源码会很有帮助。

    1.线程池框架:

    Executors是java.util.concurrent包里面的工厂类,可以用它来返回一个制定功能(包括执行策略、扩容、调度和拒绝策略等)的线程池,而这些线程池就用到了集合框架,了解这些集合的特性能更好地使用线程池和处理可能会出现的问题:

    • newFixedThreadPool:
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); 
    }

    • newCachedThreadPool:
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
    }

    • newScheduledThreadPool:
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
    }
    • newSingleThreadExecutor:
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService(
            new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
    }
    • newSingleTheadScheduledExecutor:
    public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1));
    }

    可以看到这些线程池内部的集合类都是BlockingQueue的子类(ScheduledThreadPoolExecutor的内部集合类也是继承了AbstractQueue
    <Runnable>和实现了BlockingQueue<Runnable>)。所以了解BlockingQueue的特性对于了解线程池很有帮助。

    2.几种常用Java服务,在JVM heap中数量和占用内存top 15的情况:

    (1). 基于Spring MVC 4.x + Jetty 9.x的RESTful webservice

    SpringMVC  

    (2).基于Netty 5.x的RPC server

    netty  

    (3). 基于mina 2.x 的push服务

    mina  

     

  • jstack: Java占用高CPU分析之- GC task thread

    jstack 是JDK自带的堆栈跟踪工具,作用有两个:

    1. 为Java 进程或者核心文件打印出线程的堆栈信息;
    2. 远程调试服务器。

    查看用法:

    jstack -help
    Usage:
    jstack [-l] <pid>
    (to connect to running process)
    jstack -F [-m] [-l] <pid>
    (to connect to a hung process)
    jstack [-m] [-l] <executable> <core>
    (to connect to a core file)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
    (to connect to a remote debug server)

    Options:
    -F to force a thread dump. Use when jstack <pid> does not respond (process is hung)
    -m to print both java and native frames (mixed mode)
    -l long listing. Prints additional information about locks
    -h or -help to print this help message

    下面开始具体的调试步骤:

    1. top 命令可以看到具体的Java进程号:(注意这里的PID是进程ID)

    pid

    2. 然后按 Shift + h 直接列出线程号:注意这里的PID是线程ID(对应的是native 线程的id, 即nid)

    tid

    3. 可以看到主要是19226和19227这两个线程消耗大量的CPU,而且时间累占用了一个小时左右,需要把线程号转换成十六进制,因为打印出来的堆栈信息里面的线程号是十六进制形式: printf “%x/n” 19226

    printf

    4. 获得占用CPU的Java进程号和线程号后可以用jstack工具来打印堆栈信息了:

    (1) 先把整个进程堆栈信息打印预览: jstack 19214

    stack1

    stack2

    (2)如果要快速定位线程造成的问题用:jstack [PID(进程号)] | grep [PID(线程号十六进制)] -A 10

    stack3

     

    5. 从进程和线程的堆栈信息可以看出是JVM的GC线程一直在占用大量CPU, 定位代码得出的结论是:Java程序连MySQL频繁new connection而且没有调用close方法,导致GC线程一直占用CPU。