Spring学习之——手写Spring源码V2.0(实现IOC、D、MVC、AOP)
前言
在上一篇《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的相关配置
- #扫描路径#
- scanPackage=com.wqfrw
- #模板引擎路径#
- templateRoot=template
- #切面表达式#
- pointCut=public .* com.wqfrw.service.impl..*ServiceImpl..*(.*)
- #切面类#
- aspectClass=com.wqfrw.aspect.LogAspect
- #切面前置通知#
- aspectBefore=before
- #切面后置通知#
- aspectAfter=after
- #切面异常通知#
- aspectAfterThrowing=afterThrowing
- #切面异常类型#
- 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容器中
- /**
- * 功能描述: 初始化MyApplicationContext
- *
- * @创建人: 我恰芙蓉王
- * @创建时间: 2020年08月03日 18:54:01
- * @param configLocations
- * @return:
- **/
- public MyApplicationContext(String... configLocations) {
- this.configLocations = configLocations;
- try {
- //1.读取配置文件并解析BeanDefinition对象
- beanDefinitionReader = new MyBeanDefinitionReader(configLocations);
- List<MyBeanDefinition> beanDefinitionList = beanDefinitionReader.loadBeanDefinitions();
- //2.将解析后的BeanDefinition对象注册到beanDefinitionMap中
- doRegisterBeanDefinition(beanDefinitionList);
- //3.触发创建对象的动作,调用getBean()方法(Spring默认是延时加载)
- doCreateBean();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- /**
- * 功能描述: 真正触发IoC和DI的动作 1.创建Bean 2.依赖注入
- *
- * @param beanName
- * @创建人: 我恰芙蓉王
- * @创建时间: 2020年08月03日 19:48:58
- * @return: java.lang.Object
- **/
- public Object getBean(String beanName) {
- //============ 创建实例 ============
- //1.获取配置信息,只要拿到beanDefinition对象即可
- MyBeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
- //用反射创建实例 这个实例有可能是代理对象 也有可能是原生对象 封装成BeanWrapper统一处理
- Object instance = instantiateBean(beanName, beanDefinition);
- MyBeanWrapper beanWrapper = new MyBeanWrapper(instance);
- factoryBeanInstanceCache.put(beanName, beanWrapper);
- //============ 依赖注入 ============
- populateBean(beanName, beanDefinition, beanWrapper);
- return beanWrapper.getWrapperInstance();
- }
- /**
- * 功能描述: 依赖注入
- *
- * @param beanName
- * @param beanDefinition
- * @param beanWrapper
- * @创建人: 我恰芙蓉王
- * @创建时间: 2020年08月03日 20:09:01
- * @return: void
- **/
- private void populateBean(String beanName, MyBeanDefinition beanDefinition, MyBeanWrapper beanWrapper) {
- Object instance = beanWrapper.getWrapperInstance();
- Class<?> clazz = beanWrapper.getWrapperClass();
- //只有加了注解的类才需要依赖注入
- if (!(clazz.isAnnotationPresent(MyController.class) || clazz.isAnnotationPresent(MyService.class))) {
- return;
- }
- //拿到bean所有的字段 包括private、public、protected、default
- for (Field field : clazz.getDeclaredFields()) {
- //如果没加MyAutowired注解的属性则直接跳过
- if (!field.isAnnotationPresent(MyAutowired.class)) {
- continue;
- }
- MyAutowired annotation = field.getAnnotation(MyAutowired.class);
- String autowiredBeanName = annotation.value().trim();
- if ("".equals(autowiredBeanName)) {
- autowiredBeanName = field.getType().getName();
- }
- //强制访问
- field.setAccessible(true);
- try {
- if (factoryBeanInstanceCache.get(autowiredBeanName) == null) { continue; }
- //赋值
- field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance());
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
- }
- }
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页面返回
- /**
- * 功能描述: 初始化核心组件 在Spring中有九大核心组件,这里只实现三种
- *
- * @param context
- * @创建人: 我恰芙蓉王
- * @创建时间: 2020年08月04日 11:51:55
- * @return: void
- **/
- protected void initStrategies(MyApplicationContext context) {
- //多文件上传组件
- //initMultipartResolver(context);
- //初始化本地语言环境
- //initLocaleResolver(context);
- //初始化模板处理器
- //initThemeResolver(context);
- //初始化请求分发处理器
- initHandlerMappings(context);
- //初始化参数适配器
- initHandlerAdapters(context);
- //初始化异常拦截器
- //initHandlerExceptionResolvers(context);
- //初始化视图预处理器
- //initRequestToViewNameTranslator(context);
- //初始化视图转换器
- initViewResolvers(context);
- //缓存管理器(值栈)
- //initFlashMapManager(context);
- }
- /**
- * 功能描述: 进行参数适配
- *
- * @创建人: 我恰芙蓉王
- * @创建时间: 2020年08月05日 19:41:38
- * @param req
- * @param resp
- * @param mappedHandler
- * @return: com.framework.webmvc.servlet.MyModelAndView
- **/
- public MyModelAndView handle(HttpServletRequest req, HttpServletResponse resp, MyHandlerMapping mappedHandler) throws Exception {
- //保存参数的名称和位置
- Map<String, Integer> paramIndexMapping = new HashMap<>();
- //获取这个方法所有形参的注解 因一个参数可以添加多个注解 所以是一个二维数组
- Annotation[][] pa = mappedHandler.getMethod().getParameterAnnotations();
- /**
- * 获取加了MyRequestParam注解的参数名和位置 放入到paramIndexMapping中
- */
- for (int i = 0; i < pa.length; i++) {
- for (Annotation annotation : pa[i]) {
- if (!(annotation instanceof MyRequestParam)) {
- continue;
- }
- String paramName = ((MyRequestParam) annotation).value();
- if (!"".equals(paramName.trim())) {
- paramIndexMapping.put(paramName, i);
- }
- }
- }
- //方法的形参列表
- Class<?>[] parameterTypes = mappedHandler.getMethod().getParameterTypes();
- /**
- * 获取request和response的位置(如果有的话) 放入到paramIndexMapping中
- */
- for (int i = 0; i < parameterTypes.length; i++) {
- Class<?> parameterType = parameterTypes[i];
- if (parameterType == HttpServletRequest.class || parameterType == HttpServletResponse.class) {
- paramIndexMapping.put(parameterType.getName(), i);
- }
- }
- //拿到一个请求所有传入的实际实参 因为一个url上可以多个相同的name,所以此Map的结构为一个name对应一个value[]
- //例如:request中的参数t1=1&t1=2&t2=3形成的map结构:
- //key=t1;value[0]=1,value[1]=2
- //key=t2;value[0]=3
- Map<String, String[]> paramsMap = req.getParameterMap();
- //自定义初始实参列表(反射调用Controller方法时使用)
- Object[] paramValues = new Object[parameterTypes.length];
- /**
- * 从paramIndexMapping中取出参数名与位置 动态赋值
- */
- for (Map.Entry<String, String[]> entry : paramsMap.entrySet()) {
- //拿到请求传入的实参
- String value = entry.getValue()[0];
- //如果包含url参数上的key 则动态转型赋值
- if (paramIndexMapping.containsKey(entry.getKey())) {
- //获取这个实参的位置
- int index = paramIndexMapping.get(entry.getKey());
- //动态转型并赋值
- paramValues[index] = caseStringValue(value, parameterTypes[index]);
- }
- }
- /**
- * request和response单独赋值
- */
- if (paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
- int index = paramIndexMapping.get(HttpServletRequest.class.getName());
- paramValues[index] = req;
- }
- if (paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
- int index = paramIndexMapping.get(HttpServletResponse.class.getName());
- paramValues[index] = resp;
- }
- //方法调用 拿到返回结果
- Object result = mappedHandler.getMethod().invoke(mappedHandler.getController(), paramValues);
- if (result == null || result instanceof Void) {
- return null;
- } else if (mappedHandler.getMethod().getReturnType() == MyModelAndView.class) {
- return (MyModelAndView) result;
- }
- return null;
- }
- /**
- * 功能描述: 动态转型
- *
- * @param value String类型的value
- * @param clazz 实际对象的class
- * @创建人: 我恰芙蓉王
- * @创建时间: 2020年08月04日 16:34:40
- * @return: java.lang.Object 实际对象的实例
- **/
- private Object caseStringValue(String value, Class<?> clazz) throws Exception {
- //通过class对象获取一个入参为String的构造方法 没有此方法则抛出异常
- Constructor constructor = clazz.getConstructor(new Class[]{String.class});
- //通过构造方法new一个实例返回
- return constructor.newInstance(value);
- }
- /**
- * 功能描述: 对页面内容进行渲染
- *
- * @创建人: 我恰芙蓉王
- * @创建时间: 2020年08月04日 17:54:40
- * @param model
- * @param req
- * @param resp
- * @return: void
- **/
- public void render(Map<String, ?> model, HttpServletRequest req, HttpServletResponse resp) throws Exception {
- StringBuilder sb = new StringBuilder();
- //只读模式 读取文件
- RandomAccessFile ra = new RandomAccessFile(this.viewFile, "r");
- String line = null;
- while ((line = ra.readLine()) != null) {
- line = new String(line.getBytes("ISO-8859-1"), "utf-8");
- //%{name}
- Pattern pattern = Pattern.compile("%\\{[^\\}]+\\}", Pattern.CASE_INSENSITIVE);
- Matcher matcher = pattern.matcher(line);
- while (matcher.find()) {
- String paramName = matcher.group();
- paramName = paramName.replaceAll("%\\{|\\}", "");
- Object paramValue = model.get(paramName);
- line = matcher.replaceFirst(makeStringForRegExp(paramValue.toString()));
- matcher = pattern.matcher(line);
- }
- sb.append(line);
- }
- resp.setCharacterEncoding("utf-8");
- resp.getWriter().write(sb.toString());
- }
html页面
404.html
- <!DOCTYPE html>
- <html lang="zh-cn">
- <head>
- <meta charset="UTF-8">
- <title>页面没有找到</title>
- </head>
- <body>
- <font size="25" color="red">Exception Code : 404 Not Found</font>
- <br><br><br>
- @我恰芙蓉王
- </body>
- </html>
500.html
- <!DOCTYPE html>
- <html lang="zh-cn">
- <head>
- <meta charset="UTF-8">
- <title>服务器崩溃</title>
- </head>
- <body>
- <font size="25" color="red">Exception Code : 500 <br/> 服务器崩溃了~</font>
- <br/>
- <br/>
- <b>Message:%{message}</b>
- <br/>
- <b>StackTrace:%{stackTrace}</b>
- <br/>
- <br><br><br>
- @我恰芙蓉王
- </body>
- </html>
index.html
- <!DOCTYPE html>
- <html lang="zh-cn">
- <head>
- <meta charset="UTF-8">
- <title>自定义SpringMVC模板引擎Demo</title>
- </head>
- <center>
- <h1>大家好,我是%{name}</h1>
- <h2>我爱%{food}</h2>
- <font color="red">
- <h2>时间:%{date}</h2>
- </font>
- <br><br><br>
- @我恰芙蓉王
- </center>
- </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方法,在此方法中执行切面方法与原生对象方法。
- /**
- * 功能描述: 反射实例化对象
- *
- * @param beanName
- * @param beanDefinition
- * @创建人: 我恰芙蓉王
- * @创建时间: 2020年08月03日 20:08:50
- * @return: java.lang.Object
- **/
- private Object instantiateBean(String beanName, MyBeanDefinition beanDefinition) {
- String className = beanDefinition.getBeanClassName();
- Object instance = null;
- try {
- Class<?> clazz = Class.forName(className);
- instance = clazz.newInstance();
- /**
- * ===========接入AOP begin===========
- */
- MyAdviceSupport support = instantiateAopConfig(beanDefinition);
- support.setTargetClass(clazz);
- support.setTarget(instance);
- //如果需要代理 则用代理对象覆盖目标对象
- if (support.pointCutMatch()) {
- instance = new MyJdkDynamicAopProxy(support).getProxy();
- }
- /**
- * ===========接入AOP end===========
- */
- factoryBeanObjectCache.put(beanName, instance);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return instance;
- }
- /**
- * 功能描述: 解析配置 pointCut
- *
- * @param
- * @创建人: 我恰芙蓉王
- * @创建时间: 2020年08月05日 11:20:21
- * @return: void
- **/
- private void parse() {
- String pointCut = aopConfig.getPointCut()
- .replaceAll("\\.", "\\\\.")
- .replaceAll("\\\\.\\*", ".*")
- .replaceAll("\\(", "\\\\(")
- .replaceAll("\\)", "\\\\)");
- //public .*.com.wqfrw.service..*impl..*(.*)
- String pointCutForClassRegex = pointCut.substring(0, pointCut.lastIndexOf("\\(") - 4);
- this.pointCutClassPattern = Pattern.compile(pointCutForClassRegex.substring(pointCutForClassRegex.lastIndexOf(" ") + 1));
- methodCache = new HashMap<>();
- //匹配方法的正则
- Pattern pointCutPattern = Pattern.compile(pointCut);
- //1.对回调通知进行缓存
- Map<String, Method> aspectMethods = new HashMap<>();
- try {
- //拿到切面类的class com.wqfrw.aspect.LogAspect
- Class<?> aspectClass = Class.forName(this.aopConfig.getAspectClass());
- //将切面类的通知方法缓存到aspectMethods
- Stream.of(aspectClass.getMethods()).forEach(v -> aspectMethods.put(v.getName(), v));
- //2.扫描目标类的方法,去循环匹配
- for (Method method : targetClass.getMethods()) {
- String methodString = method.toString();
- //如果目标方法有抛出异常 则截取
- if (methodString.contains("throws")) {
- methodString = methodString.substring(0, methodString.lastIndexOf("throws")).trim();
- }
- /**
- * 匹配目标类方法 如果匹配上,就将缓存好的通知与它建立联系 如果没匹配上,则忽略
- */
- Matcher matcher = pointCutPattern.matcher(methodString);
- if (matcher.matches()) {
- Map<String, MyAdvice> adviceMap = new HashMap<>();
- //前置通知
- if (!(null == aopConfig.getAspectBefore() || "".equals(aopConfig.getAspectBefore()))) {
- adviceMap.put("before", new MyAdvice(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectBefore())));
- }
- //后置通知
- if (!(null == aopConfig.getAspectAfter() || "".equals(aopConfig.getAspectAfter()))) {
- adviceMap.put("after", new MyAdvice(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectAfter())));
- }
- //异常通知
- if (!(null == aopConfig.getAspectAfterThrowing() || "".equals(aopConfig.getAspectAfterThrowing()))) {
- MyAdvice advice = new MyAdvice(aspectClass.newInstance(), aspectMethods.get(aopConfig.getAspectAfterThrowing()));
- advice.setThrowingName(aopConfig.getAspectAfterThrowingName());
- adviceMap.put("afterThrowing", advice);
- }
- //建立关联
- methodCache.put(method, adviceMap);
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- /**
- * 功能描述: 返回一个代理对象
- *
- * @创建人: 我恰芙蓉王
- * @创建时间: 2020年08月05日 14:17:22
- * @param
- * @return: java.lang.Object
- **/
- public Object getProxy() {
- return Proxy.newProxyInstance(this.getClass().getClassLoader(), this.support.getTargetClass().getInterfaces(), this);
- }
- /**
- * 功能描述: 重写invoke
- *
- * @创建人: 我恰芙蓉王
- * @创建时间: 2020年08月05日 20:29:19
- * @param proxy
- * @param method
- * @param args
- * @return: java.lang.Object
- **/
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- Map<String, MyAdvice> advices = support.getAdvices(method, support.getTargetClass());
- Object result = null;
- try {
- //调用前置通知
- invokeAdvice(advices.get("before"));
- //执行原生目标方法
- result = method.invoke(support.getTarget(), args);
- //调用后置通知
- invokeAdvice(advices.get("after"));
- } catch (Exception e) {
- //调用异常通知
- invokeAdvice(advices.get("afterThrowing"));
- throw e;
- }
- return result;
- }
- /**
- * 功能描述: 执行切面方法
- *
- * @创建人: 我恰芙蓉王
- * @创建时间: 2020年08月05日 11:09:32
- * @param advice
- * @return: void
- **/
- private void invokeAdvice(MyAdvice advice) {
- try {
- advice.getAdviceMethod().invoke(advice.getAspect());
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- } catch (InvocationTargetException e) {
- e.printStackTrace();
- }
- }
- /**
- * @ClassName LogAspect
- * @Description TODO(切面类)
- * @Author 我恰芙蓉王
- * @Date 2020年08月05日 10:03
- * @Version 2.0.0
- **/
- public class LogAspect {
- /**
- * 功能描述: 前置通知
- *
- * @创建人: 我恰芙蓉王
- * @创建时间: 2020年08月05日 17:24:30
- * @param
- * @return: void
- **/
- public void before(){
- System.err.println("=======前置通知=======");
- }
- /**
- * 功能描述: 后置通知
- *
- * @创建人: 我恰芙蓉王
- * @创建时间: 2020年08月05日 17:24:40
- * @param
- * @return: void
- **/
- public void after(){
- System.err.println("=======后置通知=======\n");
- }
- /**
- * 功能描述: 异常通知
- *
- * @创建人: 我恰芙蓉王
- * @创建时间: 2020年08月05日 17:24:47
- * @param
- * @return: void
- **/
- public void afterThrowing(){
- System.err.println("=======出现异常=======");
- }
- }
执行结果
总结
以上只贴出了部分核心实现代码,有兴趣的童鞋可以下载源码调试,具体的注释我都在代码中写得很清楚。
代码已经提交至Git : https://github.com/wqfrw/HandWritingSpringV2.0
Spring学习之——手写Spring源码V2.0(实现IOC、D、MVC、AOP)的更多相关文章
- Spring学习之——手写Mini版Spring源码
前言 Sping的生态圈已经非常大了,很多时候对Spring的理解都是在会用的阶段,想要理解其设计思想却无从下手.前些天看了某某学院的关于Spring学习的相关视频,有几篇讲到手写Spring源码,感 ...
- Spring源码 20 手写模拟源码
参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...
- 手写Redux-Saga源码
上一篇文章我们分析了Redux-Thunk的源码,可以看到他的代码非常简单,只是让dispatch可以处理函数类型的action,其作者也承认对于复杂场景,Redux-Thunk并不适用,还推荐了Re ...
- 手写koa-static源码,深入理解静态服务器原理
这篇文章继续前面的Koa源码系列,这个系列已经有两篇文章了: 第一篇讲解了Koa的核心架构和源码:手写Koa.js源码 第二篇讲解了@koa/router的架构和源码:手写@koa/router源码 ...
- 手写Tomcat源码
http://search.bilibili.com/all?keyword=%E6%89%8B%E5%86%99Tomcat%E6%BA%90%E7%A0%81 tomcat源码分析一:https: ...
- 织梦dedecms红黑配图片模板源码v2.0
dedecms红黑配风格美女图片站是采用dedecms程序搭建的图片网站源码,网站感觉很大气,简约但是不简单,适合做图片网站.网站模板是收集其他网站的模板,感谢原网站提供者.在安装过程中出现问题,现已 ...
- 手写Vuex源码
Vuex原理解析 Vuex是基于Vue的响应式原理基础,所以无法拿出来单独使用,必须在Vue的基础之上使用. 1.Vuex使用相关解析 main.js import store form './s ...
- Spring系列28:@Transactional事务源码分析
本文内容 @Transactional事务使用 @EnableTransactionManagement 详解 @Transactional事务属性的解析 TransactionInterceptor ...
- 从零开始手写 spring ioc 框架,深入学习 spring 源码
IoC Ioc 是一款 spring ioc 核心功能简化实现版本,便于学习和理解原理. 创作目的 使用 spring 很长时间,对于 spring 使用非常频繁,实际上对于源码一直没有静下心来学习过 ...
随机推荐
- JVM 专题十四:本地方法接口
1. 本地方法接口 2. 什么是本地方法? 简单来讲,一个Native Method就是一个Java调用非Java代码的接口.一个Native Method是这样一个java方法:该方法的实现由非Ja ...
- 最大熵原理(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% ...
- How to start MySQL on Linux
启动MySQL数据库 service mysql start 查看MySQL进程 ps -ef |grep mysql 查看MySQL端口号 cd /etc/init.d/ netstat -atnp ...
- 深度理解SpringIOC,面试你根本不需要慌!
文章已托管到GitHub,大家可以去GitHub查看阅读,欢迎老板们前来Star! 搜索关注微信公众号 码出Offer 领取各种学习资料! 深度理解Spring IOC(控制反转) 一.IOC概述 I ...
- Kubernetes实战指南(三十一):零宕机无缝迁移Spring Cloud至k8s
1. 项目迁移背景 1.1 为什么要在"太岁"上动土? 目前公司的测试环境.UAT环境.生产环境均已经使用k8s进行维护管理,大部分项目均已完成容器化,并且已经在线上平稳运行许久. ...
- 问题:IE11下window.history.go(-1)返回404
解决方法: 在后面添加return false,如: onclick="javascript:window.history.go(-1);return false" 这个问题在IE ...
- python-多任务编程03-迭代器(iterator)
迭代器是一个可以记住遍历的位置的对象.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不会后退. 可迭代对象(Iterable) 能够被循环遍历(迭代)的对象称为可迭代 ...
- 最大连续区间(HDU-1540)
HDU1540 线段树最大连续区间. 给定长度为n的数组,m次操作. 操作D,删除给定节点. 操作R,恢复最后一个删除的节点. 操作Q,询问给定节点的最大连续区间 维护三个值,区间的最大左连续区间,最 ...
- SQL之DDL、DML、DCL、TCL
SQL SQL(structured query language)是一种领域特定语言(DSL,domain-specific language),用于管理关系型数据库(relational data ...
- MySQL数据管理
3.MySQL数据管理 3.1外键 方式一: create table `grade`( `gradeid` int(10) not null auto_increment comment '年纪 ...