环境说明
利用的jdk11。创建的maven项目
首先引入Maven依赖
<!-- Tomcat核心包 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.12</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.4</version>
</dependency>
增加Redis配置文件
redis.properties文件:
redis.host=127.0.0.1
redis.port=6379
redis.password=
redis.timeout=100000
redis.maxIdle=100
redis.maxActive=300
redis.maxWait=1000
接口和工具类
第一步
定义接口类,用于消费者和生产者都可以调用到。
package com.provider.api;
/**
* @author :Mall
* @date :Created in 2021-06-30
* @description :接口,消费者和生产者都可以调用的到
*/
public interface IndexService {
public String sayHello(String name);
}
第二步
定义一个传参的实体类,用于消费者给生产者推送参数时使用。
package com.framework.request;
import java.io.Serializable;
/**
* @author :Mall
* @date :Created in 2021-06-30
* @description :用于生产者和消费者之间传参
*/
public class Invocation implements Serializable {
//接口名称
private String interfaceName;
//方法名
private String methodName;
//方法的参数类型
private Class[] paramTypes;
//方法的参数
private Object[] params;
public Invocation(String interfaceName, String methodName, Class[] paramTypes, Object[] params) {
this.interfaceName = interfaceName;
this.methodName = methodName;
this.paramTypes = paramTypes;
this.params = params;
}
public String getInterfaceName() {
return interfaceName;
}
public void setInterfaceName(String interfaceName) {
this.interfaceName = interfaceName;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Object[] getParams() {
return params;
}
public void setParams(Object[] params) {
this.params = params;
}
public Class[] getParamTypes() {
return paramTypes;
}
public void setParamTypes(Class[] paramTypes) {
this.paramTypes = paramTypes;
}
}
第三步
定义接口与实现类关系的仓库。用于根据接口名称获取对应的实现类。
package com.framework.register;
import java.util.HashMap;
import java.util.Map;
/**
* @author :Mall
* @date :Created in 2021-06-30
* @description :本地注册中心,主要用于存储接口名称和实现类对象的关联关系
*/
public class LocalRegister {
private static Map<String, Class> map = new HashMap<>();
public static void regist(String interfaceName, Class implClass) {
map.put(interfaceName, implClass);
}
public static Class get(String interfaceName) {
return map.get(interfaceName);
}
}
第四步
定义注册中心,这里注册中心采用Redis,用于根据接口获取服务提供者的地址列表。
package com.framework.register;
import com.alibaba.fastjson.JSONArray;
import com.framework.request.URL;
import com.util.RedisUtil;
import java.util.List;
/**
* @author :Mall
* @date :Created in 2021-07-01
* @description :远程注册中心
*/
public class RemoteMapRegister {
//将服务注册到Redis上
public static void regist(String interfaceName, URL url) {
RedisUtil.getInstance().setList(interfaceName, url.toString());
}
//从Redis中返回路径信息
public static List<URL> get(String interfaceName) {
List<String> redisList = RedisUtil.getInstance().getList(interfaceName);
List<URL> list = JSONArray.parseArray(redisList.toString(), URL.class);
return list;
}
}
第五步
增加网络协议接口,用于规范启动的服务接口。
注:此处实现的是Tomcat。
/**
* @author :Mall
* @date :Created in 2021-07-01
* @description :网络传输协议接口
*/
public interface Protocol {
public Protocol start(String hostname, Integer port) throws LifecycleException;
}
第六步
增加Protocol工厂,用于根据协议,统一返回启动的服务端和发送的消费端。
package com.framework.protocol;
import com.framework.protocol.http.MyHttpClient;
import com.framework.protocol.http.TomcatServer;
import com.framework.request.Invocation;
import java.io.IOException;
import java.net.URISyntaxException;
/**
* @author :Mall
* @date :Created in 2021-07-01
* @description :协议工厂
*/
public class ProtocolFactory {
public static Protocol getProxy(String protocol) {
switch (protocol) {
case "http":
case "https":
return new TomcatServer();
}
return null;
}
public static String send(String protocol, String hostname, Integer port, Invocation invocation) throws InterruptedException, IOException, URISyntaxException {
switch (protocol) {
case "http":
case "https":
//发起请求
MyHttpClient myHttpClient = new MyHttpClient();
return myHttpClient.send(hostname, port, invocation);
}
return null;
}
}
第七步
增加URL类,用于存储生产者的地址和端口号。
package com.framework.request;
import java.io.Serializable;
/**
* @author :Mall
* @date :Created in 2021-07-01
* @description :URL 用于存储服务提供者的路径信息
*/
public class URL implements Serializable {
private String hostname;
private Integer port;
public URL(String hostname, Integer port) {
this.hostname = hostname;
this.port = port;
}
public String getHostname() {
return hostname;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
@Override
public String toString() {
return "{" +
"hostname:'" + hostname + '\'' +
", port:" + port +
'}';
}
}
第八步
增加一个负载均衡处理类。
package com.framework.request;
import java.util.List;
import java.util.Random;
/**
* @author :Mall
* @date :Created in 2021-07-01
* @description :负载均衡请求工具类
*/
public class LoadBalance {
/**
* 随机负载均衡算法
*
* @param list
* @return
*/
public static URL random(List<URL> list) {
Random random = new Random();
int index = random.nextInt(list.size());
return list.get(index);
}
}
生产者
第一步
首先先对接口做实现,用于消费者调用的实现类。
package com.provider.impl;
import com.provider.api.IndexService;
/**
* @author :Mall
* @date :Created in 2021-06-30
* @description :
*/
public class IndexServiceImpl implements IndexService {
@Override
public String sayHello(String name) {
return "Hello," + name;
}
}
第二步
增加一个TomcatServer,和Springboot一样,利用代码启动Tomcat服务。这个服务需要实现下Protocol接口
package com.framework.protocol.http;
import com.framework.protocol.Protocol;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
/**
* @author :Mall
* @date :Created in 2021-06-30
* @description :代码实现Tomcat服务
*/
public class TomcatServer implements Protocol {
@Override
public Protocol start(String hostname, Integer port) throws LifecycleException {
Tomcat tomcat = new Tomcat();
tomcat.setHostname(hostname);
Connector conn = new Connector();
conn.setPort(port);
tomcat.getService().addConnector(conn);
//注册Servlet
Context ctx = tomcat.addContext("/", null);
Wrapper wrapper = Tomcat.addServlet(ctx, "dispatcherServlet", "com.framework.protocol.http.DispatcherServlet");
wrapper.addMapping("/*");
//启动
tomcat.start();
System.out.println("Tomcat启动成功,端口号:" + port);
//4.阻塞当前线程
tomcat.getServer().await();
return this;
}
}
第三步
实现一个Servlet,用于接收请求
package com.framework.protocol.http;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
/**
* @author :Mall
* @date :Created in 2021-06-30
* @description :接收Servlet
*/
public class DispatcherServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
new TomcatHandler().handler(req, resp);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
第四步
增加一个Handler,用于处理Servlet接收的请求。
package com.framework.protocol.http;
import com.alibaba.fastjson.JSONObject;
import com.framework.register.LocalRegister;
import com.framework.request.Invocation;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author :Mall
* @date :Created in 2021-06-30
* @description :处理发过来的请求
*/
public class TomcatHandler {
//处理请求
public void handler(HttpServletRequest req, HttpServletResponse resp) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
//从请求中,转换对象
Invocation invocation = JSONObject.parseObject(req.getInputStream(), Invocation.class);
//本地仓库中,获取接口对应的代理对象
Class clazz = LocalRegister.get(invocation.getInterfaceName());
//反射对象
Object o = clazz.getDeclaredConstructor().newInstance();
//根据对象,获取方法
Method method = clazz.getMethod(invocation.getMethodName(), invocation.getParamTypes());
//调用方法
String result = (String) method.invoke(o, invocation.getParams());
//这句话的意思,是告诉servlet用UTF-8转码,而不是用默认的ISO8859
resp.setCharacterEncoding("UTF-8");
resp.getWriter().print(result);
}
}
第五步
编写启动类。对外提供服务。对外提供前,需要往本地注册中心注册。
package com.provider;
import com.framework.protocol.Protocol;
import com.framework.protocol.ProtocolFactory;
import com.framework.register.RemoteMapRegister;
import com.framework.request.URL;
import com.provider.api.IndexService;
import com.provider.impl.IndexServiceImpl;
import com.framework.register.LocalRegister;
import org.apache.catalina.LifecycleException;
/**
* @author :Mall
* @date :Created in 2021-06-30
* @description :服务提供者
*/
public class ProviderApplication {
public static void main(String[] args) throws LifecycleException {
//接口名称
String interfaceName = IndexService.class.getName();
//当前服务的域名和端口号
URL url = new URL("localhost", 8999);
//注册接口服务
LocalRegister.regist(interfaceName, IndexServiceImpl.class);
//注册远程地址服务
RemoteMapRegister.regist(interfaceName, url);
//定义协议,发布服务
Protocol protocol = ProtocolFactory.getProxy("http");
protocol.start(url.getHostname(), url.getPort());
}
}
消费者
第一步
增加动态代理类,主要用于实现消费者可以直接调用,不关心内部细节。内部细节由代理实现。
package com.framework.proxy;
import com.framework.protocol.ProtocolFactory;
import com.framework.protocol.http.MyHttpClient;
import com.framework.register.RemoteMapRegister;
import com.framework.request.Invocation;
import com.framework.request.LoadBalance;
import com.framework.request.URL;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
/**
* @author :Mall
* @date :Created in 2021-07-01
* @description :动态代理工厂,用于对接口产生代理对象
*/
public class ProxyFactory<T> {
/**
* 根据传入的接口类,返回代理对象
*
* @param interfaceClazz
* @return
*/
public static <T> T getProxy(Class interfaceClazz) {
/**
* 此处采用JDK的动态代理工具。对接口代理
* 对传入的接口生成代理对象并处理代理逻辑
*/
return (T) Proxy.newProxyInstance(interfaceClazz.getClassLoader(), new Class[]{interfaceClazz}, new InvocationHandler() {
/**
* 调用方法都会走这个方法。
* @param proxy 代理的对象
* @param method 调用的方法
* @param args 方法参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//封装请求的参数
Invocation invocation = new Invocation(interfaceClazz.getName(), method.getName(), method.getParameterTypes(), args);
try {
//根据接口名称,获取请求地址
List<URL> urlList = RemoteMapRegister.get(interfaceClazz.getName());
//负载均衡,随机获取路径
URL url = LoadBalance.random(urlList);
//从启动配置参数上获取协议
String protocol = System.getProperty("protocol");
//发起请求
return ProtocolFactory.send(protocol,url.getHostname(), url.getPort(), invocation);
} catch (Exception e) {
return "接口请求异常";
}
}
});
}
}
第二步
增加一个HttpClient请求发起的类。
package com.framework.proxy;
import com.alibaba.fastjson.JSONObject;
import com.framework.Invocation;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.Charset;
/**
* @author :Mall
* @date :Created in 2021-06-30
* @description :发送请求
*/
public class MyHttpClient {
//利用JDK自带的HttpClient实现
public String send(String hostname, Integer port, Invocation invocation) throws URISyntaxException, IOException, InterruptedException {
URI uri = new URI("http", null, hostname, port, "/", null, null);
HttpRequest request = HttpRequest.newBuilder(uri).POST(HttpRequest.BodyPublishers.ofString(JSONObject.toJSONString(invocation))).build();
HttpResponse<String> send = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString(Charset.forName("UTF-8")));
return send.body();
}
}
第三步
消费者启动应用,调用生产者。
package com.consumer;
import com.framework.proxy.ProxyFactory;
import com.provider.api.IndexService;
import java.io.IOException;
import java.net.URISyntaxException;
/**
* @author :Mall
* @date :Created in 2021-06-30
* @description :
*/
public class ConsumerApplication {
public static void main(String[] args) throws InterruptedException, IOException, URISyntaxException {
//根据接口类,生成代理对象
IndexService indexService = ProxyFactory.getProxy(IndexService.class);
//调用接口
String result = indexService.sayHello("马罗乐");
System.out.println(result);
}
}