写在前面的话

相关背景及资源:

曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享

曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解

曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下

曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean definition的?

曹工说Spring Boot源码(5)-- 怎么从properties文件读取bean

曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的

曹工说Spring Boot源码(7)-- Spring解析xml文件,到底从中得到了什么(上)

曹工说Spring Boot源码(8)-- Spring解析xml文件,到底从中得到了什么(util命名空间)

曹工说Spring Boot源码(9)-- Spring解析xml文件,到底从中得到了什么(context命名空间上)

曹工说Spring Boot源码(10)-- Spring解析xml文件,到底从中得到了什么(context:annotation-config 解析)

曹工说Spring Boot源码(11)-- context:component-scan,你真的会用吗(这次来说说它的奇技淫巧)

曹工说Spring Boot源码(12)-- Spring解析xml文件,到底从中得到了什么(context:component-scan完整解析)

曹工说Spring Boot源码(13)-- AspectJ的运行时织入(Load-Time-Weaving),基本内容是讲清楚了(附源码)

曹工说Spring Boot源码(14)-- AspectJ的Load-Time-Weaving的两种实现方式细细讲解,以及怎么和Spring Instrumentation集成

曹工说Spring Boot源码(15)-- Spring从xml文件里到底得到了什么(context:load-time-weaver 完整解析)

曹工说Spring Boot源码(16)-- Spring从xml文件里到底得到了什么(aop:config完整解析【上】)

曹工说Spring Boot源码(17)-- Spring从xml文件里到底得到了什么(aop:config完整解析【中】)

曹工说Spring Boot源码(18)-- Spring AOP源码分析三部曲,终于快讲完了 (aop:config完整解析【下】)

曹工说Spring Boot源码(19)-- Spring 带给我们的工具利器,创建代理不用愁(ProxyFactory)

曹工说Spring Boot源码(20)-- 码网恢恢,疏而不漏,如何记录Spring RedisTemplate每次操作日志

曹工说Spring Boot源码(21)-- 为了让大家理解Spring Aop利器ProxyFactory,我已经拼了

曹工说Spring Boot源码(22)-- 你说我Spring Aop依赖AspectJ,我依赖它什么了

曹工说Spring Boot源码(23)-- ASM又立功了,Spring原来是这么递归获取注解的元注解的

曹工说Spring Boot源码(24)-- Spring注解扫描的瑞士军刀,asm技术实战(上)

曹工说Spring Boot源码(25)-- Spring注解扫描的瑞士军刀,ASM + Java Instrumentation,顺便提提Jar包破解

曹工说Spring Boot源码(26)-- 学习字节码也太难了,实在不能忍受了,写了个小小的字节码执行引擎

曹工说Spring Boot源码(27)-- Spring的component-scan,光是include-filter属性的各种配置方式,就够玩半天了

曹工说Spring Boot源码(28)-- Spring的component-scan机制,让你自己来进行简单实现,怎么办

工程代码地址 思维导图地址

工程结构图:

什么是三级缓存

在获取单例bean的时候,会进入以下方法:

  1. org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
  2. protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  3. // 1
  4. Object singletonObject = this.singletonObjects.get(beanName);
  5. if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
  6. synchronized (this.singletonObjects) {
  7. // 2
  8. singletonObject = this.earlySingletonObjects.get(beanName);
  9. if (singletonObject == null && allowEarlyReference) {
  10. // 3
  11. ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
  12. if (singletonFactory != null) {
  13. // 4
  14. singletonObject = singletonFactory.getObject();
  15. this.earlySingletonObjects.put(beanName, singletonObject);
  16. this.singletonFactories.remove(beanName);
  17. }
  18. }
  19. }
  20. }
  21. return singletonObject;
  22. }

这里面涉及到了该类中的三个field。

  1. /** 1级缓存 Cache of singleton objects: bean name to bean instance. */
  2. private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
  3. /** 2级缓存 Cache of early singleton objects: bean name to bean instance. */
  4. private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
  5. /** 3级缓存 Cache of singleton factories: bean name to ObjectFactory. */
  6. private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

接着说前面的代码。

  • 1处,在最上层的缓存singletonObjects中,获取单例bean,这里面拿到的bean,直接可以使用;如果没取到,则进入2处

  • 2处,在2级缓存earlySingletonObjects中,查找bean;

  • 3处,如果在2级缓存中,还是没找到,则在3级缓存中查找对应的工厂对象,利用拿到的工厂对象(工厂对象中,有3个field,一个是beanName,一个是RootBeanDefinition,一个是已经创建好的,但还没有注入属性的bean),去获取包装后的bean,或者说,代理后的bean。

    什么是已经创建好的,但没有注入属性的bean?

    比如一个bean,有10个字段,你new了之后,对象已经有了,内存空间已经开辟了,堆里已经分配了该对象的空间了,只是此时的10个field还是null。

ioc容器,普通循环依赖,一级缓存够用吗

说实话,如果简单写写的话,一级缓存都没问题。给大家看一个我以前写的渣渣ioc容器:

曹工说Tomcat4:利用 Digester 手撸一个轻量的 Spring IOC容器

  1. @Data
  2. public class BeanDefinitionRegistry {
  3. /**
  4. * map:存储 bean的class-》bean实例
  5. */
  6. private Map<Class, Object> beanMapByClass = new ConcurrentHashMap<>();
  7. /**
  8. * 根据bean 定义获取bean
  9. * 1、先查bean容器,查到则返回
  10. * 2、生成bean,放进容器(此时,依赖还没注入,主要是解决循环依赖问题)
  11. * 3、注入依赖
  12. *
  13. * @param beanDefiniton
  14. * @return
  15. */
  16. private Object getBean(MyBeanDefiniton beanDefiniton) {
  17. Class<?> beanClazz = beanDefiniton.getBeanClazz();
  18. Object bean = beanMapByClass.get(beanClazz);
  19. if (bean != null) {
  20. return bean;
  21. }
  22. // 0
  23. bean = generateBeanInstance(beanClazz);
  24. // 1 先行暴露,解决循环依赖问题
  25. beanMapByClass.put(beanClazz, bean);
  26. beanMapByName.put(beanDefiniton.getBeanName(), bean);
  27. // 2 查找依赖
  28. List<Field> dependencysByField = beanDefiniton.getDependencysByField();
  29. if (dependencysByField == null) {
  30. return bean;
  31. }
  32. // 3
  33. for (Field field : dependencysByField) {
  34. try {
  35. autowireField(beanClazz, bean, field);
  36. } catch (Exception e) {
  37. throw new RuntimeException(beanClazz.getName() + " 创建失败",e);
  38. }
  39. }
  40. return bean;
  41. }
  42. }

大家看上面的代码,我只定义了一个field,就是一个map,存放bean的class-》bean。

  1. /**
  2. * map:存储 bean的class-》bean实例
  3. */
  4. private Map<Class, Object> beanMapByClass = new ConcurrentHashMap<>();
  • 0处,生成bean,直接就是new
  • 1处,先把这个不完整的bean,放进map
  • 2处,获取需要注入的属性集合
  • 3处,进行自动注入,就是根据field的Class,去map里查找对应的bean,设置到field里。

上面这个代码,有啥问题没?spring为啥整整三级?

ioc,一级缓存有什么问题

一级缓存的问题在于,就1个map,里面既有完整的已经ready的bean,也有不完整的,尚未设置field的bean。

如果这时候,有其他线程去这个map里获取bean来用怎么办?拿到的bean,不完整,怎么办呢?属性都是null,直接空指针了。

所以,我们就要加一个map,这个map,用来存放那种不完整的bean。这里,还是拿spring举例。我们可以只用下面这两层:

  1. /** 1级缓存 Cache of singleton objects: bean name to bean instance. */
  2. private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
  3. /** 2级缓存 Cache of early singleton objects: bean name to bean instance. */
  4. private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

因为spring代码里是三级缓存,所以我们对源码做一点修改。

修改spring源码,只使用二级缓存

修改创建bean的代码,不放入第三级缓存,只放入第二级缓存

创建了bean之后,属性注入之前,将创建出来的不完整bean,放到earlySingletonObjects

这个代码,在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean,我这边只有4.0版本的spring源码工程,不过这套逻辑,算是spring核心逻辑,和5.x版本差别不大。

  1. protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
  2. BeanWrapper instanceWrapper = null;
  3. if (mbd.isSingleton()) {
  4. instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
  5. }
  6. if (instanceWrapper == null) {
  7. // 1
  8. instanceWrapper = createBeanInstance(beanName, mbd, args);
  9. }
  10. final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
  11. Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
  12. ...
  13. boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
  14. isSingletonCurrentlyInCreation(beanName));
  15. if (earlySingletonExposure) {
  16. // 2
  17. earlySingletonObjects.put(beanName,bean);
  18. registeredSingletonObjects.add(beanName);
  19. // 3
  20. // addSingletonFactory(beanName, new ObjectFactory() {
  21. // public Object getObject() throws BeansException {
  22. // return getEarlyBeanReference(beanName, mbd, bean);
  23. // }
  24. // });
  25. }
  • 1处,就是创建对象,就是new
  • 2处,这是我加的代码,放入二级缓存
  • 3处,本来这就是增加三级缓存的位置,被我注释了。现在,就不会往三级缓存放东西了

修改获取bean的代码,只从第一、第二级缓存获取,不从第三级获取

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

之前的代码是文章开头那样的,我这里修改为:

  1. protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  2. Object singletonObject = this.singletonObjects.get(beanName);
  3. if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
  4. synchronized (this.singletonObjects) {
  5. singletonObject = this.earlySingletonObjects.get(beanName);
  6. return singletonObject;
  7. }
  8. }
  9. return (singletonObject != NULL_OBJECT ? singletonObject : null);

这样,就是只用两级缓存了。

两级缓存,有啥问题?

ioc循环依赖,一点问题都没有,完全够用了。

我这边一个简单的例子,


  1. public class Chick{
  2. private Egg egg;
  3. public Egg getEgg() {
  4. return egg;
  5. }
  6. public void setEgg(Egg egg) {
  7. this.egg = egg;
  8. }
  9. }

  1. public class Egg {
  2. private Chick chick;
  3. public Chick getChick() {
  4. return chick;
  5. }
  6. public void setChick(Chick chick) {
  7. this.chick = chick;
  8. }
  1. <bean id="chick" class="foo.Chick" lazy-init="true">
  2. <property name="egg" ref="egg"/>
  3. </bean>
  4. <bean id="egg" class="foo.Egg" lazy-init="true">
  5. <property name="chick" ref="chick"/>
  6. </bean>
  1. ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
  2. "context-namespace-test-aop.xml");
  3. Egg egg = (Egg) ctx.getBean(Egg.class);

结论:

所以,一级缓存都能解决的问题,二级当然更没问题。

但是,如果我这里给上面的Egg类,加个切面(aop的逻辑,意思就是最终会生成Egg的一个动态代理对象),那还有问题没?

  1. <aop:config>
  2. <aop:pointcut id="mypointcut" expression="execution(public * foo.Egg.*(..))"/>
  3. <aop:aspect id="myAspect" ref="performenceAspect">
  4. <aop:after method="afterIncubate" pointcut-ref="mypointcut"/>
  5. </aop:aspect>
  6. </aop:config>

注意这里的切点:

  1. execution(public * foo.Egg.*(..))

就是切Egg类的方法。

加了这个逻辑后,我们继续运行,在 Egg egg = (Egg) ctx.getBean(Egg.class);行,会抛出如下异常:

我涂掉了一部分,因为那是官方对这个异常的推论,因为我们改了代码,所以推论不准确,因此干脆隐去。

这个异常是说:

兄弟啊,bean egg已经被注入到了其他bean:chick中。(因为我们循环依赖了),但是,注入到chick中的,是Egg类型。但是,我们这里最后对egg这个bean,进行了后置处理,生成了代理对象。那其他bean里,用原始的bean,是不是不太对啊?

所以,spring给我们抛错了。

怎么理解呢? 以io流举例,我们一开始都是用的原始字节流,然后给别人用的也是字节流,但是,最后,我感觉不方便,我自己悄悄弄了个缓存字符流(类比代理对象),我是方便了,但是,别人用的,还是原始的字节流啊。

你bean不是单例吗?不能这么玩吧?

所以,这就是二级缓存,不能解决的问题。

什么问题?aop情形下,注入到其他bean的,不是最终的代理对象。

三级缓存,怎么解决这个问题

要解决这个问题,必须在其他bean(chick),来查找我们(以上面例子为例,我们是egg)的时候,查找到最终形态的egg,即代理后的egg。

怎么做到这点呢?

加个三级缓存,里面不存具体的bean,里面存一个工厂对象。通过工厂对象,是可以拿到最终形态的代理后的egg。

ok,我们将前面修改的代码还原:

  1. protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
  2. BeanWrapper instanceWrapper = null;
  3. if (mbd.isSingleton()) {
  4. instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
  5. }
  6. if (instanceWrapper == null) {
  7. // 1
  8. instanceWrapper = createBeanInstance(beanName, mbd, args);
  9. }
  10. final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
  11. Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
  12. boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
  13. isSingletonCurrentlyInCreation(beanName));
  14. if (earlySingletonExposure) {
  15. // 2
  16. // Map<String, Object> earlySingletonObjects = this.getEarlySingletonObjects();
  17. // earlySingletonObjects.put(beanName,bean);
  18. //
  19. // Set<String> registeredSingletonObjects = this.getRegisteredSingletonObjects();
  20. // registeredSingletonObjects.add(beanName);
  21. // 3
  22. addSingletonFactory(beanName, new ObjectFactory() {
  23. public Object getObject() throws BeansException {
  24. return getEarlyBeanReference(beanName, mbd, bean);
  25. }
  26. });
  27. }
  • 1处,创建bean,单纯new,不注入

  • 2处,revert我们的代码

  • 3处,这里new了一个ObjectFactory,然后会存入到如下的第三级缓存。

    1. /** 3级缓存 Cache of singleton factories: bean name to ObjectFactory. */
    2. private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    注意,new一个匿名内部类(假设这个匿名类叫AA)的对象,其中用到的外部类的变量,都会在AA中隐式生成对应的field。

    大家看上图,里面的3个字段,和下面代码1处中的,几个字段,是一一对应的。

    1. addSingletonFactory(beanName, new ObjectFactory() {
    2. public Object getObject() throws BeansException {
    3. // 1
    4. return getEarlyBeanReference(beanName, mbd, bean);
    5. }
    6. });

ok,现在,egg已经把自己存进去了,存在了第三级缓存,1级和2级都没有,那后续chick在使用getSingleton查找egg的时候,就会进入下面的逻辑了(就是文章开头的那段代码,下面已经把我们的修改还原了):

  1. protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  2. // Object singletonObject = this.singletonObjects.get(beanName);
  3. // if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
  4. // synchronized (this.singletonObjects) {
  5. // singletonObject = this.earlySingletonObjects.get(beanName);
  6. // return singletonObject;
  7. // }
  8. // }
  9. // return (singletonObject != NULL_OBJECT ? singletonObject : null);
  10. Object singletonObject = this.singletonObjects.get(beanName);
  11. if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
  12. synchronized (this.singletonObjects) {
  13. singletonObject = this.earlySingletonObjects.get(beanName);
  14. if (singletonObject == null && allowEarlyReference) {
  15. ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
  16. if (singletonFactory != null) {
  17. // 1
  18. singletonObject = singletonFactory.getObject();
  19. this.earlySingletonObjects.put(beanName, singletonObject);
  20. this.singletonFactories.remove(beanName);
  21. }
  22. }
  23. }
  24. }
  25. return (singletonObject != NULL_OBJECT ? singletonObject : null);
  26. }

上面就会进入1处,调用singletonFactory.getObject();

而前面我们知道,这个factory的逻辑是:

  1. addSingletonFactory(beanName, new ObjectFactory() {
  2. public Object getObject() throws BeansException {
  3. // 1
  4. return getEarlyBeanReference(beanName, mbd, bean);
  5. }
  6. });

1处就是这个工厂方法的逻辑,这里面,简单说,就会去调用各个beanPostProcessor的getEarlyBeanReference方法。

其中,主要就是aop的主力beanPostProcessor,AbstractAutoProxyCreator#getEarlyBeanReference

其实现如下:

  1. public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
  2. Object cacheKey = getCacheKey(bean.getClass(), beanName);
  3. this.earlyProxyReferences.add(cacheKey);
  4. // 1
  5. return wrapIfNecessary(bean, beanName, cacheKey);
  6. }

这里的1处,就会去对egg这个bean,创建代理,此时,返回的对象,就是个代理对象了,那,注入到chick的,自然也是代理后的egg了。

关于SmartInstantiationAwareBeanPostProcessor

我们上面说的那个getEarlyBeanReference就在这个接口中。

这个接口继承了BeanPostProcessor

而创建代理对象,目前就是在如下两个方法中去创建:

  1. public interface BeanPostProcessor {
  2. Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
  3. Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
  4. }

这两个方法,都是在实例化之后,创建代理。那我们前面创建代理,是在依赖解析过程中:

  1. public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor {
  2. ...
  3. Object getEarlyBeanReference(Object bean, String beanName) throws BeansException;
  4. }

所以,spring希望我们,在这几处,要返回同样的对象,即:既然你这几处都要返回代理对象,那就不能返回不一样的代理对象。

源码

文章用到的aop循环依赖的demo,自己写一个也可以,很简单:

https://gitee.com/ckl111/spring-boot-first-version-learn/tree/master/all-demo-in-spring-learning/spring-aop-xml-demo-cycle-reference

不错的参考资料

https://blog.csdn.net/f641385712/article/details/92801300

总结

如果有问题,欢迎指出;欢迎加群讨论;有帮助的话,请点个赞吧,谢谢

曹工说Spring Boot源码(29)-- Spring 解决循环依赖为什么使用三级缓存,而不是二级缓存的更多相关文章

  1. 曹工说Spring Boot源码(19)-- Spring 带给我们的工具利器,创建代理不用愁(ProxyFactory)

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  2. 曹工说Spring Boot源码(26)-- 学习字节码也太难了,实在不能忍受了,写了个小小的字节码执行引擎

    曹工说Spring Boot源码(26)-- 学习字节码也太难了,实在不能忍受了,写了个小小的字节码执行引擎 写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean De ...

  3. 曹工说Spring Boot源码(30)-- ConfigurationClassPostProcessor 实在太硬核了,为了了解它,我可能debug了快一天

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  4. 曹工说Spring Boot源码系列开讲了(1)-- Bean Definition到底是什么,附spring思维导图分享

    写在前面的话&&About me 网上写spring的文章多如牛毛,为什么还要写呢,因为,很简单,那是人家写的:网上都鼓励你不要造轮子,为什么你还要造呢,因为,那不是你造的. 我不是要 ...

  5. 曹工说Spring Boot源码(2)-- Bean Definition到底是什么,咱们对着接口,逐个方法讲解

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码系列开讲了(1)-- Bean Definition到底是什么,附spring思维导图分享 工程代码地址 思维导图地址 工程结构图: 正 ...

  6. 曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,我们来试一下

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码系列开讲了(1)-- Bean Definition到底是什么,附spring思维导图分享 工程代码地址 思维导图地址 工程结构图: 大 ...

  7. 曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean definition的?

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码系列开讲了(1)-- Bean Definition到底是什么,附spring思维导图分享 工程代码地址 思维导图地址 工程结构图: 大 ...

  8. 曹工说Spring Boot源码(5)-- 怎么从properties文件读取bean

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  9. 曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

随机推荐

  1. restful 架构风格的curd(增删改查)

    restful架构 概念:REST指的是一组架构约束条件和原则,如果一个架构符合REST的约束条件和原则,就称之为RESTful架构. restful不是一个专门的技术,他是一个规范.规范就是写写代码 ...

  2. Spring杂谈 | 你真的了解泛型吗?从java的Type到Spring的ResolvableType

    关于泛型的基本知识在本文中不会过多提及,本文主要解决的是如何处理泛型,以及java中Type接口下对泛型的一套处理机制,进而分析Spring中的ResolvableType. 文章目录 Type 简介 ...

  3. 201771030120-王嫄 实验一 软件工程准备 <课程学习目的思考>

    项目 内容 课程班级博客链接 https://edu.cnblogs.com/campus/xbsf/nwnu2020SE 这个作业要求链接 https://www.cnblogs.com/nwnu- ...

  4. vue项目-打印页面中指定区域的内容(亲测有效!)

    关于打印整个页面的,没什么好说的.今天我给大家分享一个打印指定区域的方法,你想打印哪里,就打印哪里! 我也是刚刚开始接触打印这一块功能的,然后当然是找度娘深入了解了一番啦,期间试了网上的各种方法,有的 ...

  5. Web的Cookies,Session,Application

    Cookies:客户端(浏览器)存储信息的地方 Session:服务器的内置对象,可以在这里存储信息.按用户区分,每个客户端有一个特定的SessionID.存储时间按分钟计. Application: ...

  6. 写ssm项目的注意点

    注意事项: 输出台乱码 a链接以post提交 表单提交前验证 onsubmit 属性在提交表单时触发. onsubmit 属性只在 中使用. <form action="/demo/d ...

  7. shell 光标处理快捷键

    Ctrl+左右键 单词之间跳转Ctrl+a跳到本行的行首, Ctrl+e则跳到页尾. Ctrl+u删除当前光标前面的文字 ctrl+k-删除当前光标后面的文字 Ctrl+w和Alt+d-对于当前的单词 ...

  8. Elasticsearch系列---几个高级功能

    概要 本篇主要介绍一下搜索模板.映射模板.高亮搜索和地理位置的简单玩法. 标准搜索模板 搜索模板search tempalte高级功能之一,可以将我们的一些搜索进行模板化,使用现有模板时传入指定的参数 ...

  9. mysql计算

    select @csum := 0;select create_time,merchant_id,award as 奖励,total_count as 数量,(@csum := @csum + awa ...

  10. LeetCode链表专题

    链表 套路总结 1.多个指针 移动 2.虚假链表头:凡是有可能删除头节点的都创建一个虚拟头节点,代码可以少一些判断(需要用到首部前一个元素的时候就加虚拟头指针) 3.快慢指针 如leetcode160 ...