目标

上一节中我们使用Netty构建了一个简单的静态Http服务器。本节我们使用Netty实现一个类似spring mvc框架的功能。
spring mvc是通过servlet来实现的请求处理。spring webflux才是用netty去做的

思路

首先通过自定义注解RestController Controller定义接口类,再在其中定义我们需要的接口。项目启动时先扫描接口类并且解析接口信息缓存。当Netty Handler接收到请求时看按照定义好的接口调用并返回信息。

定义注解

  1. RestController注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RestController {
}

2.Controller注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface Controller {
}

  1. RequestMapping注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {


    @AliasFor("path")
    String value() default "";

    @AliasFor("value")
    String path() default "";

    String method() default "GET";

}
  1. RequestParam注解
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestParam {

    @AliasFor("value")
    String name() default "";

    @AliasFor("name")
    String value() default "";

}

RestController和 Controller 注解需要添加上Component注解,才能被加入到spring中

扫描注解的接口类

通过spring 的BeanPostProcessor 接口,我们可以在bean初始化后自定义处理一些逻辑。

  1. 我们定义一个BeanPostProcessor对象,解析接口类
@Bean
    public BeanPostProcessor handlerAnnotationPostProcessor() {
        return new BeanPostProcessor() {
            @Override
            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
                return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
            }

            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if (bean.getClass().isAnnotationPresent(RestController.class) || bean.getClass().isAnnotationPresent(Controller.class)){
                    if (!bean.getClass().isAnnotationPresent(RequestMapping.class)){
                        throw new Error("Controller must have RequestMapping");
                    }
                    RequestMapping requestMapping = AnnotationUtils.findAnnotation(bean.getClass(), RequestMapping.class);
                    String basePath = requestMapping.value();
                    ReflectionUtils.doWithMethods(bean.getClass(), method -> {
                        RequestMapping methodMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
                        if (methodMapping != null){
                            String methodPath = methodMapping.value();
                            String path = basePath + methodPath;
                            String requestMethod = methodMapping.method();
                            ControllerMappingHolder holder = new ControllerMappingHolder();
                            holder.setControllerName(beanName);
                            holder.setMethodName(method.getName());
                            holder.setPath(path);
                            holder.setMethodType(requestMethod);
                            if (requestMethod.equals("GET")){
                                holder.setHandler(getHandler(bean,method));
                            }else if (requestMethod.equals("POST")){
                                holder.setHandler(postHandler(bean,method));
                            }
                            requestDispatch.addHolder(holder);
                        }
                    }, method -> method.isAnnotationPresent(RequestMapping.class));
                }
                return bean;
            }
        };
    }
  1. 解析到的接口方法我们将信息存放到holder类中,并设置处理方法handle.将holder信息保存到一个Set中,netty的handler接收到请求后,根据方法和Uri 去匹配缓存的holder,获取handle方法调用并返回信息,回写到response。

ControllerMappingHolder 类

public class ControllerMappingHolder {

    private String basePath;

    private String controllerName;

    private String path;

    private String methodName;

    private String methodType;

    private Handler handler;


    public static interface Handler{
        FullHttpResponse handle(FullHttpRequest request, FullHttpResponse response) throws IOException;
    }

    public String getBasePath() {
        return basePath;
    }

    public void setBasePath(String basePath) {
        this.basePath = basePath;
    }

    public String getControllerName() {
        return controllerName;
    }

    public void setControllerName(String controllerName) {
        this.controllerName = controllerName;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public String getMethodType() {
        return methodType;
    }

    public void setMethodType(String methodType) {
        this.methodType = methodType;
    }

    public Handler getHandler() {
        return handler;
    }

    public void setHandler(Handler handler) {
        this.handler = handler;
    }
}

请求处理方法handle

public ControllerMappingHolder.Handler getHandler(Object bean,Method method){
        return (request, response) -> {
            String uri = request.uri();
            QueryStringDecoder decoder = new QueryStringDecoder(uri);
            Map<String,Object> parmMap = new HashMap<>();
            decoder.parameters().entrySet().forEach( entry -> {
                parmMap.put(entry.getKey(), entry.getValue().get(0));
            });
            int paramCount = method.getParameterCount();
            Object result = null;
            if (paramCount > 0){
                Object[] params = new Object[method.getParameterCount()];
                for(int i=0;i<paramCount;i++){
                    Parameter parameter = method.getParameters()[i];
                    RequestParam param = parameter.getAnnotation(RequestParam.class);
                    Object val = conversionService().convert(parmMap.get(param.value()),parameter.getType());
                    params[i] = val;
                }
                result = ReflectionUtils.invokeMethod(method, bean, params);
            }else{
                result = ReflectionUtils.invokeMethod(method, bean);
            }
            response.headers().add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
            return response.replace(Unpooled.copiedBuffer(gson.toJson(result).getBytes(StandardCharsets.UTF_8)));
        };
    }

    public ControllerMappingHolder.Handler postHandler(Object bean,Method method){
        return (request, response) -> {
            HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(),request);
//            decoder.setDiscardThreshold(0);
            String contentType = request.headers().get(HttpHeaderNames.CONTENT_TYPE);
            Map<String, String> parmMap = new HashMap<>();
            List<InterfaceHttpData> bodyHttpDatas = decoder.getBodyHttpDatas();
            for (InterfaceHttpData httpData:bodyHttpDatas){
                Attribute data = (Attribute) httpData;;
                parmMap.put(data.getName(), data.getValue());
            }
            String bodyStr = null;
            if (contentType.contains("application/json")){
                request.content().resetReaderIndex();
                request.content().resetReaderIndex();
                ByteBuf  buf = request.content();
                byte[] buffer = new byte[buf.readableBytes()];
                buf.readBytes(buffer);
                bodyStr = new String(buffer, StandardCharsets.UTF_8);
            }
            decoder.destroy();
            int paramCount = method.getParameterCount();
            Object result = null;
            if (paramCount > 0){
                Object[] params = new Object[method.getParameterCount()];
                for(int i=0;i<paramCount;i++){
                    Parameter parameter = method.getParameters()[i];
                    RequestBody body = parameter.getAnnotation(RequestBody.class);
                    if (Objects.nonNull(body) && Objects.nonNull(bodyStr)){
                        params[i] = gson.fromJson(bodyStr,TypeToken.get(parameter.getType()).getType());
                    }else{
                        RequestParam param = parameter.getAnnotation(RequestParam.class);
                        if (Objects.nonNull(param)){
                            Object val = conversionService().convert(parmMap.get(param.value()),parameter.getType());
                            params[i] = val;
                        }
                    }
                }
                result = ReflectionUtils.invokeMethod(method, bean, params);
            }else{
                result = ReflectionUtils.invokeMethod(method, bean);
            }
            response.headers().add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
            return response.replace(Unpooled.copiedBuffer(gson.toJson(result).getBytes(StandardCharsets.UTF_8)));
        };
    }

RequestDispatch 类

@Component
public class RequestDispatch {

    public Set<ControllerMappingHolder> holders = new HashSet<>(16);

    public void addHolder(ControllerMappingHolder holder){
        holders.add(holder);
    }

    public FullHttpResponse dispatch(FullHttpRequest request, FullHttpResponse response) {
        String uri = request.uri();
        HttpMethod method = request.method();
        QueryStringDecoder decoder = new QueryStringDecoder(uri);
        String path = decoder.path();
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        for (ControllerMappingHolder holder : holders) {
            if (antPathMatcher.match(holder.getPath(), path) && holder.getMethodType().equalsIgnoreCase(method.name())) {
                try {
                    return holder.getHandler().handle(request, response);
                } catch (Exception e) {
                    e.printStackTrace();
                    response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
                    String msg = Optional.ofNullable(e.getMessage()).orElse(e.getClass().getName());
                    return response.replace(Unpooled.copiedBuffer(msg.getBytes(StandardCharsets.UTF_8)));
                }
            }
        }
        return response.setStatus(HttpResponseStatus.NOT_FOUND);
    }



}

这样可以实现处理请求get post方法。post方法中还可以处理multipart类型,但是示例中没做处理,示例中是针对json格式请求。

写测试接口类

package com.coman404.mvc.demo.controller;

import com.coman404.mvc.annotation.RequestBody;
import com.coman404.mvc.annotation.RequestMapping;
import com.coman404.mvc.annotation.RequestParam;
import com.coman404.mvc.annotation.RestController;


@RestController
@RequestMapping("/test")
public class TestController {

    @RequestMapping("/hello")
    public String hello(@RequestParam("name") String name){
        return "hello " + name;
    }

    @RequestMapping("/obj")
    public VO world(@RequestParam("name") String name,@RequestParam("age")Integer age){
        VO vo = new VO();
        vo.setAge(age);
        vo.setName(name);
        return vo;
    }

    @RequestMapping(value = "/obj2",method = "POST")
    public VO world2(@RequestBody VO vo){
        vo.setName("post:"+vo.getName());
        return vo;
    }

    public class VO{
        private String name;
        private int age;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
    }

}

结语

通过上面的示例我们可以简单实现一个mvc 框架,示例只是提供一个思路。实际框架中需要考虑到很多方面。

posted on 2024-06-11 17:00  猿来就是尔  阅读(1)  评论(0)    收藏  举报  来源