Spring开发小组意识到在RMI服务和基于HTTP的服务如(Hessian和Burlap)之间的空白,一方面,RMI使用Java标准对象序列化,很难穿越防火墙,另一方面,Hessian/Burlap能很好的穿过防火墙工作,但是使用自己的私有的一套磁序列化机制。
  就这样,Spring的HttpInvoker应运而生,HttpInvoker是一个新的远程调用模型,作为Spring框架的一部分,来执行基于HTTP的远程调用(让防火墙高兴的事情),并使用Java序列化机制 (让程序员高兴的事情)。
  我们首先来看看HttpInvoker的使用示例,HttpInvoker是基于HTTP的远程调用,同时使用Spring中提供的web服务作为基础,所以我们测试需要首先搭建web工作。

使用示例

1.创建对外接口
public interface HttpInvokerTestI {String getTestPo(String desp);
}public class HttpInvokerTestImpl implements HttpInvokerTestI {@Overridepublic String getTestPo(String desp) {return "getTestPo " + desp;}
}
2.创建服务端配置文件web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee"version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee"><display-name>spring_tiny</display-name><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring_1_100/config_71_80/spring77_httpinvoker/spring77_service.xml</param-value></context-param><servlet><servlet-name>spring_tiny</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class></servlet><servlet-mapping><servlet-name>spring_tiny</servlet-name><url-pattern>*.service</url-pattern></servlet-mapping></web-app>
3. 在WEB-INF下创建spring_tiny-servlet.xml
<?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"><!-- 在Spring的httpInvoker服务 --><bean id="httpInvokerServiceExporter"class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"><!--需要发布的实现类 --><property name="service" ref="httpinvokertest" /><property name="serviceInterface" value="com.spring_1_100.test_71_80.test77_spring_httpinvoker.service.HttpInvokerTestI" /></bean><!-- 将特定的请求映射到具体的hessianservice --><bean id="urlMapping1"class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"><property name="mappings"><props><prop key="/httpinvokertest.service">httpInvokerServiceExporter</prop></props></property></bean><bean id="httpinvokertest" class="com.spring_1_100.test_71_80.test77_spring_httpinvoker.service.HttpInvokerTestImpl"></bean>
</beans>
4.配置Spring配置文件
<?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">
</beans>
5.添加tomcat配置


  记得选择Local tomcat 。

  在添加Deployment时,记得选第一个Artifact…


  选择要发布的war包时,记得选带 exploded后缀的war包

  去除项目名称,方便使用http://localhost:8080/直接访问,而不需要在访问时url加上项目名称,如【http://localhosttt:8080/spring_tiny_war_exploded/】】


  项目启动需要用户自己去下载和配置jdk和tomcat,这里就不再赘述了。
【注意】spring_tiny这个项目,我可能经常需要测试其他的用例,可能web.xml和spring_tiny_servlet.xml经常会变,如果你要测试httpInvoker,请将备份的文件web_77.xml和spring_tiny-servlet_77.xml的内容分别覆盖掉web.xml和spring_tiny_servlet.xml两个文件内容,再启动项目进行测试。

  至此,服务端的httpInvoker服务己经搭建完全了,启动web工程后就可以使用我们搭建的HttpInvoker服务了,以上代码实现了将远程传的字符串参数处理加入了"getTestPo"前缀的功能,服务端搭建完基于Web服务的HttpInvoker后,客户端还需要使用一定的配置才能进行远程调用。

5. 创建测试端配置client.xml
<?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="remoteService" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"><property name="serviceUrl" value="http://localhost:8080/httpinvokertest.service"></property><property name="serviceInterface" value="com.spring_1_100.test_71_80.test77_spring_httpinvoker.service.HttpInvokerTestI"></property></bean>
</beans>
6.测试客户端
public class Test77_Client {public static void main(String[] args) {ApplicationContext ct = new ClassPathXmlApplicationContext("classpath:spring_1_100/config_71_80/spring77_httpinvoker/spring77_client.xml");HttpInvokerTestI httpInvokerTestI = (HttpInvokerTestI) ct.getBean("remoteService");System.out.println(httpInvokerTestI.getTestPo("dddd"));}
}

结果输出:

  dddd 是我们传入的参数,而getTestPo 则是在服务端添加的字符串,当然上面的服务搭建与测试过程中都是在一台机器上运行的,如果需要在不同的机器上进行测试,还需要读者对服务端相关的接口打成jar包并添加到客户端的服务器上。

服务端的实现

  对于Spring中的HttpInvoker月服务的实现,我们还是首先从服务端进行分析。
  根据spring_tiny-servlet.xml中的配置,我们分析入口类应该是org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter,那么同样,根据这个类的分析其入口函数,如下图所示

  通过层次关系我们看到了HttpInvokerServiceExporter类实现了InitializingBean接口以及HttpRequestHandler接口,分析RMI服务时我们己经了解到了,当某个bean继承自InitializingBean接口的时候,Spring会确保这个bean在初始化的时候调用其afterPropertiesSet方法,而对于HttpRequestHandler接口,因为我们配置中己经将此接口配置成Web服务,那么当有相应的请求时,Spring的Web服务就会将程序引导至HttpRequestHandler的handleRequest方法中,首先,我们从afterPropetiesSet方法开始分析,看看bean的初始化过程中做了哪些逻辑。

1. 创建代理
public void afterPropertiesSet() {prepare();
}public void prepare() {this.proxy = getProxyForService();
}protected Object getProxyForService() {//验证servicecheckService();//验证serviceInterfacecheckServiceInterface();//使用JDK的方式创建代理ProxyFactory proxyFactory = new ProxyFactory();//添加代理接口proxyFactory.addInterface(getServiceInterface());if (this.registerTraceInterceptor != null ?this.registerTraceInterceptor.booleanValue() : this.interceptors == null) {//加入代理横切面RemoteInvocationTraceInterceptor并记录Exporter名称proxyFactory.addAdvice(new RemoteInvocationTraceInterceptor(getExporterName()));}if (this.interceptors != null) {AdvisorAdapterRegistry adapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();for (int i = 0; i < this.interceptors.length; i++) {proxyFactory.addAdvisor(adapterRegistry.wrap(this.interceptors[i]));}}//设置要代理的目标类proxyFactory.setTarget(getService());proxyFactory.setOpaque(true);//创建代理return proxyFactory.getProxy(getBeanClassLoader());
}

  通过将上面3个方法串联,可以看到,初始化过程实现的逻辑主要是创建一个代理中封装了对于我写请求的处理方法以及接口等信息,而这个代理最关键的上的是加入了RemoteInvocationTraceInterceptor增强器,当然创建代理还有其他的好处的,比如代码优雅,方便扩展等,RemoteInvocationTraceInterceptor中的增强主要是对增强的目标方法进行一些相关的日志打印,并没有在此基础上进行任何功能的增强,那么这个代理空间是在什么时候使用的呢?暂时留下悬念,接下来分析当有Web请求HttpRequestHandler的handlerRequest方法的处理。

2. 处理来自客户端的request

  当有Web请求时,根据配置中的规则会把路径匹配的访问直接引入到对应的HttpRequestHandler中,本例中的Web请求与普通的Web请求是有些区别的,因为在此处的请求包含着HttpInvoker的处理过程。

public void handleRequest(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {try {//从request中读取序列化数据RemoteInvocation invocation = readRemoteInvocation(request);//执行调用RemoteInvocationResult result = invokeAndCreateResult(invocation, getProxy());//将结果的序列化对象入输出流writeRemoteInvocationResult(request, response, result);}catch (ClassNotFoundException ex) {throw new NestedServletException("Class not found during deserialization", ex);}
}

  在handlerRequest函数中,我们清楚的看到了HttpInvoker处理的大致框架,HttpInvoker服务简单的来说就是将请求的方法,也就是RemoteInvocation对象,从客户端序列化并通过Web请求出入服务端,服务端在对传过来的序列化对象进行反序列化还原RemoteInvocation实例,然后通过实例中的相关信息进行相关的方法的调用,并将执行结果再次的返回给客户端,从handleRequest函数中我们也可以清晰地看到程序执行的框架结构 。

  1. 从request中读取序列化对象。

  主要是从HttpServletRequest提取相关的信息,也就是提取HttpServcletRequest中的RemoteInvocation对象的序列化信息以及反序列化的过程。

protected RemoteInvocation readRemoteInvocation(HttpServletRequest request)throws IOException, ClassNotFoundException {return readRemoteInvocation(request, request.getInputStream());
}protected RemoteInvocation readRemoteInvocation(HttpServletRequest request, InputStream is)throws IOException, ClassNotFoundException {//创建对象输入流ObjectInputStream ois = createObjectInputStream(decorateInputStream(request, is));try {//从输入流中读取序列化对象return doReadRemoteInvocation(ois);}finally {ois.close();}
}protected RemoteInvocation doReadRemoteInvocation(ObjectInputStream ois)throws IOException, ClassNotFoundException {Object obj = ois.readObject();if (!(obj instanceof RemoteInvocation)) {throw new RemoteException("Deserialized object needs to be assignable to type [" +RemoteInvocation.class.getName() + "]: " + obj);}return (RemoteInvocation) obj;
}

  对于序列化提取与转换过程其实并没有太多需要解释的东西,这里完全是标准的方式进行操作的,包括创建ObjectInputStream以及从ObjectInputStream中提取对象实例。

  1. 执行调用。

  根据反序列化方式得到的RemoteInvocation对象中的信息,进行方法调用,注意,此时调用的实体并不是服务接口或服务类,而是之前在初始化时候构造的封装了服务接口以及服务类的代理。
  完成了RemoteInvocation实例的提取,也就意味着可以通过RemoteInvocation实例提供的信息进行方法调用了。

protected RemoteInvocationResult invokeAndCreateResult(RemoteInvocation invocation, Object targetObject) {try {//激活代理类中对应的incocation中的方法Object value = invoke(invocation, targetObject);//封装结果以便于序列化return new RemoteInvocationResult(value);}catch (Throwable ex) {return new RemoteInvocationResult(ex);}
}protected Object invoke(RemoteInvocation invocation, Object targetObject)throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {if (logger.isTraceEnabled()) {logger.trace("Executing " + invocation);}try {return getRemoteInvocationExecutor().invoke(invocation, targetObject);}catch (NoSuchMethodException ex) {if (logger.isDebugEnabled()) {logger.warn("Could not find target method for " + invocation, ex);}throw ex;}catch (IllegalAccessException ex) {if (logger.isDebugEnabled()) {logger.warn("Could not access target method for " + invocation, ex);}throw ex;}catch (InvocationTargetException ex) {if (logger.isDebugEnabled()) {logger.debug("Target method failed for " + invocation, ex.getTargetException());}throw ex;}
}public Object invoke(RemoteInvocation invocation, Object targetObject)throws NoSuchMethodException, IllegalAccessException, InvocationTargetException{Assert.notNull(invocation, "RemoteInvocation must not be null");Assert.notNull(targetObject, "Target object must not be null");return invocation.invoke(targetObject);
}public Object invoke(Object targetObject)throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {Method method = targetObject.getClass().getMethod(this.methodName, this.parameterTypes);return method.invoke(targetObject, this.arguments);
}

  这段函数有两点需要说明的地方。

  • 对应方法的激活也就是invoke方法的调用,虽然经过层层环绕,但是最终还是实现了一个我们熟知的调用invocation.invoke(targetObject),也就是执行RemoteInvocation类中的invoke方法,大致的逻辑还是通过RemoteInvocation中对应的方法信息在targetObject上去执行,此方法在分析RMI功能的时候己经分析过,不再赘述,但是在对于当前方法的targetObject参数,此targetObject是代理类,调用代理类的时候要考虑增强方法的调用,这是读者需要注意的地方。
  • 对于返回结果需要使用RemoteInvocationResult进行封装,之所以需要通过RemoteInvocationResult类进行封装,是因为无法保证对于所有操作的返回结果都继承Serializable接口,也就是说无法保证所有的返回结果都可以直接进行序列化,那么就必需到什么ReMoteInvocationResult进行统一封装。
  1. 将结果的序列化对象写入输出流。
    同样这里也包括序列化过程。
protected void writeRemoteInvocationResult(HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result)throws IOException {response.setContentType(getContentType());writeRemoteInvocationResult(request, response, result, response.getOutputStream());
}protected void writeRemoteInvocationResult(HttpServletRequest request, HttpServletResponse response, RemoteInvocationResult result, OutputStream os)throws IOException {//获取输入流ObjectOutputStream oos = createObjectOutputStream(decorateOutputStream(request, response, os));try {//将序列化对象写入输入流doWriteRemoteInvocationResult(result, oos);}finally {oos.close();}
}protected void doWriteRemoteInvocationResult(RemoteInvocationResult result, ObjectOutputStream oos)throws IOException {oos.writeObject(result);
}

客户端实现

  分析了服务端的解析以及处理过程后,我们接下来分析客户端的调用过程,在服务端调用的分析是我们反复提到需要从HttpServletRequest中提取从客户端传来的RemoteInvocation实例,然后进行相应的解析,所以,在客户端,一个比较重要的任务就是构建RemoteInvocation实例,并传送到服务端,根据配置文件中的信息,我们还是首先锁定HttpInvokerProxyFactoryBean类,并查看其层次结构 。

  从层次结构中我们看到,HttpInvokerProxyFactoryBean类同样实现了InitializingBean接口,同时,又实现了FactoryBean以及MethodInterceptro,这己经是老生常谈的问题了,实现这几个接口以及这几个接口在Spring中会有什么作用就不再赘述了,我们还是根据实现了的初始化过程的逻辑。

public void afterPropertiesSet() {super.afterPropertiesSet();if (getServiceInterface() == null) {throw new IllegalArgumentException("Property 'serviceInterface' is required");}//创建代理并使用当前方法为拦截器增强this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());
}

  在afterPropertiesSet中主要创建了一个代理,该代理封装了配置的服务接口,并使用当前类也就是HttpInvokerProxyFactoryBean作为增强,因为HttpInvokerProxyFactoryBean实现了MethodInterceptor方法,所以可以作为增强拦截器。
  同样,又由于HttpInvokerProxyFactoryBean实现了FactoryBean接口,所以通过Spring中普通方式调用该bean时调用的并不是该bean本身,而是此类中getObject方法返回的实例,也就是实例化过程中所创建的代理 。

public Object getObject() {return this.serviceProxy;
}

  那么综合之前的使用示例,我们再次回顾一下,HttpInvokeProxyFactoryBean类型的bean在初始化过程中创建的封装服务接口的代理,并使用自身作为增强拦截器,然后因为实现了FactoryBean接口,所以获取Bean提时候返回的其实是创建的代理 ,那么,汇总上面的逻辑,当调用如下代码时,其实是调用代理类中的服务方法,而在调用代理类中的服务方法时又会使用代理类中加入的增强器进行增强。
  ApplicationContext ct = new ClassPathXmlApplicationContext(“classpath:spring_1_100/config_71_80/spring77_httpinvoker/spring77_client.xml”);
  HttpInvokerTestI httpInvokerTestI = (HttpInvokerTestI) ct.getBean(“remoteService”);
  System.out.println(httpInvokerTestI.getTestPo(“dddd”));   这时,所有的逻辑分析其实己经被转向到了对于增强器也就是HttpInvokerProxyFactoryBean类本身的invoke方法的分析了。
  在分析invoke方法分析之前,其实我们己经猜测出该方法所提供的主要功能就是将调用的信息封装在RemoteInvocation中,发送给服务端并等待返回结果。

public Object invoke(MethodInvocation methodInvocation) throws Throwable {if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {return "HTTP invoker proxy for service URL [" + getServiceUrl() + "]";}//封装methodName,parameterTypes,arguments 到RemoteInovcation中RemoteInvocation invocation = createRemoteInvocation(methodInvocation);RemoteInvocationResult result;try {result = executeRequest(invocation, methodInvocation);}catch (Throwable ex) {throw convertHttpInvokerAccessException(ex);}try {return recreateRemoteInvocationResult(result);}catch (Throwable ex) {if (result.hasInvocationTargetException()) {throw ex;}else {throw new RemoteInvocationFailureException("Invocation of method [" + methodInvocation.getMethod() +"] failed in HTTP invoker remote service at [" + getServiceUrl() + "]", ex);}}
}protected RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) {return getRemoteInvocationFactory().createRemoteInvocation(methodInvocation);
}public RemoteInvocation createRemoteInvocation(MethodInvocation methodInvocation) {return new RemoteInvocation(methodInvocation);
}public RemoteInvocation(MethodInvocation methodInvocation) {this.methodName = methodInvocation.getMethod().getName();this.parameterTypes = methodInvocation.getMethod().getParameterTypes();this.arguments = methodInvocation.getArguments();
}

  函数主要有3个步骤。

  1. 构建RemoteInvocation实例

  因为是代理中增强方法调用,调用的方法及参数信息会在代理中封装至MethoInvocation实例中,并在增强器中进行传递,也就意味着当程序进入invoke方法时其实是己经包含了调用的接口的相关信息的,那么,首先要做的就是将MethodInvocation中的信息提取并构建RemoteInvocation。

  1. 执行远程方法
  2. 提取结果

  考虑到序列化的问题,在Spring中约定使用HttpInvoker方式进行方法的调用,结果使用RemoteInvocationResult进行封装,那么在提取结果后还需要从封装的结果中提取对应的结果。
  而这3个步骤最关键的就是远程方法的执行,执行远程方法的首先步骤就是将调用的方法的实例写入到输入流中。

protected RemoteInvocationResult executeRequest(RemoteInvocation invocation, MethodInvocation originalInvocation) throws Exception {return executeRequest(invocation);
}protected RemoteInvocationResult executeRequest(RemoteInvocation invocation) throws Exception {return getHttpInvokerRequestExecutor().executeRequest(this, invocation);
}public final RemoteInvocationResult executeRequest(HttpInvokerClientConfiguration config, RemoteInvocation invocation) throws Exception {//获取输入流ByteArrayOutputStream baos = getByteArrayOutputStream(invocation);if (logger.isDebugEnabled()) {logger.debug("Sending HTTP invoker request for service at [" + config.getServiceUrl() +"], with size " + baos.size());}return doExecuteRequest(config, baos);
}

  在doExecuteRequest方法中真正的实例了对远程方法的构造及通信,与远程方法的连接功能实现中,Spring引入了第三方JAR:HttpClient,HttpClient是Apache Jakarta Common 下的子项目,可以用来提供高效,最新的,功能丰富的支持HTTP协义的客户端编程工具包,并且它支持HTTP协义最新的版本和建义,对HTTP Client 的可以自己去参考更多的资料和文档。

protected RemoteInvocationResult doExecuteRequest(HttpInvokerClientConfiguration config, ByteArrayOutputStream baos)throws IOException, ClassNotFoundException {//创建ConnectionHttpURLConnection con = openConnection(config);//执行POST方法prepareConnection(con, baos.size());writeRequestBody(config, con, baos);//验证validateResponse(config, con);InputStream responseBody = readResponseBody(config, con);return readRemoteInvocationResult(responseBody, config.getCodebaseUrl());
}

  

  1. 创建connection
protected HttpURLConnection openConnection(HttpInvokerClientConfiguration config) throws IOException {//设置需要访问的urlURLConnection con = new URL(config.getServiceUrl()).openConnection();if (!(con instanceof HttpURLConnection)) {throw new IOException("Service URL [" + config.getServiceUrl() + "] is not an HTTP URL");}return (HttpURLConnection) con;
}protected void prepareConnection(HttpURLConnection connection, int contentLength) throws IOException {if (this.connectTimeout >= 0) {connection.setConnectTimeout(this.connectTimeout);}if (this.readTimeout >= 0) {connection.setReadTimeout(this.readTimeout);}connection.setDoOutput(true);connection.setRequestMethod("POST");connection.setRequestProperty("Content-Type", getContentType());connection.setRequestProperty("Content-Length", Integer.toString(contentLength));LocaleContext localeContext = LocaleContextHolder.getLocaleContext();if (localeContext != null) {Locale locale = localeContext.getLocale();if (locale != null) {//加入Accept-Language属性connection.setRequestProperty("Accept-Language", StringUtils.toLanguageTag(locale));}}if (isAcceptGzipEncoding()) {//加入Accept-Encoding属性connection.setRequestProperty("Accept-Encoding", "gzip");}
}
  1. 执行远程调用
protected void writeRequestBody(HttpInvokerClientConfiguration config, HttpURLConnection con, ByteArrayOutputStream baos)throws IOException {baos.writeTo(con.getOutputStream());
}
  1. 远程相应验证

  对于HTTP调用响应码处理,大于300则是非正常调用码。

protected void validateResponse(HttpInvokerClientConfiguration config, HttpURLConnection con)throws IOException {if (con.getResponseCode() >= 300) {throw new IOException("Did not receive successful HTTP response: status code = " + con.getResponseCode() +", status message = [" + con.getResponseMessage() + "]");}
}
  1. 提取响应信息

  从服务器中返回的输入流可能是经过压缩的,不同的方式采用不同的办法进行提前。

protected InputStream readResponseBody(HttpInvokerClientConfiguration config, HttpURLConnection con)throws IOException {if (isGzipResponse(con)) {// GZIP response found - need to unzip.return new GZIPInputStream(con.getInputStream());}else {// Plain response found.return con.getInputStream();}
}
  1. 提取返回结果
protected RemoteInvocationResult readRemoteInvocationResult(InputStream is, String codebaseUrl)throws IOException, ClassNotFoundException {ObjectInputStream ois = createObjectInputStream(decorateInputStream(is), codebaseUrl);try {return doReadRemoteInvocationResult(ois);}finally {ois.close();}
}protected RemoteInvocationResult doReadRemoteInvocationResult(ObjectInputStream ois)throws IOException, ClassNotFoundException {//读取响应结果Object obj = ois.readObject();if (!(obj instanceof RemoteInvocationResult)) {throw new RemoteException("Deserialized object needs to be assignable to type [" +RemoteInvocationResult.class.getName() + "]: " + obj);}return (RemoteInvocationResult) obj;
}

  整个调用过程其实并不复杂,客户端将方法名称,参数类型,方法参数值封装成一个RemoteInvocation对象,通过http请求发送综给服务端,服务端接收到这个请求,反序列化RemoteInvocation对象,根据RemoteInvocation对象中的方法名称和方法参数类型定位出要调用的方法,再通过反射传入RemoteInvocation对象中的方法参数调用此方法,将返回值封装成一个可序列化对象RemoteInvocationResult返回给客户端,客户端得到序列化RemoteInvocationResult对象,获取其value即可,我们写一个简单的伪代码来说明一个整个过程。

  1. 服务端代码
@RestController
public class HttpInvokerServerController {@RequestMapping("/httpinvokertest.service")public String test(String methodName,Class<?> [] paramterTypes,Object [] args) throws Exception{Method method = HttpInvokerTestImpl.class.getDeclaredMethod(methodName,paramterTypes);return method.invoke(args) + "";}
}
  1. 客户端代码
public class HttpInvokerClientTest {protected static final Logger logger = LoggerFactory.getLogger(HttpInvokerClientTest.class);public static void main(String[] args) {doGet("http://localhost:8080/httpinvokertest.service?methodName&xxxx",1000);}public static String doGet(String url, int timeout) {logger.info("doGet url = {}", url);BufferedReader in = null;OutputStreamWriter out = null;String result = "";try {URL realUrl = new URL(url);URLConnection conn = realUrl.openConnection();conn.setConnectTimeout(timeout);conn.setDoOutput(true);conn.setDoInput(true);conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));String line;while ((line = in.readLine()) != null) {result += line;}} catch (Exception e) {logger.error("发送失败" + e);return "";} finally {try {if (out != null) {out.close();}if (in != null) {in.close();}} catch (IOException ex) {ex.printStackTrace();}}logger.info("doGet result = {}", result);return result;}
}
总结 :

  整个过程,我们来总结一下RMI和HttpInvoker的异同。
不同点:

  • 客户端和服务端数据传输方式不同,RMI使用rmi协义来传输数据,HttpInvoker使用Http协义传输数据。
  • RMI 客户端拿到Remote对象后,直接远程调用,HttpInvoker 客户端和服务端请求参数和返回参数都有一个序列化和反序列化的过程。

相同点:

  • 客户端和服务端都实现了InitializingBean接口,在bean的afterPropertiesSet()方法中初始化bean数据
  • 服务端都使用代理来追踪日志
  • 服务端都是通过获取方法名称,方法参数 通过反射来定位调用的方法的。
  • 客户端都实现了FactoryBean接口,通过getObject返回代理类
  • 客户端都是在代理类的内部完成服务端的调用以及返回值的处理

  今天的源码分析就到这里了,有问题跟我反馈,我们一起来解决问题,一起进步。

本文的github地址是
https://github.com/quyixiao/spring_tiny/tree/master/src/main/java/com/spring_1_100/test_71_80/test77_spring_httpinvoker

Spring源码深度解析(郝佳)-学习-HttpInvoker使用及源码解析相关推荐

  1. Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean定义(一)

    我们在之前的博客 Spring源码深度解析(郝佳)-学习-ASM 类字节码解析 简单的对字节码结构进行了分析,今天我们站在前面的基础上对Spring中类注解的读取,并创建BeanDefinition做 ...

  2. Spring源码深度解析(郝佳)-学习-源码解析-创建AOP静态代理实现(八)

    继上一篇博客,我们继续来分析下面示例的 Spring 静态代理源码实现. 静态 AOP使用示例 加载时织入(Load -Time WEaving,LTW) 指的是在虚拟机载入字节码时动态织入 Aspe ...

  3. Spring源码深度解析(郝佳)-学习-源码解析-基于注解切面解析(一)

    我们知道,使用面积对象编程(OOP) 有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共的行为时,例如日志,安全检测等,我们只有在每个对象引用公共的行为,这样程序中能产生大量的重复代码,程序就 ...

  4. Spring源码深度解析(郝佳)-学习-源码解析-Spring MVC(三)-Controller 解析

    在之前的博客中Spring源码深度解析(郝佳)-学习-源码解析-Spring MVC(一),己经对 Spring MVC 的框架做了详细的分析,但是有一个问题,发现举的例子不常用,因为我们在实际开发项 ...

  5. Spring源码深度解析(郝佳)-学习-源码解析-基于注解注入(二)

    在Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean解析(一)博客中,己经对有注解的类进行了解析,得到了BeanDefinition,但是我们看到属性并没有封装到BeanDefinit ...

  6. Spring源码深度解析(郝佳)-学习-源码解析-Spring整合MyBatis

    了解了MyBatis的单独使用过程之后,我们再来看看它也Spring整合的使用方式,比对之前的示例来找出Spring究竟为我们做了什么操作,哪些操作简化了程序开发. 准备spring71.xml &l ...

  7. Spring源码深度解析(郝佳)-学习-源码解析-创建AOP静态代理(七)

    加载时织入(Load-Time Weaving ,LTW) 指的是在虚拟机加载入字节码文件时动态织入Aspect切面,Spring框架的值添加为 AspectJ LTW在动态织入过程中提供了更细粒度的 ...

  8. Spring源码深度解析(郝佳)-学习-构造器注入

    本文主要是Spring源码有一定基础的小伙伴而言的,因为这里我只想讲一下,Spring对于构造器的注入参数是如何解析,不同参数个数构造器. 相同参数个数,不同参数类型. Spring是如何选择的. 1 ...

  9. Spring源码深度解析(郝佳)-学习-源码解析-factory-method

    本文要解析的是Spring factory-method是如何来实现的,话不多说,示例先上来. Stu.java public class Stu {public String stuId;publi ...

最新文章

  1. 英伟达GPU“屠榜”,谷歌TPU“退赛”,MLPerf最新推理榜单出炉
  2. Draft-微软出品的云原生下的本地开发辅助工具
  3. Hadoop中RPC机制详解之Server端
  4. Maven 手动安装Jar包的例子
  5. SAP License:SAP Concur是什么?
  6. 思岚科技受邀2018高交会 携多项“黑科技”亮相
  7. 2.看板方法---什么是看板方法
  8. python怎么用pandas查找指定字符串_Python Pandas:通过搜索子字符串查找表
  9. python环境搭建和pycharm的安装配置
  10. html如何转换成中文,html中文乱码怎么解决怎么造成如何避免中文乱码
  11. 群晖通过计划任务挂载USB盘做主力下载盘
  12. Unity 鼠标进入UI控件,显示控件名称
  13. 2009年毕业设计题目:网上自助装机系统的设计与实现
  14. 解决Aid Learning无法联网问题
  15. 什么是POSIX system
  16. web前端—前端三剑客之JS(12):字符串
  17. ubuntu系统强制解锁
  18. Springcloud使用全局捕获异常管理接口异常
  19. 白色在html中怎么写,html如何设置文字颜色白色
  20. idea自定义archetype及错误处理

热门文章

  1. raise KeyError(key) from err KeyError: ‘日期‘
  2. NTU-Coursera机器学习:HomeWork 2 Q16-20
  3. 莱卡公司宣布领导层变动
  4. HC18P110L芯圣开发笔记(二)ADC,管脚,仿真,if判断问题和解决方法汇总
  5. 英语四六级翻译13:丝绸之路
  6. 学堂在线-邓俊辉-数据结构-习题解析PDF一页版本
  7. mysql查最新的记录
  8. 如何区分螺杆支撑座的规格?
  9. (十)Spring中Bean的生命周期(下)
  10. CCNA 网络工程师