源码分析系列 | 从零开始写MVC框架
1. 前言
源码地址:https://github.com/Evan43789596/eshare-it-subject-learning.git
前段时间在网上无意中上参与了一节腾讯课堂的公开课,里面讲到了一些分析思路,感觉挺有意思,也学习到了别人的一些讲课技巧,正好自己也打算对过往知识网络做个整理回顾,计划后面开展一系列源码分析教程,本章先从一个入门简单的手写MVC框架入门,模仿springMVC一些基本原理带领大家通过自己实现MVC框架的过程了解到一些MVC框架的核心原理,从而在以后学习一些新的MVC框架或者使用springMVC过程中更从容。在本章学习过程中,大家可以暂时抛开springMVC的概念,通过实现过程去理解。
友情提示:
大家在一开始实现代码过程中,可以不必纠结太多设计模式、代码风格的问题,先按照基本思路实现功能,再优化
2. 为什么要自己手写框架
模仿优秀的开源框架可以加深我们对框架的理解,让我们更加深刻地理解其原理,从而在日后使用框架过程中遇到问题也可以快速定位解决,甚至能开发更优秀的框架来满足业务需求,而不仅仅只是停留在使用阶段
以车主和4S店修理员对话举个栗子:
以下是车主和4S店修理员的对话,车主是一个不了解汽车的小白,大家想象下他在修车过程中会发生什么事?
场景:是汽车电池没电,启动不了
 
 
如上图情景,假如车主自己对汽车结构和原理有一定了解的话,那他就可以对这次的维修费用项心里有底,不会随便被维修员忽悠,减少无谓的金钱损失。如果我们自己对框架有深入的了解,就可以对框架使用更得心应手,对使用的框架有更多的思考,而不仅仅是一个只会用工具的马畜。
3. 简单MVC框架设计思路
按照以往经验,框架应用一般会分为3个阶段: 
- 配置阶段 
- 初始化阶段 
- 运行阶段 
 

4. 课程目标
成功根据用户请求URL交给对应的contoller处理,并响应结果到浏览器 
 

5. 编码实战
5.1 配置阶段
这个阶段是完成框架启动或者运行时依赖的一些配置前期准备操作。
例如:
如果没有配置web.xml,tomcat容器启动的时候就不会拦截请求交给你MVC框架指定的servlet类上;
如果没有指定一定的扫描规则(注解/xml),MVC框架就不知道该怎么加载用户自定义的类或者反射调用哪些元素;
web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:javaee="http://java.sun.com/xml/ns/javaee"
    xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">
    <display-name>Eshare Web Application</display-name>
    <!-- mvcframework config start -->
    <servlet>
      <servlet-name>dispatcher</servlet-name>
      <servlet-class>com.eshare.framework.mvc.servlet.EsDispatcherServlet</servlet-class>
      <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:config.properties</param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
      <servlet-name>dispatcher</servlet-name>
      <url-pattern>*.do</url-pattern>
    </servlet-mapping>
    <!-- mvcframework config end -->
    <!-- welcome page -->
    <welcome-file-list>
      <welcome-file>/index.html</welcome-file>
    </welcome-file-list>
</web-app>- 第12行:配置自定义分发器用于处理用户请求分到到具体的处理类
- 第15行:配置配置文件的路径
- 第21行:配置请求拦截规则
config.properties
创建config.properties,指定框架加载的包名(配置文件是由框架使用者自己创建,这里为了演示方便提前创建好)
package-scan=用户自定义包名自定义注解
自定义注解用于标记哪些元素需要交给框架IOC管理或者需要框架去做一些列操作的,在本次Demo主要是定义以下几个注解:
- EsController 标识用作控制器的类,放在类上方
- EsService 标识用作具体业务处理服务类,放在类上方
- EsAutowired 标识需要注入的属性,放在属性上方
- EsRequestMapping 请求规则映射,放在控制器的类或者方法上方
- EsRequestParam 标识方法中自定义参数,放在处理方法参数左方
EsController
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EsController {
    String value() default "";
}
- 第1行:@Target({ElementType.TYPE})指定放在目标类、接口、枚举声明上
- 第2行:@Retention(RetentionPolicy.RUNTIME)指定作用范围是运行时
EsService
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EsService {
    String value() default "";
}- 第1行:@Target({ElementType.TYPE})指定放在目标类、接口、枚举声明上
- 第2行:@Retention(RetentionPolicy.RUNTIME)指定作用范围是运行时
EsAutowired
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EsAutowired {
    String value() default "";
}
- 第1行:@Target({ElementType.FIELD})指定放在目标属性字段声明上
- 第2行:@Retention(RetentionPolicy.RUNTIME)指定作用范围是运行时
EsRequestMapping
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EsRequestMapping {
    String value() default "";
}- 第1行:@Target({ElementType.TYPE})指定放在目标类、接口、枚举声明上
- 第2行:@Retention(RetentionPolicy.RUNTIME)指定作用范围是运行时
EsRequestParam
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EsRequestParam {
    String value() default "";
}- 第1行:@Target({ElementType.PARAMETER})指定放在目标参数声明上
- 第2行:@Retention(RetentionPolicy.RUNTIME)指定作用范围是运行时
5.2 初始化阶段
创建自定义EsDispatcherServlet
EsDispatcherServlet是继承HttpServlet
public class EsDispatcherServlet extends HttpServlet 重写HttpServlet的init(ServletConfig config),定义好我们接下来要实现的几个步骤,然后在针对每个方法一一填充逻辑,如下:
  @Override
    public void init(ServletConfig config) throws ServletException {
        try {
            //1.读取配置
            doLoadConfig(config);
            String packageName = configProperties.getProperty("package-scan");
            //2.扫描指定包下的类
            doScanClass(packageName);
            //3.对扫描出来的类实例化
            doInitializeInstance();
            //4.执行类依赖自动注入
            doAutowired();
            //5.配置url和handler映射关系handlerMapping
            doHandlerMapping();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }- 第5行:doLoadConfig方法用于读取用户自定义配置
- 第6行:根据配置中的package-scan提取用户指定扫描的包路径
- 第8行:doScanClass方法用于扫描用户指定包下的类,然后把要加载的类名存放下来
- 第10行:doInitializeInstance方法用于执行初始化实例,这个阶段就是IOC初始化阶段
- 第12行:doAutowired方法用户对用户加上该注解的属性,由MVC框架反射注入
- 第14行:doHandlerMapping方法用于配置url和handler映射关系,用于后续MVC处理用户请求映射到具体类的某个方法
doLoadConfig加载用户自定义配置
这里需要先定义一个全局的properties实例,用于加载文件后存放配置文件信息
/**
     * 配置
     */
    private Properties configProperties = new Properties();根据web.xml上的配置信息,去找到用户自定义配置
 /**
     * 加载配置
     *
     * @param config
     */
    private void doLoadConfig(ServletConfig config) throws IOException {
        String configFilePath = config.getInitParameter("contextConfigLocation");
        String configName = configFilePath.replace("classpath*:", "");
        InputStream in = this.getClass().getClassLoader().getResourceAsStream(configName);
        configProperties.load(in);
    }
- 第7行:从web.xml配置中,根据contextConfigLocation去找到文件具体路径值
- 第8行:这里是为了方便演示,直接把classpath相关的去掉,把classpath*:后的值作为加载路径
- 第9行:利用类加载器去加载指定路径下的文件流
doScanClass扫描指定包下的类
根据上一步获取的指定包路径,去扫描对应的类,并把要加载的类名存放起来,这里需要先定义一个集合存放类名
 /**
     * 需要加载的类列表
     */
    private List<String> classNames = new ArrayList<String>();接下来开始遍历指定包目录,递归扫描class文件
   private void doScanClass(String packageName) {
        URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.", "/"));
        File file = new File(url.getFile());
        for (File f : file.listFiles()) {
            if (f.isDirectory()) {
                doScanClass(packageName + "." + f.getName());
            } else {
                String className = packageName + "." + f.getName().replace(".class", "");
                //存放到类名集合
                classNames.add(className);
            }
        }
    }- 第1-2行:这里是对包名转换成字节码目录相对路径,例如com.eshare.demo转换成/com/eshare/demo,然后根据url去加载文件
- 第4-13行:遍历目录下的文件,遇到目录就以包名方式继续递归寻找,如果是文件就对文件名称转换成类全限定名,放到集合里
doInitializeInstance类实例化
这里对应的是spring里面IOC容器初始化阶段,对类实例化并存放到容器中,这里我们定义一个集合模拟IOC容器
 /**
     * 类全限定名与实例映射集合,模拟applicationContext容器
     */
    private Map<String, Object> applicationContext = new ConcurrentHashMap<String, Object>();接下来我们完成类实例化工作,原理很简单,其实就是利用反射创建类实例
 private void doInitializeInstance() {
        //如果不存在要加载的类,则直接返回
        if (classNames.isEmpty()) {
            return;
        }
        for (String className : classNames) {
            try {
                Class<?> clazz = Class.forName(className);
                //查看当前类字节码是否存在EsController、EsService注解
                if (clazz.isAnnotationPresent(EsController.class)) {
                    String beanName = lowerInitial(className);
                    applicationContext.put(beanName, clazz.newInstance());
                } else if (clazz.isAnnotationPresent(EsService.class)) {
                    EsService esService = clazz.getAnnotation(EsService.class);
                    //检查是否存在自定义名称
                    String beanName = esService.value().trim();
                    if (!"".equals(beanName.trim())) {
                        applicationContext.put(beanName, clazz.newInstance());
                        continue;
                    }
                    //没有自定义名称,则以接口名称作为key
                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (Class i : interfaces) {
                        applicationContext.put(i.getName(), clazz.newInstance());
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }- 第3-6行:先判断是否存需要实例化的类,不存在则直接返回
- 第11-13行:这里是对作为控制器的类进行实例化,这里默认对类名首字母转为小写作为KEY,存放在IOC容器中
- 第14-28行:这里是是针对业务服务类进行实例化,@EsCotroller和@EsService标注的类实例化有点不同,前者是控制器,后者是业务服务类,一般来说@EsService的类还会充当其他类的属性使用,因此这里的IOC容器中的key名会跟后面依赖注入的名称有一定联系,这里优先是使用用户自定义名称作为key,否则以接口名称与实例作为映射存放
上面还依赖到一个工具类方法lowerInitial,这里也顺便写下
 private String lowerInitial(String className) {
        char[] chars = className.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }- 第3行:这里使用acsii码方式进行转换,提升转换性能
doAutowired依赖注入
这一步原理也比较简单,就是利用反射调用目标类的setObject方法进行注入,主要私有成员变量要进行暴力破解,否则访问不了
  private void doAutowired() {
        if (applicationContext.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : applicationContext.entrySet()) {
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                //暴力破解权限
                field.setAccessible(true);
                //检查是否带有@Autowired注解
                if (field.isAnnotationPresent(EsAutowired.class)) {
                    EsAutowired esAutowired = field.getAnnotation(EsAutowired.class);
                    String beanName = esAutowired.value().trim();
                    if ("".equals(beanName)) {
                        beanName = field.getType().getName();
                    }
                    try {
                        field.set(entry.getValue(), applicationContext.get(beanName));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                        continue;
                    }
                }
            }
        }
    }- 第7行:获取所有声明的成员变量
- 第10行:对私有变量进行暴力破解,这样后面才可以对私有变量进行复制
- 第12-23行:对带有@Autowired注解的成员变量才进行注入,优先使用用户自定义的名称,否则使用成员变量名称作为key从IOC容器找到对应的类实力进行赋值
doHandlerMapping配置URL和具体Handler方法映射规则
这一步最为复杂,我们首先要想到在运行阶段MVC框架要根据用户请求URL怎样可以找到具体的对应方法,这时候我们应该是需要有一个URL匹配规则和method的映射关系,这样才可以所有URL和method对应起来,但是只有这两个已知条件并不够,用过反射调用方法都了解,反射调用具体方法时,需要的条件有参数和参数顺序、目标类、目标方法,我们需要有一个东西可以让URL和这些我们需要的条件装起来,提供给运行阶段使用,这时候我们可以创建一个Handler类去把我们需要反射用到的已知条件装起来。
简单整理下思路过程: 
 
接下来我们就先创建这个Handler类,用于存放URL匹配规则和其他已知条件
 class Handler {
        private Pattern pattern;
        private Object controller;
        private Method method;
        private Map<String, Integer> paramMapping;
        public Handler(Pattern pattern, Object controller, Method method, Map<String, Integer> paramMapping) {
            this.pattern = pattern;
            this.controller = controller;
            this.method = method;
            this.paramMapping = paramMapping;
        }
        public Pattern getPattern() {
            return pattern;
        }
        public void setPattern(Pattern pattern) {
            this.pattern = pattern;
        }
        public Object getController() {
            return controller;
        }
        public void setController(Object controller) {
            this.controller = controller;
        }
        public Method getMethod() {
            return method;
        }
        public void setMethod(Method method) {
            this.method = method;
        }
        public Map<String, Integer> getParamMapping() {
            return paramMapping;
        }
        public void setParamMapping(Map<String, Integer> paramMapping) {
            this.paramMapping = paramMapping;
        }
    }因此定义的控制器可能会有多个,这里需要用集合把handler存放起来
 /**
     * 处理器映射列表
     */
    private List<Handler> handlerMapping = new ArrayList<Handler>();接下来我们开始配置URL匹配规则和需要的已知条件的映射
/**
     * 执行处理类映射
     */
    private void doHandlerMapping() {
        if (applicationContext.isEmpty()) return;
        for (Map.Entry entry : applicationContext.entrySet()) {
            //先获取controller上的requestMapping值
            Class<?> clazz = entry.getValue().getClass();
            String baseUrl = "";
            //检查是否为controller
            if (clazz.isAnnotationPresent(EsController.class)) {
                if (clazz.isAnnotationPresent(EsRequestMapping.class)) {
                    EsRequestMapping esRequestMapping = clazz.getAnnotation(EsRequestMapping.class);
                    baseUrl = esRequestMapping.value();
                }
            }
            //获取当前类的所有方法
            Method[] method = clazz.getMethods();
            for (Method m : method) {
                //方法上是否存在EsRequestMapping注解
                if (!m.isAnnotationPresent(EsRequestMapping.class)) {
                    continue;
                }
                EsRequestMapping esRequestMapping = m.getAnnotation(EsRequestMapping.class);
                String customRegex = ("/" + baseUrl + esRequestMapping.value()).replaceAll("/+", "/");
                String reqRegex = customRegex.replaceAll("\\*", ".*");
                Map<String, Integer> paramMapping = new HashMap<String, Integer>();
                //获取方法中的参数,对有自定义注解的参数将其名称和顺序映射放到集合中
                Annotation[][] paramAnnotations = m.getParameterAnnotations();
                for (int i = 0; i < paramAnnotations.length; i++) {
                    for (Annotation a : paramAnnotations[i]) {
                        //如果注解是EsRequestParam类型
                        if (a instanceof EsRequestParam) {
                            String paramName = ((EsRequestParam) a).value();
                            if (!"".equals(paramName)) {
                                paramMapping.put(paramName, i);
                            }
                        }
                    }
                }
                //处理非自定义注解参数,request,response
                Class<?>[] types = m.getParameterTypes();
                for (int i = 0; i < types.length; i++) {
                    Class<?> type = types[i];
                    if (type == HttpServletRequest.class || type == HttpServletResponse.class) {
                        String paramName = type.getName();
                        paramMapping.put(paramName, i);
                    }
                }
                //创建handler
                handlerMapping.add(new Handler(Pattern.compile(reqRegex), entry.getValue(), m, paramMapping));
                System.out.println("Mapping " + reqRegex + " " + m);
            }
        }
    }- 第5行:IOC容器如果没有任何元素,则直接返回,不作处理
- 第6行:开始对IOC容器里面的元素进行遍历
- 第8-16行:先获取controller上@EsRequestMapping注解的自定义的URL请求路径,作为baseURL
- 第18-30行:获取当前类的所有方法,然后根据方法上的@EsRequestMapping注解定义的URL请求路径与baseURL进行拼接成一个完整的方法请求路径,然后对URL路径转换成所有请求URL的通用正则表达式,/xxx/xxx.*
- 第35-47:遍历方法中的所有带有自定义注解的参数,获取@EsRequestParam注解中定义的参数名,参数名和对应的参数顺序存放到paramMapping
- 第48-56:遍历所有非自定义注解的参数,如reqeust,respon原生自带的参数取其类名作为key与参数顺序映射存放到paramMapping
以上所有MVC框架初始化阶段的工作已经完成了
5.3 运行阶段
运行阶段主要就是通过用户URL请求路径匹配到对应的目标控制器中的方法,利用反射将参数传到方法进行调用,这里我们先重写doPost和doGet方法
 @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        boolean isSuccess = processRequest(req, resp);
        if (!isSuccess) {
            resp.getWriter().write("404 Page Not Found !");
        }
    }- 第8-10行:把请求传到我们自定义的执行方法中,如果执行失败则模拟一个异常输出到浏览器
5.3.1 processRequest处理用户请求
/**
     * 处理请求映射
     *
     * @param req
     * @param resp
     * @return
     */
    private boolean processRequest(HttpServletRequest req, HttpServletResponse resp) {
        if (handlerMapping.isEmpty()) {
            return false;
        }
        try {
            String url = req.getRequestURI();
            String contextPath = req.getContextPath();
            //去掉Url中的上下文,保留请求资源路径
            url = url.replace(contextPath, "").replaceAll("/+", "/");
            //遍历handlerMapping,查找匹配的handler去处理
            for (Handler handler : handlerMapping) {
                Matcher matcher = handler.getPattern().matcher(url);
                if (!matcher.matches()) {
                    continue;
                }
                Method targetMethod = handler.getMethod();
                //获取方法参数类型
                Class<?>[] paramTypes = targetMethod.getParameterTypes();
                Object[] targetParamsValues = new Object[paramTypes.length];
                //对于用户自定义参数,从请求参数获取用户传参
                Map<String, String[]> paramMap = req.getParameterMap();
                for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
                    //转换数组格式为字符串,去掉"[]",注意存在多个参数时要把单词变为“,”分割
                    String value = Arrays.toString(entry.getValue()).replaceAll("\\[|\\]", "").replaceAll("\\s", ",");
                    //假如参数集合不包含当前请求参数,直接跳过
                    if (!handler.getParamMapping().containsKey(entry.getKey())) {
                        continue;
                    }
                    //取出当前传入参数在方法里的索引位置
                    Integer index = handler.getParamMapping().get(entry.getKey());
                    //进行类型转换并复制
                    targetParamsValues[index] = castStringToTargetType(value, paramTypes[index]);
                }
                //对于非用户自定义参数,如容器自带request和response,获取它们在集合中的索引,直接注入
                Integer reqIndex = handler.getParamMapping().get(HttpServletRequest.class.getName());
                targetParamsValues[reqIndex] = req;
                Integer respIndex = handler.getParamMapping().get(HttpServletResponse.class.getName());
                targetParamsValues[respIndex] = resp;
                resp.getWriter().write((String) targetMethod.invoke(handler.getController(), targetParamsValues));
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }- 第10-11行:如果用户没有定义任何作为控制器的类,则直接返回
- 第14-17行:获取请求的url,这里注意的是用户请求URL会带有项目上下文路径,而我们handler匹配的url规则里面是没有上下文路径的,因此要把它去掉,如:用户定义的它项目名是test,那么他请求路径就会变成localhost:8080/test/hello/sayHello.do,这里会截断/test这部分,只保留/hello/sayHello.do
- 第20-23行:利用现有的handler中的pattern去匹配请求URL是否符合规则,不符合则直接获取下一个handler
- 第24-30行:获取匹配到对应的method,声明后面调用方法需要用到的一些已知条件
- 第33-51行:遍历请求的参数集合,根据用户的请求参数类型从之前存放好的参数名称与参数顺序集合中匹配,获取对应的参数顺序,然后一个一个将请求参数按顺序填充到targetParamsValues数组中,用作反射调用方法的传参,这里执行完直接调用resp.getWriter().write输出结果到浏览器。第35行要注意的是,因为一个传参可能是一个可变数组会有多个参数[arg0][arg1][arg2],所以这里把数据转换成字符串再进行格式转换为arg0,arg1,arg2。第43行castStringToTargetType会对传参与目标方法的参数类型进行转换,因为这里获取的请求参数都是字符串类型,需要转为目标方法的参数类型才能正常调用。
以上已经完成MVC框架的开发了,接下来我们创建一个demo去测试下框架是否正常运行
6. 框架测试
配置config
配置扫描包名是com.eshare.demo
package-scan=com.eshare.demo创建控制器Controller类
/**
 * hello控制器
 * Created by liangyh on 2018/6/20.
 */
@EsController
@EsRequestMapping("/hello")
public class HelloController {
    @EsAutowired
    private HelloService helloService;
    @EsRequestMapping("/sayHello.do")
    public String sayHello(HttpServletRequest request, HttpServletResponse response,
                           @EsRequestParam("message") String message){
        return helloService.sayHello(message);
    }
}创建业务处理Service类
/**
 * Hello业务处理类接口
 * Created by liangyh on 2018/6/23.
 */
public interface HelloService {
    public String sayHello(String message);
}创建业务处理Service接口实现类
/**
 * Created by liangyh on 2018/6/23.
 */
@EsService
public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String message) {
        return "hello :"+message;
    }
}浏览器输入URL测试
在浏览器输入http://localhost:8080/hello/sayHello.do?message=springMVC,浏览器显示如下: 
源码分析系列 | 从零开始写MVC框架的更多相关文章
- MyBatis 源码分析系列文章导读
		1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ... 
- Spring mvc源码分析系列--前言
		Spring mvc源码分析系列--前言 前言 距离上次写文章已经过去接近两个月了,Spring mvc系列其实一直都想写,但是却不知道如何下笔,原因有如下几点: 现在项目开发前后端分离的趋势不可阻挡 ... 
- Spring mvc源码分析系列--Servlet的前世今生
		Spring mvc源码分析系列--Servlet的前世今生 概述 上一篇文章Spring mvc源码分析系列--前言挖了坑,但是由于最近需求繁忙,一直没有时间填坑.今天暂且来填一个小坑,这篇文章我们 ... 
- Spring AOP 源码分析系列文章导读
		1. 简介 前一段时间,我学习了 Spring IOC 容器方面的源码,并写了数篇文章对此进行讲解.在写完 Spring IOC 容器源码分析系列文章中的最后一篇后,没敢懈怠,趁热打铁,花了3天时间阅 ... 
- Spring IOC 容器源码分析系列文章导读
		1. 简介 Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本.经过十几年的迭代,现在的 Spring 框架已经非常成熟了.Spring ... 
- Thinkphp源码分析系列–开篇
		目前国内比较流行的php框架由thinkphp,yii,Zend Framework,CodeIgniter等.一直觉得自己在php方面还是一个小学生,只会用别人的框架,自己也没有写过,当然不是自己不 ... 
- Bootstrap源码分析系列之初始化和依赖项
		在上一节中我们介绍了Bootstrap整体架构,本节我们将介绍Bootstrap框架第二部分初始化及依赖项,这部分内容位于源码的第8~885行,打开源码这部分内容似乎也不是很难理解.但是请站在一个开发 ... 
- netty源码分析系列文章
		netty源码分析系列文章 nettynetty源码阅读netty源码分析 想在年终之际将对netty研究的笔记记录下来,先看netty3,然后有时间了再写netty4的,希望对大家有所帮助,这个是 ... 
- jQuery源码分析系列(转载来源Aaron.)
		声明:非本文原创文章,转载来源原文链接Aaron. 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAa ... 
随机推荐
- DOCKER学习_005:Flannel网络配置
			一 简介 Flannel是一种基于overlay网络的跨主机容器网络解决方案,也就是将TCP数据包封装在另一种网络包里面进行路由转发和通信, Flannel是CoreOS开发,专门用于docker多机 ... 
- DEVOPS技术实践_10:安装部署Artifactory
			需要一种机制去存储所有的二进制代码(build,packages,third-party plugins等)到类似于版本控制系统的系统. 像Git,SVN存储代码,它们存储的往往是源代码,不是二进制文 ... 
- 【一起学源码-微服务】Nexflix Eureka 源码十一:EurekaServer自我保护机制竟然有这么多Bug?
			前言 前情回顾 上一讲主要讲了服务下线,已经注册中心自动感知宕机的服务. 其实上一讲已经包含了很多EurekaServer自我保护的代码,其中还发现了1.7.x(1.9.x)包含的一些bug,但这些问 ... 
- CountDownLanuch,CyclicBarrier,Semaphore,Lock
			一.你在项目中用过CountDownLanuch,CyclicBarrier,Semaphore吗? 1.CountDownLanuch是一个同步的工具类,它允许一个或多个线程一直等待,直到其他线程执 ... 
- 极光推送SDK通过泰尔终端实验室检测,符合统一推送接口标准
			1月7日,中国深圳--国内领先的开发者服务提供商极光(Aurora Mobile, NASDAQ:JG)宣布其旗下产品极光推送SDK通过中国信息通信研究院泰尔终端实验室的检测,其性能和接口标准符合统一 ... 
- Go中的Package和Module分析
			Package 所谓package(包)其实就是代码的一种组织管理方式,代码多了就需要放入文件,文件多了就需要归类放入文件夹,就好比我们在给电脑装软件时会进行归类安装,其实也是有意无意对电脑软件安装的 ... 
- 从N个元素中抽取K个不重复元素(抽奖问题)
			核心就是 把N数组抽中的元素给K数组 把N数组最后一位给N数组被抽走的那一位(这时候N数组最后一位元素和被抽走的那位元素值相等) 把N数组长度减一,去除最后一位 
- 快速掌握—HTML快速实现自定义Input开关
			HTML <input id="customSwitch" type="checkbox" /> <label for="custo ... 
- SpringBoot系列之集成Dubbo的方式
			SpringBoot系列之集成Dubbo的方式 本博客介绍Springboot框架集成Dubbo实现微服务的3种常用方式,对于Dubbo知识不是很熟悉的,请先学习我上一篇博客:SpringBoot系列 ... 
- 除了闹过腥风血雨的fastjson,你还知道哪些Java解析JSON的利器?
			昨天下午 5 点 10 分左右,我解决掉了最后一个 bug,轻舒一口气,准备关机下班.可这个时候,老板朝我走来,脸上挂着神秘的微笑,我就知道他不怀好意.果不其然,他扔给了我一个新的需求,要我在 Jav ... 
