Spring MVC

#手写一个Spring MVC框架

##一次浏览器请求的大致过程
Sping MVC是一个Web框架,所以我们要了解Spring MVC就要先了解一次浏览器请求到收到服务器的响应大概发生了哪些事情。


1.域名解析;
2.请求连接(三次握手);
3.浏览器发起http请求;
4.服务器解析http交给Spring MVC处理;
5.将响应的结果封装返回;
##Socket编程
了解了这个过程之后开始做一个简单的Socket编程
首先服务端代码:

public class Server {
    public static void main(String[] args) throws IOException {
        //1.建立服务端
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("等待链接...");
        //2.监听客户端连接
        Socket socket = serverSocket.accept();
        System.out.println("客户端已连接:"+socket.getInetAddress().getHostAddress());

        try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
             PrintWriter out = new PrintWriter(socket.getOutputStream(),true)){
            //4.从客户端读取消息,会阻塞
            System.out.println("client:"+in.readLine());
            //6.从命令行读取消息传给客户端
            String line = sin.readLine();
            out.println(line);
        }
    }
}

客户端代码

  public static void main(String[] args) throws IOException {
        //3.请求连接服务器
        Socket socket = new Socket("localhost",8080);
        System.out.println("成功连接服务器");
        try (BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
             PrintWriter out = new PrintWriter(socket.getOutputStream(),true);
             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
            //5.从命令行读取消息传给服务端
            String line = sin.readLine();
            out.println(line);
            //7.收到服务器消息
          System.out.println("server:"+in.readLine());
        }
    }

上面的serverSocket.accept()和 in.readLine()都是阻塞函数
这知识一个简单的Socket编程一次读写后就会关闭(try-with-resource,资源会自动释放),但是服务端未接收到消息就不能给客户端发送消息;
##多线程版

Server

  
public class HTTPServer {
    private static Map<String,String> contentMap = new HashMap<>();
    static {
        contentMap.put("/","welcome");
        contentMap.put("/hello","world");
    }

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);

        while (true){
            Socket socket = serverSocket.accept();
            System.out.println("客户端已连接"+socket.getInetAddress().getHostAddress());
            new Thread(()->{
                try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                     PrintWriter out = new PrintWriter(socket.getOutputStream(),true)){
                    while (true){
                        String line = in.readLine();
                        System.out.println(line);
                        if (line == null || line.equals("bye")){
                            break;
                        }
                        String s = contentMap.get(line);
                        out.println(s==null?404:s);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();

        }

    }
}

Client

public static void main(String[] args) throws IOException {
        Socket socket = new Socket("localhost", 8080);
        try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             PrintWriter out = new PrintWriter(socket.getOutputStream(),true);
             BufferedReader sin = new BufferedReader(new InputStreamReader(System.in))){
            while (true){
                String line = sin.readLine();
                if (line==null || line.equals("bye")){
                    break;
                }
                out.println(line);
                String readLine = in.readLine();
                System.out.println(readLine);
            }
        }
    }

这样我们就可以实现客户端和服务端之间的多次请求响应了,而且可以实现多个客户端连接;那我们再写一个服务器浏览器的例子
##BS版

public class HTTPServer {
    public static Map<String,String> contentMap = new HashMap<>();
    static {
        contentMap.put("/","welcome");
        contentMap.put("/hello","world");
        contentMap.put("/yuxuyang","sb");
    }

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(80);

        while (true){
            Socket socket = serverSocket.accept();
            new Thread(()->{
                //System.out.println("客户端已连接"+socket.getInetAddress().getHostAddress());
                try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                     PrintWriter out = new PrintWriter(socket.getOutputStream(),true)){
                    String line = null;
                    StringBuffer buffer = new StringBuffer();
                    String request = null;
                    while ((line = in.readLine()) !=null && !line.equals("")){
                        buffer.append(line).append("\n");
                        if (request==null){
                            request = line;
                        }
                    }
                    //System.out.println(buffer.toString());

                    //System.out.println(buffer);
                    out.println("HTTP/1.1 200 OK");
                    out.println("Content-Type:text/html;charset=utf-8");
                    //空行表示请求头结束
                    out.println();
                    out.println("<html><head><title>HttpTest</title></head><body>");

                    if (request != null){
                        String url = request.split(" ")[1].split("\\?")[0];
                        System.out.println(url);
                        if (request.split(" ")[1].split("\\?").length>1){
                            String parameter = request.split(" ")[1].split("\\?")[1];
                            String[] parameters = parameter.split("&");
                            for (String s:parameters) {
                                String[] split = s.split("=");
                                String s1 = split[0];
                                String s2 = split[1];
                                System.out.println(s1 +":"+s2);
                            }
                        }
                        String s = contentMap.get(url);
                        out.println(s==null?"404":s);
                    }
                    out.println("</body></html>");
                } catch (IOException e) {
                    //e.printStackTrace();
                }
            }).start();
        }
    }
}

这样我们在浏览器访问localhost的80端口就能从map里取到值显示在前端页面;
但是上面的两个例子都是多线程实现的,所以说如果访问量大的时候就会有很多线程,线程不断的创建销毁就会消耗资源;所以肯定是不行的;

##IO多路复用

public static void main(String[] args) throws IOException {
        ServerSocketChannel socketChannel = ServerSocketChannel.open();
        socketChannel.socket().bind(new InetSocketAddress(80));
        socketChannel.configureBlocking(false);
        Selector selector = Selector.open();
        socketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (true){
            if (selector.select(1000)<=0){
                continue;
            }
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                httpHandle(key);
                iterator.remove();
            }
        }
    }

    private static void httpHandle(SelectionKey key) throws IOException {
        if (key.isAcceptable()){
            acceptHandle(key);
        }else if (key.isReadable()){
            requestHandle(key);
        }
    }

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

    }

    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 request = new String(byteBuffer.array());
        String url = request.split("\r\n")[0].split(" ")[1];
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("HTTP/1.1 200 OK\r\n");
        stringBuffer.append("Content-Type:text/html;charset=utf-8\r\n\r\n");
        stringBuffer.append("<html><head><title>HttpTest</title></head><body>");
        String s = HTTPServer.contentMap.get(url);
        stringBuffer.append(s==null?"404":s);

        stringBuffer.append("</body></html>");
        socketChannel.write(ByteBuffer.wrap(stringBuffer.toString().getBytes()));
        socketChannel.close();
    }
}

在这里插入图片描述

为了实现Selector管理多个SocketChannel,必须将具体的SocketChannel对象注册到Selector,并声明需要监听的事件(这样Selector才知道需要记录什么数据),一共有4种事件:

1、connect:客户端连接服务端事件,对应值为SelectionKey.OP_CONNECT 2、accept:服务端接收客户端连接事件,对应值为SelectionKey.OP_ACCEPT 3、read:读事件,对应值为SelectionKey.OP_READ 4、write:写事件,对应值为SelectionKey.OP_WRITE

当socket有请求时遍历这些请求然后一个个处理或者发送到每一个线程。

##手写Spring MVC
在了解了IO多路复用后我们就可以自己写一个Spring MVC了

@MyRestController
public class UserController {

    private static List<User> userList = new ArrayList<>();

    static {
        userList.add(new User(1, "Jim"));
        userList.add(new User(2, "Lily"));
    }

    @MyRequestMapping("/get")
    public String get(int id) {
        for (User user:userList) {
            if (user.getId()==id){
                return user.getName();
            }
        }
        return "";
    }

    @MyRequestMapping("/getAll")
    public String getAll() {
        StringBuffer stringBuffer = new StringBuffer();
        for (User user:userList) {
            stringBuffer.append("id:").append(user.getId()).append(" name:").append(user.getName()).append("\r\n");
        }
        return stringBuffer.toString();
    }
    @MyRequestMapping("/get2")
    public String get2(int id,String name){
        return id+":"+name;
    }

    @MyRequestMapping("/addUser")
    public String addUser(int id,String name){
        User user = new User(id, name);
        userList.add(user);
        return getAll();
    }
}

@MyRestController 用来将类实例化后放到容器里
@MyRequestMapping用来映射路径

解析路径

public class ParseUrl {
    private static Map<String,Object> beanMap = new HashMap<>();
    private static Map<String, MethodInfo> methodMap = new HashMap<>();

    // 1. 初始化 beanMap
    // 2. 初始化 methodMap
    public static void refreshBeanFactory(String pkg) {
        URL url = ParseUrl.class.getClassLoader().getResource(pkg.replace(".","/"));
        File file = new File(url.getPath());
        parseFile(file);
        //System.out.println(file);
       /* for (Map.Entry entry:beanMap.entrySet()) {
            System.out.println(entry.getKey()+":"+entry.getValue());
        }*/
    }
    //解析file
    private static void parseFile(File file) {
        if (!file.isDirectory()){
            return;
        }
        File[] files = file.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathName) {
                if (pathName.isDirectory()){
                    parseFile(pathName);
                    return false;
                }
                return pathName.getName().endsWith(".class");
            }
        });
        for (File f:files) {
            String classPath = f.getAbsolutePath().split("classes\\\\")[1].replace("\\", ".").replace(".class", "");
            try {
                Class<?> aClass = Class.forName(classPath);
                if (aClass.getDeclaredAnnotation(MyRestController.class)!=null){
                    parseClass(aClass);
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    private static void parseClass(Class<?> aClass) {
        try {
            beanMap.put(aClass.getSimpleName(),aClass.getDeclaredConstructor().newInstance());
        } catch (Exception e) {
            e.printStackTrace();
        }
        Method[] methods = aClass.getDeclaredMethods();
        for (Method method:methods) {
            MyRequestMapping myRequestMapping = method.getDeclaredAnnotation(MyRequestMapping.class);
            if (myRequestMapping!=null){
                String url = myRequestMapping.value();
                methodMap.put(url,new MethodInfo(method,aClass.getSimpleName()));
            }
        }
    }
    //解析url

    /**
     *
     * @param url 全路径
     */
    public static String parseUrl(String url) throws InvocationTargetException, IllegalAccessException {
        if (url.contains(".ico")){
            return "";
        }
        HashMap<String,String> urlParameter = new HashMap<String,String>();
        if (!url.contains("?")){
            return methodInvoke(url,urlParameter);
        }
        String[] split = url.split("\\?");
        String[] parameters = url.replaceFirst(".*?\\?","").split("&");
        url = split[0];
        for (String parameter:parameters) {
            if (!parameter.contains("=")){
                continue;
            }
            String[] split1 = parameter.split("=");
            urlParameter.put(split1[0],split1[1]);
        }

        return methodInvoke(url,urlParameter);

    }
    //调用方法

    /**
     *
     * @param url 不带参数的路径
     * @param urlParameter 路径带的参数(参数名,value)
     * @return
     */
    public static String methodInvoke(String url,HashMap<String,String> urlParameter) throws InvocationTargetException, IllegalAccessException {
        //根据URl从容器取出method和对象
        MethodInfo methodInfo = methodMap.get(url);
        if (methodInfo==null){
            return "404";
        }
        String className = methodInfo.getClassName();
        Object o = beanMap.get(className);
        Method method = methodInfo.getMethod();
        //判断有无参数
       /* if (urlParameter.size()==0){
            return (String) method.invoke(o);
        }*/
        //接收参数的数组
        Object[] parameters = new Object[urlParameter.size()];
        Parameter[] methodParameters = method.getParameters();
        if(parameters.length!=methodParameters.length){
            return "参数个数不匹配";
        }
        //判断参数类型
        for (int i = 0; i<methodParameters.length;i++){
            String name = methodParameters[i].getName();
            String type = methodParameters[i].getType().getSimpleName();
            boolean flag = false;
            if(type.equals("int")){
                parameters[i] = Integer.parseInt(urlParameter.get(name));
                flag = true;
            }else if (type.equals("String")){
                parameters[i] = urlParameter.get(name);
                flag = true;
            }
            if (!flag){
                return "404";
            }
        }
        return (String) method.invoke(o, parameters);
    }

}

首先我们要将类和方法解析出来放到我们的map里,也就是初始化容器;beanMap就相当于Spring的IOC容器,methodMap用来映射访问的路径和对应的方法;

Server

public class NIOTest {
    static {
        ParseUrl.refreshBeanFactory("com.bailiban.day1.myMVC");
    }
    public static void main(String[] args) throws IOException {

        ServerSocketChannel socketChannel = ServerSocketChannel.open();
        socketChannel.socket().bind(new InetSocketAddress(80));
        socketChannel.configureBlocking(false);
        Selector selector = Selector.open();
        socketChannel.register(selector,SelectionKey.OP_ACCEPT);
        while (true){
            if (selector.select(1000)<=0){
                continue;
            }
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                httpHandle(key);
                iterator.remove();
            }
        }
    }

    private static void httpHandle(SelectionKey key) throws IOException {
        if (key.isAcceptable()){
            acceptHandle(key);
        }else if (key.isReadable()){
            requestHandle(key);
        }
    }

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

    }

    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 request = new String(byteBuffer.array());
        String url = request.split("\r\n")[0].split(" ")[1];
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("HTTP/1.1 200 OK\r\n");
        stringBuffer.append("Content-Type:text/html;charset=utf-8\r\n\r\n");
        stringBuffer.append("<html><head><title>HttpTest</title></head><body>");

       /* String s = HTTPServer.contentMap.get(url);*/
        ParseUrl parseUrl = new ParseUrl();

        String response = null;
        try {
            response = parseUrl.parseUrl(url);
        } catch (Exception e) {
            e.printStackTrace();
        }
        stringBuffer.append(response);
        /* stringBuffer.append(s==null?"404":s);*/

        stringBuffer.append("</body></html>");
        socketChannel.write(ByteBuffer.wrap(stringBuffer.toString().getBytes()));
        socketChannel.close();
    }

}

在我们一步步的完善下,这样一个简单的Spring MVC就完成了;
完整代码:添加链接描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值