В последнее время я работаю с Netty, и мне нужно предоставить HTTP-веб-сервер для вызова вызывающего абонента. Принять Netty для обеспеченияHttpServerCodec
Обработчик анализирует протокол Http, но ему необходимо обеспечить собственную маршрутизацию.
Сначала он направляется в реальный класс контроллера с использованием нескольких вложенных суждений if else для метода Http и uri:
String uri = request.uri();
HttpMethod method = request.method();
if (method == HttpMethod.POST) {
if (uri.startsWith("/login")) {
//url参数解析,调用controller的方法
} else if (uri.startsWith("/logout")) {
//同上
}
} else if (method == HttpMethod.GET) {
if (uri.startsWith("/")) {
} else if (uri.startsWith("/status")) {
}
}
просто предоставляяlogin
а такжеlogout
Когда используется API, код может выполнять функцию, но по мере увеличения количества API необходимо поддерживать все больше и больше методов и URI.else if
Все больше и больше код становится все более и более сложным.
Это также упоминается в руководстве по разработке Ali:
Поэтому сначала рассмотрите возможность использования шаблона проектирования состояния и рефакторинга шаблона проектирования стратегии.
режим состояния
Роль государственного режима:
- состояние Представляет состояние, определяет интерфейс для различной обработки в соответствии с различными состояниями, интерфейс представляет собой набор методов, содержание обработки которых зависит от состояния и соответствует классу состояния экземпляра.
- конкретное состояние Реализован интерфейс состояния, соответствующий дневному и ночному состояниям.
- контекст Контекст содержит экземпляр конкретного состояния текущего состояния и, кроме того, определяет интерфейс шаблона состояния для использования внешними вызывающими объектами.
Прежде всего, мы знаем, что каждый http-запрос однозначно идентифицируется методом и uri, так называемая маршрутизация заключается в поиске метода в классе контроллера с помощью этой уникальной идентификации.
Так что поставьте HttpLabel как состояние
@Data
@AllArgsConstructor
public class HttpLabel {
private String uri;
private HttpMethod method;
}
Государственный интерфейс:
public interface Route {
/**
* 路由
*
* @param request
* @return
*/
GeneralResponse call(FullHttpRequest request);
}
Добавьте реализацию состояния для каждого состояния:
public void route() {
//单例controller类
final DemoController demoController = DemoController.getInstance();
Map<HttpLabel, Route> map = new HashMap<>();
map.put(new HttpLabel("/login", HttpMethod.POST), demoController::login);
map.put(new HttpLabel("/logout", HttpMethod.POST), demoController::login);
}
Получите запрос, оцените статус и вызовите разные интерфейсы:
public class ServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
String uri = request.uri();
GeneralResponse generalResponse;
if (uri.contains("?")) {
uri = uri.substring(0, uri.indexOf("?"));
}
Route route = map.get(new HttpLabel(uri, request.method()));
if (route != null) {
ResponseUtil.response(ctx, request, route.call(request));
} else {
generalResponse = new GeneralResponse(HttpResponseStatus.BAD_REQUEST, "请检查你的请求方法及url", null);
ResponseUtil.response(ctx, request, generalResponse);
}
}
}
Используйте шаблон проектирования состояния для рефакторинга кода. При добавлении URL-адреса вам нужно только указать значение в карте сети.
Подобно функции маршрутизации SpringMVC
Я смотрел это позжеОтражение JAVA + аннотация времени выполнения для реализации маршрутизации URL-адресовСпособ поиска отражения + аннотации очень элегантен, а код не сложен.
Далее описывается использование Netty отражения для реализации маршрутизации URL-адресов.
Аннотации маршрутизации:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
/**
* 路由的uri
*
* @return
*/
String uri();
/**
* 路由的方法
*
* @return
*/
String method();
}
Сканируйте путь к классам с помощью@RequestMapping
Аннотированный метод, поместите этот метод в карту маршрутизации:Map<HttpLabel, Action<GeneralResponse>> httpRouterAction
, ключ — это уникальный идентификатор HTTP, упомянутый выше.HttpLabel
, Value — метод отражения вызовов:
@Slf4j
public class HttpRouter extends ClassLoader {
private Map<HttpLabel, Action<GeneralResponse>> httpRouterAction = new HashMap<>();
private String classpath = this.getClass().getResource("").getPath();
private Map<String, Object> controllerBeans = new HashMap<>();
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String path = classpath + name.replaceAll("\\.", "/");
byte[] bytes;
try (InputStream ins = new FileInputStream(path)) {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024 * 5];
int b = 0;
while ((b = ins.read(buffer)) != -1) {
out.write(buffer, 0, b);
}
bytes = out.toByteArray();
}
} catch (Exception e) {
throw new ClassNotFoundException();
}
return defineClass(name, bytes, 0, bytes.length);
}
public void addRouter(String controllerClass) {
try {
Class<?> cls = loadClass(controllerClass);
Method[] methods = cls.getDeclaredMethods();
for (Method invokeMethod : methods) {
Annotation[] annotations = invokeMethod.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType() == RequestMapping.class) {
RequestMapping requestMapping = (RequestMapping) annotation;
String uri = requestMapping.uri();
String httpMethod = requestMapping.method().toUpperCase();
// 保存Bean单例
if (!controllerBeans.containsKey(cls.getName())) {
controllerBeans.put(cls.getName(), cls.newInstance());
}
Action action = new Action(controllerBeans.get(cls.getName()), invokeMethod);
//如果需要FullHttpRequest,就注入FullHttpRequest对象
Class[] params = invokeMethod.getParameterTypes();
if (params.length == 1 && params[0] == FullHttpRequest.class) {
action.setInjectionFullhttprequest(true);
}
// 保存映射关系
httpRouterAction.put(new HttpLabel(uri, new HttpMethod(httpMethod)), action);
}
}
}
} catch (Exception e) {
log.warn("{}", e);
}
}
public Action getRoute(HttpLabel httpLabel) {
return httpRouterAction.get(httpLabel);
}
}
Вызывается отражениемcontroller
Методы в классе:
@Data
@RequiredArgsConstructor
@Slf4j
public class Action<T> {
@NonNull
private Object object;
@NonNull
private Method method;
private boolean injectionFullhttprequest;
public T call(Object... args) {
try {
return (T) method.invoke(object, args);
} catch (IllegalAccessException | InvocationTargetException e) {
log.warn("{}", e);
}
return null;
}
ServerHandler.java
Обработка заключается в следующем:
//根据不同的请求API做不同的处理(路由分发)
Action<GeneralResponse> action = httpRouter.getRoute(new HttpLabel(uri, request.method()));
if (action != null) {
if (action.isInjectionFullhttprequest()) {
ResponseUtil.response(ctx, request, action.call(request));
} else {
ResponseUtil.response(ctx, request, action.call());
}
} else {
//错误处理
generalResponse = new GeneralResponse(HttpResponseStatus.BAD_REQUEST, "请检查你的请求方法及url", null);
ResponseUtil.response(ctx, request, generalResponse);
}
DemoController
Конфигурация метода:
@RequestMapping(uri = "/login", method = "POST")
public GeneralResponse login(FullHttpRequest request) {
User user = JsonUtil.fromJson(request, User.class);
log.info("/login called,user: {}", user);
return new GeneralResponse(null);
}
Результаты теста следующие:
Полный код находится наGithub.com/more думаю / n ...