前言

在上一篇《Spring学习之——手写Spring源码(V1.0)》中,我实现了一个Mini版本的Spring框架,在这几天,博主又看了不少关于Spring源码解析的视频,受益匪浅,也对Spring的各组件有了自己的理解和认识,于是乎,在空闲时间把之前手写Spring的代码重构了一遍,遵循了单一职责的原则,使结构更清晰,并且实现了AOP,这次还是只引用一个servlet包,其他全部手写实现。

全部源码照旧放在文章末尾~

开发工具

环境:jdk8 + IDEA + maven

jar包:javax.servlet-2.5

项目结构

具体实现

配置文件

web.xml 与之前一样  并无改变

application.properties   增加了html页面路径和AOP的相关配置

  1. #扫描路径#
  2. scanPackage=com.wqfrw
  3.  
  4. #模板引擎路径#
  5. templateRoot=template
  6.  
  7. #切面表达式#
  8. pointCut=public .* com.wqfrw.service.impl..*ServiceImpl..*(.*)
  9. #切面类#
  10. aspectClass=com.wqfrw.aspect.LogAspect
  11. #切面前置通知#
  12. aspectBefore=before
  13. #切面后置通知#
  14. aspectAfter=after
  15. #切面异常通知#
  16. aspectAfterThrowing=afterThrowing
  17. #切面异常类型#
  18. aspectAfterThrowingName=java.lang.Exception

IOC与DI实现

1.在DispatcherServlet的init方法中初始化ApplicationContent;

2.ApplicationContent是Spring容器的主入口,通过创建BeanDefintionReader对象加载配置文件;

3.在BeanDefintionReader中将扫描到的类解析成BeanDefintion返回;

4.ApplicationContent中通过BeanDefintionMap这个缓存来关联BeanName与BeanDefintion对象之间的关系;

5.通过getBean方法,进行Bean的创建并封装为BeanWrapper对象,进行依赖注入,缓存到IoC容器中

  1.   /**
  2. * 功能描述: 初始化MyApplicationContext
  3. *
  4. * @创建人: 我恰芙蓉王
  5. * @创建时间: 2020年08月03日 18:54:01
  6. * @param configLocations
  7. * @return:
  8. **/
  9. public MyApplicationContext(String... configLocations) {
  10. this.configLocations = configLocations;
  11.  
  12. try {
  13. //1.读取配置文件并解析BeanDefinition对象
  14. beanDefinitionReader = new MyBeanDefinitionReader(configLocations);
  15. List<MyBeanDefinition> beanDefinitionList = beanDefinitionReader.loadBeanDefinitions();
  16.  
  17. //2.将解析后的BeanDefinition对象注册到beanDefinitionMap中
  18. doRegisterBeanDefinition(beanDefinitionList);
  19.  
  20. //3.触发创建对象的动作,调用getBean()方法(Spring默认是延时加载)
  21. doCreateBean();
  22. } catch (Exception e) {
  23. e.printStackTrace();
  24. }
  25. }
  1. /**
  2. * 功能描述: 真正触发IoC和DI的动作 1.创建Bean 2.依赖注入
  3. *
  4. * @param beanName
  5. * @创建人: 我恰芙蓉王
  6. * @创建时间: 2020年08月03日 19:48:58
  7. * @return: java.lang.Object
  8. **/
  9. public Object getBean(String beanName) {
  10. //============ 创建实例 ============
  11.  
  12. //1.获取配置信息,只要拿到beanDefinition对象即可
  13. MyBeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
  14.  
  15. //用反射创建实例 这个实例有可能是代理对象 也有可能是原生对象 封装成BeanWrapper统一处理
  16. Object instance = instantiateBean(beanName, beanDefinition);
  17. MyBeanWrapper beanWrapper = new MyBeanWrapper(instance);
  18.  
  19. factoryBeanInstanceCache.put(beanName, beanWrapper);
  20.  
  21. //============ 依赖注入 ============
  22. populateBean(beanName, beanDefinition, beanWrapper);
  23.  
  24. return beanWrapper.getWrapperInstance();
  25. }
  1.   /**
  2. * 功能描述: 依赖注入
  3. *
  4. * @param beanName
  5. * @param beanDefinition
  6. * @param beanWrapper
  7. * @创建人: 我恰芙蓉王
  8. * @创建时间: 2020年08月03日 20:09:01
  9. * @return: void
  10. **/
  11. private void populateBean(String beanName, MyBeanDefinition beanDefinition, MyBeanWrapper beanWrapper) {
  12. Object instance = beanWrapper.getWrapperInstance();
  13. Class<?> clazz = beanWrapper.getWrapperClass();
  14.  
  15. //只有加了注解的类才需要依赖注入
  16. if (!(clazz.isAnnotationPresent(MyController.class) || clazz.isAnnotationPresent(MyService.class))) {
  17. return;
  18. }
  19.  
  20. //拿到bean所有的字段 包括private、public、protected、default
  21. for (Field field : clazz.getDeclaredFields()) {
  22.  
  23. //如果没加MyAutowired注解的属性则直接跳过
  24. if (!field.isAnnotationPresent(MyAutowired.class)) {
  25. continue;
  26. }
  27.  
  28. MyAutowired annotation = field.getAnnotation(MyAutowired.class);
  29. String autowiredBeanName = annotation.value().trim();
  30. if ("".equals(autowiredBeanName)) {
  31. autowiredBeanName = field.getType().getName();
  32. }
  33. //强制访问
  34. field.setAccessible(true);
  35. try {
  36. if (factoryBeanInstanceCache.get(autowiredBeanName) == null) { continue; }
  37. //赋值
  38. field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance());
  39. } catch (IllegalAccessException e) {
  40. e.printStackTrace();
  41. }
  42. }
  43. }

MVC实现

1.在DispatcherServlet的init方法中调用initStrategies方法初始化九大核心组件;

2.通过循环BeanDefintionMap拿到每个接口的url、实例对象、对应方法封装成一个HandlerMapping对象的集合,并建立HandlerMapping与HandlerAdapter(参数适配器)的关联;

3.初始化ViewResolver(视图解析器),解析配置文件中模板文件路径(即html文件的路径,其作用类似于BeanDefintionReader);

4.在运行阶段,调用doDispatch方法,根据请求的url找到对应的HandlerMapping;

5.在HandlerMapping对应的HandlerAdapter中,调用handle方法,进行参数动态赋值,反射调用接口方法,拿到返回值与返回页面封装成一个MyModelAndView对象返回;

6.通过ViewResolver拿到View(模板页面文件),在View中通过render方法,通过正则将返回值与页面取值符号进行适配替换,渲染成html页面返回

  1.   /**
  2. * 功能描述: 初始化核心组件 在Spring中有九大核心组件,这里只实现三种
  3. *
  4. * @param context
  5. * @创建人: 我恰芙蓉王
  6. * @创建时间: 2020年08月04日 11:51:55
  7. * @return: void
  8. **/
  9. protected void initStrategies(MyApplicationContext context) {
  10. //多文件上传组件
  11. //initMultipartResolver(context);
  12. //初始化本地语言环境
  13. //initLocaleResolver(context);
  14. //初始化模板处理器
  15. //initThemeResolver(context);
  16. //初始化请求分发处理器
  17. initHandlerMappings(context);
  18. //初始化参数适配器
  19. initHandlerAdapters(context);
  20. //初始化异常拦截器
  21. //initHandlerExceptionResolvers(context);
  22. //初始化视图预处理器
  23. //initRequestToViewNameTranslator(context);
  24. //初始化视图转换器
  25. initViewResolvers(context);
  26. //缓存管理器(值栈)
  27. //initFlashMapManager(context);
  28. }
  1.   /**
  2. * 功能描述: 进行参数适配
  3. *
  4. * @创建人: 我恰芙蓉王
  5. * @创建时间: 2020年08月05日 19:41:38
  6. * @param req
  7. * @param resp
  8. * @param mappedHandler
  9. * @return: com.framework.webmvc.servlet.MyModelAndView
  10. **/
  11. public MyModelAndView handle(HttpServletRequest req, HttpServletResponse resp, MyHandlerMapping mappedHandler) throws Exception {
  12.  
  13. //保存参数的名称和位置
  14. Map<String, Integer> paramIndexMapping = new HashMap<>();
  15.  
  16. //获取这个方法所有形参的注解 因一个参数可以添加多个注解 所以是一个二维数组
  17. Annotation[][] pa = mappedHandler.getMethod().getParameterAnnotations();
  18.  
  19. /**
  20. * 获取加了MyRequestParam注解的参数名和位置 放入到paramIndexMapping中
  21. */
  22. for (int i = 0; i < pa.length; i++) {
  23. for (Annotation annotation : pa[i]) {
  24. if (!(annotation instanceof MyRequestParam)) {
  25. continue;
  26. }
  27. String paramName = ((MyRequestParam) annotation).value();
  28. if (!"".equals(paramName.trim())) {
  29. paramIndexMapping.put(paramName, i);
  30. }
  31. }
  32. }
  33.  
  34. //方法的形参列表
  35. Class<?>[] parameterTypes = mappedHandler.getMethod().getParameterTypes();
  36.  
  37. /**
  38. * 获取request和response的位置(如果有的话) 放入到paramIndexMapping中
  39. */
  40. for (int i = 0; i < parameterTypes.length; i++) {
  41. Class<?> parameterType = parameterTypes[i];
  42. if (parameterType == HttpServletRequest.class || parameterType == HttpServletResponse.class) {
  43. paramIndexMapping.put(parameterType.getName(), i);
  44. }
  45. }
  46.  
  47. //拿到一个请求所有传入的实际实参 因为一个url上可以多个相同的name,所以此Map的结构为一个name对应一个value[]
  48. //例如:request中的参数t1=1&t1=2&t2=3形成的map结构:
  49. //key=t1;value[0]=1,value[1]=2
  50. //key=t2;value[0]=3
  51. Map<String, String[]> paramsMap = req.getParameterMap();
  52.  
  53. //自定义初始实参列表(反射调用Controller方法时使用)
  54. Object[] paramValues = new Object[parameterTypes.length];
  55.  
  56. /**
  57. * 从paramIndexMapping中取出参数名与位置 动态赋值
  58. */
  59. for (Map.Entry<String, String[]> entry : paramsMap.entrySet()) {
  60. //拿到请求传入的实参
  61. String value = entry.getValue()[0];
  62.  
  63. //如果包含url参数上的key 则动态转型赋值
  64. if (paramIndexMapping.containsKey(entry.getKey())) {
  65. //获取这个实参的位置
  66. int index = paramIndexMapping.get(entry.getKey());
  67. //动态转型并赋值
  68. paramValues[index] = caseStringValue(value, parameterTypes[index]);
  69. }
  70. }
  71.  
  72. /**
  73. * request和response单独赋值
  74. */
  75. if (paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
  76. int index = paramIndexMapping.get(HttpServletRequest.class.getName());
  77. paramValues[index] = req;
  78. }
  79. if (paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
  80. int index = paramIndexMapping.get(HttpServletResponse.class.getName());
  81. paramValues[index] = resp;
  82. }
  83.  
  84. //方法调用 拿到返回结果
  85. Object result = mappedHandler.getMethod().invoke(mappedHandler.getController(), paramValues);
  86. if (result == null || result instanceof Void) {
  87. return null;
  88. } else if (mappedHandler.getMethod().getReturnType() == MyModelAndView.class) {
  89. return (MyModelAndView) result;
  90. }
  91. return null;
  92. }
  93.  
  94. /**
  95. * 功能描述: 动态转型
  96. *
  97. * @param value String类型的value
  98. * @param clazz 实际对象的class
  99. * @创建人: 我恰芙蓉王
  100. * @创建时间: 2020年08月04日 16:34:40
  101. * @return: java.lang.Object 实际对象的实例
  102. **/
  103. private Object caseStringValue(String value, Class<?> clazz) throws Exception {
  104. //通过class对象获取一个入参为String的构造方法 没有此方法则抛出异常
  105. Constructor constructor = clazz.getConstructor(new Class[]{String.class});
  106. //通过构造方法new一个实例返回
  107. return constructor.newInstance(value);
  108. }
  1. /**
  2. * 功能描述: 对页面内容进行渲染
  3. *
  4. * @创建人: 我恰芙蓉王
  5. * @创建时间: 2020年08月04日 17:54:40
  6. * @param model
  7. * @param req
  8. * @param resp
  9. * @return: void
  10. **/
  11. public void render(Map<String, ?> model, HttpServletRequest req, HttpServletResponse resp) throws Exception {
  12. StringBuilder sb = new StringBuilder();
  13. //只读模式 读取文件
  14. RandomAccessFile ra = new RandomAccessFile(this.viewFile, "r");
  15.  
  16. String line = null;
  17. while ((line = ra.readLine()) != null) {
  18. line = new String(line.getBytes("ISO-8859-1"), "utf-8");
  19.  
  20. //%{name}
  21. Pattern pattern = Pattern.compile("%\\{[^\\}]+\\}", Pattern.CASE_INSENSITIVE);
  22. Matcher matcher = pattern.matcher(line);
  23.  
  24. while (matcher.find()) {
  25. String paramName = matcher.group();
  26.  
  27. paramName = paramName.replaceAll("%\\{|\\}", "");
  28. Object paramValue = model.get(paramName);
  29. line = matcher.replaceFirst(makeStringForRegExp(paramValue.toString()));
  30. matcher = pattern.matcher(line);
  31. }
  32. sb.append(line);
  33. }
  34.  
  35. resp.setCharacterEncoding("utf-8");
  36. resp.getWriter().write(sb.toString());
  37. }

html页面

404.html

  1. <!DOCTYPE html>
  2. <html lang="zh-cn">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>页面没有找到</title>
  6. </head>
  7. <body>
  8. <font size="25" color="red">Exception Code : 404 Not Found</font>
  9. <br><br><br>
  10. @我恰芙蓉王
  11. </body>
  12. </html>

500.html

  1. <!DOCTYPE html>
  2. <html lang="zh-cn">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>服务器崩溃</title>
  6. </head>
  7. <body>
  8. <font size="25" color="red">Exception Code : 500 <br/> 服务器崩溃了~</font>
  9. <br/>
  10. <br/>
  11. <b>Message:%{message}</b>
  12. <br/>
  13. <b>StackTrace:%{stackTrace}</b>
  14. <br/>
  15. <br><br><br>
  16. @我恰芙蓉王
  17. </body>
  18. </html>

index.html

  1. <!DOCTYPE html>
  2. <html lang="zh-cn">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>自定义SpringMVC模板引擎Demo</title>
  6. </head>
  7. <center>
  8. <h1>大家好,我是%{name}</h1>
  9. <h2>我爱%{food}</h2>
  10. <font color="red">
  11. <h2>时间:%{date}</h2>
  12. </font>
  13. <br><br><br>
  14. @我恰芙蓉王
  15. </center>
  16. </html>

测试接口调用返回页面

404.html  接口未找到

500.html  服务器错误

index.html   正常返回页面

AOP实现

1.参照IOC与DI实现第五点,在对象实例化之后,依赖注入之前,将配置文件中AOP的配置解析至AopConfig中;

2.通过配置的pointCut参数,正则匹配此实例对象的类名与方法名,如果匹配上,将配置的三个通知方法(Advice)与此方法建立联系,生成一个  Map<Method, Map<String, MyAdvice>> methodCache  的缓存;

3.将原生对象、原生对象class、原生对象方法与通知方法的映射关系封装成AdviceSupport对象;

4.如果需要代理,则使用JdkDynamicAopProxy中getProxy方法,获得一个此原生对象的代理对象,并将原生对象覆盖;

5.JdkDynamicAopProxy实现了InvocationHandler接口(使用JDK的动态代理),重写invoke方法,在此方法中执行切面方法与原生对象方法。

  1.   /**
  2. * 功能描述: 反射实例化对象
  3. *
  4. * @param beanName
  5. * @param beanDefinition
  6. * @创建人: 我恰芙蓉王
  7. * @创建时间: 2020年08月03日 20:08:50
  8. * @return: java.lang.Object
  9. **/
  10. private Object instantiateBean(String beanName, MyBeanDefinition beanDefinition) {
  11. String className = beanDefinition.getBeanClassName();
  12.  
  13. Object instance = null;
  14. try {
  15. Class<?> clazz = Class.forName(className);
  16. instance = clazz.newInstance();
  17.  
  18. /**
  19. * ===========接入AOP begin===========
  20. */
  21. MyAdviceSupport support = instantiateAopConfig(beanDefinition);
  22. support.setTargetClass(clazz);
  23. support.setTarget(instance);
  24. //如果需要代理 则用代理对象覆盖目标对象
  25. if (support.pointCutMatch()) {
  26. instance = new MyJdkDynamicAopProxy(support).getProxy();
  27. }
  28. /**
  29. * ===========接入AOP end===========
  30. */
  31.  
  32. factoryBeanObjectCache.put(beanName, instance);
  33. } catch (Exception e) {
  34. e.printStackTrace();
  35. }
  36. return instance;
  37. }
  1.   /**
  2. * 功能描述: 解析配置 pointCut
  3. *
  4. * @param
  5. * @创建人: 我恰芙蓉王
  6. * @创建时间: 2020年08月05日 11:20:21
  7. * @return: void
  8. **/
  9. private void parse() {
  10. String pointCut = aopConfig.getPointCut()
  11. .replaceAll("\\.", "\\\\.")
  12. .replaceAll("\\\\.\\*", ".*")
  13. .replaceAll("\\(", "\\\\(")
  14. .replaceAll("\\)", "\\\\)");
  15.  
  16. //public .*.com.wqfrw.service..*impl..*(.*)
  17. String pointCutForClassRegex = pointCut.substring(0, pointCut.lastIndexOf("\\(") - 4);
  18. this.pointCutClassPattern = Pattern.compile(pointCutForClassRegex.substring(pointCutForClassRegex.lastIndexOf(" ") + 1));
  19.  
  20. methodCache = new HashMap<>();
  21. //匹配方法的正则
  22. Pattern pointCutPattern = Pattern.compile(pointCut);
  23.  
  24. //1.对回调通知进行缓存
  25. Map<String, Method> aspectMethods = new HashMap<>();
  26. try {
  27. //拿到切面类的class com.wqfrw.aspect.LogAspect
  28. Class<?> aspectClass = Class.forName(this.aopConfig.getAspectClass());
  29. //将切面类的通知方法缓存到aspectMethods
  30. Stream.of(aspectClass.getMethods()).forEach(v -> aspectMethods.put(v.getName(), v));
  31.  
  32. //2.扫描目标类的方法,去循环匹配
  33. for (Method method : targetClass.getMethods()) {
  34. String methodString = method.toString();
  35. //如果目标方法有抛出异常 则截取
  36. if (methodString.contains("throws")) {
  37. methodString = methodString.substring(0, methodString.lastIndexOf("throws")).trim();
  38. }
  39. /**
  40. * 匹配目标类方法 如果匹配上,就将缓存好的通知与它建立联系 如果没匹配上,则忽略
  41. */
  42. Matcher matcher = pointCutPattern.matcher(methodString);
  43. if (matcher.matches()) {
  44. Map<String, MyAdvice> adviceMap = new HashMap<>();
  45. //前置通知
  46. if (!(null == aopConfig.getAspectBefore() || "".equals(aopConfig.getAspectBefore()))) {
  47. adviceMap.put("before", new MyAdvice(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectBefore())));
  48. }
  49.  
  50. //后置通知
  51. if (!(null == aopConfig.getAspectAfter() || "".equals(aopConfig.getAspectAfter()))) {
  52. adviceMap.put("after", new MyAdvice(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectAfter())));
  53. }
  54.  
  55. //异常通知
  56. if (!(null == aopConfig.getAspectAfterThrowing() || "".equals(aopConfig.getAspectAfterThrowing()))) {
  57. MyAdvice advice = new MyAdvice(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectAfterThrowing()));
  58. advice.setThrowingName(aopConfig.getAspectAfterThrowingName());
  59. adviceMap.put("afterThrowing", advice);
  60. }
  61. //建立关联
  62. methodCache.put(method, adviceMap);
  63. }
  64. }
  65. } catch (Exception e) {
  66. e.printStackTrace();
  67. }
  68. }
  1.   /**
  2. * 功能描述: 返回一个代理对象
  3. *
  4. * @创建人: 我恰芙蓉王
  5. * @创建时间: 2020年08月05日 14:17:22
  6. * @param
  7. * @return: java.lang.Object
  8. **/
  9. public Object getProxy() {
  10. return Proxy.newProxyInstance(this.getClass().getClassLoader(), this.support.getTargetClass().getInterfaces(), this);
  11. }
  12.  
  13. /**
  14. * 功能描述: 重写invoke
  15. *
  16. * @创建人: 我恰芙蓉王
  17. * @创建时间: 2020年08月05日 20:29:19
  18. * @param proxy
  19. * @param method
  20. * @param args
  21. * @return: java.lang.Object
  22. **/
  23. @Override
  24. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  25.  
  26. Map<String, MyAdvice> advices = support.getAdvices(method, support.getTargetClass());
  27.  
  28. Object result = null;
  29. try {
  30. //调用前置通知
  31. invokeAdvice(advices.get("before"));
  32.  
  33. //执行原生目标方法
  34. result = method.invoke(support.getTarget(), args);
  35.  
  36. //调用后置通知
  37. invokeAdvice(advices.get("after"));
  38. } catch (Exception e) {
  39. //调用异常通知
  40. invokeAdvice(advices.get("afterThrowing"));
  41. throw e;
  42. }
  43.  
  44. return result;
  45. }
  46.  
  47. /**
  48. * 功能描述: 执行切面方法
  49. *
  50. * @创建人: 我恰芙蓉王
  51. * @创建时间: 2020年08月05日 11:09:32
  52. * @param advice
  53. * @return: void
  54. **/
  55. private void invokeAdvice(MyAdvice advice) {
  56. try {
  57. advice.getAdviceMethod().invoke(advice.getAspect());
  58. } catch (IllegalAccessException e) {
  59. e.printStackTrace();
  60. } catch (InvocationTargetException e) {
  61. e.printStackTrace();
  62. }
  63. }
  1. /**
  2. * @ClassName LogAspect
  3. * @Description TODO(切面类)
  4. * @Author 我恰芙蓉王
  5. * @Date 2020年08月05日 10:03
  6. * @Version 2.0.0
  7. **/
  8.  
  9. public class LogAspect {
  10.  
  11. /**
  12. * 功能描述: 前置通知
  13. *
  14. * @创建人: 我恰芙蓉王
  15. * @创建时间: 2020年08月05日 17:24:30
  16. * @param
  17. * @return: void
  18. **/
  19. public void before(){
  20. System.err.println("=======前置通知=======");
  21. }
  22.  
  23. /**
  24. * 功能描述: 后置通知
  25. *
  26. * @创建人: 我恰芙蓉王
  27. * @创建时间: 2020年08月05日 17:24:40
  28. * @param
  29. * @return: void
  30. **/
  31. public void after(){
  32. System.err.println("=======后置通知=======\n");
  33. }
  34.  
  35. /**
  36. * 功能描述: 异常通知
  37. *
  38. * @创建人: 我恰芙蓉王
  39. * @创建时间: 2020年08月05日 17:24:47
  40. * @param
  41. * @return: void
  42. **/
  43. public void afterThrowing(){
  44. System.err.println("=======出现异常=======");
  45. }
  46. }

执行结果

总结

以上只贴出了部分核心实现代码,有兴趣的童鞋可以下载源码调试,具体的注释我都在代码中写得很清楚。

代码已经提交至Git : https://github.com/wqfrw/HandWritingSpringV2.0

Spring学习之——手写Spring源码V2.0(实现IOC、D、MVC、AOP)的更多相关文章

  1. Spring学习之——手写Mini版Spring源码

    前言 Sping的生态圈已经非常大了,很多时候对Spring的理解都是在会用的阶段,想要理解其设计思想却无从下手.前些天看了某某学院的关于Spring学习的相关视频,有几篇讲到手写Spring源码,感 ...

  2. Spring源码 20 手写模拟源码

    参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...

  3. 手写Redux-Saga源码

    上一篇文章我们分析了Redux-Thunk的源码,可以看到他的代码非常简单,只是让dispatch可以处理函数类型的action,其作者也承认对于复杂场景,Redux-Thunk并不适用,还推荐了Re ...

  4. 手写koa-static源码,深入理解静态服务器原理

    这篇文章继续前面的Koa源码系列,这个系列已经有两篇文章了: 第一篇讲解了Koa的核心架构和源码:手写Koa.js源码 第二篇讲解了@koa/router的架构和源码:手写@koa/router源码 ...

  5. 手写Tomcat源码

    http://search.bilibili.com/all?keyword=%E6%89%8B%E5%86%99Tomcat%E6%BA%90%E7%A0%81 tomcat源码分析一:https: ...

  6. 织梦dedecms红黑配图片模板源码v2.0

    dedecms红黑配风格美女图片站是采用dedecms程序搭建的图片网站源码,网站感觉很大气,简约但是不简单,适合做图片网站.网站模板是收集其他网站的模板,感谢原网站提供者.在安装过程中出现问题,现已 ...

  7. 手写Vuex源码

    Vuex原理解析 Vuex是基于Vue的响应式原理基础,所以无法拿出来单独使用,必须在Vue的基础之上使用. 1.Vuex使用相关解析 main.js   import store form './s ...

  8. Spring系列28:@Transactional事务源码分析

    本文内容 @Transactional事务使用 @EnableTransactionManagement 详解 @Transactional事务属性的解析 TransactionInterceptor ...

  9. 从零开始手写 spring ioc 框架,深入学习 spring 源码

    IoC Ioc 是一款 spring ioc 核心功能简化实现版本,便于学习和理解原理. 创作目的 使用 spring 很长时间,对于 spring 使用非常频繁,实际上对于源码一直没有静下心来学习过 ...

随机推荐

  1. JVM 专题十四:本地方法接口

    1. 本地方法接口 2. 什么是本地方法? 简单来讲,一个Native Method就是一个Java调用非Java代码的接口.一个Native Method是这样一个java方法:该方法的实现由非Ja ...

  2. 最大熵原理(The Maximum Entropy Principle)

    https://wanghuaishi.wordpress.com/2017/02/21/%E5%9B%BE%E8%A7%A3%E6%9C%80%E5%A4%A7%E7%86%B5%E5%8E%9F% ...

  3. How to start MySQL on Linux

    启动MySQL数据库 service mysql start 查看MySQL进程 ps -ef |grep mysql 查看MySQL端口号 cd /etc/init.d/ netstat -atnp ...

  4. 深度理解SpringIOC,面试你根本不需要慌!

    文章已托管到GitHub,大家可以去GitHub查看阅读,欢迎老板们前来Star! 搜索关注微信公众号 码出Offer 领取各种学习资料! 深度理解Spring IOC(控制反转) 一.IOC概述 I ...

  5. Kubernetes实战指南(三十一):零宕机无缝迁移Spring Cloud至k8s

    1. 项目迁移背景 1.1 为什么要在"太岁"上动土? 目前公司的测试环境.UAT环境.生产环境均已经使用k8s进行维护管理,大部分项目均已完成容器化,并且已经在线上平稳运行许久. ...

  6. 问题:IE11下window.history.go(-1)返回404

    解决方法: 在后面添加return false,如: onclick="javascript:window.history.go(-1);return false" 这个问题在IE ...

  7. python-多任务编程03-迭代器(iterator)

    迭代器是一个可以记住遍历的位置的对象.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不会后退. 可迭代对象(Iterable) 能够被循环遍历(迭代)的对象称为可迭代 ...

  8. 最大连续区间(HDU-1540)

    HDU1540 线段树最大连续区间. 给定长度为n的数组,m次操作. 操作D,删除给定节点. 操作R,恢复最后一个删除的节点. 操作Q,询问给定节点的最大连续区间 维护三个值,区间的最大左连续区间,最 ...

  9. SQL之DDL、DML、DCL、TCL

    SQL SQL(structured query language)是一种领域特定语言(DSL,domain-specific language),用于管理关系型数据库(relational data ...

  10. MySQL数据管理

    3.MySQL数据管理 3.1外键 方式一:  create table `grade`(  `gradeid` int(10) not null auto_increment comment '年纪 ...