前言
服务端在开发一般大致分为:
- controller
- service
- dao/mapper(ROM框架处理)
开发完成后用nginx进行部署,nginx支持多服务的负载均衡,在和tomcat进行反向代理后可以完美实现部署
负载
graph LR
客户端-->服务端1
客户端-->服务端2
客户端-->...
服务端暴露的服务实在@Controller
实现
通过dispaterServlet拦截请求后找到对应HttpRequestHandler找到对应的controller
通过上面的模式即可实现简单的分布式
到目前为止好像说的跟远程调用没关系
远程调用的场景
- 服务和客户端不在一个机器上
- 为了实现客户端和服务端分布式
- rpc框架:dubbo , spring-cloud 其中dubbo算是一个spring-cloud的一个功能分支
远程调用方法 HttpInvoker
- 服务端
- 服务端定义接口
public interface UserHttpService {
List<User> getUserByAcount(Stringname,String password);
void insert(User user);
}
- 服务端接口实现
publicclass UserHttpServiceImpl implements UserHttpService {
@Autowired
private UserMapper userMapper;
@Override
public List<User>getUserByAcount(String name, String password) {
System.err.println("httpInvoker获取用户信息:"+ name + password);
return new ArrayList<User>();
}
@Override
public void insert(User user) {
System.err.println("httpInvoker开始插入用户信息:"+ user.toString());
}
}
- 服务端接口暴露 类似与Controller
<bean name="userHttpService"class="com.lm.core.service.impl.UserHttpServiceImpl"/>
<bean name="userExporter" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="userHttpService"/>
<property name="serviceInterface" value="com.lm.core.service.UserHttpService"/>
</bean>
<bean id="simpleUrlRequestHandler"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="alwaysUseFullPath" value="true" />
<property name="urlMap">
<map>
<entry key="/remoting/userExporter" value-ref="userExporter" />
</map>
</property>
</bean>
- web.xml
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContext-httpinvoker.xml
</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>*</url-pattern>
</servlet-mapping>
- 客户端
- 客户端接口
public interface UserHttpService {
List<User> getUserByAcount(Stringname,String password);
void insert(User user);
}
- 客户端配置
<bean id="httpInvokerProxy"
class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl"
value="http://127.0.0.1:8080/spring_remote_server/remoting/userExporter"/>
<property name="serviceInterface"value="com.lm.core.service.UserHttpService"/>
</bean>
- 客户端调用
@RequestMapping(value = "/httpInvokerTest")
@ResponseBody
public BaseMapVo httpInvokerTest(String name, String password) {
BaseMapVo vo = new BaseMapVo();
long startDate = Calendar.getInstance().getTimeInMillis();
System.out.println("httpInvoker客户端开始调用" + startDate);
UserHttpService rmi = (UserHttpService) ApplicationContextUtil.getInstance().getBean("httpInvokerProxy");
rmi.getUserByAcount("张三", ":张三的密码");
System.out.println("httpInvoker客户端调用结束" + (Calendar.getInstance().getTimeInMillis()-startDate));
vo.setRslt("sucess");
return vo;
}
原理解析
通过http请求,封装序列化的对象,通过动态代理的方式进行信息获取
spring 源码解析
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
// 如果是调用toString()方法则直接本地打印下方法信息
if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
return "HTTP invoker proxy for service URL [" + getServiceUrl() + "]";
}
// 构建RemoteInvocation对象,服务器和客户端统一使用该类进行通信
RemoteInvocation invocation = createRemoteInvocation(methodInvocation);
RemoteInvocationResult result;
try {
// 使用JDK自带的HttpURLConnection将序列化后的invocation的发送出去
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);
}
}
我们最关心的是当我们调用接口的方法时,HttpInvoker是如何做到调用到远方系统的方法的,其实HttpInvokerProxyFactoryBean最后返回的是一个代理类(Cglib Proxy或者Jdk Proxy),我们调用接口的任何方法时,都会先执行HttpInvokerClientInterceptor的invoke()方法,
result = executeRequest(invocation, methodInvocation);
然后通过HttpUrlClient将序列化的invocation传输到服务端,服务端在返回invocationResult