目标
上一节中我们使用Netty构建了一个简单的静态Http服务器。本节我们使用Netty实现一个类似spring mvc框架的功能。
spring mvc是通过servlet来实现的请求处理。spring webflux才是用netty去做的
思路
首先通过自定义注解RestController Controller定义接口类,再在其中定义我们需要的接口。项目启动时先扫描接口类并且解析接口信息缓存。当Netty Handler接收到请求时看按照定义好的接口调用并返回信息。
定义注解
- RestController注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RestController {
}
2.Controller注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface Controller {
}
- 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";
}
- 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初始化后自定义处理一些逻辑。
- 我们定义一个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;
}
};
}
- 解析到的接口方法我们将信息存放到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 框架,示例只是提供一个思路。实际框架中需要考虑到很多方面。
浙公网安备 33010602011771号