Skip to main content
Jkyo Chen Blog

看透 SpringMvc 源代码分析与实践

网络基础知识 #

软件的三大类型 #

基础的结构 #

架构演变的起点 #

海量数据的解决方案 #

缓存和页面静态化 #

数据库优化 #

表结构优化 #

SQL 语句优化 #

分区 #

分表 #

索引优化 #

使用存储过程代替直接操作 #

分离活跃数据 #

批量读区和延迟修改 #

读写分离 #

分布式数据库 #

NoSQL 和 Hadoop #

高并发的解决方案 #

应用和静态资源分离 #

页面缓存 #

集群与分布式 #

反向代理 #

CDN #

底层的优化 #

小结 #

常见协议和标准 #

DNS 协议 #

TCP/IP 协议 与 Socket #

缺点 #

UDP #

HTTP 协议 #

Servlet 和 Java Web 开发 #

DNS 的设置 #

Java 中 Socket 的用法 #

普通的 Socket 的用法 #

// sever
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class Server
{
    public static void main(String[] args)
    {
        try
        {
            ServerSocket server = new ServerSocket(8080);
            Socket socket = server.accept();
            BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String line = is.readLine();
            System.out.println("received from client:" + line);

            PrintWriter pw = new PrintWriter(socket.getOutputStream());
            pw.println("received data:" + line);
            pw.close();
            is.close();
            socket.close();
            server.close();

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

// Client
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class Client
{
    public static void main(String[] args)
    {
        String msg = "Client Data";
        try
        {
            Socket socket = new Socket("127.0.0.1", 8080);
            PrintWriter pw = new PrintWriter(socket.getOutputStream());
            BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            pw.println(msg);
            pw.flush();
            String line = is.readLine();
            System.out.println("received from server:" + line);
            pw.close();
            is.close();
            socket.close();

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

NioSocket 的用法 #

自己动手实现 HTTP 协议 #

import java.io.IOException;
import java.net.InetSocketAddress;
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.nio.charset.Charset;
import java.util.Iterator;

public class HttpServer
{
    public static void main(String[] args) throws Exception
    {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.socket().bind(new InetSocketAddress(8080));
        ssc.configureBlocking(false);

        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        while (true)
        {
            if (selector.select(3000) == 0)
            {
                continue;
            }

            Iterator keyIter = selector.selectedKeys().iterator();

            while (keyIter.hasNext())
            {
                SelectionKey key = keyIter.next();
                new Thread(new HttpHandler(key)).run();
                keyIter.remove();
            }
        }

    }

    private static class HttpHandler implements Runnable
    {
        private int bufferSize = 1024;
        private String localCharset = "UTF-8";
        private SelectionKey key;

        public HttpHandler(SelectionKey key)
        {
            this.key = key;
        }

        public void handleAccept() throws IOException
        {
            SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();
            clientChannel.configureBlocking(false);
            clientChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));
        }

        public void handleRead() throws IOException
        {
            SocketChannel sc = (SocketChannel) key.channel();
            ByteBuffer buffer = (ByteBuffer) key.attachment();
            buffer.clear();

            if (sc.read(buffer) == -1)
            {
                sc.close();
            } else
            {
                buffer.flip();
                String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString();
                String[] requestMessage = receivedString.split("\r\n");
                for (String s : requestMessage)
                {
                    System.out.println(s);
                    if (s.isEmpty())
                    {
                        break;
                    }
                }
                String[] firstLine = requestMessage[0].split("");
                System.out.println();
                System.out.println("Method:\t" + firstLine[0]);
                System.out.println("url:\t" + firstLine[1]);
                System.out.println("HTTP Version:\t" + firstLine[2]);
                System.out.println();

                // 返回客户端
                StringBuilder sendString = new StringBuilder();
                sendString.append("HTTP/1.1 200 OK\r\t");
                sendString.append("Content-Type:text/html;charset=" + localCharset + "\r\n");
                sendString.append("\r\n");

                sendString.append("显示报文");
                sendString.append("接收到请求报文是:
"); for (String s : requestMessage) { sendString.append(s + "
"); } sendString.append(""); buffer = ByteBuffer.wrap(sendString.toString().getBytes(localCharset)); sc.write(buffer); sc.close(); } } @Override public void run() { try { if (key.isAcceptable()) { handleAccept(); } if (key.isReadable()) { handleRead(); } } catch (IOException e) { e.printStackTrace(); } } } }

详解 Servlet #

Servlet 接口 #

//web.xml
initParam Demo

    contextConfigLocation
    application-context.xml


    DemoServlet
    com.excelib.DemoServlet
    
        contextConfigLocation
        demo-servlet.xml
    


// 获取
String contextLocation = getServletConfig().getServletContext().getInitParamter("contextConfigLocation");
String servletLocation = getServletConfig().getInitParameter("contextConfigLocation");
// ServletContext - 保存 Application 级的属性
getServletContext().setAttribute("contextCongfigLocation", "new path");

// 需要注意的是,这里设置的同名 Attribute 并不会覆盖 initParameter 中的参数值,它们是两套数据,互不干扰。
// ServletConfig 不可以设置属性。

GenericServlet #

public ServletContext getServletContext()
{
    ServletConfig sc = getServletConfig();
    if(sc == null)
    {
        throw new IllegalStateException(1Strings.getString("err.servlet_config_not_initialized"));
    }
    return sc.getServletContext();
}

public void init(ServletConfig config) throws ServletException
{
    this.config = config;
    this.init();
}
public void init() throws ServletException{} // 模版方法,在子类可以通过覆盖它来完成自己的初始化工作。
public void log(String msg)
{
    getServletContext().log(getServletName() + ": " + msg);
}
public void log(String message, Throwable t)
{
    getServletContext().log(getServletName() + ": " + message, t);
}

HttpServlet #

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
    String protocol = req.getProtocol();
    String msg = 1Strings.getString("http.method_get_not_supported");

    if(protocol.endsWith("1.1"))
    {
        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
    } else
    {
        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
    }
}

Tomcat 分析 #

Tomcat 的顶层结构及启动过程 #

Tomcat 的顶层结构 #

俯视 Spring MVC #

环境搭建 #


    java.servlet
    javax.servlet-api
    3.1.0
    provided


    org.springframework
    spring-webmvc
    4.1.5.RELEASE

Spring MVC 最简单的配置 #

在 web.xml 中配置 Servlet #


    let'sGo
    org.springframework.web.servlet.DispatherServlet
    



    let'sGo
    /



    index

创建 Spring MVC 的 xml 配置文件 #

// let'sGo-servlet.xml

     // 配置后,Spring MVC 会帮我们自动做一些注册组件之类的事
     // 扫描通过注册配置的类

    //  或者设置 context:include-filter 子标签来设置只扫描 @Controller 就可以
    
        
    

创建 Controller 和 View #

package com.excelib.controller;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class GoController
{
    private final Log logger = LogFactory.getLog(GoController.class);

    @RequestMapping(value = {"/"}, method = {RequestMethod.HEAD})
    public String head()
    {
        return "go.jsp";
    }

    @RequestMapping(value = {"/index", "/"}, method = {RequestMethod.GET})
    public String index(Model model) throws Exception
    {
        logger.info("===== processed by index======");
        model.addAttribute("msg", "Go Go Go!");
        return "go.jsp";
    }
}

关联 spring 源代码 #

创建 Spring MVC 之器 #

整体结构介绍 #

interface Aware
⇡⇡⇡
interface EnvironmentCapable
interface EnvironmentAware
interface ApplicationContextAware
↑
class HttpServletBean(implements HttpServlet,EnvironmentCapable, EnvironmentAware)
↑
class FrameworkServlet(extends HttpServletBean implements ApplicationContextAware)
↑
class DispatcherServlet(implements DispatcherServlet)

HttpServletBean #

initBeanWrapper(bw);
bw.setPropertyValues(pbs, true);

// 模版方法。子类初始化的入口方法
initServletBean();

FrameworkServlet #

this.webApplicationContext = initWebApplicationContext(); // 初始化 WebApplicationContext
initFrameworkServlet(); // 模版方法

DispatcherServlet #

// org.springframework.web.servlet.DispatcherSerlvlet
protected void onRefresh(ApplicationContext contexrt)
{
    initStrategies(context);
}

protected void initStrategies(ApplicationContext context)
{
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

// 分层的原因
// onRefresh 是用来刷新容器的
// initStrategies 用来初始化一些策略组件
// initStrategies 直接写到 onRefresh 中
//      如果在 onRefresh 中想要再添加别的功能,就会没有将其单独写一个方法出来的逻辑清晰。
//      单独将 initStrategies 写出来还可以被子类覆盖,使用新的模式进行初始化。
private void initLocaleResolver(ApplicationContext context)
{
    try
    {
        this.localeResolver = (LocaleResolver)context.getBean("localeResolver", LocaleResolver.class);

        if(logger.isDebugEnabled()) {...}
    }
    catch (NoSuchBeanDefinitionException ex)
    {
        // 使用默认策略
        this.localResolver = getDefaultStrategy(context, LocalResolver.class);
        if(logger.isDebugEnabled()) {...}
    }
}

getDefaultStrategy
    getDefaultStrategies
        ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
            defaultStrategies.getProperty(key);

            static {
                try {
                    ClassPathResource resource = new ClassPathResource("DispatcherServlet.properties", DispatcherServlet.class);
                    defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
                } catch (IOException var1) {
                    throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + var1.getMessage());
                }
            }

DispatcherServlet.properties // 存放 8 个组件

解析 spring 的 xml 文件中通过命名空间配置的标签。 #

// 解析配置的接口
NamespaceHandler
⇡⇡⇡
NamespaceHandlerSupport // 默认实现
SimplePropertyNamespaceHandler // 用于统一对通过 c: 配置的构造方法进行解析
SimpleConstructorNamespaceHandler // 用于统一对通过 p: 配置的参数进行解析
↑
MvcNamespaceHandler(extends NamespaceHandlerSupport)

NamespaceHandler #

+ init 用来初始化自己的 + parse 用于将配置的标签转换成 spring 所需要的 BeanDefinition + decorate 方法的作用是对所在 BeanDefinition 进行一些修改

NamespaceHandlerSupport #

MvcNamespaceHandler #

小结 #

Spring MVC 之用 #

HttpServletBean #

FrameworkServlet #

LocaleContext #

RequestAttributes #

// org.springframework.web.context.request.ServletRequestAttributes
public void setAttribute(String name, Object value, int scope) {
   if(scope == 0) {
       if(!this.isRequestActive()) {
           throw new IllegalStateException("Cannot set request attribute - request is not active anymore!");
       }

       this.request.setAttribute(name, value);
   } else {
       HttpSession session = this.getSession(true);
       this.sessionAttributesToUpdate.remove(name);
       session.setAttribute(name, value);
   }

}

LocaleContextHolder #