【Java】NIO多路复用技术编程实现SpringMVC

31 篇文章 0 订阅

在这里插入图片描述

IO多路复用实现高性能SpringMVC

自定义注解MyRestController、MyRequestMapping,手写nio多路复用Server,用浏览器访问,效果跟平时直接用springMVC和tomcat的一样。练完这个,发现spring框架原来如此!(当然spring的源码实际上很难,只是原理简单而已)。

手写Server思路:

1、先实现多路复用;
2、写处理请求的方法;
3、实现MyRestController、MyRequestMapping功能;
我们要扫描包,扫描每一个class文件,用反射得到类的注解,如果类有注解,就要解析注解。
4、模拟IOC容器,初始化时将带MyRestController的类实例化并注入到容器里;
5、请求与方法的映射,用反射调用方法(注意解析参数);
6、实现各功能,将处理请求的代码写完整;
7、组织好代码,进行调试和测试。

完整代码如下:

MyHelloController

package com.bailiban.controller;

import com.bailiban.model.User;


import java.util.ArrayList;
import java.util.List;
@MyRestController("MyHelloController")
public class MyHelloController {
    private static List<User> list=new ArrayList<User>();
    static {
        list.add(new User(1,"刘星"));
        list.add(new User(2,"蔡徐坤"));
    }

    @MyRequestMapping("/hello")
    public String getUserById(int id){
        for(User u:list){
            if(u.getId()==id){
                return u.toString();
            }
        }
        return "404 not found";
    }
    @MyRequestMapping("/all")
    public String getAll(){
        StringBuilder stringBuilder=new StringBuilder();
        for(User u:list){
            stringBuilder.append(u.toString()+"<br />");
        }
        return stringBuilder.toString();
    }
    @MyRequestMapping("/newuser")
    public User newUser(int id,String name){
      return new User(id,name);
    }
}


自定义的注解

package com.bailiban.controller;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyRequestMapping {
    String value() default "";
    String[] locations() default {};
}

package com.bailiban.controller;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyRestController {
    String value() default "";
}

多路复用的Server

package com.bailiban.controller;

import com.bailiban.socket.bs.ServerTest;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.InetSocketAddress;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class ServerNioHttp {
    //注解value,被注解的方法
    public static Map<String,Method> methodMap=new HashMap<String, Method>();
    //参数名,参数值。例如 /hello?id=1  key="id",value="1"
    public static Map<String,String> paraMap=new HashMap<String, String>();
    //模拟的IOC容器
    public static Map<String,Object> beanMap=new HashMap<>();

    static {
        //扫描包,初始化IOC容器
        refreshBeanByPackageFile("com.bailiban.controller");
    }

    public static void main(String[] args) {
        try {
            //运行主程序
            run();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 以文件的形式来获取包下的所有Class
     * @param packageName
     */
    public static void refreshBeanByPackageFile(String packageName){
        String packagePath=packageName.replace(".","/");
        URL resource = ServerNioHttp.class.getClassLoader().getResource(packagePath);
        //获取此包的目录 建立一个File
        File dir = new File(resource.getPath());
        fileParseClass(dir);
    }

    public static void fileParseClass(File dir){
        //如果不存在或者 也不是目录就直接返回
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        //如果存在 就获取包下的所有文件 包括目录
        //自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
        File[] dirfiles = dir.listFiles(file -> {
            if(file.isDirectory()){
                fileParseClass(file);
                return false;
            }
            return file.getName().contains(".class");
        });

        for(File f:dirfiles){
            fileParseObject(f);
        }
    }

    private static void fileParseObject(File f) {
        if(f.isDirectory()){
            return;
        }
        String className=f.getName().replace(".class","");
        //System.out.println(f.getPath());//D:\test\mvc01\target\classes\com\bailiban\controller\HelloController.class
        String wholeName=f.getPath().split("classes\\\\")[1].split("\\.class")[0].replace("\\",".");
        //注解解析
        parseAnnotation(className,wholeName);
    }

    /**
     * 解析自定义注解
     */
    public static void parseAnnotation(String className,String wholeName){
        try {
            Class<?> aClass = Class.forName(wholeName);
            MyRestController annotation = aClass.getAnnotation(MyRestController.class);
            if(annotation!=null){
                beanMap.put(className,aClass.newInstance());
                Method[] declaredMethods = aClass.getDeclaredMethods();
                for(Method method:declaredMethods){
                    MyRequestMapping myRequestMapping = method.getAnnotation(MyRequestMapping.class);
                    if(myRequestMapping==null){
                        continue;
                    }
                    methodMap.put(myRequestMapping.value(),method);
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }


    /**
     * 多路复用
     * @throws IOException
     */
    public static void run() throws IOException {
        //建立server socket channel
        ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
        //绑定端口
        serverSocketChannel.socket().bind(new InetSocketAddress(80));
        //设置阻塞为false
        serverSocketChannel.configureBlocking(false);
        //选择器
        Selector selector=Selector.open();
        //注册选择器
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("http服务器开启");
        while (true){
            //如果选择的socket没有请求,就不处理
            if(selector.select(3000) == 0){
                continue;
            }
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey selectionKey = iterator.next();
                //处理http请求
                httpHandle(selectionKey);
                //处理完删除掉
                iterator.remove();
            }
        }
    }

    //处理请求
    private static void httpHandle(SelectionKey key) throws IOException {
        if (key.isAcceptable()) {
            //服务器等待客户端
            acceptHandle(key);
        } else if (key.isReadable()) {
            //客户端http请求服务器
            requestHandle(key);
        }
    }

    private static void requestHandle(SelectionKey key) throws IOException {
        SocketChannel socketChannel= (SocketChannel) key.channel();
        ByteBuffer byteBuffer=(ByteBuffer) key.attachment();
        //设置写模式
        byteBuffer.clear();
        if(socketChannel.read(byteBuffer) == -1){
            socketChannel.close();
            return;
        }
        //设置读模式
        byteBuffer.flip();
        String text=new String(byteBuffer.array());
        //请求头第一行,按空格分隔,得到完整url
        String url=text.split("\r\n")[0].split(" ")[1];
        //通过url解析出参数
        setParaMap(url);
        Method method=null;
        if(url.contains("?")){
            //有参数把参数去掉
            url=url.split("\\?")[0];
            //按url与注解来找到方法
            method = methodMap.get(url);
        }else {
            //无参数直接找方法
            method = methodMap.get(url);
        }
        Object invoke="404 not found";
        try {
            invoke=myInvoke(method);
        } catch(IllegalAccessException e){
            e.printStackTrace();
        } catch(InvocationTargetException e){
            e.printStackTrace();
        }

        StringBuilder stringBuilder=new StringBuilder();
        stringBuilder.append("HTTP/1.1 200 OK\r\n");
        stringBuilder.append("Content-Type:text/html;charset=utf-8\r\n");
        stringBuilder.append("\r\n");
        stringBuilder.append("<html><head><title>HttpTest</title></head><body>");
        stringBuilder.append(invoke);
        System.out.println(url+">>>"+invoke);
        stringBuilder.append("</body></html>");

        socketChannel.write(ByteBuffer.wrap(stringBuilder.toString().getBytes()));
        socketChannel.close();
    }

    private static Object myInvoke(Method method) throws InvocationTargetException, IllegalAccessException {
        //调用方法返回值(因为controller我写的都是返回字符串)
        Object controller=null;
        if (method != null) {//如果方法不为空,说明url是正确的,否则页面显示"404 not found"
            //得到方法的参数
            String simpleName = method.getDeclaringClass().getSimpleName();
            controller=beanMap.get(simpleName);
            Parameter[] parameters = method.getParameters();
            //如果有参数且解析url有参数
            if (parameters.length != 0) {
                if(paraMap.size()==0){//方法需要参数,都是URL没给参数
                    return "404 not found";
                }
                //参数数组
                Object[] objects = new Object[parameters.length];
                for (int i = 0; i < parameters.length; i++) {
                    String typeName = parameters[i].getType().getSimpleName();
                    String paraname = parameters[i].getName();
                    System.out.println(typeName + "<+++>" + paraname);
                    //参数数组塞对象,将 类型名+字符串值 结合类型转换成对应的对象存入数组
                    String value=paraMap.get(paraname);
                    if(value==null){
                        return "参数错误";
                    }
                    objects[i] = change(typeName,value);
                }
                //方法调用,有参数的
                return method.invoke(controller, objects);
            } else{
                return method.invoke(controller);
            }
        }
        return "404 not found";
    }

    private static void acceptHandle(SelectionKey selectionKey) throws IOException {
        SocketChannel socketChannel=((ServerSocketChannel)selectionKey.channel()).accept();
        socketChannel.configureBlocking(false);
        socketChannel.register(selectionKey.selector(),SelectionKey.OP_READ, ByteBuffer.allocate(1024));

    }

    /**
     * 解析url参数,存入paraMap
     * @param url
     */
    public static void setParaMap(String url){
        //每次解析请求url时先清空参数map,因为里面存着上次请求的内容
        paraMap.clear();
        try {
            //如果没有参数,那么参数map是空的,size=0
            //如果有参数,把参数和值存在参数map里
            if(url.contains("?")){
                String paraname;
                String value;
                String param=url.replaceFirst(".*?\\?","");
                String[] split2 = param.split("&");

                for(String ky:split2){
                    String[] split3 = ky.split("=");
                    paraname=split3[0];
                    value=split3[1];
                    System.out.println(paraname+"<--->"+value);
                    paraMap.put(paraname,value);
                }
            }

        }catch (Exception e){
            e.printStackTrace();
        }


    }

    public static Object change(String type,String value){
        //将字符串的值与类型 转换成对应的对象
        if(type.equalsIgnoreCase("int")){
            return Integer.valueOf(value);
        }else if(type.equalsIgnoreCase("string")) {
            return value;
        }else if(type.equalsIgnoreCase("double")){
            return Double.valueOf(value);
        }else{
            return value;
        }
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值