基于Netty手工实现springMVC框架

不安分的猿人 8天前 ⋅ 27 阅读

一、Netty简介

Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。

也就是说,Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。

二、实例代码

1.Netty启动web服务

package server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import utils.LoadWebConfig;

/**
 * Created by wangzhiguo on 2019/1/28 0028.
 */
public class NettyHttpServer {

    LoadWebConfig loadWebConfig = new LoadWebConfig();
    public  NettyHttpServer(){
        loadWebConfig.init();
    }
    public static void main(String[] args) {
        //todo 端口暂时不考虑,后期需要可配置
        int port = 2222;
        new NettyHttpServer().run(port);
    }
    public void run(int port) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(
                                new HttpResponseEncoder(),
                                new HttpRequestDecoder(),
                                new NettyHttpServerHandler(loadWebConfig));
                    }
                }).option(ChannelOption.SO_BACKLOG, 128)
                .childOption(ChannelOption.SO_KEEPALIVE, true);
        try {
            ChannelFuture f = bootstrap.bind(port).sync();
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();

        }
    }
}

2.访问Netty Server后的处理逻辑

import bean.ControllerContext;
import bean.ViewBean;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.EmptyByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import utils.ByteUtils;
import utils.HtmlUtils;
import utils.LoadWebConfig;
import utils.ReadWebTemplate;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

/**
 * Created by wangzhiguo on 2019/1/28 0028.
 */
public class NettyHttpServerHandler extends ChannelInboundHandlerAdapter {

    private LoadWebConfig loadWebConfig;
    public NettyHttpServerHandler(LoadWebConfig loadWebConfig){
        this.loadWebConfig = loadWebConfig;
    }


    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        ControllerContext context = new ControllerContext();

        if (msg instanceof HttpRequest) {
            DefaultHttpRequest request = (DefaultHttpRequest) msg;
            String returnView = "";
            //1. 组装上下文 context
            if(StringUtils.isNotEmpty(request.getUri())){
                Map<String, Object> urlParams = HtmlUtils.getUrlParams(request.getUri());
                context.setParams(urlParams);
                //2. 获取要被执行的方法
                String executeMethod = HtmlUtils.getControllerMethod(request.getUri());

                if(StringUtils.isNotEmpty(executeMethod)){
                    //3. 通过反射机制执行action的方法,返回结果视图索引
                    returnView = returnView(executeMethod, context);
                }
            }
            //4.返回结果视图
            String returnViewStr = ReadWebTemplate.readTemplate(returnView);
            FullHttpResponse response = new DefaultFullHttpResponse(
                    HttpVersion.HTTP_1_1,
                    HttpResponseStatus.OK,
                    Unpooled.wrappedBuffer(returnViewStr.getBytes("utf-8")));
//        response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/plain;charset=UTF-8");
            //text/html类型输入网页形式
            response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/html;charset=UTF-8");
            response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
            response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
            ctx.write(response);
            ctx.flush();
        }
        if (msg instanceof HttpContent) {
            LastHttpContent httpContent = (LastHttpContent) msg;
            ByteBuf byteData = httpContent.content();
            if (byteData instanceof EmptyByteBuf) {
                System.out.println("Content:无数据");
            } else {
                String content = new String(ByteUtils.objectToByte(byteData));
                System.out.println("Content:" + content);
            }
        }


    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
        cause.printStackTrace();
    }

    /**
     * 获取返回视图
     * @param executeMethod
     * @param context
     * @return
     */
    private String returnView(String executeMethod, ControllerContext context) throws Exception {

        String returnView = "";
        List<ViewBean> resultViews = loadWebConfig.getResultViews();
        if(CollectionUtils.isNotEmpty(resultViews) && StringUtils.isNotEmpty(executeMethod)){
            for(ViewBean view: resultViews){
                //action 的name属性相同时
                if(executeMethod.equals(view.getActionName())){
                    Class clazz = Class.forName(view.getControllerClass());
                    Method[] methods = clazz.getDeclaredMethods();
                    Object controller = clazz.newInstance();
                    for(Method method: methods){
                        if(method.getName().equals(view.getControllerClassMethod())){
                            returnView = (String)method.invoke(controller,context);
                        }
                    }
                }
            }
        }else {
            throw new Exception("executeMethod or context is null!");
        }

        return returnView;
    }
}

3.编写自定义Controller

import bean.ControllerContext;

/**
 * Created by wangzhiguo on 2019/1/29 0029.
 */
public class UserController {

    public String index(ControllerContext context){

        context.addParams("key", "this is index method!");
        return "index";
    }
    public String login(ControllerContext context){

        context.addParam("key", "this is login method!");
        return "login";
    }
}

创建controller的配置文件webConfig.xml (接下来我用自定义注解来替代xml配置文件) 

<?xml version="1.0" encoding="UTF-8"?>
<package name="wangzg">
    <constants>
        <viewSuffix>.html</viewSuffix>
    </constants>
    <action name="index" method="index" class="controller.UserController" type="text/html" page="index" />
    <action name="login" method="login" class="controller.UserController" type="text/html" page="login" />
</package>

代码结构如下图:

4.其他的一些工具类

1.解析webConfig.xml工具类LoadWebConfig.java

import bean.ViewBean;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.util.*;

/**
 * Created by wangzhiguo on 2019/1/29 0029.
 * 加载配置文件
 */
public class LoadWebConfig {

    private final static String CONFIG_PATH = "/config/webConfig.xml";
    private final static String CONSTANT_LABEL = "constants";
    private final static String ACTION_LABEL = "action";

    private final static String ACTION_PROP_NAME = "name";
    private final static String ACTION_PROP_METHOD = "method";
    private final static String ACTION_PROP_CLASS = "class";
    private final static String ACTION_PROP_TYPE = "type";
    private final static String ACTION_PROP_PAGE = "page";
    /**
     * 配置文件中基本参数
     */
    public  static Map<String, String> constants = new HashMap<>();

    public static List<ViewBean> resultViews = new ArrayList<>();

    public static void main(String [] args){
        LoadWebConfig loadWebConfig = new LoadWebConfig();
        loadWebConfig.init();
        MapUtils.debugPrint(System.out, 1,loadWebConfig.constants);
        System.out.println(StringUtils.join(resultViews,"###"));
    }
    public void init(){
        // 解析webConfig.xml文件
        // 创建SAXReader的对象reader
        SAXReader reader = new SAXReader();
        try {
            String userPath = this.getClass().getClassLoader().getResource("").getPath();
            // 通过reader对象的read方法加载books.xml文件,获取docuemnt对象。
            Document document = reader.read(new File( userPath + CONFIG_PATH));
            Element configPackage = document.getRootElement();
            String packageName = configPackage.getName();
            // 通过element对象的elementIterator方法获取迭代器
            Iterator it = configPackage.elementIterator();
            // 遍历迭代器,获取根节点中的信息(书籍)
            while (it.hasNext()) {
                Element element = (Element) it.next();

                if(CONSTANT_LABEL.equals(element.getName())){
                    //解析常量
                    Iterator itt = element.elementIterator();
                    while (itt.hasNext()) {
                        Element subElement = (Element) itt.next();
                        constants.put(subElement.getName(),  subElement.getStringValue());
//                        System.out.println("节点名:" + subElement.getName() + "--节点值:" + subElement.getStringValue());
                    }
                }else if(ACTION_LABEL.equals(element.getName())){
                    //解析action的配置
                    //todo 判断是否为常量
                    // todo 若不为常量即为配置action的信息
                    // 获取action的属性名以及 属性值
                    List<Attribute> actionAttrs = element.attributes();
                    ViewBean viewMapper = setAttrByAttribute(actionAttrs);
                    //设置包名
                    viewMapper.setPackageName(packageName);
                    resultViews.add(viewMapper);
                }
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

    private ViewBean setAttrByAttribute(List<Attribute> actionAttrs){
        ViewBean viewBean = new ViewBean();
        if(CollectionUtils.isNotEmpty(actionAttrs)){
            //转化为对象
            for (Attribute attr : actionAttrs) {
                String propName = attr.getName();
                String propValue = attr.getValue();
                switch (propName){
                    case ACTION_PROP_NAME:
                        viewBean.setActionName(propValue);
                        break;
                    case ACTION_PROP_METHOD:
                        viewBean.setControllerClassMethod(propValue);
                        break;
                    case ACTION_PROP_CLASS:
                        viewBean.setControllerClass(propValue);
                        break;
                    case ACTION_PROP_TYPE:
                        viewBean.setViewTyp(propValue);
                        break;
                    case ACTION_PROP_PAGE:
                        viewBean.setViewName(propValue);
                        break;
                    default:break;
                }
            }
        }
        return viewBean;
    }


    public  Map<String, String> getConstants() {
        return constants;
    }

    public  void setConstants(Map<String, String> constants) {
        constants = constants;
    }

    public  List<ViewBean> getResultViews() {
        return resultViews;
    }

    public  void setResultViews(List<ViewBean> resultViews) {
        resultViews = resultViews;
    }
}

2.解析controller返回页面视图工具类ReadWebTemplate

import bean.ViewBean;
import org.apache.commons.lang3.StringUtils;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;

/**
 * Created by wangzhiguo on 2019/1/29 0029.
 */
public class ReadWebTemplate {

    private static String templatePath = "/templates/";
    /**
     * controller的配置信息
     */
    private List<ViewBean> resultViews  = LoadWebConfig.resultViews;
    private final static String viewSuffix = LoadWebConfig.constants.get("viewSuffix");

    public static String readTemplate(String prefixName) throws Exception{

        if(StringUtils.isNotEmpty(prefixName)){
            InputStream inputStream = ReadWebTemplate.class.getResourceAsStream(templatePath + prefixName + viewSuffix);
            InputStreamReader reader = new InputStreamReader(inputStream); // 建立一个输入流对象reader
            BufferedReader br = new BufferedReader(reader); // 建立一个对象,它把文件内容转成计算机能读懂的语言
            StringBuffer stringBuffer = new StringBuffer();
            String line  = br.readLine();
            stringBuffer.append(line);
            while (line != null) {
                line = br.readLine(); // 一次读入一行数据
                if(StringUtils.isNotEmpty(line)){
                    stringBuffer.append(line);
                }
            }
            return stringBuffer.toString();
        }
        return "";
    }
}

三、运行效果

1.在浏览器中访问:http://localhost:2222/index?param1=pars

至此一个手工制作的springMVC的框架已经创建成功。

 

备注:项目还在不断完善中,希望收获您宝贵的意见和建议。

代码路径:手工制作SpringMVC框架

 

欢迎关注我的公众号"不安分的猿人",也可扫描二维码:

 

 

推荐

自己搭建了一套logoly环境,欢迎搭建来体验。

http://www.mhtclub.com/logoDesign/

也欢迎朋友们来我的博客逛逛!

http://www.mhtclub.com

一位朋友的人工智能教程。零基础,通俗易懂!

 


全部评论: 0

    我有话说: