webstorm激活方法 webstorm激活码 jetbrains激活pyCharm激活IDEA激活2018jetbrains所有产品激活都可用

安装完成后,打开 WebStorm,

在打开的 License Activation 窗口中选择 License server。

在输入框输入网址即可:

http://hb5.s.osidea.cc:1017(网址在下面 更新 , 最新网址不能用请换以前的试试)

最后点击 Activate。

提示: jetbrains软件都可以用此方法激活

上面的不能用了请用下面的尝试 , 博主也会在第一时间更新.

原始网址列表:

http://idea.imsxm.com/

http://im.js.cn:8888

http://idea.codebeta.cn

一、锁机制

常用的锁机制有两种:1、悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。悲观锁的实现,往往依靠底层提供的锁机制;悲观锁会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。

2、乐观锁:假设不会发生并发冲突,每次不加锁而是假设没有冲突而去完成某项操作,只在提交操作时检查是否违反数据完整性。如果因为冲突失败就重试,直到成功为止。乐观锁大多是基于数据版本记录机制实现。为数据增加一个版本标识,比如在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

乐观锁的缺点是不能解决脏读的问题。在实际生产环境里边,如果并发量不大且不允许脏读,可以使用悲观锁解决并发问题;但如果系统的并发非常大的话,悲观锁定会带来非常大的性能问题,所以我们就要选择乐观锁定的方法.锁机制存在以下问题:(1)在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。(2)一个线程持有锁会导致其它所有需要此锁的线程挂起。(3)如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。

二、CAS 操作

JDK 5之前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。java.util.concurrent(J.U.C)种提供的atomic包中的类,使用的是乐观锁,用到的机制就是CAS,CAS(Compare and Swap)有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

现代的CPU提供了特殊的指令,允许算法执行读-修改-写操作,而无需害怕其他线程同时修改变量,因为如果其他线程修改变量,那么CAS会检测它(并失败),算法可以对该操作重新计算。而 compareAndSet() 就用这些代替了锁定。

以AtomicInteger为例,研究在没有锁的情况下是如何做到数据正确性的。

[java] view plain copypublic class AtomicInteger extends Number implements java.io.Serializable {            private volatile int value;                public final int get() {          return value;      }            public final int getAndIncrement() {          for (;;) {              int current = get();              int next = current + 1;              if (compareAndSet(current, next))                  return current;          }      }            public final boolean compareAndSet(int expect, int update) {          return unsafe.compareAndSwapInt(this, valueOffset, expect, update);      }

字段value需要借助volatile原语,保证线程间的数据是可见的(共享的)。这样在获取变量的值的时候才能直接读取。然后来看看++i是怎么做到的。getAndIncrement采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。而compareAndSet利用JNI来完成CPU指令的操作。

[java] view plain copypublic final boolean compareAndSet(int expect, int update) {         return unsafe.compareAndSwapInt(this, valueOffset, expect, update);   }

整体的过程就是这样子的,利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。而整个J.U.C都是建立在CAS之上的,因此对于synchronized阻塞算法,J.U.C在性能上有了很大的提升。

CAS第一个问题是会导致“ABA问题”。

aba实际上是乐观锁无法解决脏数据读取的一种体现。CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。如果链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。因此AtomicStampedReference/AtomicMarkableReference就很有用了。

AtomicMarkableReference 类描述的一个<Object,Boolean>的对,可以原子的修改Object或者Boolean的值,这种数据结构在一些缓存或者状态描述中比较有用。这种结构在单个或者同时修改Object/Boolean的时候能够有效的提高吞吐量。

AtomicStampedReference 类维护带有整数“标志”的对象引用,可以用原子方式对其进行更新。对比AtomicMarkableReference 类的<Object,Boolean>,AtomicStampedReference 维护的是一种类似<Object,int>的数据结构,其实就是对对象(引用)的一个并发计数(标记版本戳stamp)。但是与AtomicInteger 不同的是,此数据结构可以携带一个对象引用(Object),并且能够对此对象和计数同时进行原子操作。[java] view plain copy

一.静态变量跟实例变量的区别:

1.静态变量:由static修饰,在JVM中,静态变量的加载顺序在对象之前,因此静态变量不依附于对象存在,可以在不实例化类的情况下直接使用静态变量,如下代码所示。

123456789

静态变量属于类,不属于类中任何一个对象,因此静态变量又叫做类变量,一个类不管创建多少个对象(对象是类的一个实例),静态变量在内存中有且仅有一个。

2.实例变量:必须依附于对象存在,只有实例化类后才可以使用此类中的实例变量。

123456789

二.静态方法跟实例方法的区别:

1.静态方法:方法用static关键字修饰,静态方法与静态成员变量一样,属于类本身,在类装载的时候被装载到内存,不自动进行销毁,会一直存在于内存中,直到JVM关闭。使用时也是不需要实例化类,能够直接使用。静态方法无法被重写。

12345678910

需要注意的是:在静态方法中只能访问类中的静态成员跟静态方法,不能直接访问类中的实例变量跟实例方法,原因是静态方法在JVM中的加载顺序也在对象之前,直接使用实例变量跟实例方法的话,可能实例变量跟实例方法所依附的对象并没有被创建,会导致无法找到所使用的实例变量跟实例方法。

要想使用实例变量跟实例方法可以采用如下方法:在静态方法中创建实例变量和实例方法所在的对象,通过这个对象来使用实例变量跟实例方法。如代码所示:

123456789101112131415

2.实例化方法:属于实例对象,实例化后才会分配内存,必须通过类的实例来引用。不会常驻内存,当实例对象被JVM 回收之后,也跟着消失。

1234567891011

附加:

1.线程安全:静态方法是共享代码段,静态变量是共享数据段。既然是“共享”就有并发的问题。非静态方法是针对确定的一个对象的,所以不会存在线程安全的问题。

2.如果静态方法在系统中定义太多,会占用大量的资源,最后造成内存溢出,所以静态方法不能滥用。

Spring IOC设计原理解析:本文乃学习整理参考而来

一、 什么是Ioc/DI?

二、 Spring IOC体系结构

(1) BeanFactory

(2) BeanDefinition

三、 IoC容器的初始化

1、 XmlBeanFactory(屌丝IOC)的整个流程

2、 FileSystemXmlApplicationContext 的IOC容器流程

1、高富帅IOC解剖

2、 设置资源加载器和资源定位

3、AbstractApplicationContext的refresh函数载入Bean定义过程:

4、AbstractApplicationContext子类的refreshBeanFactory()方法:

5、AbstractRefreshableApplicationContext子类的loadBeanDefinitions方法:

6、AbstractBeanDefinitionReader读取Bean定义资源:

7、资源加载器获取要读入的资源:

8、XmlBeanDefinitionReader加载Bean定义资源:

9、DocumentLoader将Bean定义资源转换为Document对象:

10、XmlBeanDefinitionReader解析载入的Bean定义资源文件:

11、DefaultBeanDefinitionDocumentReader对Bean定义的Document对象解析:

12、BeanDefinitionParserDelegate解析Bean定义资源文件中的<Bean>元素:

13、BeanDefinitionParserDelegate解析<property>元素:

14、解析<property>元素的子元素:

15、解析<list>子元素:

16、解析过后的BeanDefinition在IoC容器中的注册:

17、DefaultListableBeanFactory向IoC容器注册解析后的BeanDefinition:

总结:

四、IOC容器的依赖注入

1、依赖注入发生的时间

2、AbstractBeanFactory通过getBean向IoC容器获取被管理的Bean:

3、AbstractAutowireCapableBeanFactory创建Bean实例对象:

4、createBeanInstance方法创建Bean的java实例对象:

5、SimpleInstantiationStrategy类使用默认的无参构造方法创建Bean实例化对象:

6、populateBean方法对Bean属性的依赖注入:

7、BeanDefinitionValueResolver解析属性值:

8、BeanWrapperImpl对Bean属性的依赖注入:

五、IoC容器的高级特性

1、介绍

2、Spring IoC容器的lazy-init属性实现预实例化:

(1) .refresh()

(2).finishBeanFactoryInitialization处理预实例化Bean:

(3) .DefaultListableBeanFactory对配置lazy-init属性单态Bean的预实例化:

3、FactoryBean的实现:

(1).FactoryBean的源码如下:

(2). AbstractBeanFactory的getBean方法调用FactoryBean:

(3)、AbstractBeanFactory生产Bean实例对象:

(4).工厂Bean的实现类getObject方法创建Bean实例对象:

4.BeanPostProcessor后置处理器的实现:

(1).BeanPostProcessor的源码如下:

(2).AbstractAutowireCapableBeanFactory类对容器生成的Bean添加后置处理器:

(3).initializeBean方法为容器产生的Bean实例对象添加BeanPostProcessor后置处理器:

(4).AdvisorAdapterRegistrationManager在Bean对象初始化后注册通知适配器:

5.Spring IoC容器autowiring实现原理:

(1). AbstractAutoWireCapableBeanFactory对Bean实例进行属性依赖注入:

(2).Spring IoC容器根据Bean名称或者类型进行autowiring自动依赖注入:

(3).DefaultSingletonBeanRegistry的registerDependentBean方法对属性注入:

一、什么是Ioc/DI?

IoC 容器:最主要是完成了完成对象的创建和依赖的管理注入等等。

先从我们自己设计这样一个视角来考虑:

所谓控制反转,就是把原先我们代码里面需要实现的对象创建、依赖的代码,反转给容器来帮忙实现。那么必然的我们需要创建一个容器,同时需要一种描述来让容器知道需要创建的对象与对象的关系。这个描述最具体表现就是我们可配置的文件。

对象和对象关系怎么表示?

可以用 xml , properties 文件等语义化配置文件表示。

描述对象关系的文件存放在哪里?

可能是 classpath , filesystem ,或者是 URL 网络资源, servletContext 等。

回到正题,有了配置文件,还需要对配置文件解析。

不同的配置文件对对象的描述不一样,如标准的,自定义声明式的,如何统一? 在内部需要有一个统一的关于对象的定义,所有外部的描述都必须转化成统一的描述定义。

如何对不同的配置文件进行解析?需要对不同的配置文件语法,采用不同的解析器

二、 Spring IOC体系结构?

(1) BeanFactory

Spring Bean的创建是典型的工厂模式,这一系列的Bean工厂,也即IOC容器为开发者管理对象间的依赖关系提供了很多便利和基础服务,在Spring中有许多的IOC容器的实现供用户选择和使用,其相互关系如下:

其中BeanFactory作为最顶层的一个接口类,它定义了IOC容器的基本功能规范,BeanFactory 有三个子类:ListableBeanFactory、HierarchicalBeanFactory 和AutowireCapableBeanFactory。但是从上图中我们可以发现最终的默认实现类是 DefaultListableBeanFactory,他实现了所有的接口。那为何要定义这么多层次的接口呢?查阅这些接口的源码和说明发现,每个接口都有他使用的场合,它主要是为了区分在 Spring 内部在操作过程中对象的传递和转化过程中,对对象的数据访问所做的限制。例如 ListableBeanFactory 接口表示这些 Bean 是可列表的,而 HierarchicalBeanFactory 表示的是这些 Bean 是有继承关系的,也就是每个Bean 有可能有父 Bean。AutowireCapableBeanFactory 接口定义 Bean 的自动装配规则。这四个接口共同定义了 Bean 的集合、Bean 之间的关系、以及 Bean 行为.

最基本的IOC容器接口BeanFactory

复制代码

1 public interface BeanFactory { 2 3 //对FactoryBean的转义定义,因为如果使用bean的名字检索FactoryBean得到的对象是工厂生成的对象, 4 //如果需要得到工厂本身,需要转义 5 String FACTORY_BEAN_PREFIX = "&"; 6 7 //根据bean的名字,获取在IOC容器中得到bean实例 8 Object getBean(String name) throws BeansException; 9 10 //根据bean的名字和Class类型来得到bean实例,增加了类型安全验证机制。 11 Object getBean(String name, Class requiredType) throws BeansException; 12 13 //提供对bean的检索,看看是否在IOC容器有这个名字的bean 14 boolean containsBean(String name); 15 16 //根据bean名字得到bean实例,并同时判断这个bean是不是单例 17 boolean isSingleton(String name) throws NoSuchBeanDefinitionException; 18 19 //得到bean实例的Class类型 20 Class getType(String name) throws NoSuchBeanDefinitionException; 21 22 //得到bean的别名,如果根据别名检索,那么其原名也会被检索出来 23 String[] getAliases(String name); 24 }

复制代码

在BeanFactory里只对IOC容器的基本行为作了定义,根本不关心你的bean是如何定义怎样加载的。正如我们只关心工厂里得到什么的产品对象,至于工厂是怎么生产这些对象的,这个基本的接口不关心。

而要知道工厂是如何产生对象的,我们需要看具体的IOC容器实现,spring提供了许多IOC容器的实现。比如XmlBeanFactory,ClasspathXmlApplicationContext等。其中XmlBeanFactory就是针对最基本的ioc容器的实现,这个IOC容器可以读取XML文件定义的BeanDefinition(XML文件中对bean的描述),如果说XmlBeanFactory是容器中的屌丝,ApplicationContext应该算容器中的高帅富.

ApplicationContext是Spring提供的一个高级的IoC容器,它除了能够提供IoC容器的基本功能外,还为用户提供了以下的附加服务。

从ApplicationContext接口的实现,我们看出其特点:

1.  支持信息源,可以实现国际化。(实现MessageSource接口)

2.  访问资源。(实现ResourcePatternResolver接口,这个后面要讲)

3.  支持应用事件。(实现ApplicationEventPublisher接口)

(2) BeanDefinition

SpringIOC容器管理了我们定义的各种Bean对象及其相互的关系,Bean对象在Spring实现中是以BeanDefinition来描述的,其继承体系如下:

Bean 的解析过程非常复杂,功能被分的很细,因为这里需要被扩展的地方很多,必须保证有足够的灵活性,以应对可能的变化。Bean 的解析主要就是对 Spring 配置文件的解析。这个解析过程主要通过下图中的类完成:

三、IoC容器的初始化?

IoC容器的初始化包括BeanDefinition的Resource定位、载入和注册这三个基本的过程。我们以ApplicationContext为例讲解,ApplicationContext系列容器也许是我们最熟悉的,因为web项目中使用的XmlWebApplicationContext就属于这个继承体系,还有ClasspathXmlApplicationContext等,其继承体系如下图所示:

ApplicationContext允许上下文嵌套,通过保持父上下文可以维持一个上下文体系。对于bean的查找可以在这个上下文体系中发生,首先检查当前上下文,其次是父上下文,逐级向上,这样为不同的Spring应用提供了一个共享的bean定义环境。

下面我们分别简单地演示一下两种ioc容器的创建过程

1、XmlBeanFactory(屌丝IOC)的整个流程

通过XmlBeanFactory的源码,我们可以发现:

复制代码

public class XmlBeanFactory extends DefaultListableBeanFactory{ private final XmlBeanDefinitionReader reader; public XmlBeanFactory(Resource resource)throws BeansException{ this(resource, null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException{ super(parentBeanFactory); this.reader = new XmlBeanDefinitionReader(this); this.reader.loadBeanDefinitions(resource); } }

复制代码

复制代码

//根据Xml配置文件创建Resource资源对象,该对象中包含了BeanDefinition的信息 ClassPathResource resource =new ClassPathResource("application-context.xml");//创建DefaultListableBeanFactory DefaultListableBeanFactory factory =new DefaultListableBeanFactory();//创建XmlBeanDefinitionReader读取器,用于载入BeanDefinition。之所以需要BeanFactory作为参数,是因为会将读取的信息回调配置给factory XmlBeanDefinitionReader reader =new XmlBeanDefinitionReader(factory);//XmlBeanDefinitionReader执行载入BeanDefinition的方法,最后会完成Bean的载入和注册。完成后Bean就成功的放置到IOC容器当中,以后我们就可以从中取得Bean来使用 reader.loadBeanDefinitions(resource);

复制代码

通过前面的源码,this.reader = new XmlBeanDefinitionReader(this); 中其中this 传的是factory对象

2、FileSystemXmlApplicationContext 的IOC容器流程

1、高富帅IOC解剖

1   ApplicationContext =new FileSystemXmlApplicationContext(xmlPath);

先看其构造函数:

调用构造函数:

复制代码

/*** Create a new FileSystemXmlApplicationContext, loading the definitions* from the given XML files and automatically refreshing the context.* @param configLocations array of file paths* @throws BeansException if context creation failed */public FileSystemXmlApplicationContext(String... configLocations) throws BeansException { this(configLocations, true, null); }

复制代码

实际调用

复制代码

public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } }

复制代码

2、设置资源加载器和资源定位

通过分析FileSystemXmlApplicationContext的源代码可以知道,在创建FileSystemXmlApplicationContext容器时,构造方法做以下两项重要工作:

首先,调用父类容器的构造方法(super(parent)方法)为容器设置好Bean资源加载器。

然后,再调用父类AbstractRefreshableConfigApplicationContext的setConfigLocations(configLocations)方法设置Bean定义资源文件的定位路径。

通过追踪FileSystemXmlApplicationContext的继承体系,发现其父类的父类AbstractApplicationContext中初始化IoC容器所做的主要源码如下:

复制代码

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext, DisposableBean { //静态初始化块,在整个容器创建过程中只执行一次 static { //为了避免应用程序在Weblogic8.1关闭时出现类加载异常加载问题,加载IoC容 //器关闭事件(ContextClosedEvent)类 ContextClosedEvent.class.getName(); } //FileSystemXmlApplicationContext调用父类构造方法调用的就是该方法 public AbstractApplicationContext(ApplicationContext parent) { this.parent = parent; this.resourcePatternResolver = getResourcePatternResolver(); } //获取一个Spring Source的加载器用于读入Spring Bean定义资源文件 protected ResourcePatternResolver getResourcePatternResolver() { // AbstractApplicationContext继承DefaultResourceLoader,也是一个S //Spring资源加载器,其getResource(String location)方法用于载入资源 return new PathMatchingResourcePatternResolver(this); } …… }

复制代码

AbstractApplicationContext构造方法中调用PathMatchingResourcePatternResolver的构造方法创建Spring资源加载器:

public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) { Assert.notNull(resourceLoader, "ResourceLoader must not be null"); //设置Spring的资源加载器 this.resourceLoader = resourceLoader; }

在设置容器的资源加载器之后,接下来FileSystemXmlApplicationContet执行setConfigLocations方法通过调用其父类AbstractRefreshableConfigApplicationContext的方法进行对Bean定义资源文件的定位,该方法的源码如下:

复制代码

//处理单个资源文件路径为一个字符串的情况 public void setConfigLocation(String location) { //String CONFIG_LOCATION_DELIMITERS = ",; /t/n"; //即多个资源文件路径之间用” ,; /t/n”分隔,解析成数组形式 setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS)); } //解析Bean定义资源文件的路径,处理多个资源文件字符串数组 public void setConfigLocations(String[] locations) { if (locations != null) { Assert.noNullElements(locations, "Config locations must not be null"); this.configLocations = new String[locations.length]; for (int i = 0; i < locations.length; i++) { // resolvePath为同一个类中将字符串解析为路径的方法 this.configLocations[i] = resolvePath(locations[i]).trim(); } } else { this.configLocations = null; } }

复制代码

通过这两个方法的源码我们可以看出,我们既可以使用一个字符串来配置多个Spring Bean定义资源文件,也可以使用字符串数组,即下面两种方式都是可以的:

a.    ClasspathResource res = new ClasspathResource(“a.xml,b.xml,……”);

多个资源文件路径之间可以是用” ,; /t/n”等分隔。

b.    ClasspathResource res = new ClasspathResource(newString[]{“a.xml”,”b.xml”,……});

至此,Spring IoC容器在初始化时将配置的Bean定义资源文件定位为Spring封装的Resource。

3、AbstractApplicationContext的refresh函数载入Bean定义过程:

Spring IoC容器对Bean定义资源的载入是从refresh()函数开始的,refresh()是一个模板方法,refresh()方法的作用是:在创建IoC容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IoC容器。refresh的作用类似于对IoC容器的重启,在新建立好的容器中对容器进行初始化,对Bean定义资源进行载入

FileSystemXmlApplicationContext通过调用其父类AbstractApplicationContext的refresh()函数启动整个IoC容器对Bean定义的载入过程:

复制代码

1 public void refresh() throws BeansException, IllegalStateException { 2 synchronized (this.startupShutdownMonitor) { 3 //调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识 4 prepareRefresh(); 5 //告诉子类启动refreshBeanFactory()方法,Bean定义资源文件的载入从 6 //子类的refreshBeanFactory()方法启动 7 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); 8 //为BeanFactory配置容器特性,例如类加载器、事件处理器等 9 prepareBeanFactory(beanFactory); 10 try { 11 //为容器的某些子类指定特殊的BeanPost事件处理器 12 postProcessBeanFactory(beanFactory); 13 //调用所有注册的BeanFactoryPostProcessor的Bean 14 invokeBeanFactoryPostProcessors(beanFactory); 15 //为BeanFactory注册BeanPost事件处理器. 16 //BeanPostProcessor是Bean后置处理器,用于监听容器触发的事件 17 registerBeanPostProcessors(beanFactory); 18 //初始化信息源,和国际化相关. 19 initMessageSource(); 20 //初始化容器事件传播器. 21 initApplicationEventMulticaster(); 22 //调用子类的某些特殊Bean初始化方法 23 onRefresh(); 24 //为事件传播器注册事件监听器. 25 registerListeners(); 26 //初始化所有剩余的单态Bean. 27 finishBeanFactoryInitialization(beanFactory); 28 //初始化容器的生命周期事件处理器,并发布容器的生命周期事件 29 finishRefresh(); 30 } 31 catch (BeansException ex) { 32 //销毁以创建的单态Bean 33 destroyBeans(); 34 //取消refresh操作,重置容器的同步标识. 35 cancelRefresh(ex); 36 throw ex; 37 } 38 } 39 }

复制代码

refresh()方法主要为IoC容器Bean的生命周期管理提供条件,Spring IoC容器载入Bean定义资源文件从其子类容器的refreshBeanFactory()方法启动,所以整个refresh()中“ConfigurableListableBeanFactory beanFactory =obtainFreshBeanFactory();”这句以后代码的都是注册容器的信息源和生命周期事件,载入过程就是从这句代码启动。

refresh()方法的作用是:在创建IoC容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IoC容器。refresh的作用类似于对IoC容器的重启,在新建立好的容器中对容器进行初始化,对Bean定义资源进行载入

AbstractApplicationContext的obtainFreshBeanFactory()方法调用子类容器的refreshBeanFactory()方法,启动容器载入Bean定义资源文件的过程,代码如下:

复制代码

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { //这里使用了委派设计模式,父类定义了抽象的refreshBeanFactory()方法,具体实现调用子类容器的refreshBeanFactory()方法 refreshBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) { logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); } return beanFactory; }

复制代码

AbstractApplicationContext子类的refreshBeanFactory()方法:

AbstractApplicationContext类中只抽象定义了refreshBeanFactory()方法,容器真正调用的是其子类AbstractRefreshableApplicationContext实现的    refreshBeanFactory()方法,方法的源码如下:

复制代码

1 protected final void refreshBeanFactory() throws BeansException { 2 if (hasBeanFactory()) {//如果已经有容器,销毁容器中的bean,关闭容器 3 destroyBeans(); 4 closeBeanFactory(); 5 } 6 try { 7 //创建IoC容器 8 DefaultListableBeanFactory beanFactory = createBeanFactory(); 9 beanFactory.setSerializationId(getId()); 10 //对IoC容器进行定制化,如设置启动参数,开启注解的自动装配等 11 customizeBeanFactory(beanFactory); 12 //调用载入Bean定义的方法,主要这里又使用了一个委派模式,在当前类中只定义了抽象的loadBeanDefinitions方法,具体的实现调用子类容器 13 loadBeanDefinitions(beanFactory); 14 synchronized (this.beanFactoryMonitor) { 15 this.beanFactory = beanFactory; 16 } 17 } 18 catch (IOException ex) { 19 throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); 20 } 21 }

复制代码

在这个方法中,先判断BeanFactory是否存在,如果存在则先销毁beans并关闭beanFactory,接着创建DefaultListableBeanFactory,并调用loadBeanDefinitions(beanFactory)装载bean

定义。

5、AbstractRefreshableApplicationContext子类的loadBeanDefinitions方法:

AbstractRefreshableApplicationContext中只定义了抽象的loadBeanDefinitions方法,容器真正调用的是其子类AbstractXmlApplicationContext对该方法的实现,AbstractXmlApplicationContext的主要源码如下:

loadBeanDefinitions方法同样是抽象方法,是由其子类实现的,也即在AbstractXmlApplicationContext中。

复制代码

1 public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext { 2 …… 3 //实现父类抽象的载入Bean定义方法 4 @Override 5 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { 6 //创建XmlBeanDefinitionReader,即创建Bean读取器,并通过回调设置到容器中去,容 器使用该读取器读取Bean定义资源 7 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); 8 //为Bean读取器设置Spring资源加载器,AbstractXmlApplicationContext的 9 //祖先父类AbstractApplicationContext继承DefaultResourceLoader,因此,容器本身也是一个资源加载器 10 beanDefinitionReader.setResourceLoader(this); 11 //为Bean读取器设置SAX xml解析器 12 beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); 13 //当Bean读取器读取Bean定义的Xml资源文件时,启用Xml的校验机制 14 initBeanDefinitionReader(beanDefinitionReader); 15 //Bean读取器真正实现加载的方法 16 loadBeanDefinitions(beanDefinitionReader); 17 } 18 //Xml Bean读取器加载Bean定义资源 19 protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { 20 //获取Bean定义资源的定位 21 Resource[] configResources = getConfigResources(); 22 if (configResources != null) { 23 //Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位 24 //的Bean定义资源 25 reader.loadBeanDefinitions(configResources); 26 } 27 //如果子类中获取的Bean定义资源定位为空,则获取FileSystemXmlApplicationContext构造方法中setConfigLocations方法设置的资源 28 String[] configLocations = getConfigLocations(); 29 if (configLocations != null) { 30 //Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位 31 //的Bean定义资源 32 reader.loadBeanDefinitions(configLocations); 33 } 34 } 35 //这里又使用了一个委托模式,调用子类的获取Bean定义资源定位的方法 36 //该方法在ClassPathXmlApplicationContext中进行实现,对于我们 37 //举例分析源码的FileSystemXmlApplicationContext没有使用该方法 38 protected Resource[] getConfigResources() { 39 return null; 40 } …… 41}

复制代码

Xml Bean读取器(XmlBeanDefinitionReader)调用其父类AbstractBeanDefinitionReader的 reader.loadBeanDefinitions方法读取Bean定义资源。

由于我们使用FileSystemXmlApplicationContext作为例子分析,因此getConfigResources的返回值为null,因此程序执行reader.loadBeanDefinitions(configLocations)分支。

6、AbstractBeanDefinitionReader读取Bean定义资源:

AbstractBeanDefinitionReader的loadBeanDefinitions方法源码如下:

可以到org.springframework.beans.factory.support看一下BeanDefinitionReader的结构

在其抽象父类AbstractBeanDefinitionReader中定义了载入过程

复制代码

1 //重载方法,调用下面的loadBeanDefinitions(String, Set<Resource>);方法 2 public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { 3 return loadBeanDefinitions(location, null); 4 } 5 public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException { 6 //获取在IoC容器初始化过程中设置的资源加载器 7 ResourceLoader resourceLoader = getResourceLoader(); 8 if (resourceLoader == null) { 9 throw new BeanDefinitionStoreException( 10 "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); 11 } 12 if (resourceLoader instanceof ResourcePatternResolver) { 13 try { 14 //将指定位置的Bean定义资源文件解析为Spring IoC容器封装的资源 15 //加载多个指定位置的Bean定义资源文件 16 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); 17 //委派调用其子类XmlBeanDefinitionReader的方法,实现加载功能 18 int loadCount = loadBeanDefinitions(resources); 19 if (actualResources != null) { 20 for (Resource resource : resources) { 21 actualResources.add(resource); 22 } 23 } 24 if (logger.isDebugEnabled()) { 25 logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]"); 26 } 27 return loadCount; 28 } 29 catch (IOException ex) { 30 throw new BeanDefinitionStoreException( 31 "Could not resolve bean definition resource pattern [" + location + "]", ex); 32 } 33 } 34 else { 35 //将指定位置的Bean定义资源文件解析为Spring IoC容器封装的资源 36 //加载单个指定位置的Bean定义资源文件 37 Resource resource = resourceLoader.getResource(location); 38 //委派调用其子类XmlBeanDefinitionReader的方法,实现加载功能 39 int loadCount = loadBeanDefinitions(resource); 40 if (actualResources != null) { 41 actualResources.add(resource); 42 } 43 if (logger.isDebugEnabled()) { 44 logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); 45 } 46 return loadCount; 47 } 48 } 49 //重载方法,调用loadBeanDefinitions(String); 50 public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException { 51 Assert.notNull(locations, "Location array must not be null"); 52 int counter = 0; 53 for (String location : locations) { 54 counter += loadBeanDefinitions(location); 55 } 56 return counter; }

复制代码

loadBeanDefinitions(Resource...resources)方法和上面分析的3个方法类似,同样也是调用XmlBeanDefinitionReader的loadBeanDefinitions方法。

从对AbstractBeanDefinitionReader的loadBeanDefinitions方法源码分析可以看出该方法做了以下两件事:

首先,调用资源加载器的获取资源方法resourceLoader.getResource(location),获取到要加载的资源。

其次,真正执行加载功能是其子类XmlBeanDefinitionReader的loadBeanDefinitions方法。

看到第8、16行,结合上面的ResourceLoader与ApplicationContext的继承关系图,可以知道此时调用的是DefaultResourceLoader中的getSource()方法定位Resource,因为FileSystemXmlApplicationContext本身就是DefaultResourceLoader的实现类,所以此时又回到了FileSystemXmlApplicationContext中来。

7、资源加载器获取要读入的资源:

XmlBeanDefinitionReader通过调用其父类DefaultResourceLoader的getResource方法获取要加载的资源,其源码如下

复制代码

1 //获取Resource的具体实现方法 2 public Resource getResource(String location) { 3 Assert.notNull(location, "Location must not be null"); 4 //如果是类路径的方式,那需要使用ClassPathResource 来得到bean 文件的资源对象 5 if (location.startsWith(CLASSPATH_URL_PREFIX)) { 6 return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); 7 } 8 try { 9 // 如果是URL 方式,使用UrlResource 作为bean 文件的资源对象 10 URL url = new URL(location); 11 return new UrlResource(url); 12 } 13 catch (MalformedURLException ex) { 14 } 15 //如果既不是classpath标识,又不是URL标识的Resource定位,则调用 16 //容器本身的getResourceByPath方法获取Resource 17 return getResourceByPath(location); 18 19 }

复制代码

FileSystemXmlApplicationContext容器提供了getResourceByPath方法的实现,就是为了处理既不是classpath标识,又不是URL标识的Resource定位这种情况。

复制代码

protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } //这里使用文件系统资源对象来定义bean 文件 return new FileSystemResource(path); }

复制代码

这样代码就回到了 FileSystemXmlApplicationContext 中来,他提供了FileSystemResource 来完成从文件系统得到配置文件的资源定义。

这样,就可以从文件系统路径上对IOC 配置文件进行加载 - 当然我们可以按照这个逻辑从任何地方加载,在Spring 中我们看到它提供 的各种资源抽象,比如ClassPathResource, URLResource,FileSystemResource 等来供我们使用。上面我们看到的是定位Resource 的一个过程,而这只是加载过程的一部分.

8、XmlBeanDefinitionReader加载Bean定义资源:

Bean定义的Resource得到了

继续回到XmlBeanDefinitionReader的loadBeanDefinitions(Resource …)方法看到代表bean文件的资源定义以后的载入过程。

复制代码

1 //XmlBeanDefinitionReader加载资源的入口方法 2 public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { 3 //将读入的XML资源进行特殊编码处理 4 return loadBeanDefinitions(new EncodedResource(resource)); 5 } //这里是载入XML形式Bean定义资源文件方法6 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { 7 ....... 8 try { 9 //将资源文件转为InputStream的IO流 10 InputStream inputStream = encodedResource.getResource().getInputStream(); 11 try { 12 //从InputStream中得到XML的解析源 13 InputSource inputSource = new InputSource(inputStream); 14 if (encodedResource.getEncoding() != null) { 15 inputSource.setEncoding(encodedResource.getEncoding()); 16 } 17 //这里是具体的读取过程 18 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); 19 } 20 finally { 21 //关闭从Resource中得到的IO流 22 inputStream.close(); 23 } 24 } 25 ......... 26} 27 //从特定XML文件中实际载入Bean定义资源的方法 28 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) 29 throws BeanDefinitionStoreException { 30 try { 31 int validationMode = getValidationModeForResource(resource); 32 //将XML文件转换为DOM对象,解析过程由documentLoader实现 33 Document doc = this.documentLoader.loadDocument( 34 inputSource, this.entityResolver, this.errorHandler, validationMode, this.namespaceAware); 35 //这里是启动对Bean定义解析的详细过程,该解析过程会用到Spring的Bean配置规则36 return registerBeanDefinitions(doc, resource); 37 } 38 ....... }

复制代码

通过源码分析,载入Bean定义资源文件的最后一步是将Bean定义资源转换为Document对象,该过程由documentLoader实现

9、DocumentLoader将Bean定义资源转换为Document对象:

DocumentLoader将Bean定义资源转换成Document对象的源码如下:

复制代码

1 //使用标准的JAXP将载入的Bean定义资源转换成document对象 2 public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, 3 ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception { 4 //创建文件解析器工厂 5 DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware); 6 if (logger.isDebugEnabled()) { 7 logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]"); 8 } 9 //创建文档解析器 10 DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler); 11 //解析Spring的Bean定义资源 12 return builder.parse(inputSource); 13 } 14 protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware) 15 throws ParserConfigurationException { 16 //创建文档解析工厂 17 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 18 factory.setNamespaceAware(namespaceAware); 19 //设置解析XML的校验 20 if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) { 21 factory.setValidating(true); 22 if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) { 23 factory.setNamespaceAware(true); 24 try { 25 factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE); 26 } 27 catch (IllegalArgumentException ex) { 28 ParserConfigurationException pcex = new ParserConfigurationException( 29 "Unable to validate using XSD: Your JAXP provider [" + factory + 30 "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " + 31 "Upgrade to Apache Xerces (or Java 1.5) for full XSD support."); 32 pcex.initCause(ex); 33 throw pcex; 34 } 35 } 36 } 37 return factory; 38 }

复制代码

该解析过程调用JavaEE标准的JAXP标准进行处理。

至此Spring IoC容器根据定位的Bean定义资源文件,将其加载读入并转换成为Document对象过程完成。

接下来我们要继续分析Spring IoC容器将载入的Bean定义资源文件转换为Document对象之后,是如何将其解析为Spring IoC管理的Bean对象并将其注册到容器中的。

10、XmlBeanDefinitionReader解析载入的Bean定义资源文件:

XmlBeanDefinitionReader类中的doLoadBeanDefinitions方法是从特定XML文件中实际载入Bean定义资源的方法,该方法在载入Bean定义资源之后将其转换为Document对象,接下来调用registerBeanDefinitions启动Spring IoC容器对Bean定义的解析过程,registerBeanDefinitions方法源码如下:

复制代码

1 //按照Spring的Bean语义要求将Bean定义资源解析并转换为容器内部数据结构 2 public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { 3 //得到BeanDefinitionDocumentReader来对xml格式的BeanDefinition解析 4 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); 5 //获得容器中注册的Bean数量 6 int countBefore = getRegistry().getBeanDefinitionCount(); 7 //解析过程入口,这里使用了委派模式,BeanDefinitionDocumentReader只是个接口,//具体的解析实现过程有实现类DefaultBeanDefinitionDocumentReader完成 8 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); 9 //统计解析的Bean数量 10 return getRegistry().getBeanDefinitionCount() - countBefore; 11 } 12 //创建BeanDefinitionDocumentReader对象,解析Document对象 13 protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() { 14 return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass)); }

复制代码

Bean定义资源的载入解析分为以下两个过程:

首先,通过调用XML解析器将Bean定义资源文件转换得到Document对象,但是这些Document对象并没有按照Spring的Bean规则进行解析。这一步是载入的过程

其次,在完成通用的XML解析之后,按照Spring的Bean规则对Document对象进行解析。

按照Spring的Bean规则对Document对象解析的过程是在接口BeanDefinitionDocumentReader的实现类DefaultBeanDefinitionDocumentReader中实现的。

11、DefaultBeanDefinitionDocumentReader对Bean定义的Document对象解析:

BeanDefinitionDocumentReader接口通过registerBeanDefinitions方法调用其实现类DefaultBeanDefinitionDocumentReader对Document对象进行解析,解析的代码如下:

复制代码

1 //根据Spring DTD对Bean的定义规则解析Bean定义Document对象 2 public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { 3 //获得XML描述符 4 this.readerContext = readerContext; 5 logger.debug("Loading bean definitions"); 6 //获得Document的根元素 7 Element root = doc.getDocumentElement(); 8 //具体的解析过程由BeanDefinitionParserDelegate实现, 9 //BeanDefinitionParserDelegate中定义了Spring Bean定义XML文件的各种元素 10 BeanDefinitionParserDelegate delegate = createHelper(readerContext, root); 11 //在解析Bean定义之前,进行自定义的解析,增强解析过程的可扩展性 12 preProcessXml(root); 13 //从Document的根元素开始进行Bean定义的Document对象 14 parseBeanDefinitions(root, delegate); 15 //在解析Bean定义之后,进行自定义的解析,增加解析过程的可扩展性 16 postProcessXml(root); 17 } 18 //创建BeanDefinitionParserDelegate,用于完成真正的解析过程 19 protected BeanDefinitionParserDelegate createHelper(XmlReaderContext readerContext, Element root) { 20 BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext); 21 //BeanDefinitionParserDelegate初始化Document根元素 22 delegate.initDefaults(root); 23 return delegate; 24 } 25 //使用Spring的Bean规则从Document的根元素开始进行Bean定义的Document对象 26 protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { 27 //Bean定义的Document对象使用了Spring默认的XML命名空间 28 if (delegate.isDefaultNamespace(root)) { 29 //获取Bean定义的Document对象根元素的所有子节点 30 NodeList nl = root.getChildNodes(); 31 for (int i = 0; i < nl.getLength(); i++) { 32 Node node = nl.item(i); 33 //获得Document节点是XML元素节点 34 if (node instanceof Element) { 35 Element ele = (Element) node; 36 //Bean定义的Document的元素节点使用的是Spring默认的XML命名空间 37 if (delegate.isDefaultNamespace(ele)) { 38 //使用Spring的Bean规则解析元素节点 39 parseDefaultElement(ele, delegate); 40 } 41 else { 42 //没有使用Spring默认的XML命名空间,则使用用户自定义的解//析规则解析元素节点 43 delegate.parseCustomElement(ele); 44 } 45 } 46 } 47 } 48 else { 49 //Document的根节点没有使用Spring默认的命名空间,则使用用户自定义的 50 //解析规则解析Document根节点 51 delegate.parseCustomElement(root); 52 } 53 } 54 //使用Spring的Bean规则解析Document元素节点 55 private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { 56 //如果元素节点是<Import>导入元素,进行导入解析 57 if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { 58 importBeanDefinitionResource(ele); 59 } 60 //如果元素节点是<Alias>别名元素,进行别名解析 61 else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { 62 processAliasRegistration(ele); 63 } 64 //元素节点既不是导入元素,也不是别名元素,即普通的<Bean>元素, 65 //按照Spring的Bean规则解析元素 66 else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { 67 processBeanDefinition(ele, delegate); 68 } 69 } 70 //解析<Import>导入元素,从给定的导入路径加载Bean定义资源到Spring IoC容器中 71 protected void importBeanDefinitionResource(Element ele) { 72 //获取给定的导入元素的location属性 73 String location = ele.getAttribute(RESOURCE_ATTRIBUTE); 74 //如果导入元素的location属性值为空,则没有导入任何资源,直接返回 75 if (!StringUtils.hasText(location)) { 76 getReaderContext().error("Resource location must not be empty", ele); 77 return; 78 } 79 //使用系统变量值解析location属性值 80 location = SystemPropertyUtils.resolvePlaceholders(location); 81 Set<Resource> actualResources = new LinkedHashSet<Resource>(4); 82 //标识给定的导入元素的location是否是绝对路径 83 boolean absoluteLocation = false; 84 try { 85 absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute(); 86 } 87 catch (URISyntaxException ex) { 88 //给定的导入元素的location不是绝对路径 89 } 90 //给定的导入元素的location是绝对路径 91 if (absoluteLocation) { 92 try { 93 //使用资源读入器加载给定路径的Bean定义资源 94 int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources); 95 if (logger.isDebugEnabled()) { 96 logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]"); 97 } 98 } 99 catch (BeanDefinitionStoreException ex) { 100 getReaderContext().error( 101 "Failed to import bean definitions from URL location [" + location + "]", ele, ex); 102 } 103 } 104 else { 105 //给定的导入元素的location是相对路径 106 try { 107 int importCount; 108 //将给定导入元素的location封装为相对路径资源 109 Resource relativeResource = getReaderContext().getResource().createRelative(location); 110 //封装的相对路径资源存在 111 if (relativeResource.exists()) { 112 //使用资源读入器加载Bean定义资源 113 importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource); 114 actualResources.add(relativeResource); 115 } 116 //封装的相对路径资源不存在 117 else { 118 //获取Spring IoC容器资源读入器的基本路径 119 String baseLocation = getReaderContext().getResource().getURL().toString(); 120 //根据Spring IoC容器资源读入器的基本路径加载给定导入 121 //路径的资源 122 importCount = getReaderContext().getReader().loadBeanDefinitions( 123 StringUtils.applyRelativePath(baseLocation, location), actualResources); 124 } 125 if (logger.isDebugEnabled()) { 126 logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]"); 127 } 128 } 129 catch (IOException ex) { 130 getReaderContext().error("Failed to resolve current resource location", ele, ex); 131 } 132 catch (BeanDefinitionStoreException ex) { 133 getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]", 134 ele, ex); 135 } 136 } 137 Resource[] actResArray = actualResources.toArray(new Resource[actualResources.size()]); 138 //在解析完<Import>元素之后,发送容器导入其他资源处理完成事件 139 getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele)); 140 } 141 //解析<Alias>别名元素,为Bean向Spring IoC容器注册别名 142 protected void processAliasRegistration(Element ele) { 143 //获取<Alias>别名元素中name的属性值 144 String name = ele.getAttribute(NAME_ATTRIBUTE); 145 //获取<Alias>别名元素中alias的属性值 146 String alias = ele.getAttribute(ALIAS_ATTRIBUTE); 147 boolean valid = true; 148 //<alias>别名元素的name属性值为空 149 if (!StringUtils.hasText(name)) { 150 getReaderContext().error("Name must not be empty", ele); 151 valid = false; 152 } 153 //<alias>别名元素的alias属性值为空 154 if (!StringUtils.hasText(alias)) { 155 getReaderContext().error("Alias must not be empty", ele); 156 valid = false; 157 } 158 if (valid) { 159 try { 160 //向容器的资源读入器注册别名 161 getReaderContext().getRegistry().registerAlias(name, alias); 162 } 163 catch (Exception ex) { 164 getReaderContext().error("Failed to register alias '" + alias + 165 "' for bean with name '" + name + "'", ele, ex); 166 } 167 //在解析完<Alias>元素之后,发送容器别名处理完成事件 168 getReaderContext().fireAliasRegistered(name, alias, extractSource(ele)); 169 } 170 } 171 //解析Bean定义资源Document对象的普通元素 172 protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { 173 // BeanDefinitionHolder是对BeanDefinition的封装,即Bean定义的封装类 174 //对Document对象中<Bean>元素的解析由BeanDefinitionParserDelegate实现 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); 175 if (bdHolder != null) { 176 bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); 177 try { 178 //向Spring IoC容器注册解析得到的Bean定义,这是Bean定义向IoC容器注册的入口 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); 179 } 180 catch (BeanDefinitionStoreException ex) { 181 getReaderContext().error("Failed to register bean definition with name '" + 182 bdHolder.getBeanName() + "'", ele, ex); 183 } 184 //在完成向Spring IoC容器注册解析得到的Bean定义之后,发送注册事件 185 getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); 186 } 187 }

复制代码

通过上述Spring IoC容器对载入的Bean定义Document解析可以看出,我们使用Spring时,在Spring配置文件中可以使用<Import>元素来导入IoC容器所需要的其他资源,Spring IoC容器在解析时会首先将指定导入的资源加载进容器中。使用<Ailas>别名时,Spring IoC容器首先将别名元素所定义的别名注册到容器中。

对于既不是<Import>元素,又不是<Alias>元素的元素,即Spring配置文件中普通的<Bean>元素的解析由BeanDefinitionParserDelegate类的parseBeanDefinitionElement方法来实现。

12、BeanDefinitionParserDelegate解析Bean定义资源文件中的<Bean>元素:

Bean定义资源文件中的<Import>和<Alias>元素解析在DefaultBeanDefinitionDocumentReader中已经完成,对Bean定义资源文件中使用最多的<Bean>元素交由BeanDefinitionParserDelegate来解析,其解析实现的源码如下:

复制代码

1 //解析<Bean>元素的入口 2 public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) { 3 return parseBeanDefinitionElement(ele, null); 4 } 5 //解析Bean定义资源文件中的<Bean>元素,这个方法中主要处理<Bean>元素的id,name 6 //和别名属性 7 public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) { 8 //获取<Bean>元素中的id属性值 9 String id = ele.getAttribute(ID_ATTRIBUTE); 10 //获取<Bean>元素中的name属性值 11 String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); 12 ////获取<Bean>元素中的alias属性值 13 List<String> aliases = new ArrayList<String>(); 14 //将<Bean>元素中的所有name属性值存放到别名中 15 if (StringUtils.hasLength(nameAttr)) { 16 String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, BEAN_NAME_DELIMITERS); 17 aliases.addAll(Arrays.asList(nameArr)); 18 } 19 String beanName = id; 20 //如果<Bean>元素中没有配置id属性时,将别名中的第一个值赋值给beanName 21 if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { 22 beanName = aliases.remove(0); 23 if (logger.isDebugEnabled()) { 24 logger.debug("No XML 'id' specified - using '" + beanName + 25 "' as bean name and " + aliases + " as aliases"); 26 } 27 } 28 //检查<Bean>元素所配置的id或者name的唯一性,containingBean标识<Bean> 29 //元素中是否包含子<Bean>元素 30 if (containingBean == null) { 31 //检查<Bean>元素所配置的id、name或者别名是否重复 32 checkNameUniqueness(beanName, aliases, ele); 33 } 34 //详细对<Bean>元素中配置的Bean定义进行解析的地方 35 AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); 36 if (beanDefinition != null) { 37 if (!StringUtils.hasText(beanName)) { 38 try { 39 if (containingBean != null) { 40 //如果<Bean>元素中没有配置id、别名或者name,且没有包含子//<Bean>元素,为解析的Bean生成一个唯一beanName并注册 41 beanName = BeanDefinitionReaderUtils.generateBeanName( 42 beanDefinition, this.readerContext.getRegistry(), true); 43 } 44 else { 45 //如果<Bean>元素中没有配置id、别名或者name,且包含了子//<Bean>元素,为解析的Bean使用别名向IoC容器注册 46 beanName = this.readerContext.generateBeanName(beanDefinition); 47 //为解析的Bean使用别名注册时,为了向后兼容 //Spring1.2/2.0,给别名添加类名后缀 48 String beanClassName = beanDefinition.getBeanClassName(); 49 if (beanClassName != null && 50 beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && 51 !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { 52 aliases.add(beanClassName); 53 } 54 } 55 if (logger.isDebugEnabled()) { 56 logger.debug("Neither XML 'id' nor 'name' specified - " + 57 "using generated bean name [" + beanName + "]"); 58 } 59 } 60 catch (Exception ex) { 61 error(ex.getMessage(), ele); 62 return null; 63 } 64 } 65 String[] aliasesArray = StringUtils.toStringArray(aliases); 66 return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); 67 } 68 //当解析出错时,返回null 69 return null; 70 } 71 //详细对<Bean>元素中配置的Bean定义其他属性进行解析,由于上面的方法中已经对//Bean的id、name和别名等属性进行了处理,该方法中主要处理除这三个以外的其他属性数据 72 public AbstractBeanDefinition parseBeanDefinitionElement( 73 Element ele, String beanName, BeanDefinition containingBean) { 74 //记录解析的<Bean> 75 this.parseState.push(new BeanEntry(beanName)); 76 //这里只读取<Bean>元素中配置的class名字,然后载入到BeanDefinition中去 77 //只是记录配置的class名字,不做实例化,对象的实例化在依赖注入时完成 78 String className = null; 79 if (ele.hasAttribute(CLASS_ATTRIBUTE)) { 80 className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); 81 } 82 try { 83 String parent = null; 84 //如果<Bean>元素中配置了parent属性,则获取parent属性的值 85 if (ele.hasAttribute(PARENT_ATTRIBUTE)) { 86 parent = ele.getAttribute(PARENT_ATTRIBUTE); 87 } 88 //根据<Bean>元素配置的class名称和parent属性值创建BeanDefinition 89 //为载入Bean定义信息做准备 90 AbstractBeanDefinition bd = createBeanDefinition(className, parent); 91 //对当前的<Bean>元素中配置的一些属性进行解析和设置,如配置的单态(singleton)属性等 92 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); 93 //为<Bean>元素解析的Bean设置description信息 bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); 94 //对<Bean>元素的meta(元信息)属性解析 95 parseMetaElements(ele, bd); 96 //对<Bean>元素的lookup-method属性解析 97 parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); 98 //对<Bean>元素的replaced-method属性解析 99 parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); 100 //解析<Bean>元素的构造方法设置 101 parseConstructorArgElements(ele, bd); 102 //解析<Bean>元素的<property>设置 103 parsePropertyElements(ele, bd); 104 //解析<Bean>元素的qualifier属性 105 parseQualifierElements(ele, bd); 106 //为当前解析的Bean设置所需的资源和依赖对象 107 bd.setResource(this.readerContext.getResource()); 108 bd.setSource(extractSource(ele)); 109 return bd; 110 } 111 catch (ClassNotFoundException ex) { 112 error("Bean class [" + className + "] not found", ele, ex); 113 } 114 catch (NoClassDefFoundError err) { 115 error("Class that bean class [" + className + "] depends on not found", ele, err); 116 } 117 catch (Throwable ex) { 118 error("Unexpected failure during bean definition parsing", ele, ex); 119 } 120 finally { 121 this.parseState.pop(); 122 } 123 //解析<Bean>元素出错时,返回null 124 return null; 125 }

复制代码

只要使用过Spring,对Spring配置文件比较熟悉的人,通过对上述源码的分析,就会明白我们在Spring配置文件中<Bean>元素的中配置的属性就是通过该方法解析和设置到Bean中去的。

注意:在解析<Bean>元素过程中没有创建和实例化Bean对象,只是创建了Bean对象的定义类BeanDefinition,将<Bean>元素中的配置信息设置到BeanDefinition中作为记录,当依赖注入时才使用这些记录信息创建和实例化具体的Bean对象。

上面方法中一些对一些配置如元信息(meta)、qualifier等的解析,我们在Spring中配置时使用的也不多,我们在使用Spring的<Bean>元素时,配置最多的是<property>属性,因此我们下面继续分析源码,了解Bean的属性在解析时是如何设置的。

13、BeanDefinitionParserDelegate解析<property>元素:

BeanDefinitionParserDelegate在解析<Bean>调用parsePropertyElements方法解析<Bean>元素中的<property>属性子元素,解析源码如下:

复制代码

1 //解析<Bean>元素中的<property>子元素 2 public void parsePropertyElements(Element beanEle, BeanDefinition bd) { 3 //获取<Bean>元素中所有的子元素 4 NodeList nl = beanEle.getChildNodes(); 5 for (int i = 0; i < nl.getLength(); i++) { 6 Node node = nl.item(i); 7 //如果子元素是<property>子元素,则调用解析<property>子元素方法解析 8 if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) { 9 parsePropertyElement((Element) node, bd); 10 } 11 } 12 } 13 //解析<property>元素 14 public void parsePropertyElement(Element ele, BeanDefinition bd) { 15 //获取<property>元素的名字 16 String propertyName = ele.getAttribute(NAME_ATTRIBUTE); 17 if (!StringUtils.hasLength(propertyName)) { 18 error("Tag 'property' must have a 'name' attribute", ele); 19 return; 20 } 21 this.parseState.push(new PropertyEntry(propertyName)); 22 try { 23 //如果一个Bean中已经有同名的property存在,则不进行解析,直接返回。 24 //即如果在同一个Bean中配置同名的property,则只有第一个起作用 25 if (bd.getPropertyValues().contains(propertyName)) { 26 error("Multiple 'property' definitions for property '" + propertyName + "'", ele); 27 return; 28 } 29 //解析获取property的值 30 Object val = parsePropertyValue(ele, bd, propertyName); 31 //根据property的名字和值创建property实例 32 PropertyValue pv = new PropertyValue(propertyName, val); 33 //解析<property>元素中的属性 34 parseMetaElements(ele, pv); 35 pv.setSource(extractSource(ele)); 36 bd.getPropertyValues().addPropertyValue(pv); 37 } 38 finally { 39 this.parseState.pop(); 40 } 41 } 42 //解析获取property值 43 public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) { 44 String elementName = (propertyName != null) ? 45 "<property> element for property '" + propertyName + "'" : 46 "<constructor-arg> element"; 47 //获取<property>的所有子元素,只能是其中一种类型:ref,value,list等 48 NodeList nl = ele.getChildNodes(); 49 Element subElement = null; 50 for (int i = 0; i < nl.getLength(); i++) { 51 Node node = nl.item(i); 52 //子元素不是description和meta属性 53 if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) && 54 !nodeNameEquals(node, META_ELEMENT)) { 55 if (subElement != null) { 56 error(elementName + " must not contain more than one sub-element", ele); 57 } 58 else {//当前<property>元素包含有子元素 59 subElement = (Element) node; 60 } 61 } 62 } 63 //判断property的属性值是ref还是value,不允许既是ref又是value 64 boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE); 65 boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE); 66 if ((hasRefAttribute && hasValueAttribute) || 67 ((hasRefAttribute || hasValueAttribute) && subElement != null)) { 68 error(elementName + 69 " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele); 70 } 71 //如果属性是ref,创建一个ref的数据对象RuntimeBeanReference,这个对象 72 //封装了ref信息 73 if (hasRefAttribute) { 74 String refName = ele.getAttribute(REF_ATTRIBUTE); 75 if (!StringUtils.hasText(refName)) { 76 error(elementName + " contains empty 'ref' attribute", ele); 77 } 78 //一个指向运行时所依赖对象的引用 79 RuntimeBeanReference ref = new RuntimeBeanReference(refName); 80 //设置这个ref的数据对象是被当前的property对象所引用 81 ref.setSource(extractSource(ele)); 82 return ref; 83 } 84 //如果属性是value,创建一个value的数据对象TypedStringValue,这个对象 85 //封装了value信息 86 else if (hasValueAttribute) { 87 //一个持有String类型值的对象 88 TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE)); 89 //设置这个value数据对象是被当前的property对象所引用 90 valueHolder.setSource(extractSource(ele)); 91 return valueHolder; 92 } 93 //如果当前<property>元素还有子元素 94 else if (subElement != null) { 95 //解析<property>的子元素 96 return parsePropertySubElement(subElement, bd); 97 } 98 else { 99 //propery属性中既不是ref,也不是value属性,解析出错返回null error(elementName + " must specify a ref or value", ele); 100 return null; 101 } }

复制代码

通过对上述源码的分析,我们可以了解在Spring配置文件中,<Bean>元素中<property>元素的相关配置是如何处理的:

a. ref被封装为指向依赖对象一个引用。

b.value配置都会封装成一个字符串类型的对象。

c.ref和value都通过“解析的数据类型属性值.setSource(extractSource(ele));”方法将属性值/引用与所引用的属性关联起来。

在方法的最后对于<property>元素的子元素通过parsePropertySubElement 方法解析,我们继续分析该方法的源码,了解其解析过程。

14、解析<property>元素的子元素:

在BeanDefinitionParserDelegate类中的parsePropertySubElement方法对<property>中的子元素解析,源码如下:

复制代码

1 //解析<property>元素中ref,value或者集合等子元素 2 public Object parsePropertySubElement(Element ele, BeanDefinition bd, String defaultValueType) { 3 //如果<property>没有使用Spring默认的命名空间,则使用用户自定义的规则解析//内嵌元素 4 if (!isDefaultNamespace(ele)) { 5 return parseNestedCustomElement(ele, bd); 6 } 7 //如果子元素是bean,则使用解析<Bean>元素的方法解析 8 else if (nodeNameEquals(ele, BEAN_ELEMENT)) { 9 BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd); 10 if (nestedBd != null) { 11 nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd); 12 } 13 return nestedBd; 14 } 15 //如果子元素是ref,ref中只能有以下3个属性:bean、local、parent 16 else if (nodeNameEquals(ele, REF_ELEMENT)) { 17 //获取<property>元素中的bean属性值,引用其他解析的Bean的名称 18 //可以不再同一个Spring配置文件中,具体请参考Spring对ref的配置规则 19 String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE); 20 boolean toParent = false; 21 if (!StringUtils.hasLength(refName)) { 22 //获取<property>元素中的local属性值,引用同一个Xml文件中配置 23 //的Bean的id,local和ref不同,local只能引用同一个配置文件中的Bean 24 refName = ele.getAttribute(LOCAL_REF_ATTRIBUTE); 25 if (!StringUtils.hasLength(refName)) { 26 //获取<property>元素中parent属性值,引用父级容器中的Bean 27 refName = ele.getAttribute(PARENT_REF_ATTRIBUTE); 28 toParent = true; 29 if (!StringUtils.hasLength(refName)) { 30 error("'bean', 'local' or 'parent' is required for <ref> element", ele); 31 return null; 32 } 33 } 34 } 35 //没有配置ref的目标属性值 36 if (!StringUtils.hasText(refName)) { 37 error("<ref> element contains empty target attribute", ele); 38 return null; 39 } 40 //创建ref类型数据,指向被引用的对象 41 RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent); 42 //设置引用类型值是被当前子元素所引用 43 ref.setSource(extractSource(ele)); 44 return ref; 45 } 46 //如果子元素是<idref>,使用解析ref元素的方法解析 47 else if (nodeNameEquals(ele, IDREF_ELEMENT)) { 48 return parseIdRefElement(ele); 49 } 50 //如果子元素是<value>,使用解析value元素的方法解析 51 else if (nodeNameEquals(ele, VALUE_ELEMENT)) { 52 return parseValueElement(ele, defaultValueType); 53 } 54 //如果子元素是null,为<property>设置一个封装null值的字符串数据 55 else if (nodeNameEquals(ele, NULL_ELEMENT)) { 56 TypedStringValue nullHolder = new TypedStringValue(null); 57 nullHolder.setSource(extractSource(ele)); 58 return nullHolder; 59 } 60 //如果子元素是<array>,使用解析array集合子元素的方法解析 61 else if (nodeNameEquals(ele, ARRAY_ELEMENT)) { 62 return parseArrayElement(ele, bd); 63 } 64 //如果子元素是<list>,使用解析list集合子元素的方法解析 65 else if (nodeNameEquals(ele, LIST_ELEMENT)) { 66 return parseListElement(ele, bd); 67 } 68 //如果子元素是<set>,使用解析set集合子元素的方法解析 69 else if (nodeNameEquals(ele, SET_ELEMENT)) { 70 return parseSetElement(ele, bd); 71 } 72 //如果子元素是<map>,使用解析map集合子元素的方法解析 73 else if (nodeNameEquals(ele, MAP_ELEMENT)) { 74 return parseMapElement(ele, bd); 75 } 76 //如果子元素是<props>,使用解析props集合子元素的方法解析 77 else if (nodeNameEquals(ele, PROPS_ELEMENT)) { 78 return parsePropsElement(ele); 79 } 80 //既不是ref,又不是value,也不是集合,则子元素配置错误,返回null 81 else { 82 error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele); 83 return null; 84 } }

复制代码

通过上述源码分析,我们明白了在Spring配置文件中,对<property>元素中配置的Array、List、Set、Map、Prop等各种集合子元素的都通过上述方法解析,生成对应的数据对象,比如ManagedList、ManagedArray、ManagedSet等,这些Managed类是Spring对象BeanDefiniton的数据封装,对集合数据类型的具体解析有各自的解析方法实现,解析方法的命名非常规范,一目了然,我们对<list>集合元素的解析方法进行源码分析,了解其实现过程。

15、解析<list>子元素:

在BeanDefinitionParserDelegate类中的parseListElement方法就是具体实现解析<property>元素中的<list>集合子元素,源码如下:

复制代码

1 //解析<list>集合子元素 2 public List parseListElement(Element collectionEle, BeanDefinition bd) { 3 //获取<list>元素中的value-type属性,即获取集合元素的数据类型 4 String defaultElementType = collectionEle.getAttribute(VALUE_TYPE_ATTRIBUTE); 5 //获取<list>集合元素中的所有子节点 6 NodeList nl = collectionEle.getChildNodes(); 7 //Spring中将List封装为ManagedList 8 ManagedList<Object> target = new ManagedList<Object>(nl.getLength()); 9 target.setSource(extractSource(collectionEle)); 10 //设置集合目标数据类型 11 target.setElementTypeName(defaultElementType); 12 target.setMergeEnabled(parseMergeAttribute(collectionEle)); 13 //具体的<list>元素解析 14 parseCollectionElements(nl, target, bd, defaultElementType); 15 return target; 16 } 17 //具体解析<list>集合元素,<array>、<list>和<set>都使用该方法解析 18 protected void parseCollectionElements( 19 NodeList elementNodes, Collection<Object> target, BeanDefinition bd, String defaultElementType) { 20 //遍历集合所有节点 21 for (int i = 0; i < elementNodes.getLength(); i++) { 22 Node node = elementNodes.item(i); 23 //节点不是description节点 24 if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT)) { 25 //将解析的元素加入集合中,递归调用下一个子元素 26 target.add(parsePropertySubElement((Element) node, bd, defaultElementType)); 27 } 28 } }

复制代码

经过对Spring Bean定义资源文件转换的Document对象中的元素层层解析,Spring IoC现在已经将XML形式定义的Bean定义资源文件转换为Spring IoC所识别的数据结构——BeanDefinition,它是Bean定义资源文件中配置的POJO对象在Spring IoC容器中的映射,我们可以通过AbstractBeanDefinition为入口,荣IoC容器进行索引、查询和操作。

通过Spring IoC容器对Bean定义资源的解析后,IoC容器大致完成了管理Bean对象的准备工作,即初始化过程,但是最为重要的依赖注入还没有发生,现在在IoC容器中BeanDefinition存储的只是一些静态信息,接下来需要向容器注册Bean定义信息才能全部完成IoC容器的初始化过程

16、解析过后的BeanDefinition在IoC容器中的注册:

让我们继续跟踪程序的执行顺序,接下来会到我们第3步中分析DefaultBeanDefinitionDocumentReader对Bean定义转换的Document对象解析的流程中,在其parseDefaultElement方法中完成对Document对象的解析后得到封装BeanDefinition的BeanDefinitionHold对象,然后调用BeanDefinitionReaderUtils的registerBeanDefinition方法向IoC容器注册解析的Bean,BeanDefinitionReaderUtils的注册的源码如下:

复制代码

//将解析的BeanDefinitionHold注册到容器中 public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { //获取解析的BeanDefinition的名称 String beanName = definitionHolder.getBeanName(); //向IoC容器注册BeanDefinition registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); //如果解析的BeanDefinition有别名,向容器为其注册别名 String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String aliase : aliases) { registry.registerAlias(beanName, aliase); } } }

复制代码

当调用BeanDefinitionReaderUtils向IoC容器注册解析的BeanDefinition时,真正完成注册功能的是DefaultListableBeanFactory。

17、DefaultListableBeanFactory向IoC容器注册解析后的BeanDefinition:

DefaultListableBeanFactory中使用一个HashMap的集合对象存放IoC容器中注册解析的BeanDefinition,向IoC容器注册的主要源码如下:

复制代码

1 //存储注册的俄BeanDefinition 2 private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(); 3 //向IoC容器注册解析的BeanDefiniton 4 public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) 5 throws BeanDefinitionStoreException { 6 Assert.hasText(beanName, "Bean name must not be empty"); 7 Assert.notNull(beanDefinition, "BeanDefinition must not be null"); 8 //校验解析的BeanDefiniton 9 if (beanDefinition instanceof AbstractBeanDefinition) { 10 try { 11 ((AbstractBeanDefinition) beanDefinition).validate(); 12 } 13 catch (BeanDefinitionValidationException ex) { 14 throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, 15 "Validation of bean definition failed", ex); 16 } 17 } 18 //注册的过程中需要线程同步,以保证数据的一致性 19 synchronized (this.beanDefinitionMap) { 20 Object oldBeanDefinition = this.beanDefinitionMap.get(beanName); 21 //检查是否有同名的BeanDefinition已经在IoC容器中注册,如果已经注册, 22 //并且不允许覆盖已注册的Bean,则抛出注册失败异常 23 if (oldBeanDefinition != null) { 24 if (!this.allowBeanDefinitionOverriding) { 25 throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, 26 "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName + 27 "': There is already [" + oldBeanDefinition + "] bound."); 28 } 29 else {//如果允许覆盖,则同名的Bean,后注册的覆盖先注册的 30 if (this.logger.isInfoEnabled()) { 31 this.logger.info("Overriding bean definition for bean '" + beanName + 32 "': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]"); 33 } 34 } 35 } 36 //IoC容器中没有已经注册同名的Bean,按正常注册流程注册 37 else { 38 this.beanDefinitionNames.add(beanName); 39 this.frozenBeanDefinitionNames = null; 40 } 41 this.beanDefinitionMap.put(beanName, beanDefinition); 42 //重置所有已经注册过的BeanDefinition的缓存 43 resetBeanDefinition(beanName); 44 } }

复制代码

至此,Bean定义资源文件中配置的Bean被解析过后,已经注册到IoC容器中,被容器管理起来,真正完成了IoC容器初始化所做的全部工作。现  在IoC容器中已经建立了整个Bean的配置信息,这些BeanDefinition信息已经可以使用,并且可以被检索,IoC容器的作用就是对这些注册的Bean定义信息进行处理和维护。这些的注册的Bean定义信息是IoC容器控制反转的基础,正是有了这些注册的数据,容器才可以进行依赖注入。

总结:

现在通过上面的代码,总结一下IOC容器初始化的基本步骤:

u 初始化的入口在容器实现中的 refresh()调用来完成

u 对 bean 定义载入 IOC 容器使用的方法是 loadBeanDefinition,其中的大致过程如下:通过 ResourceLoader 来完成资源文件位置的定位,DefaultResourceLoader 是默认的实现,同时上下文本身就给出了 ResourceLoader 的实现,可以从类路径,文件系统, URL 等方式来定为资源位置。如果是 XmlBeanFactory作为 IOC 容器,那么需要为它指定 bean 定义的资源,也就是说 bean 定义文件时通过抽象成 Resource 来被 IOC 容器处理的,容器通过 BeanDefinitionReader来完成定义信息的解析和 Bean 信息的注册,往往使用的是XmlBeanDefinitionReader 来解析 bean 的 xml 定义文件 - 实际的处理过程是委托给 BeanDefinitionParserDelegate 来完成的,从而得到 bean 的定义信息,这些信息在 Spring 中使用 BeanDefinition 对象来表示 - 这个名字可以让我们想到loadBeanDefinition,RegisterBeanDefinition  这些相关的方法 - 他们都是为处理 BeanDefinitin 服务的, 容器解析得到 BeanDefinitionIoC 以后,需要把它在 IOC 容器中注册,这由 IOC 实现 BeanDefinitionRegistry 接口来实现。注册过程就是在 IOC 容器内部维护的一个HashMap 来保存得到的 BeanDefinition 的过程。这个 HashMap 是 IoC 容器持有 bean 信息的场所,以后对 bean 的操作都是围绕这个HashMap 来实现的.

u 然后我们就可以通过 BeanFactory 和 ApplicationContext 来享受到 Spring IOC 的服务了,在使用 IOC 容器的时候,我们注意到除了少量粘合代码,绝大多数以正确 IoC 风格编写的应用程序代码完全不用关心如何到达工厂,因为容器将把这些对象与容器管理的其他对象钩在一起。基本的策略是把工厂放到已知的地方,最好是放在对预期使用的上下文有意义的地方,以及代码将实际需要访问工厂的地方。 Spring 本身提供了对声明式载入 web 应用程序用法的应用程序上下文,并将其存储在ServletContext 中的框架实现。具体可以参见以后的文章

在使用 Spring IOC 容器的时候我们还需要区别两个概念:

Beanfactory 和 Factory bean,其中 BeanFactory 指的是 IOC 容器的编程抽象,比如 ApplicationContext, XmlBeanFactory 等,这些都是 IOC 容器的具体表现,需要使用什么样的容器由客户决定,但 Spring 为我们提供了丰富的选择。 FactoryBean 只是一个可以在 IOC而容器中被管理的一个 bean,是对各种处理过程和资源使用的抽象,Factory bean 在需要时产生另一个对象,而不返回 FactoryBean本身,我们可以把它看成是一个抽象工厂,对它的调用返回的是工厂生产的产品。所有的 Factory bean 都实现特殊的org.springframework.beans.factory.FactoryBean 接口,当使用容器中 factory bean 的时候,该容器不会返回 factory bean 本身,而是返回其生成的对象。Spring 包括了大部分的通用资源和服务访问抽象的 Factory bean 的实现,其中包括:对 JNDI 查询的处理,对代理对象的处理,对事务性代理的处理,对 RMI 代理的处理等,这些我们都可以看成是具体的工厂,看成是SPRING 为我们建立好的工厂。也就是说 Spring 通过使用抽象工厂模式为我们准备了一系列工厂来生产一些特定的对象,免除我们手工重复的工作,我们要使用时只需要在 IOC 容器里配置好就能很方便的使用了

四、IOC容器的依赖注入

1、依赖注入发生的时间

当Spring IoC容器完成了Bean定义资源的定位、载入和解析注册以后,IoC容器中已经管理类Bean定义的相关数据,但是此时IoC容器还没有对所管理的Bean进行依赖注入,依赖注入在以下两种情况发生:

(1).用户第一次通过getBean方法向IoC容索要Bean时,IoC容器触发依赖注入。

(2).当用户在Bean定义资源中为<Bean>元素配置了lazy-init属性,即让容器在解析注册Bean定义时进行预实例化,触发依赖注入。

BeanFactory接口定义了Spring IoC容器的基本功能规范,是Spring IoC容器所应遵守的最底层和最基本的编程规范。BeanFactory接口中定义了几个getBean方法,就是用户向IoC容器索取管理的Bean的方法,我们通过分析其子类的具体实现,理解Spring IoC容器在用户索取Bean时如何完成依赖注入。

在BeanFactory中我们看到getBean(String…)函数,它的具体实现在AbstractBeanFactory中

2、AbstractBeanFactory通过getBean向IoC容器获取被管理的Bean:

AbstractBeanFactory的getBean相关方法的源码如下:

复制代码

1 //获取IoC容器中指定名称的Bean 2 public Object getBean(String name) throws BeansException { 3 //doGetBean才是真正向IoC容器获取被管理Bean的过程 4 return doGetBean(name, null, null, false); 5 } 6 //获取IoC容器中指定名称和类型的Bean 7 public <T> T getBean(String name, Class<T> requiredType) throws BeansException { 8 //doGetBean才是真正向IoC容器获取被管理Bean的过程 9 return doGetBean(name, requiredType, null, false); 10 } 11 //获取IoC容器中指定名称和参数的Bean 12 public Object getBean(String name, Object... args) throws BeansException { 13 //doGetBean才是真正向IoC容器获取被管理Bean的过程 14 return doGetBean(name, null, args, false); 15 } 16 //获取IoC容器中指定名称、类型和参数的Bean 17 public <T> T getBean(String name, Class<T> requiredType, Object... args) throws BeansException { 18 //doGetBean才是真正向IoC容器获取被管理Bean的过程 19 return doGetBean(name, requiredType, args, false); 20 } 21 //真正实现向IoC容器获取Bean的功能,也是触发依赖注入功能的地方 22 @SuppressWarnings("unchecked") 23 protected <T> T doGetBean( 24 final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) 25 throws BeansException { 26 //根据指定的名称获取被管理Bean的名称,剥离指定名称中对容器的相关依赖 27 //如果指定的是别名,将别名转换为规范的Bean名称 28 final String beanName = transformedBeanName(name); 29 Object bean; 30 //先从缓存中取是否已经有被创建过的单态类型的Bean,对于单态模式的Bean整 31 //个IoC容器中只创建一次,不需要重复创建 32 Object sharedInstance = getSingleton(beanName); 33 //IoC容器创建单态模式Bean实例对象 34 if (sharedInstance != null && args == null) { 35 if (logger.isDebugEnabled()) { 36 //如果指定名称的Bean在容器中已有单态模式的Bean被创建,直接返回 37 //已经创建的Bean 38 if (isSingletonCurrentlyInCreation(beanName)) { 39 logger.debug("Returning eagerly cached instance of singleton bean '" + beanName + 40 "' that is not fully initialized yet - a consequence of a circular reference"); 41 } 42 else { 43 logger.debug("Returning cached instance of singleton bean '" + beanName + "'"); 44 } 45 } 46 //获取给定Bean的实例对象,主要是完成FactoryBean的相关处理 47 //注意:BeanFactory是管理容器中Bean的工厂,而FactoryBean是 48 //创建创建对象的工厂Bean,两者之间有区别 49 bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); 50 } 51 else {//缓存没有正在创建的单态模式Bean 52 //缓存中已经有已经创建的原型模式Bean,但是由于循环引用的问题导致实 53 //例化对象失败 54 if (isPrototypeCurrentlyInCreation(beanName)) { 55 throw new BeanCurrentlyInCreationException(beanName); 56 } 57 //对IoC容器中是否存在指定名称的BeanDefinition进行检查,首先检查是否 58 //能在当前的BeanFactory中获取的所需要的Bean,如果不能则委托当前容器 59 //的父级容器去查找,如果还是找不到则沿着容器的继承体系向父级容器查找 60 BeanFactory parentBeanFactory = getParentBeanFactory(); 61 //当前容器的父级容器存在,且当前容器中不存在指定名称的Bean 62 if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { 63 //解析指定Bean名称的原始名称 64 String nameToLookup = originalBeanName(name); 65 if (args != null) { 66 //委派父级容器根据指定名称和显式的参数查找 67 return (T) parentBeanFactory.getBean(nameToLookup, args); 68 } 69 else { 70 //委派父级容器根据指定名称和类型查找 71 return parentBeanFactory.getBean(nameToLookup, requiredType); 72 } 73 } 74 //创建的Bean是否需要进行类型验证,一般不需要 75 if (!typeCheckOnly) { 76 //向容器标记指定的Bean已经被创建 77 markBeanAsCreated(beanName); 78 } 79 //根据指定Bean名称获取其父级的Bean定义,主要解决Bean继承时子类 80 //合并父类公共属性问题 81 final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); 82 checkMergedBeanDefinition(mbd, beanName, args); 83 //获取当前Bean所有依赖Bean的名称 84 String[] dependsOn = mbd.getDependsOn(); 85 //如果当前Bean有依赖Bean 86 if (dependsOn != null) { 87 for (String dependsOnBean : dependsOn) { 88 //递归调用getBean方法,获取当前Bean的依赖Bean 89 getBean(dependsOnBean); 90 //把被依赖Bean注册给当前依赖的Bean 91 registerDependentBean(dependsOnBean, beanName); 92 } 93 } 94 //创建单态模式Bean的实例对象 95 if (mbd.isSingleton()) { 96 //这里使用了一个匿名内部类,创建Bean实例对象,并且注册给所依赖的对象 97 sharedInstance = getSingleton(beanName, new ObjectFactory() { 98 public Object getObject() throws BeansException { 99 try { 100 //创建一个指定Bean实例对象,如果有父级继承,则合并子//类和父类的定义 101 return createBean(beanName, mbd, args); 102 } 103 catch (BeansException ex) { 104 //显式地从容器单态模式Bean缓存中清除实例对象 105 destroySingleton(beanName); 106 throw ex; 107 } 108 } 109 }); 110 //获取给定Bean的实例对象 111 bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); 112 } 113 //IoC容器创建原型模式Bean实例对象 114 else if (mbd.isPrototype()) { 115 //原型模式(Prototype)是每次都会创建一个新的对象 116 Object prototypeInstance = null; 117 try { 118 //回调beforePrototypeCreation方法,默认的功能是注册当前创//建的原型对象 119 beforePrototypeCreation(beanName); 120 //创建指定Bean对象实例 121 prototypeInstance = createBean(beanName, mbd, args); 122 } 123 finally { 124 //回调afterPrototypeCreation方法,默认的功能告诉IoC容器指//定Bean的原型对象不再创建了 125 afterPrototypeCreation(beanName); 126 } 127 //获取给定Bean的实例对象 128 bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); 129 } 130 //要创建的Bean既不是单态模式,也不是原型模式,则根据Bean定义资源中 131 //配置的生命周期范围,选择实例化Bean的合适方法,这种在Web应用程序中 132 //比较常用,如:request、session、application等生命周期 133 else { 134 String scopeName = mbd.getScope(); 135 final Scope scope = this.scopes.get(scopeName); 136 //Bean定义资源中没有配置生命周期范围,则Bean定义不合法 137 if (scope == null) { 138 throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'"); 139 } 140 try { 141 //这里又使用了一个匿名内部类,获取一个指定生命周期范围的实例 142 Object scopedInstance = scope.get(beanName, new ObjectFactory() { 143 public Object getObject() throws BeansException { 144 beforePrototypeCreation(beanName); 145 try { 146 return createBean(beanName, mbd, args); 147 } 148 finally { 149 afterPrototypeCreation(beanName); 150 } 151 } 152 }); 153 //获取给定Bean的实例对象 154 bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); 155 } 156 catch (IllegalStateException ex) { 157 throw new BeanCreationException(beanName, 158 "Scope '" + scopeName + "' is not active for the current thread; " + 159 "consider defining a scoped proxy for this bean if you intend to refer to it from a singleton", 160 ex); 161 } 162 } 163 } 164 //对创建的Bean实例对象进行类型检查 165 if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) { 166 throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass()); 167 } 168 return (T) bean; 169 }

复制代码

通过上面对向IoC容器获取Bean方法的分析,我们可以看到在Spring中,如果Bean定义的单态模式(Singleton),则容器在创建之前先从缓存中查找,以确保整个容器中只存在一个实例对象。如果Bean定义的是原型模式(Prototype),则容器每次都会创建一个新的实例对象。除此之外,Bean定义还可以扩展为指定其生命周期范围。

上面的源码只是定义了根据Bean定义的模式,采取的不同创建Bean实例对象的策略,具体的Bean实例对象的创建过程由实现了ObejctFactory接口的匿名内部类的createBean方法完成,ObejctFactory使用委派模式,具体的Bean实例创建过程交由其实现类AbstractAutowireCapableBeanFactory完成,我们继续分析AbstractAutowireCapableBeanFactory的createBean方法的源码,理解其创建Bean实例的具体实现过程。

3、AbstractAutowireCapableBeanFactory创建Bean实例对象:

AbstractAutowireCapableBeanFactory类实现了ObejctFactory接口,创建容器指定的Bean实例对象,同时还对创建的Bean实例对象进行初始化处理。其创建Bean实例对象的方法源码如下:

复制代码

1 //创建Bean实例对象 2 protected Object createBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) 3 throws BeanCreationException { 4 if (logger.isDebugEnabled()) { 5 logger.debug("Creating instance of bean '" + beanName + "'"); 6 } 7 //判断需要创建的Bean是否可以实例化,即是否可以通过当前的类加载器加载 8 resolveBeanClass(mbd, beanName); 9 //校验和准备Bean中的方法覆盖 10 try { 11 mbd.prepareMethodOverrides(); 12 } 13 catch (BeanDefinitionValidationException ex) { 14 throw new BeanDefinitionStoreException(mbd.getResourceDescription(), 15 beanName, "Validation of method overrides failed", ex); 16 } 17 try { 18 //如果Bean配置了初始化前和初始化后的处理器,则试图返回一个需要创建//Bean的代理对象 19 Object bean = resolveBeforeInstantiation(beanName, mbd); 20 if (bean != null) { 21 return bean; 22 } 23 } 24 catch (Throwable ex) { 25 throw new BeanCreationException(mbd.getResourceDescription(), beanName, 26 "BeanPostProcessor before instantiation of bean failed", ex); 27 } 28 //创建Bean的入口 29 Object beanInstance = doCreateBean(beanName, mbd, args); 30 if (logger.isDebugEnabled()) { 31 logger.debug("Finished creating instance of bean '" + beanName + "'"); 32 } 33 return beanInstance; 34 } 35 //真正创建Bean的方法 36 protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) { 37 //封装被创建的Bean对象 38 BeanWrapper instanceWrapper = null; 39 if (mbd.isSingleton()){//单态模式的Bean,先从容器中缓存中获取同名Bean 40 instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); 41 } 42 if (instanceWrapper == null) { 43 //创建实例对象 44 instanceWrapper = createBeanInstance(beanName, mbd, args); 45 } 46 final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null); 47 //获取实例化对象的类型 48 Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null); 49 //调用PostProcessor后置处理器 50 synchronized (mbd.postProcessingLock) { 51 if (!mbd.postProcessed) { 52 applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); 53 mbd.postProcessed = true; 54 } 55 } 56 // Eagerly cache singletons to be able to resolve circular references 57 //向容器中缓存单态模式的Bean对象,以防循环引用 58 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && 59 isSingletonCurrentlyInCreation(beanName)); 60 if (earlySingletonExposure) { 61 if (logger.isDebugEnabled()) { 62 logger.debug("Eagerly caching bean '" + beanName + 63 "' to allow for resolving potential circular references"); 64 } 65 //这里是一个匿名内部类,为了防止循环引用,尽早持有对象的引用 66 addSingletonFactory(beanName, new ObjectFactory() { 67 public Object getObject() throws BeansException { 68 return getEarlyBeanReference(beanName, mbd, bean); 69 } 70 }); 71 } 72 //Bean对象的初始化,依赖注入在此触发 73 //这个exposedObject在初始化完成之后返回作为依赖注入完成后的Bean 74 Object exposedObject = bean; 75 try { 76 //将Bean实例对象封装,并且Bean定义中配置的属性值赋值给实例对象 77 populateBean(beanName, mbd, instanceWrapper); 78 if (exposedObject != null) { 79 //初始化Bean对象 80 exposedObject = initializeBean(beanName, exposedObject, mbd); 81 } 82 } 83 catch (Throwable ex) { 84 if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { 85 throw (BeanCreationException) ex; 86 } 87 else { 88 throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); 89 } 90 } 91 if (earlySingletonExposure) { 92 //获取指定名称的已注册的单态模式Bean对象 93 Object earlySingletonReference = getSingleton(beanName, false); 94 if (earlySingletonReference != null) { 95 //根据名称获取的以注册的Bean和正在实例化的Bean是同一个 96 if (exposedObject == bean) { 97 //当前实例化的Bean初始化完成 98 exposedObject = earlySingletonReference; 99 } 100 //当前Bean依赖其他Bean,并且当发生循环引用时不允许新创建实例对象 101 else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { 102 String[] dependentBeans = getDependentBeans(beanName); 103 Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length); 104 //获取当前Bean所依赖的其他Bean 105 for (String dependentBean : dependentBeans) { 106 //对依赖Bean进行类型检查 107 if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { 108 actualDependentBeans.add(dependentBean); 109 } 110 } 111 if (!actualDependentBeans.isEmpty()) { 112 throw new BeanCurrentlyInCreationException(beanName, 113 "Bean with name '" + beanName + "' has been injected into other beans [" + 114 StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + 115 "] in its raw version as part of a circular reference, but has eventually been " + 116 "wrapped. This means that said other beans do not use the final version of the " + 117 "bean. This is often the result of over-eager type matching - consider using " + 118 "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example."); 119 } 120 } 121 } 122 } 123 //注册完成依赖注入的Bean 124 try { 125 registerDisposableBeanIfNecessary(beanName, bean, mbd); 126 } 127 catch (BeanDefinitionValidationException ex) { 128 throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); 129 } 130 return exposedObject; }

复制代码

通过对方法源码的分析,我们看到具体的依赖注入实现在以下两个方法中:

(1).createBeanInstance:生成Bean所包含的java对象实例。

(2).populateBean :对Bean属性的依赖注入进行处理。

下面继续分析这两个方法的代码实现。

4、createBeanInstance方法创建Bean的java实例对象:

在createBeanInstance方法中,根据指定的初始化策略,使用静态工厂、工厂方法或者容器的自动装配特性生成java实例对象,创建对象的源码如下:

复制代码

1 //创建Bean的实例对象 2 protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) { 3 //检查确认Bean是可实例化的 4 Class beanClass = resolveBeanClass(mbd, beanName); 5 //使用工厂方法对Bean进行实例化 6 if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) { 7 throw new BeanCreationException(mbd.getResourceDescription(), beanName, 8 "Bean class isn't public, and non-public access not allowed: " + beanClass.getName()); 9 } 10 if (mbd.getFactoryMethodName() != null) { 11 //调用工厂方法实例化 12 return instantiateUsingFactoryMethod(beanName, mbd, args); 13 } 14 //使用容器的自动装配方法进行实例化 15 boolean resolved = false; 16 boolean autowireNecessary = false; 17 if (args == null) { 18 synchronized (mbd.constructorArgumentLock) { 19 if (mbd.resolvedConstructorOrFactoryMethod != null) { 20 resolved = true; 21 autowireNecessary = mbd.constructorArgumentsResolved; 22 } 23 } 24 } 25 if (resolved) { 26 if (autowireNecessary) { 27 //配置了自动装配属性,使用容器的自动装配实例化 28 //容器的自动装配是根据参数类型匹配Bean的构造方法 29 return autowireConstructor(beanName, mbd, null, null); 30 } 31 else { 32 //使用默认的无参构造方法实例化 33 return instantiateBean(beanName, mbd); 34 } 35 } 36 //使用Bean的构造方法进行实例化 37 Constructor[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); 38 if (ctors != null || 39 mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR || 40 mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { 41 //使用容器的自动装配特性,调用匹配的构造方法实例化 42 return autowireConstructor(beanName, mbd, ctors, args); 43 } 44 //使用默认的无参构造方法实例化 45 return instantiateBean(beanName, mbd); 46 } 47 //使用默认的无参构造方法实例化Bean对象 48 protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) { 49 try { 50 Object beanInstance; 51 final BeanFactory parent = this; 52 //获取系统的安全管理接口,JDK标准的安全管理API 53 if (System.getSecurityManager() != null) { 54 //这里是一个匿名内置类,根据实例化策略创建实例对象 55 beanInstance = AccessController.doPrivileged(new PrivilegedAction<Object>() { 56 public Object run() { 57 return getInstantiationStrategy().instantiate(mbd, beanName, parent); 58 } 59 }, getAccessControlContext()); 60 } 61 else { 62 //将实例化的对象封装起来 63 beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent); 64 } 65 BeanWrapper bw = new BeanWrapperImpl(beanInstance); 66 initBeanWrapper(bw); 67 return bw; 68 } 69 catch (Throwable ex) { 70 throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex); 71 } 72 }

复制代码

经过对上面的代码分析,我们可以看出,对使用工厂方法和自动装配特性的Bean的实例化相当比较清楚,调用相应的工厂方法或者参数匹配的构造方法即可完成实例化对象的工作,但是对于我们最常使用的默认无参构造方法就需要使用相应的初始化策略(JDK的反射机制或者CGLIB)来进行初始化了,在方法getInstantiationStrategy().instantiate中就具体实现类使用初始策略实例化对象。

5、SimpleInstantiationStrategy类使用默认的无参构造方法创建Bean实例化对象:

在使用默认的无参构造方法创建Bean的实例化对象时,方法getInstantiationStrategy().instantiate调用了SimpleInstantiationStrategy类中的实例化Bean的方法,其源码如下:

复制代码

1 //使用初始化策略实例化Bean对象 2 public Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner) { 3 //如果Bean定义中没有方法覆盖,则就不需要CGLIB父类类的方法 4 if (beanDefinition.getMethodOverrides().isEmpty()) { 5 Constructor<?> constructorToUse; 6 synchronized (beanDefinition.constructorArgumentLock) { 7 //获取对象的构造方法或工厂方法 8 constructorToUse = (Constructor<?>) beanDefinition.resolvedConstructorOrFactoryMethod; 9 //如果没有构造方法且没有工厂方法 10 if (constructorToUse == null) { 11 //使用JDK的反射机制,判断要实例化的Bean是否是接口 12 final Class clazz = beanDefinition.getBeanClass(); 13 if (clazz.isInterface()) { 14 throw new BeanInstantiationException(clazz, "Specified class is an interface"); 15 } 16 try { 17 if (System.getSecurityManager() != null) { 18 //这里是一个匿名内置类,使用反射机制获取Bean的构造方法 19 constructorToUse = AccessController.doPrivileged(new PrivilegedExceptionAction<Constructor>() { 20 public Constructor run() throws Exception { 21 return clazz.getDeclaredConstructor((Class[]) null); 22 } 23 }); 24 } 25 else { 26 constructorToUse = clazz.getDeclaredConstructor((Class[]) null); 27 } 28 beanDefinition.resolvedConstructorOrFactoryMethod = constructorToUse; 29 } 30 catch (Exception ex) { 31 throw new BeanInstantiationException(clazz, "No default constructor found", ex); 32 } 33 } 34 } 35 //使用BeanUtils实例化,通过反射机制调用”构造方法.newInstance(arg)”来进行实例化 36 return BeanUtils.instantiateClass(constructorToUse); 37 } 38 else { 39 //使用CGLIB来实例化对象 40 return instantiateWithMethodInjection(beanDefinition, beanName, owner); 41 } }

复制代码

通过上面的代码分析,我们看到了如果Bean有方法被覆盖了,则使用JDK的反射机制进行实例化,否则,使用CGLIB进行实例化。

instantiateWithMethodInjection方法调用SimpleInstantiationStrategy的子类CglibSubclassingInstantiationStrategy使用CGLIB来进行初始化,其源码如下:

复制代码

1 //使用CGLIB进行Bean对象实例化 2 public Object instantiate(Constructor ctor, Object[] args) { 3 //CGLIB中的类 4 Enhancer enhancer = new Enhancer(); 5 //将Bean本身作为其基类 6 enhancer.setSuperclass(this.beanDefinition.getBeanClass()); 7 enhancer.setCallbackFilter(new CallbackFilterImpl()); 8 enhancer.setCallbacks(new Callback[] { 9 NoOp.INSTANCE, 10 new LookupOverrideMethodInterceptor(), 11 new ReplaceOverrideMethodInterceptor() 12 }); 13 //使用CGLIB的create方法生成实例对象 14 return (ctor == null) ? 15 enhancer.create() : 16 enhancer.create(ctor.getParameterTypes(), args); 17 }

复制代码

CGLIB是一个常用的字节码生成器的类库,它提供了一系列API实现java字节码的生成和转换功能。我们在学习JDK的动态代理时都知道,JDK的动态代理只能针对接口,如果一个类没有实现任何接口,要对其进行动态代理只能使用CGLIB。

6、populateBean方法对Bean属性的依赖注入:

在第3步的分析中我们已经了解到Bean的依赖注入分为以下两个过程:

(1).createBeanInstance:生成Bean所包含的java对象实例。

(2).populateBean :对Bean属性的依赖注入进行处理。

第4、5步中我们已经分析了容器初始化生成Bean所包含的Java实例对象的过程,现在我们继续分析生成对象后,Spring IoC容器是如何将Bean的属性依赖关系注入Bean实例对象中并设置好的,属性依赖注入的代码如下:

复制代码

1 //将Bean属性设置到生成的实例对象上 2 protected void populateBean(String beanName, AbstractBeanDefinition mbd, BeanWrapper bw) { 3 //获取容器在解析Bean定义资源时为BeanDefiniton中设置的属性值 4 PropertyValues pvs = mbd.getPropertyValues(); 5 //实例对象为null 6 if (bw == null) { 7 //属性值不为空 8 if (!pvs.isEmpty()) { 9 throw new BeanCreationException( 10 mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance"); 11 } 12 else { 13 //实例对象为null,属性值也为空,不需要设置属性值,直接返回 14 return; 15 } 16 } 17 //在设置属性之前调用Bean的PostProcessor后置处理器 18 boolean continueWithPropertyPopulation = true; 19 if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { 20 for (BeanPostProcessor bp : getBeanPostProcessors()) { 21 if (bp instanceof InstantiationAwareBeanPostProcessor) { 22 InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; 23 if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { 24 continueWithPropertyPopulation = false; 25 break; 26 } 27 } 28 } 29 } 30 if (!continueWithPropertyPopulation) { 31 return; 32 } 33 //依赖注入开始,首先处理autowire自动装配的注入 34 if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME || 35 mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) { 36 MutablePropertyValues newPvs = new MutablePropertyValues(pvs); 37 //对autowire自动装配的处理,根据Bean名称自动装配注入 38 if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) { 39 autowireByName(beanName, mbd, bw, newPvs); 40 } 41 //根据Bean类型自动装配注入 42 if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) { 43 autowireByType(beanName, mbd, bw, newPvs); 44 } 45 pvs = newPvs; 46 } 47 //检查容器是否持有用于处理单态模式Bean关闭时的后置处理器 48 boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors(); 49 //Bean实例对象没有依赖,即没有继承基类 50 boolean needsDepCheck = (mbd.getDependencyCheck() != RootBeanDefinition.DEPENDENCY_CHECK_NONE); 51 if (hasInstAwareBpps || needsDepCheck) { 52 //从实例对象中提取属性描述符 53 PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw); 54 if (hasInstAwareBpps) { 55 for (BeanPostProcessor bp : getBeanPostProcessors()) { 56 if (bp instanceof InstantiationAwareBeanPostProcessor) { 57 InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; 58 //使用BeanPostProcessor处理器处理属性值 59 pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName); 60 if (pvs == null) { 61 return; 62 } 63 } 64 } 65 } 66 if (needsDepCheck) { 67 //为要设置的属性进行依赖检查 68 checkDependencies(beanName, mbd, filteredPds, pvs); 69 } 70 } 71 //对属性进行注入 72 applyPropertyValues(beanName, mbd, bw, pvs); 73 } 74 //解析并注入依赖属性的过程 75 protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) { 76 if (pvs == null || pvs.isEmpty()) { 77 return; 78 } 79 //封装属性值 80 MutablePropertyValues mpvs = null; 81 List<PropertyValue> original; 82 if (System.getSecurityManager()!= null) { 83 if (bw instanceof BeanWrapperImpl) { 84 //设置安全上下文,JDK安全机制 85 ((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext()); 86 } 87 } 88 if (pvs instanceof MutablePropertyValues) { 89 mpvs = (MutablePropertyValues) pvs; 90 //属性值已经转换 91 if (mpvs.isConverted()) { 92 try { 93 //为实例化对象设置属性值 94 bw.setPropertyValues(mpvs); 95 return; 96 } 97 catch (BeansException ex) { 98 throw new BeanCreationException( 99 mbd.getResourceDescription(), beanName, "Error setting property values", ex); 100 } 101 } 102 //获取属性值对象的原始类型值 103 original = mpvs.getPropertyValueList(); 104 } 105 else { 106 original = Arrays.asList(pvs.getPropertyValues()); 107 } 108 //获取用户自定义的类型转换 109 TypeConverter converter = getCustomTypeConverter(); 110 if (converter == null) { 111 converter = bw; 112 } 113 //创建一个Bean定义属性值解析器,将Bean定义中的属性值解析为Bean实例对象 114 //的实际值 115 BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter); 116 //为属性的解析值创建一个拷贝,将拷贝的数据注入到实例对象中 117 List<PropertyValue> deepCopy = new ArrayList<PropertyValue>(original.size()); 118 boolean resolveNecessary = false; 119 for (PropertyValue pv : original) { 120 //属性值不需要转换 121 if (pv.isConverted()) { 122 deepCopy.add(pv); 123 } 124 //属性值需要转换 125 else { 126 String propertyName = pv.getName(); 127 //原始的属性值,即转换之前的属性值 128 Object originalValue = pv.getValue(); 129 //转换属性值,例如将引用转换为IoC容器中实例化对象引用 130 Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue); 131 //转换之后的属性值 132 Object convertedValue = resolvedValue; 133 //属性值是否可以转换 134 boolean convertible = bw.isWritableProperty(propertyName) && 135 !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName); 136 if (convertible) { 137 //使用用户自定义的类型转换器转换属性值 138 convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter); 139 } 140 //存储转换后的属性值,避免每次属性注入时的转换工作 141 if (resolvedValue == originalValue) { 142 if (convertible) { 143 //设置属性转换之后的值 144 pv.setConvertedValue(convertedValue); 145 } 146 deepCopy.add(pv); 147 } 148 //属性是可转换的,且属性原始值是字符串类型,且属性的原始类型值不是 149 //动态生成的字符串,且属性的原始值不是集合或者数组类型 150 else if (convertible && originalValue instanceof TypedStringValue && 151 !((TypedStringValue) originalValue).isDynamic() && 152 !(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) { 153 pv.setConvertedValue(convertedValue); 154 deepCopy.add(pv); 155 } 156 else { 157 resolveNecessary = true; 158 //重新封装属性的值 159 deepCopy.add(new PropertyValue(pv, convertedValue)); 160 } 161 } 162 } 163 if (mpvs != null && !resolveNecessary) { 164 //标记属性值已经转换过 165 mpvs.setConverted(); 166 } 167 //进行属性依赖注入 168 try { 169 bw.setPropertyValues(new MutablePropertyValues(deepCopy)); 170 } 171 catch (BeansException ex) { 172 throw new BeanCreationException( 173 mbd.getResourceDescription(), beanName, "Error setting property values", ex); 174 } }

复制代码

分析上述代码,我们可以看出,对属性的注入过程分以下两种情况:

(1).属性值类型不需要转换时,不需要解析属性值,直接准备进行依赖注入。

(2).属性值需要进行类型转换时,如对其他对象的引用等,首先需要解析属性值,然后对解析后的属性值进行依赖注入。

对属性值的解析是在BeanDefinitionValueResolver类中的resolveValueIfNecessary方法中进行的,对属性值的依赖注入是通过bw.setPropertyValues方法实现的,在分析属性值的依赖注入之前,我们先分析一下对属性值的解析过程。

7、BeanDefinitionValueResolver解析属性值:

当容器在对属性进行依赖注入时,如果发现属性值需要进行类型转换,如属性值是容器中另一个Bean实例对象的引用,则容器首先需要根据属性值解析出所引用的对象,然后才能将该引用对象注入到目标实例对象的属性上去,对属性进行解析的由resolveValueIfNecessary方法实现,其源码如下:

复制代码

1 //解析属性值,对注入类型进行转换 2 public Object resolveValueIfNecessary(Object argName, Object value) { 3 //对引用类型的属性进行解析 4 if (value instanceof RuntimeBeanReference) { 5 RuntimeBeanReference ref = (RuntimeBeanReference) value; 6 //调用引用类型属性的解析方法 7 return resolveReference(argName, ref); 8 } 9 //对属性值是引用容器中另一个Bean名称的解析 10 else if (value instanceof RuntimeBeanNameReference) { 11 String refName = ((RuntimeBeanNameReference) value).getBeanName(); 12 refName = String.valueOf(evaluate(refName)); 13 //从容器中获取指定名称的Bean 14 if (!this.beanFactory.containsBean(refName)) { 15 throw new BeanDefinitionStoreException( 16 "Invalid bean name '" + refName + "' in bean reference for " + argName); 17 } 18 return refName; 19 } 20 //对Bean类型属性的解析,主要是Bean中的内部类 21 else if (value instanceof BeanDefinitionHolder) { 22 BeanDefinitionHolder bdHolder = (BeanDefinitionHolder) value; 23 return resolveInnerBean(argName, bdHolder.getBeanName(), bdHolder.getBeanDefinition()); 24 } 25 else if (value instanceof BeanDefinition) { 26 BeanDefinition bd = (BeanDefinition) value; 27 return resolveInnerBean(argName, "(inner bean)", bd); 28 } 29 //对集合数组类型的属性解析 30 else if (value instanceof ManagedArray) { 31 ManagedArray array = (ManagedArray) value; 32 //获取数组的类型 33 Class elementType = array.resolvedElementType; 34 if (elementType == null) { 35 //获取数组元素的类型 36 String elementTypeName = array.getElementTypeName(); 37 if (StringUtils.hasText(elementTypeName)) { 38 try { 39 //使用反射机制创建指定类型的对象 40 elementType = ClassUtils.forName(elementTypeName, this.beanFactory.getBeanClassLoader()); 41 array.resolvedElementType = elementType; 42 } 43 catch (Throwable ex) { 44 throw new BeanCreationException( 45 this.beanDefinition.getResourceDescription(), this.beanName, 46 "Error resolving array type for " + argName, ex); 47 } 48 } 49 //没有获取到数组的类型,也没有获取到数组元素的类型,则直接设置数 50 //组的类型为Object 51 else { 52 elementType = Object.class; 53 } 54 } 55 //创建指定类型的数组 56 return resolveManagedArray(argName, (List<?>) value, elementType); 57 } 58 //解析list类型的属性值 59 else if (value instanceof ManagedList) { 60 return resolveManagedList(argName, (List<?>) value); 61 } 62 //解析set类型的属性值 63 else if (value instanceof ManagedSet) { 64 return resolveManagedSet(argName, (Set<?>) value); 65 } 66 //解析map类型的属性值 67 else if (value instanceof ManagedMap) { 68 return resolveManagedMap(argName, (Map<?, ?>) value); 69 } 70 //解析props类型的属性值,props其实就是key和value均为字符串的map 71 else if (value instanceof ManagedProperties) { 72 Properties original = (Properties) value; 73 //创建一个拷贝,用于作为解析后的返回值 74 Properties copy = new Properties(); 75 for (Map.Entry propEntry : original.entrySet()) { 76 Object propKey = propEntry.getKey(); 77 Object propValue = propEntry.getValue(); 78 if (propKey instanceof TypedStringValue) { 79 propKey = evaluate((TypedStringValue) propKey); 80 } 81 if (propValue instanceof TypedStringValue) { 82 propValue = evaluate((TypedStringValue) propValue); 83 } 84 copy.put(propKey, propValue); 85 } 86 return copy; 87 } 88 //解析字符串类型的属性值 89 else if (value instanceof TypedStringValue) { 90 TypedStringValue typedStringValue = (TypedStringValue) value; 91 Object valueObject = evaluate(typedStringValue); 92 try { 93 //获取属性的目标类型 94 Class<?> resolvedTargetType = resolveTargetType(typedStringValue); 95 if (resolvedTargetType != null) { 96 //对目标类型的属性进行解析,递归调用 97 return this.typeConverter.convertIfNecessary(valueObject, resolvedTargetType); 98 } 99 //没有获取到属性的目标对象,则按Object类型返回 100 else { 101 return valueObject; 102 } 103 } 104 catch (Throwable ex) { 105 throw new BeanCreationException( 106 this.beanDefinition.getResourceDescription(), this.beanName, 107 "Error converting typed String value for " + argName, ex); 108 } 109 } 110 else { 111 return evaluate(value); 112 } 113 } 114 //解析引用类型的属性值 115 private Object resolveReference(Object argName, RuntimeBeanReference ref) { 116 try { 117 //获取引用的Bean名称 118 String refName = ref.getBeanName(); 119 refName = String.valueOf(evaluate(refName)); 120 //如果引用的对象在父类容器中,则从父类容器中获取指定的引用对象 121 if (ref.isToParent()) { 122 if (this.beanFactory.getParentBeanFactory() == null) { 123 throw new BeanCreationException( 124 this.beanDefinition.getResourceDescription(), this.beanName, 125 "Can't resolve reference to bean '" + refName + 126 "' in parent factory: no parent factory available"); 127 } 128 return this.beanFactory.getParentBeanFactory().getBean(refName); 129 } 130 //从当前的容器中获取指定的引用Bean对象,如果指定的Bean没有被实例化 131 //则会递归触发引用Bean的初始化和依赖注入 132 else { 133 Object bean = this.beanFactory.getBean(refName); 134 //将当前实例化对象的依赖引用对象 135 this.beanFactory.registerDependentBean(refName, this.beanName); 136 return bean; 137 } 138 } 139 catch (BeansException ex) { 140 throw new BeanCreationException( 141 this.beanDefinition.getResourceDescription(), this.beanName, 142 "Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex); 143 } 144 } 145 //解析array类型的属性 146 private Object resolveManagedArray(Object argName, List<?> ml, Class elementType) { 147 //创建一个指定类型的数组,用于存放和返回解析后的数组 148 Object resolved = Array.newInstance(elementType, ml.size()); 149 for (int i = 0; i < ml.size(); i++) { 150 //递归解析array的每一个元素,并将解析后的值设置到resolved数组中,索引为i 151 Array.set(resolved, i, 152 resolveValueIfNecessary(new KeyedArgName(argName, i), ml.get(i))); 153 } 154 return resolved; 155 } 156 //解析list类型的属性 157 private List resolveManagedList(Object argName, List<?> ml) { 158 List<Object> resolved = new ArrayList<Object>(ml.size()); 159 for (int i = 0; i < ml.size(); i++) { 160 //递归解析list的每一个元素 161 resolved.add( 162 resolveValueIfNecessary(new KeyedArgName(argName, i), ml.get(i))); 163 } 164 return resolved; 165 } 166 //解析set类型的属性 167 private Set resolveManagedSet(Object argName, Set<?> ms) { 168 Set<Object> resolved = new LinkedHashSet<Object>(ms.size()); 169 int i = 0; 170 //递归解析set的每一个元素 171 for (Object m : ms) { 172 resolved.add(resolveValueIfNecessary(new KeyedArgName(argName, i), m)); 173 i++; 174 } 175 return resolved; 176 } 177 //解析map类型的属性 178 private Map resolveManagedMap(Object argName, Map<?, ?> mm) { 179 Map<Object, Object> resolved = new LinkedHashMap<Object, Object>(mm.size()); 180 //递归解析map中每一个元素的key和value 181 for (Map.Entry entry : mm.entrySet()) { 182 Object resolvedKey = resolveValueIfNecessary(argName, entry.getKey()); 183 Object resolvedValue = resolveValueIfNecessary( 184 new KeyedArgName(argName, entry.getKey()), entry.getValue()); 185 resolved.put(resolvedKey, resolvedValue); 186 } 187 return resolved; 188 }

复制代码

通过上面的代码分析,我们明白了Spring是如何将引用类型,内部类以及集合类型等属性进行解析的,属性值解析完成后就可以进行依赖注入了,依赖注入的过程就是Bean对象实例设置到它所依赖的Bean对象属性上去,在第7步中我们已经说过,依赖注入是通过bw.setPropertyValues方法实现的,该方法也使用了委托模式,在BeanWrapper接口中至少定义了方法声明,依赖注入的具体实现交由其实现类BeanWrapperImpl来完成,下面我们就分析依BeanWrapperImpl中赖注入相关的源码。

8、BeanWrapperImpl对Bean属性的依赖注入:

BeanWrapperImpl类主要是对容器中完成初始化的Bean实例对象进行属性的依赖注入,即把Bean对象设置到它所依赖的另一个Bean的属性中去,依赖注入的相关源码如下:

复制代码

1 //实现属性依赖注入功能 2 private void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException { 3 //PropertyTokenHolder主要保存属性的名称、路径,以及集合的size等信息 4 String propertyName = tokens.canonicalName; 5 String actualName = tokens.actualName; 6 //keys是用来保存集合类型属性的size 7 if (tokens.keys != null) { 8 //将属性信息拷贝 9 PropertyTokenHolder getterTokens = new PropertyTokenHolder(); 10 getterTokens.canonicalName = tokens.canonicalName; 11 getterTokens.actualName = tokens.actualName; 12 getterTokens.keys = new String[tokens.keys.length - 1]; 13 System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1); 14 Object propValue; 15 try { 16 //获取属性值,该方法内部使用JDK的内省( Introspector)机制,调用属性//的getter(readerMethod)方法,获取属性的值 17 propValue = getPropertyValue(getterTokens); 18 } 19 catch (NotReadablePropertyException ex) { 20 throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName, 21 "Cannot access indexed value in property referenced " + 22 "in indexed property path '" + propertyName + "'", ex); 23 } 24 //获取集合类型属性的长度 25 String key = tokens.keys[tokens.keys.length - 1]; 26 if (propValue == null) { 27 throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + propertyName, 28 "Cannot access indexed value in property referenced " + 29 "in indexed property path '" + propertyName + "': returned null"); 30 } 31 //注入array类型的属性值 32 else if (propValue.getClass().isArray()) { 33 //获取属性的描述符 34 PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); 35 //获取数组的类型 36 Class requiredType = propValue.getClass().getComponentType(); 37 //获取数组的长度 38 int arrayIndex = Integer.parseInt(key); 39 Object oldValue = null; 40 try { 41 //获取数组以前初始化的值 42 if (isExtractOldValueForEditor()) { 43 oldValue = Array.get(propValue, arrayIndex); 44 } 45 //将属性的值赋值给数组中的元素 46 Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType, 47 new PropertyTypeDescriptor(pd, new MethodParameter(pd.getReadMethod(), -1), requiredType)); 48 Array.set(propValue, arrayIndex, convertedValue); 49 } 50 catch (IndexOutOfBoundsException ex) { 51 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, 52 "Invalid array index in property path '" + propertyName + "'", ex); 53 } 54 } 55 //注入list类型的属性值 56 else if (propValue instanceof List) { 57 PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); 58 //获取list集合的类型 59 Class requiredType = GenericCollectionTypeResolver.getCollectionReturnType( 60 pd.getReadMethod(), tokens.keys.length); 61 List list = (List) propValue; 62 //获取list集合的size 63 int index = Integer.parseInt(key); 64 Object oldValue = null; 65 if (isExtractOldValueForEditor() && index < list.size()) { 66 oldValue = list.get(index); 67 } 68 //获取list解析后的属性值 69 Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(), requiredType, 70 new PropertyTypeDescriptor(pd, new MethodParameter(pd.getReadMethod(), -1), requiredType)); 71 if (index < list.size()) { 72 //为list属性赋值 73 list.set(index, convertedValue); 74 } 75 //如果list的长度大于属性值的长度,则多余的元素赋值为null 76 else if (index >= list.size()) { 77 for (int i = list.size(); i < index; i++) { 78 try { 79 list.add(null); 80 } 81 catch (NullPointerException ex) { 82 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, 83 "Cannot set element with index " + index + " in List of size " + 84 list.size() + ", accessed using property path '" + propertyName + 85 "': List does not support filling up gaps with null elements"); 86 } 87 } 88 list.add(convertedValue); 89 } 90 } 91 //注入map类型的属性值 92 else if (propValue instanceof Map) { 93 PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); 94 //获取map集合key的类型 95 Class mapKeyType = GenericCollectionTypeResolver.getMapKeyReturnType( 96 pd.getReadMethod(), tokens.keys.length); 97 //获取map集合value的类型 98 Class mapValueType = GenericCollectionTypeResolver.getMapValueReturnType( 99 pd.getReadMethod(), tokens.keys.length); 100 Map map = (Map) propValue; 101 //解析map类型属性key值 102 Object convertedMapKey = convertIfNecessary(null, null, key, mapKeyType, 103 new PropertyTypeDescriptor(pd, new MethodParameter(pd.getReadMethod(), -1), mapKeyType)); 104 Object oldValue = null; 105 if (isExtractOldValueForEditor()) { 106 oldValue = map.get(convertedMapKey); 107 } 108 //解析map类型属性value值 109 Object convertedMapValue = convertIfNecessary( 110 propertyName, oldValue, pv.getValue(), mapValueType, 111 new TypeDescriptor(new MethodParameter(pd.getReadMethod(), -1, tokens.keys.length + 1))); 112 //将解析后的key和value值赋值给map集合属性 113 map.put(convertedMapKey, convertedMapValue); 114 } 115 else { 116 throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, 117 "Property referenced in indexed property path '" + propertyName + 118 "' is neither an array nor a List nor a Map; returned value was [" + pv.getValue() + "]"); 119 } 120 } 121 //对非集合类型的属性注入 122 else { 123 PropertyDescriptor pd = pv.resolvedDescriptor; 124 if (pd == null || !pd.getWriteMethod().getDeclaringClass().isInstance(this.object)) { 125 pd = getCachedIntrospectionResults().getPropertyDescriptor(actualName); 126 //无法获取到属性名或者属性没有提供setter(写方法)方法 127 if (pd == null || pd.getWriteMethod() == null) { 128 //如果属性值是可选的,即不是必须的,则忽略该属性值 129 if (pv.isOptional()) { 130 logger.debug("Ignoring optional value for property '" + actualName + 131 "' - property not found on bean class [" + getRootClass().getName() + "]"); 132 return; 133 } 134 //如果属性值是必须的,则抛出无法给属性赋值,因为每天提供setter方法异常 135 else { 136 PropertyMatches matches = PropertyMatches.forProperty(propertyName, getRootClass()); 137 throw new NotWritablePropertyException( 138 getRootClass(), this.nestedPath + propertyName, 139 matches.buildErrorMessage(), matches.getPossibleMatches()); 140 } 141 } 142 pv.getOriginalPropertyValue().resolvedDescriptor = pd; 143 } 144 Object oldValue = null; 145 try { 146 Object originalValue = pv.getValue(); 147 Object valueToApply = originalValue; 148 if (!Boolean.FALSE.equals(pv.conversionNecessary)) { 149 if (pv.isConverted()) { 150 valueToApply = pv.getConvertedValue(); 151 } 152 else { 153 if (isExtractOldValueForEditor() && pd.getReadMethod() != null) { 154 //获取属性的getter方法(读方法),JDK内省机制 155 final Method readMethod = pd.getReadMethod(); 156 //如果属性的getter方法不是public访问控制权限的,即访问控制权限比较严格, 157 //则使用JDK的反射机制强行访问非public的方法(暴力读取属性值) 158 if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers()) && 159 !readMethod.isAccessible()) { 160 if (System.getSecurityManager()!= null) { 161 //匿名内部类,根据权限修改属性的读取控制限制 162 AccessController.doPrivileged(new PrivilegedAction<Object>() { 163 public Object run() { 164 readMethod.setAccessible(true); 165 return null; 166 } 167 }); 168 } 169 else { 170 readMethod.setAccessible(true); 171 } 172 } 173 try { 174 //属性没有提供getter方法时,调用潜在的读取属性值//的方法,获取属性值 175 if (System.getSecurityManager() != null) { 176 oldValue = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { 177 public Object run() throws Exception { 178 return readMethod.invoke(object); 179 } 180 }, acc); 181 } 182 else { 183 oldValue = readMethod.invoke(object); 184 } 185 } 186 catch (Exception ex) { 187 if (ex instanceof PrivilegedActionException) { 188 ex = ((PrivilegedActionException) ex).getException(); 189 } 190 if (logger.isDebugEnabled()) { 191 logger.debug("Could not read previous value of property '" + 192 this.nestedPath + propertyName + "'", ex); 193 } 194 } 195 } 196 //设置属性的注入值 197 valueToApply = convertForProperty(propertyName, oldValue, originalValue, pd); 198 } 199 pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue); 200 } 201 //根据JDK的内省机制,获取属性的setter(写方法)方法 202 final Method writeMethod = (pd instanceof GenericTypeAwarePropertyDescriptor ? 203 ((GenericTypeAwarePropertyDescriptor) pd).getWriteMethodForActualAccess() : 204 pd.getWriteMethod()); 205 //如果属性的setter方法是非public,即访问控制权限比较严格,则使用JDK的反射机制, 206 //强行设置setter方法可访问(暴力为属性赋值) 207 if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()) && !writeMethod.isAccessible()) { 208 //如果使用了JDK的安全机制,则需要权限验证 209 if (System.getSecurityManager()!= null) { 210 AccessController.doPrivileged(new PrivilegedAction<Object>() { 211 public Object run() { 212 writeMethod.setAccessible(true); 213 return null; 214 } 215 }); 216 } 217 else { 218 writeMethod.setAccessible(true); 219 } 220 } 221 final Object value = valueToApply; 222 if (System.getSecurityManager() != null) { 223 try { 224 //将属性值设置到属性上去 225 AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { 226 public Object run() throws Exception { 227 writeMethod.invoke(object, value); 228 return null; 229 } 230 }, acc); 231 } 232 catch (PrivilegedActionException ex) { 233 throw ex.getException(); 234 } 235 } 236 else { 237 writeMethod.invoke(this.object, value); 238 } 239 } 240 catch (TypeMismatchException ex) { 241 throw ex; 242 } 243 catch (InvocationTargetException ex) { 244 PropertyChangeEvent propertyChangeEvent = 245 new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); 246 if (ex.getTargetException() instanceof ClassCastException) { 247 throw new TypeMismatchException(propertyChangeEvent, pd.getPropertyType(), ex.getTargetException()); 248 } 249 else { 250 throw new MethodInvocationException(propertyChangeEvent, ex.getTargetException()); 251 } 252 } 253 catch (Exception ex) { 254 PropertyChangeEvent pce = 255 new PropertyChangeEvent(this.rootObject, this.nestedPath + propertyName, oldValue, pv.getValue()); 256 throw new MethodInvocationException(pce, ex); 257 } 258 } }

复制代码

通过对上面注入依赖代码的分析,我们已经明白了Spring IoC容器是如何将属性的值注入到Bean实例对象中去的:

(1).对于集合类型的属性,将其属性值解析为目标类型的集合后直接赋值给属性。

(2).对于非集合类型的属性,大量使用了JDK的反射和内省机制,通过属性的getter方法(reader method)获取指定属性注入以前的值,同时调用属性的setter方法(writer method)为属性设置注入后的值。看到这里相信很多人都明白了Spring的setter注入原理。

至此Spring IoC容器对Bean定义资源文件的定位,载入、解析和依赖注入已经全部分析完毕,现在Spring IoC容器中管理了一系列靠依赖关系联系起来的Bean,程序不需要应用自己手动创建所需的对象,Spring IoC容器会在我们使用的时候自动为我们创建,并且为我们注入好相关的依赖,这就是Spring核心功能的控制反转和依赖注入的相关功能。

五、IoC容器的高级特性

1、介绍

通过前面4篇文章对Spring IoC容器的源码分析,我们已经基本上了解了Spring IoC容器对Bean定义资源的定位、读入和解析过程,同时也清楚了当用户通过getBean方法向IoC容器获取被管理的Bean时,IoC容器对Bean进行的初始化和依赖注入过程,这些是Spring IoC容器的基本功能特性。Spring IoC容器还有一些高级特性,如使用lazy-init属性对Bean预初始化、FactoryBean产生或者修饰Bean对象的生成、IoC容器初始化Bean过程中使用BeanPostProcessor后置处理器对Bean声明周期事件管理和IoC容器的autowiring自动装配功能等。

2、Spring IoC容器的lazy-init属性实现预实例化:

通过前面我们对IoC容器的实现和工作原理分析,我们知道IoC容器的初始化过程就是对Bean定义资源的定位、载入和注册,此时容器对Bean的依赖注入并没有发生,依赖注入主要是在应用程序第一次向容器索取Bean时,通过getBean方法的调用完成。

当Bean定义资源的<Bean>元素中配置了lazy-init属性时,容器将会在初始化的时候对所配置的Bean进行预实例化,Bean的依赖注入在容器初始化的时候就已经完成。这样,当应用程序第一次向容器索取被管理的Bean时,就不用再初始化和对Bean进行依赖注入了,直接从容器中获取已经完成依赖注入的现成Bean,可以提高应用第一次向容器获取Bean的性能。

下面我们通过代码分析容器预实例化的实现过程:

(1).refresh()

先从IoC容器的初始会过程开始,通过前面文章分析,我们知道IoC容器读入已经定位的Bean定义资源是从refresh方法开始的,我们首先从AbstractApplicationContext类的refresh方法入手分析,源码如下:

复制代码

1 //容器初始化的过程,读入Bean定义资源,并解析注册 2 public void refresh() throws BeansException, IllegalStateException { 3 synchronized (this.startupShutdownMonitor) { 4 //调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识 5 prepareRefresh(); 6 //告诉子类启动refreshBeanFactory()方法,Bean定义资源文件的载入从 7 //子类的refreshBeanFactory()方法启动 8 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); 9 //为BeanFactory配置容器特性,例如类加载器、事件处理器等 10 prepareBeanFactory(beanFactory); 11 try { 12 //为容器的某些子类指定特殊的BeanPost事件处理器 13 postProcessBeanFactory(beanFactory); 14 //调用所有注册的BeanFactoryPostProcessor的Bean 15 invokeBeanFactoryPostProcessors(beanFactory); 16 //为BeanFactory注册BeanPost事件处理器. 17 //BeanPostProcessor是Bean后置处理器,用于监听容器触发的事件 18 registerBeanPostProcessors(beanFactory); 19 //初始化信息源,和国际化相关. 20 initMessageSource(); 21 //初始化容器事件传播器. 22 initApplicationEventMulticaster(); 23 //调用子类的某些特殊Bean初始化方法 24 onRefresh(); 25 //为事件传播器注册事件监听器. 26 registerListeners(); 27 //这里是对容器lazy-init属性进行处理的入口方法 28 finishBeanFactoryInitialization(beanFactory); 29 //初始化容器的生命周期事件处理器,并发布容器的生命周期事件 30 finishRefresh(); 31 } 32 catch (BeansException ex) { 33 //销毁以创建的单态Bean 34 destroyBeans(); 35 //取消refresh操作,重置容器的同步标识. 36 cancelRefresh(ex); 37 throw ex; 38 } 39 } }

复制代码

在refresh方法中ConfigurableListableBeanFactorybeanFactory = obtainFreshBeanFactory();启动了Bean定义资源的载入、注册过程,而finishBeanFactoryInitialization方法是对注册后的Bean定义中的预实例化(lazy-init=false,Spring默认就是预实例化,即为true)的Bean进行处理的地方。

(2).finishBeanFactoryInitialization处理预实例化Bean:

当Bean定义资源被载入IoC容器之后,容器将Bean定义资源解析为容器内部的数据结构BeanDefinition注册到容器中,AbstractApplicationContext类中的finishBeanFactoryInitialization方法对配置了预实例化属性的Bean进行预初始化过程,源码如下:

复制代码

1 //对配置了lazy-init属性的Bean进行预实例化处理 2 protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { 3 //这是Spring3以后新加的代码,为容器指定一个转换服务(ConversionService) 4 //在对某些Bean属性进行转换时使用 5 if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) && 6 beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) { 7 beanFactory.setConversionService( 8 beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)); 9 } 10 //为了类型匹配,停止使用临时的类加载器 11 beanFactory.setTempClassLoader(null); 12 //缓存容器中所有注册的BeanDefinition元数据,以防被修改 13 beanFactory.freezeConfiguration(); 14 //对配置了lazy-init属性的单态模式Bean进行预实例化处理 15 beanFactory.preInstantiateSingletons(); }

复制代码

ConfigurableListableBeanFactory是一个接口,其preInstantiateSingletons方法由其子类DefaultListableBeanFactory提供。

(3)、DefaultListableBeanFactory对配置lazy-init属性单态Bean的预实例化:

复制代码

1//对配置lazy-init属性单态Bean的预实例化 2public void preInstantiateSingletons() throws BeansException { 3 if (this.logger.isInfoEnabled()) { 4 this.logger.info("Pre-instantiating singletons in " + this); 5 } 6 //在对配置lazy-init属性单态Bean的预实例化过程中,必须多线程同步,以确保数据一致性 7 synchronized (this.beanDefinitionMap) { 8 for (String beanName : this.beanDefinitionNames) { 9 //获取指定名称的Bean定义 10 RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); 11 //Bean不是抽象的,是单态模式的,且lazy-init属性配置为false 12 if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) { 13 //如果指定名称的bean是创建容器的Bean 14 if (isFactoryBean(beanName)) { 15 //FACTORY_BEAN_PREFIX=”&”,当Bean名称前面加”&”符号 16 //时,获取的是产生容器对象本身,而不是容器产生的Bean. 17 //调用getBean方法,触发容器对Bean实例化和依赖注入过程 18 final FactoryBean factory = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName); 19 //标识是否需要预实例化 20 boolean isEagerInit; 21 if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) { 22 //一个匿名内部类 23 isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() { 24 public Boolean run() { 25 return ((SmartFactoryBean) factory).isEagerInit(); 26 } 27 }, getAccessControlContext()); 28 } 29 else { 30 isEagerInit = factory instanceof SmartFactoryBean && ((SmartFactoryBean) factory).isEagerInit(); 31 } 32 if (isEagerInit) { 33 //调用getBean方法,触发容器对Bean实例化和依赖注入过程 34 getBean(beanName); 35 } 36 } 37 else { 38 //调用getBean方法,触发容器对Bean实例化和依赖注入过程 39 getBean(beanName); 40 } 41 } 42 } 43 } }

复制代码

通过对lazy-init处理源码的分析,我们可以看出,如果设置了lazy-init属性,则容器在完成Bean定义的注册之后,会通过getBean方法,触发对指定Bean的初始化和依赖注入过程,这样当应用第一次向容器索取所需的Bean时,容器不再需要对Bean进行初始化和依赖注入,直接从已经完成实例化和依赖注入的Bean中取一个线程的Bean,这样就提高了第一次获取Bean的性能。

3、FactoryBean的实现:

在Spring中,有两个很容易混淆的类:BeanFactory和FactoryBean。BeanFactory:Bean工厂,是一个工厂(Factory),我们Spring IoC容器的最顶层接口就是这个BeanFactory,它的作用是管理Bean,即实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。

FactoryBean:工厂Bean,是一个Bean,作用是产生其他bean实例。通常情况下,这种bean没有什么特别的要求,仅需要提供一个工厂方法,该方法用来返回其他bean实例。通常情况下,bean无须自己实现工厂模式,Spring容器担任工厂角色;但少数情况下,容器中的bean本身就是工厂,其作用是产生其它bean实例。

当用户使用容器本身时,可以使用转义字符”&”来得到FactoryBean本身,以区别通过FactoryBean产生的实例对象和FactoryBean对象本身。在BeanFactory中通过如下代码定义了该转义字符:

StringFACTORY_BEAN_PREFIX = "&";

如果myJndiObject是一个FactoryBean,则使用&myJndiObject得到的是myJndiObject对象,而不是myJndiObject产生出来的对象。

(1).FactoryBean的源码如下:

复制代码

//工厂Bean,用于产生其他对象 public interface FactoryBean<T> { //获取容器管理的对象实例 T getObject() throws Exception; //获取Bean工厂创建的对象的类型 Class<?> getObjectType(); //Bean工厂创建的对象是否是单态模式,如果是单态模式,则整个容器中只有一个实例 //对象,每次请求都返回同一个实例对象 boolean isSingleton(); }

复制代码

(2). AbstractBeanFactory的getBean方法调用FactoryBean:

在前面我们分析Spring Ioc容器实例化Bean并进行依赖注入过程的源码时,提到在getBean方法触发容器实例化Bean的时候会调用AbstractBeanFactory的doGetBean方法来进行实例化的过程,源码如下:

复制代码

1 //真正实现向IoC容器获取Bean的功能,也是触发依赖注入功能的地方 2 @SuppressWarnings("unchecked") 3 protected <T> T doGetBean( 4 final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) 5 throws BeansException { 6 //根据指定的名称获取被管理Bean的名称,剥离指定名称中对容器的相关依赖 7 //如果指定的是别名,将别名转换为规范的Bean名称 8 final String beanName = transformedBeanName(name); 9 Object bean; 10 //先从缓存中取是否已经有被创建过的单态类型的Bean,对于单态模式的Bean整 11 //个IoC容器中只创建一次,不需要重复创建 12 Object sharedInstance = getSingleton(beanName); 13 //IoC容器创建单态模式Bean实例对象 14 if (sharedInstance != null && args == null) { 15 if (logger.isDebugEnabled()) { 16 //如果指定名称的Bean在容器中已有单态模式的Bean被创建,直接返回 17 //已经创建的Bean 18 if (isSingletonCurrentlyInCreation(beanName)) { 19 logger.debug("Returning eagerly cached instance of singleton bean '" + beanName + 20 "' that is not fully initialized yet - a consequence of a circular reference"); 21 } 22 else { 23 logger.debug("Returning cached instance of singleton bean '" + beanName + "'"); 24 } 25 } 26 //获取给定Bean的实例对象,主要是完成FactoryBean的相关处理 27 bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); 28 } 29 …… 30 } 31 //获取给定Bean的实例对象,主要是完成FactoryBean的相关处理 32 protected Object getObjectForBeanInstance( 33 Object beanInstance, String name, String beanName, RootBeanDefinition mbd) { 34 //容器已经得到了Bean实例对象,这个实例对象可能是一个普通的Bean,也可能是 35 //一个工厂Bean,如果是一个工厂Bean,则使用它创建一个Bean实例对象,如果 36 //调用本身就想获得一个容器的引用,则指定返回这个工厂Bean实例对象 37 //如果指定的名称是容器的解引用(dereference,即是对象本身而非内存地址), 38 //且Bean实例也不是创建Bean实例对象的工厂Bean 39 if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) { 40 throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass()); 41 } 42 //如果Bean实例不是工厂Bean,或者指定名称是容器的解引用,调用者向获取对 43 //容器的引用,则直接返回当前的Bean实例 44 if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) { 45 return beanInstance; 46 } 47 //处理指定名称不是容器的解引用,或者根据名称获取的Bean实例对象是一个工厂Bean 48 //使用工厂Bean创建一个Bean的实例对象 49 Object object = null; 50 if (mbd == null) { 51 //从Bean工厂缓存中获取给定名称的Bean实例对象 52 object = getCachedObjectForFactoryBean(beanName); 53 } 54 //让Bean工厂生产给定名称的Bean对象实例 55 if (object == null) { 56 FactoryBean factory = (FactoryBean) beanInstance; 57 //如果从Bean工厂生产的Bean是单态模式的,则缓存 58 if (mbd == null && containsBeanDefinition(beanName)) { 59 //从容器中获取指定名称的Bean定义,如果继承基类,则合并基类相关属性 60 mbd = getMergedLocalBeanDefinition(beanName); 61 } 62 //如果从容器得到Bean定义信息,并且Bean定义信息不是虚构的,则让工厂 63 //Bean生产Bean实例对象 64 boolean synthetic = (mbd != null && mbd.isSynthetic()); 65 //调用FactoryBeanRegistrySupport类的getObjectFromFactoryBean 66 //方法,实现工厂Bean生产Bean对象实例的过程 67 object = getObjectFromFactoryBean(factory, beanName, !synthetic); 68 } 69 return object; }

复制代码

在上面获取给定Bean的实例对象的getObjectForBeanInstance方法中,会调用FactoryBeanRegistrySupport类的getObjectFromFactoryBean方法,该方法实现了Bean工厂生产Bean实例对象。

Dereference(解引用):一个在C/C++中应用比较多的术语,在C++中,”*”是解引用符号,而”&”是引用符号,解引用是指变量指向的是所引用对象的本身数据,而不是引用对象的内存地址。

(3)、AbstractBeanFactory生产Bean实例对象:

AbstractBeanFactory类中生产Bean实例对象的主要源码如下:

复制代码

71 //Bean工厂生产Bean实例对象 72 protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName, boolean shouldPostProcess) { 73 //Bean工厂是单态模式,并且Bean工厂缓存中存在指定名称的Bean实例对象 74 if (factory.isSingleton() && containsSingleton(beanName)) { 75 //多线程同步,以防止数据不一致 76 synchronized (getSingletonMutex()) { 77 //直接从Bean工厂缓存中获取指定名称的Bean实例对象 78 Object object = this.factoryBeanObjectCache.get(beanName); 79 //Bean工厂缓存中没有指定名称的实例对象,则生产该实例对象 80 if (object == null) { 81 //调用Bean工厂的getObject方法生产指定Bean的实例对象 82 object = doGetObjectFromFactoryBean(factory, beanName, shouldPostProcess); 83 //将生产的实例对象添加到Bean工厂缓存中 84 this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT)); 85 } 86 return (object != NULL_OBJECT ? object : null); 87 } 88 } 89 //调用Bean工厂的getObject方法生产指定Bean的实例对象 90 else { 91 return doGetObjectFromFactoryBean(factory, beanName, shouldPostProcess); 92 } 93 } 94 //调用Bean工厂的getObject方法生产指定Bean的实例对象 95 private Object doGetObjectFromFactoryBean( 96 final FactoryBean factory, final String beanName, final boolean shouldPostProcess) 97 throws BeanCreationException { 98 Object object; 99 try { 100 if (System.getSecurityManager() != null) { 101 AccessControlContext acc = getAccessControlContext(); 102 try { 103 //实现PrivilegedExceptionAction接口的匿名内置类 104 //根据JVM检查权限,然后决定BeanFactory创建实例对象 105 object = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { 106 public Object run() throws Exception { 107 //调用BeanFactory接口实现类的创建对象方法 108 return factory.getObject(); 109 } 110 }, acc); 111 } 112 catch (PrivilegedActionException pae) { 113 throw pae.getException(); 114 } 115 } 116 else { 117 //调用BeanFactory接口实现类的创建对象方法 118 object = factory.getObject(); 119 } 120 } 121 catch (FactoryBeanNotInitializedException ex) { 122 throw new BeanCurrentlyInCreationException(beanName, ex.toString()); 123 } 124 catch (Throwable ex) { 125 throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex); 126 } 127 //创建出来的实例对象为null,或者因为单态对象正在创建而返回null 128 if (object == null && isSingletonCurrentlyInCreation(beanName)) { 129 throw new BeanCurrentlyInCreationException( 130 beanName, "FactoryBean which is currently in creation returned null from getObject"); 131 } 132 //为创建出来的Bean实例对象添加BeanPostProcessor后置处理器 133 if (object != null && shouldPostProcess) { 134 try { 135 object = postProcessObjectFromFactoryBean(object, beanName); 136 } 137 catch (Throwable ex) { 138 throw new BeanCreationException(beanName, "Post-processing of the FactoryBean's object failed", ex); 139 } 140 } 141 return object; }

复制代码

从上面的源码分析中,我们可以看出,BeanFactory接口调用其实现类的getObject方法来实现创建Bean实例对象的功能。

(4).工厂Bean的实现类getObject方法创建Bean实例对象:

FactoryBean的实现类有非常多,比如:Proxy、RMI、JNDI、ServletContextFactoryBean等等,FactoryBean接口为Spring容器提供了一个很好的封装机制,具体的getObject有不同的实现类根据不同的实现策略来具体提供,我们分析一个最简单的AnnotationTestFactoryBean的实现源码:

复制代码

143 public class AnnotationTestBeanFactory implements FactoryBean<IJmxTestBean> { 144 private final FactoryCreatedAnnotationTestBean instance = new FactoryCreatedAnnotationTestBean(); 145 public AnnotationTestBeanFactory() { 146 this.instance.setName("FACTORY"); 147 } 148 //AnnotationTestBeanFactory产生Bean实例对象的实现 149 public IJmxTestBean getObject() throws Exception { 150 return this.instance; 151 } 152 public Class<? extends IJmxTestBean> getObjectType() { 153 return FactoryCreatedAnnotationTestBean.class; 154 } 155 public boolean isSingleton() { 156 return true; 157 } }

复制代码

其他的Proxy,RMI,JNDI等等,都是根据相应的策略提供getObject的实现。这里不做一一分析,这已经不是Spring的核心功能,有需要的时候再去深入研究。

4.BeanPostProcessor后置处理器的实现:

BeanPostProcessor后置处理器是Spring IoC容器经常使用到的一个特性,这个Bean后置处理器是一个监听器,可以监听容器触发的Bean声明周期事件。后置处理器向容器注册以后,容器中管理的Bean就具备了接收IoC容器事件回调的能力。

BeanPostProcessor的使用非常简单,只需要提供一个实现接口BeanPostProcessor的实现类,然后在Bean的配置文件中设置即可。

(1).BeanPostProcessor的源码如下:

复制代码

1 package org.springframework.beans.factory.config; 2 import org.springframework.beans.BeansException; 3 public interface BeanPostProcessor { 4 //为在Bean的初始化前提供回调入口 5 Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; 6 //为在Bean的初始化之后提供回调入口 7 Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; }

复制代码

这两个回调的入口都是和容器管理的Bean的生命周期事件紧密相关,可以为用户提供在Spring IoC容器初始化Bean过程中自定义的处理操作。

(2).AbstractAutowireCapableBeanFactory类对容器生成的Bean添加后置处理器:

BeanPostProcessor后置处理器的调用发生在Spring IoC容器完成对Bean实例对象的创建和属性的依赖注入完成之后,在对Spring依赖注入的源码分析过程中我们知道,当应用程序第一次调用getBean方法(lazy-init预实例化除外)向Spring IoC容器索取指定Bean时触发Spring IoC容器创建Bean实例对象并进行依赖注入的过程,其中真正实现创建Bean对象并进行依赖注入的方法是AbstractAutowireCapableBeanFactory类的doCreateBean方法,主要源码如下:

复制代码

1 //真正创建Bean的方法 2 protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) { 3 //创建Bean实例对象 4 …… 5 try { 6 //对Bean属性进行依赖注入 7 populateBean(beanName, mbd, instanceWrapper); 8 if (exposedObject != null) { 9 //在对Bean实例对象生成和依赖注入完成以后,开始对Bean实例对象 10 //进行初始化 ,为Bean实例对象应用BeanPostProcessor后置处理器 11 exposedObject = initializeBean(beanName, exposedObject, mbd); 12 } 13 } 14 catch (Throwable ex) { 15 if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) { 16 throw (BeanCreationException) ex; 17 } 18 …… 19 //为应用返回所需要的实例对象 20 return exposedObject; }

复制代码

从上面的代码中我们知道,为Bean实例对象添加BeanPostProcessor后置处理器的入口的是initializeBean方法。

(3).initializeBean方法为容器产生的Bean实例对象添加BeanPostProcessor后置处理器:

同样在AbstractAutowireCapableBeanFactory类中,initializeBean方法实现为容器创建的Bean实例对象添加BeanPostProcessor后置处理器,源码如下:

复制代码

1 //初始容器创建的Bean实例对象,为其添加BeanPostProcessor后置处理器 2 protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) { 3 //JDK的安全机制验证权限 4 if (System.getSecurityManager() != null) { 5 //实现PrivilegedAction接口的匿名内部类 6 AccessController.doPrivileged(new PrivilegedAction<Object>() { 7 public Object run() { 8 invokeAwareMethods(beanName, bean); 9 return null; 10 } 11 }, getAccessControlContext()); 12 } 13 else { 14 //为Bean实例对象包装相关属性,如名称,类加载器,所属容器等信息 15 invokeAwareMethods(beanName, bean); 16 } 17 Object wrappedBean = bean; 18 //对BeanPostProcessor后置处理器的postProcessBeforeInitialization 19 //回调方法的调用,为Bean实例初始化前做一些处理 20 if (mbd == null || !mbd.isSynthetic()) { 21 wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); 22 } 23 //调用Bean实例对象初始化的方法,这个初始化方法是在Spring Bean定义配置 24 //文件中通过init-method属性指定的 25 try { 26 invokeInitMethods(beanName, wrappedBean, mbd); 27 } 28 catch (Throwable ex) { 29 throw new BeanCreationException( 30 (mbd != null ? mbd.getResourceDescription() : null), 31 beanName, "Invocation of init method failed", ex); 32 } 33 //对BeanPostProcessor后置处理器的postProcessAfterInitialization 34 //回调方法的调用,为Bean实例初始化之后做一些处理 35 if (mbd == null || !mbd.isSynthetic()) { 36 wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); 37 } 38 return wrappedBean; 39 } 40 //调用BeanPostProcessor后置处理器实例对象初始化之前的处理方法 41 public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) 42 throws BeansException { 43 Object result = existingBean; 44 //遍历容器为所创建的Bean添加的所有BeanPostProcessor后置处理器 45 for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) { 46 //调用Bean实例所有的后置处理中的初始化前处理方法,为Bean实例对象在 47 //初始化之前做一些自定义的处理操作 48 result = beanProcessor.postProcessBeforeInitialization(result, beanName); 49 if (result == null) { 50 return result; 51 } 52 } 53 return result; 54 } 55 //调用BeanPostProcessor后置处理器实例对象初始化之后的处理方法 56 public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) 57 throws BeansException { 58 Object result = existingBean; 59 //遍历容器为所创建的Bean添加的所有BeanPostProcessor后置处理器 60 for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) { 61 //调用Bean实例所有的后置处理中的初始化后处理方法,为Bean实例对象在 62 //初始化之后做一些自定义的处理操作 63 result = beanProcessor.postProcessAfterInitialization(result, beanName); 64 if (result == null) { 65 return result; 66 } 67 } 68 return result; }

复制代码

BeanPostProcessor是一个接口,其初始化前的操作方法和初始化后的操作方法均委托其实现子类来实现,在Spring中,BeanPostProcessor的实现子类非常的多,分别完成不同的操作,如:AOP面向切面编程的注册通知适配器、Bean对象的数据校验、Bean继承属性/方法的合并等等,我们以最简单的AOP切面织入来简单了解其主要的功能。

(4).AdvisorAdapterRegistrationManager在Bean对象初始化后注册通知适配器:

AdvisorAdapterRegistrationManager是BeanPostProcessor的一个实现类,其主要的作用为容器中管理的Bean注册一个面向切面编程的通知适配器,以便在Spring容器为所管理的Bean进行面向切面编程时提供方便,其源码如下:

复制代码

1 //为容器中管理的Bean注册一个面向切面编程的通知适配器 2 public class AdvisorAdapterRegistrationManager implements BeanPostProcessor { 3 //容器中负责管理切面通知适配器注册的对象 4 private AdvisorAdapterRegistry advisorAdapterRegistry = GlobalAdvisorAdapterRegistry.getInstance(); 5 public void setAdvisorAdapterRegistry(AdvisorAdapterRegistry advisorAdapterRegistry) { 6 this.advisorAdapterRegistry = advisorAdapterRegistry; 7 } 8 //BeanPostProcessor在Bean对象初始化前的操作 9 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { 10 //没有做任何操作,直接返回容器创建的Bean对象 11 return bean; 12 } 13 //BeanPostProcessor在Bean对象初始化后的操作 14 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { 15 if (bean instanceof AdvisorAdapter){ 16 //如果容器创建的Bean实例对象是一个切面通知适配器,则向容器的注册this.advisorAdapterRegistry.registerAdvisorAdapter((AdvisorAdapter) bean); 17 } 18 return bean; 19 } }

复制代码

其他的BeanPostProcessor接口实现类的也类似,都是对Bean对象使用到的一些特性进行处理,或者向IoC容器中注册,为创建的Bean实例对象做一些自定义的功能增加,这些操作是容器初始化Bean时自动触发的,不需要认为的干预。

5.Spring IoC容器autowiring实现原理:

Spring IoC容器提供了两种管理Bean依赖关系的方式:

a.      显式管理:通过BeanDefinition的属性值和构造方法实现Bean依赖关系管理。

b. autowiring:Spring IoC容器的依赖自动装配功能,不需要对Bean属性的依赖关系做显式的声明,只需要在配置好autowiring属性,IoC容器会自动使用反射查找属性的类型和名称,然后基于属性的类型或者名称来自动匹配容器中管理的Bean,从而自动地完成依赖注入。

通过对autowiring自动装配特性的理解,我们知道容器对Bean的自动装配发生在容器对Bean依赖注入的过程中。在前面对Spring IoC容器的依赖注入过程源码分析中,我们已经知道了容器对Bean实例对象的属性注入的处理发生在AbstractAutoWireCapableBeanFactory类中的populateBean方法中,我们通过程序流程分析autowiring的实现原理:

(1). AbstractAutoWireCapableBeanFactory对Bean实例进行属性依赖注入:

应用第一次通过getBean方法(配置了lazy-init预实例化属性的除外)向IoC容器索取Bean时,容器创建Bean实例对象,并且对Bean实例对象进行属性依赖注入,AbstractAutoWireCapableBeanFactory的populateBean方法就是实现Bean属性依赖注入的功能,其主要源码如下:

复制代码

1 protected void populateBean(String beanName, AbstractBeanDefinition mbd, BeanWrapper bw) { 2 //获取Bean定义的属性值,并对属性值进行处理 3 PropertyValues pvs = mbd.getPropertyValues(); 4 …… 5 //对依赖注入处理,首先处理autowiring自动装配的依赖注入 6 if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME || 7 mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) { 8 MutablePropertyValues newPvs = new MutablePropertyValues(pvs); 9 //根据Bean名称进行autowiring自动装配处理 10 if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) { 11 autowireByName(beanName, mbd, bw, newPvs); 12 } 13 //根据Bean类型进行autowiring自动装配处理 14 if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) { 15 autowireByType(beanName, mbd, bw, newPvs); 16 } 17 } 18 //对非autowiring的属性进行依赖注入处理 19 …… }

复制代码

(2).Spring IoC容器根据Bean名称或者类型进行autowiring自动依赖注入:

复制代码

1 //根据名称对属性进行自动依赖注入 2 protected void autowireByName( 3 String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) { 4 //对Bean对象中非简单属性(不是简单继承的对象,如8中原始类型,字符串,URL等//都是简单属性)进行处理 5 String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw); 6 for (String propertyName : propertyNames) { 7 //如果Spring IoC容器中包含指定名称的Bean 8 if (containsBean(propertyName)) { 9 //调用getBean方法向IoC容器索取指定名称的Bean实例,迭代触发属性的//初始化和依赖注入 10 Object bean = getBean(propertyName); 11 //为指定名称的属性赋予属性值 12 pvs.add(propertyName, bean); 13 //指定名称属性注册依赖Bean名称,进行属性依赖注入 14 registerDependentBean(propertyName, beanName); 15 if (logger.isDebugEnabled()) { 16 logger.debug("Added autowiring by name from bean name '" + beanName + 17 "' via property '" + propertyName + "' to bean named '" + propertyName + "'"); 18 } 19 } 20 else { 21 if (logger.isTraceEnabled()) { 22 logger.trace("Not autowiring property '" + propertyName + "' of bean '" + beanName + 23 "' by name: no matching bean found"); 24 } 25 } 26 } 27 } 28 //根据类型对属性进行自动依赖注入 29 protected void autowireByType( 30 String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) { 31 //获取用户定义的类型转换器 32 TypeConverter converter = getCustomTypeConverter(); 33 if (converter == null) { 34 converter = bw; 35 } 36 //存放解析的要注入的属性 37 Set<String> autowiredBeanNames = new LinkedHashSet<String>(4); 38 //对Bean对象中非简单属性(不是简单继承的对象,如8中原始类型,字符 39 //URL等都是简单属性)进行处理 40 String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw); 41 for (String propertyName : propertyNames) { 42 try { 43 //获取指定属性名称的属性描述器 44 PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName); 45 //不对Object类型的属性进行autowiring自动依赖注入 46 if (!Object.class.equals(pd.getPropertyType())) { 47 //获取属性的setter方法 48 MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd); 49 //检查指定类型是否可以被转换为目标对象的类型 50 boolean eager = !PriorityOrdered.class.isAssignableFrom(bw.getWrappedClass()); 51 //创建一个要被注入的依赖描述 52 DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(methodParam, eager); 53 //根据容器的Bean定义解析依赖关系,返回所有要被注入的Bean对象 54 Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter); 55 if (autowiredArgument != null) { 56 //为属性赋值所引用的对象 57 pvs.add(propertyName, autowiredArgument); 58 } 59 for (String autowiredBeanName : autowiredBeanNames) { 60 //指定名称属性注册依赖Bean名称,进行属性依赖注入 61 registerDependentBean(autowiredBeanName, beanName); 62 if (logger.isDebugEnabled()) { 63 logger.debug("Autowiring by type from bean name '" + beanName + "' via property '" + 64 propertyName + "' to bean named '" + autowiredBeanName + "'"); 65 } 66 } 67 //释放已自动注入的属性 68 autowiredBeanNames.clear(); 69 } 70 } 71 catch (BeansException ex) { 72 throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex); 73 } 74 } }

复制代码

通过上面的源码分析,我们可以看出来通过属性名进行自动依赖注入的相对比通过属性类型进行自动依赖注入要稍微简单一些,但是真正实现属性注入的是DefaultSingletonBeanRegistry类的registerDependentBean方法。

(3).DefaultSingletonBeanRegistry的registerDependentBean方法对属性注入:

复制代码

1 //为指定的Bean注入依赖的Bean 2 public void registerDependentBean(String beanName, String dependentBeanName) { 3 //处理Bean名称,将别名转换为规范的Bean名称 4 String canonicalName = canonicalName(beanName); 5 //多线程同步,保证容器内数据的一致性 6 //先从容器中:bean名称-->全部依赖Bean名称集合找查找给定名称Bean的依赖Bean 7 synchronized (this.dependentBeanMap) { 8 //获取给定名称Bean的所有依赖Bean名称 9 Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName); 10 if (dependentBeans == null) { 11 //为Bean设置依赖Bean信息 12 dependentBeans = new LinkedHashSet<String>(8); 13 this.dependentBeanMap.put(canonicalName, dependentBeans); 14 } 15 //向容器中:bean名称-->全部依赖Bean名称集合添加Bean的依赖信息 16 //即,将Bean所依赖的Bean添加到容器的集合中 17 dependentBeans.add(dependentBeanName); 18 } 19 //从容器中:bean名称-->指定名称Bean的依赖Bean集合找查找给定名称 20 //Bean的依赖Bean 21 synchronized (this.dependenciesForBeanMap) { 22 Set<String> dependenciesForBean = this.dependenciesForBeanMap.get(dependentBeanName); 23 if (dependenciesForBean == null) { 24 dependenciesForBean = new LinkedHashSet<String>(8); 25 this.dependenciesForBeanMap.put(dependentBeanName, dependenciesForBean); 26 } 27 //向容器中:bean名称-->指定Bean的依赖Bean名称集合添加Bean的依赖信息 28 //即,将Bean所依赖的Bean添加到容器的集合中 29 dependenciesForBean.add(canonicalName); 30 } }

复制代码

通过对autowiring的源码分析,我们可以看出,autowiring的实现过程:

a.    对Bean的属性迭代调用getBean方法,完成依赖Bean的初始化和依赖注入。

b.    将依赖Bean的属性引用设置到被依赖的Bean属性上。

c.     将依赖Bean的名称和被依赖Bean的名称存储在IoC容器的集合中。

Spring IoC容器的autowiring属性自动依赖注入是一个很方便的特性,可以简化开发时的配置,但是凡是都有两面性,自动属性依赖注入也有不足,首先,Bean的依赖关系在配置文件中无法很清楚地看出来,对于维护造成一定困难。其次,由于自动依赖注入是Spring容器自动执行的,容器是不会智能判断的,如果配置不当,将会带来无法预料的后果,所以自动依赖注入特性在使用时还是综合考虑。

最近,买了本spring入门书:spring In Action 。大致浏览了下感觉还不错。就是入门了点。Manning的书还是不错的,我虽然不像哪些只看Manning书的人那样专注于Manning,但怀着崇敬的心情和激情通览了一遍。又一次接受了IOC 、DI、AOP等Spring核心概念。 先就IOC和DI谈一点我的看法。

IOC(DI):其实这个Spring架构核心的概念没有这么复杂,更不像有些书上描述的那样晦涩。Java程序员都知道:java程序中的每个业务逻辑至少需要两个或以上的对象来协作完成,通常,每个对象在使用他的合作对象时,自己均要使用像new object() 这样的语法来完成合作对象的申请工作。你会发现:对象间的耦合度高了。而IOC的思想是:Spring容器来实现这些相互依赖对象的创建、协调工作。对象只需要关系业务逻辑本身就可以了。从这方面来说,对象如何得到他的协作对象的责任被反转了(IOC、DI)。

这是我对Spring的IOC的体会。DI其实就是IOC的另外一种说法。DI是由Martin Fowler 在2004年初的一篇论文中首次提出的。他总结:控制的什么被反转了?就是:获得依赖对象的方式反转了。

如果对这一核心概念还不理解:这里引用一个叫Bromon的blog上找到的浅显易懂的答案:

IoC与DI

首先想说说IoC(Inversion of Control,控制倒转)。这是spring的核心,贯穿始终。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。

那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。如果你还不明白的话,我决定放弃。

IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。关于反射的相关资料请查阅java doc。

理解了IoC和DI的概念后,一切都将变得简单明了,剩下的工作只是在spring的框架中堆积木而已。

如果还不明白,放弃java吧!

下面来让大家了解一下Spring到底是怎么运行的。

Java代码 public static void main(String[] args) {           ApplicationContext context = new FileSystemXmlApplicationContext(                   "applicationContext.xml");           Animal animal = (Animal) context.getBean("animal");           animal.say();       }  [java] view plain copypublic static void main(String[] args) {          ApplicationContext context = new FileSystemXmlApplicationContext(                  "applicationContext.xml");          Animal animal = (Animal) context.getBean("animal");          animal.say();      }

这段代码你一定很熟悉吧,不过还是让我们分析一下它吧,首先是applicationContext.xmlJava代码 <bean id="animal" class="phz.springframework.test.Cat">           <property name="name" value="kitty" />       </bean>  [java] view plain copy<bean id="animal" class="phz.springframework.test.Cat">          <property name="name" value="kitty" />      </bean>

他有一个类phz.springframework.test.CatJava代码 public class Cat implements Animal {       private String name;       public void say() {           System.out.println("I am " + name + "!");       }       public void setName(String name) {           this.name = name;       }   }  [java] view plain copypublic class Cat implements Animal {      private String name;      public void say() {          System.out.println("I am " + name + "!");      }      public void setName(String name) {          this.name = name;      }  }

实现了phz.springframework.test.Animal接口Java代码 public interface Animal {       public void say();   }  [java] view plain copypublic interface Animal {      public void say();  }

很明显上面的代码输出I am kitty!

那么到底Spring是如何做到的呢?

接下来就让我们自己写个Spring 来看看Spring 到底是怎么运行的吧!

首先,我们定义一个Bean类,这个类用来存放一个Bean拥有的属性Java代码 /* Bean Id */      private String id;       /* Bean Class */      private String type;       /* Bean Property */      private Map<String, Object> properties = new HashMap<String, Object>();  [java] view plain copy/* Bean Id */      private String id;      /* Bean Class */      private String type;      /* Bean Property */      private Map<String, Object> properties = new HashMap<String, Object>();

一个Bean包括id,type,和Properties。

接下来Spring 就开始加载我们的配置文件了,将我们配置的信息保存在一个HashMap中,HashMap的key就是Bean 的 Id ,HasMap 的value是这个Bean,只有这样我们才能通过context.getBean("animal")这个方法获得Animal这个类。我们都知道Spirng可以注入基本类型,而且可以注入像List,Map这样的类型,接下来就让我们以Map为例看看Spring是怎么保存的吧

Map配置可以像下面的Java代码 <bean id="test" class="Test">           <property name="testMap">               <map>                   <entry key="a">                       <value>1</value>                   </entry>                   <entry key="b">                       <value>2</value>                   </entry>               </map>           </property>       </bean>  [java] view plain copy<bean id="test" class="Test">          <property name="testMap">              <map>                  <entry key="a">                      <value>1</value>                  </entry>                  <entry key="b">                      <value>2</value>                  </entry>              </map>          </property>      </bean>

Spring是怎样保存上面的配置呢?,代码如下:Java代码 if (beanProperty.element("map") != null) {                       Map<String, Object> propertiesMap = new HashMap<String, Object>();                       Element propertiesListMap = (Element) beanProperty                               .elements().get(0);                       Iterator<?> propertiesIterator = propertiesListMap                               .elements().iterator();                       while (propertiesIterator.hasNext()) {                           Element vet = (Element) propertiesIterator.next();                           if (vet.getName().equals("entry")) {                               String key = vet.attributeValue("key");                               Iterator<?> valuesIterator = vet.elements()                                       .iterator();                               while (valuesIterator.hasNext()) {                                   Element value = (Element) valuesIterator.next();                                   if (value.getName().equals("value")) {                                       propertiesMap.put(key, value.getText());                                   }                                   if (value.getName().equals("ref")) {                                       propertiesMap.put(key, new String[] { value                                               .attributeValue("bean") });                                   }                               }                           }                       }                       bean.getProperties().put(name, propertiesMap);                   }  [java] view plain copyif (beanProperty.element("map") != null) {                      Map<String, Object> propertiesMap = new HashMap<String, Object>();                      Element propertiesListMap = (Element) beanProperty                              .elements().get(0);                      Iterator<?> propertiesIterator = propertiesListMap                              .elements().iterator();                      while (propertiesIterator.hasNext()) {                          Element vet = (Element) propertiesIterator.next();                          if (vet.getName().equals("entry")) {                              String key = vet.attributeValue("key");                              Iterator<?> valuesIterator = vet.elements()                                      .iterator();                              while (valuesIterator.hasNext()) {                                  Element value = (Element) valuesIterator.next();                                  if (value.getName().equals("value")) {                                      propertiesMap.put(key, value.getText());                                  }                                  if (value.getName().equals("ref")) {                                      propertiesMap.put(key, new String[] { value                                              .attributeValue("bean") });                                  }                              }                          }                      }                      bean.getProperties().put(name, propertiesMap);                  }

接下来就进入最核心部分了,让我们看看Spring 到底是怎么依赖注入的吧,其实依赖注入的思想也很简单,它是通过反射机制实现的,在实例化一个类时,它通过反射调用类中set方法将事先保存在HashMap中的类属性注入到类中。让我们看看具体它是怎么做的吧。

首先实例化一个类,像这样Java代码 public static Object newInstance(String className) {           Class<?> cls = null;           Object obj = null;           try {               cls = Class.forName(className);               obj = cls.newInstance();           } catch (ClassNotFoundException e) {               throw new RuntimeException(e);           } catch (InstantiationException e) {               throw new RuntimeException(e);           } catch (IllegalAccessException e) {               throw new RuntimeException(e);           }           return obj;       }  [java] view plain copypublic static Object newInstance(String className) {          Class<?> cls = null;          Object obj = null;          try {              cls = Class.forName(className);              obj = cls.newInstance();          } catch (ClassNotFoundException e) {              throw new RuntimeException(e);          } catch (InstantiationException e) {              throw new RuntimeException(e);          } catch (IllegalAccessException e) {              throw new RuntimeException(e);          }          return obj;      }

接着它将这个类的依赖注入进去,像这样Java代码 public static void setProperty(Object obj, String name, String value) {           Class<? extends Object> clazz = obj.getClass();           try {               String methodName = returnSetMthodName(name);               Method[] ms = clazz.getMethods();               for (Method m : ms) {                   if (m.getName().equals(methodName)) {                       if (m.getParameterTypes().length == 1) {                           Class<?> clazzParameterType = m.getParameterTypes()[0];                           setFieldValue(clazzParameterType.getName(), value, m,                                   obj);                           break;                       }                   }               }           } catch (SecurityException e) {               throw new RuntimeException(e);           } catch (IllegalArgumentException e) {               throw new RuntimeException(e);           } catch (IllegalAccessException e) {               throw new RuntimeException(e);           } catch (InvocationTargetException e) {               throw new RuntimeException(e);           }   }  [java] view plain copypublic static void setProperty(Object obj, String name, String value) {          Class<? extends Object> clazz = obj.getClass();          try {              String methodName = returnSetMthodName(name);              Method[] ms = clazz.getMethods();              for (Method m : ms) {                  if (m.getName().equals(methodName)) {                      if (m.getParameterTypes().length == 1) {                          Class<?> clazzParameterType = m.getParameterTypes()[0];                          setFieldValue(clazzParameterType.getName(), value, m,                                  obj);                          break;                      }                  }              }          } catch (SecurityException e) {              throw new RuntimeException(e);          } catch (IllegalArgumentException e) {              throw new RuntimeException(e);          } catch (IllegalAccessException e) {              throw new RuntimeException(e);          } catch (InvocationTargetException e) {              throw new RuntimeException(e);          }  }

最后它将这个类的实例返回给我们,我们就可以用了。我们还是以Map为例看看它是怎么做的,我写的代码里面是创建一个HashMap并把该HashMap注入到需要注入的类中,像这样,Java代码 if (value instanceof Map) {                   Iterator<?> entryIterator = ((Map<?, ?>) value).entrySet()                           .iterator();                   Map<String, Object> map = new HashMap<String, Object>();                   while (entryIterator.hasNext()) {                       Entry<?, ?> entryMap = (Entry<?, ?>) entryIterator.next();                       if (entryMap.getValue() instanceof String[]) {                           map.put((String) entryMap.getKey(),                                   getBean(((String[]) entryMap.getValue())[0]));                       }                   }                   BeanProcesser.setProperty(obj, property, map);               }  [java] view plain copyif (value instanceof Map) {                  Iterator<?> entryIterator = ((Map<?, ?>) value).entrySet()                          .iterator();                  Map<String, Object> map = new HashMap<String, Object>();                  while (entryIterator.hasNext()) {                      Entry<?, ?> entryMap = (Entry<?, ?>) entryIterator.next();                      if (entryMap.getValue() instanceof String[]) {                          map.put((String) entryMap.getKey(),                                  getBean(((String[]) entryMap.getValue())[0]));                      }                  }                  BeanProcesser.setProperty(obj, property, map);              }

好了,这样我们就可以用Spring 给我们创建的类了,是不是也不是很难啊?当然Spring能做到的远不止这些,这个示例程序仅仅提供了Spring最核心的依赖注入功能中的一部分。

1) 非抢占式优先权算法

在这种方式下,系统一旦把处理机分配给就绪队列中优先权最高的进程后,该进程便一直执行下去,直至完成;或因发生某事件使该进程放弃处理机时,系统方可再将处理机重新分配给另一优先权最高的进程。这种调度算法主要用于批处理系统中;也可用于某些对实时性要求不严的实时系统中。

2) 抢占式优先权调度算法

在这种方式下,系统同样是把处理机分配给优先权最高的进程,使之执行。但在其执行期间,只要又出现了另一个其优先权更高的进程,进程调度程序就立即停止当前进程(原优先权最高的进程)的执行,重新将处理机分配给新到的优先权最高的进程。因此,在采用这种调度算法时,是每当系统中出现一个新的就绪进程i 时,就将其优先权Pi与正在执行的进程j 的优先权Pj进行比较。如果Pi≤Pj,原进程Pj便继续执行;但如果是Pi>Pj,则立即停止Pj的执行,做进程切换,使i 进程投入执行。显然,这种抢占式的优先权调度算法能更好地满足紧迫作业的要求,故而常用于要求比较严格的实时系统中,以及对性能要求较高的批处理和分时系统中。

非抢占式(Nonpreemptive) 让进程运行直到结束或阻塞的调度方式 容易实现 适合专用系统,不适合通用系统 抢占式(Preemptive) 允许将逻辑上可继续运行的在运行过程暂停的调度方式 可防止单一进程长时间独占CPU 系统开销大(降低途径:硬件实现进程切换,或扩充主存以贮存大部分程序)

一、HashMap概述二、HashMap的数据结构三、HashMap源码分析     1、关键属性     2、构造方法     3、存储数据     4、调整大小

5、数据读取

6、HashMap的性能参数              7、Fail-Fast机制

一、HashMap概述

HashMap基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

值得注意的是HashMap不是线程安全的,如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap。

Map map = Collections.synchronizedMap(new HashMap());

二、HashMap的数据结构

HashMap的底层主要是基于数组和链表来实现的,它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置。HashMap中主要是通过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就一样。如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,这就出现了所谓的hash冲突。学过数据结构的同学都知道,解决hash冲突的方法有很多,HashMap底层是通过链表来解决hash冲突的。

图中,0~15部分即代表哈希表,也称为哈希数组,数组的每个元素都是一个单链表的头节点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单链表中。

从上图我们可以发现哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点Bucket桶。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。

HashMap其实也是一个线性的数组实现的,所以可以理解为其存储数据的容器就是一个线性数组。这可能让我们很不解,一个线性的数组怎么实现按键值对来存取数据呢?这里HashMap有做一些处理。

首先HashMap里面实现一个静态内部类Entry,其重要的属性有 key , value, next,从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean,我们上面说到HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Entry[]里面。

我们看看HashMap中Entry类的代码:

复制代码

/** Entry是单向链表。 * 它是 “HashMap链式存储法”对应的链表。 *它实现了Map.Entry 接口,即实现getKey(), getValue(), setValue(V value), equals(Object o), hashCode()这些函数 **/ static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; // 指向下一个节点 Entry<K,V> next; final int hash; // 构造函数。 // 输入参数包括"哈希值(h)", "键(k)", "值(v)", "下一节点(n)" Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } public final K getKey() { return key; } public final V getValue() { return value; } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } // 判断两个Entry是否相等 // 若两个Entry的“key”和“value”都相等,则返回true。 // 否则,返回false public final boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; Object k1 = getKey(); Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; } return false; } // 实现hashCode() public final int hashCode() { return (key==null ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode()); } public final String toString() { return getKey() + "=" + getValue(); } // 当向HashMap中添加元素时,绘调用recordAccess()。 // 这里不做任何处理 void recordAccess(HashMap<K,V> m) { } // 当从HashMap中删除元素时,绘调用recordRemoval()。 // 这里不做任何处理 void recordRemoval(HashMap<K,V> m) { } }

复制代码

HashMap其实就是一个Entry数组,Entry对象中包含了键和值,其中next也是一个Entry对象,它就是用来处理hash冲突的,形成一个链表。

三、HashMap源码分析

1、关键属性

先看看HashMap类中的一些关键属性:

复制代码

transient Entry[] table;//存储元素的实体数组 transient int size;//存放元素的个数 int threshold; //临界值 当实际大小超过临界值时,会进行扩容threshold = 加载因子*容量 final float loadFactor; //加载因子 transient int modCount;//被修改的次数

复制代码

其中loadFactor加载因子是表示Hsah表中元素的填满的程度.

若:加载因子越大,填满的元素越多,好处是,空间利用率高了,但:冲突的机会加大了.链表长度会越来越长,查找效率降低。

反之,加载因子越小,填满的元素越少,好处是:冲突的机会减小了,但:空间浪费多了.表中的数据将过于稀疏(很多空间还没用,就开始扩容了)

冲突的机会越大,则查找的成本越高.

因此,必须在 "冲突的机会"与"空间利用率"之间寻找一种平衡与折衷. 这种平衡与折衷本质上是数据结构中有名的"时-空"矛盾的平衡与折衷.

如果机器内存足够,并且想要提高查询速度的话可以将加载因子设置小一点;相反如果机器内存紧张,并且对查询速度没有什么要求的话可以将加载因子设置大一点。不过一般我们都不用去设置它,让它取默认值0.75就好了。

2、构造方法

下面看看HashMap的几个构造方法:

复制代码

public HashMap(int initialCapacity, float loadFactor) { //确保数字合法 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); // Find a power of 2 >= initialCapacity int capacity = 1; //初始容量 while (capacity < initialCapacity) //确保容量为2的n次幂,使capacity为大于initialCapacity的最小的2的n次幂 capacity <<= 1; this.loadFactor = loadFactor; threshold = (int)(capacity * loadFactor); table = new Entry[capacity]; init(); } public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); table = new Entry[DEFAULT_INITIAL_CAPACITY]; init(); }

复制代码

我们可以看到在构造HashMap的时候如果我们指定了加载因子和初始容量的话就调用第一个构造方法,否则的话就是用默认的。默认初始容量为16,默认加载因子为0.75。我们可以看到上面代码中13-15行,这段代码的作用是确保容量为2的n次幂,使capacity为大于initialCapacity的最小的2的n次幂,至于为什么要把容量设置为2的n次幂,我们等下再看。

重点分析下HashMap中用的最多的两个方法put和get

3、存储数据

下面看看HashMap存储数据的过程是怎样的,首先看看HashMap的put方法:

复制代码

public V put(K key, V value) { // 若“key为null”,则将该键值对添加到table[0]中。 if (key == null) return putForNullKey(value); // 若“key不为null”,则计算该key的哈希值,然后将其添加到该哈希值对应的链表中。 int hash = hash(key.hashCode()); //搜索指定hash值在对应table中的索引 int i = indexFor(hash, table.length); // 循环遍历Entry数组,若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出! for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { //如果key相同则覆盖并返回旧值 V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } //修改次数+1 modCount++; //将key-value添加到table[i]处 addEntry(hash, key, value, i); return null;}

复制代码

上面程序中用到了一个重要的内部接口:Map.Entry,每个 Map.Entry 其实就是一个 key-value 对。从上面程序中可以看出:当系统决定存储 HashMap 中的 key-value 对时,完全没有考虑 Entry 中的 value,仅仅只是根据 key 来计算并决定每个 Entry 的存储位置。这也说明了前面的结论:我们完全可以把 Map 集合中的 value 当成 key 的附属,当系统决定了 key 的存储位置之后,value 随之保存在那里即可。

我们慢慢的来分析这个函数,第2和3行的作用就是处理key值为null的情况,我们看看putForNullKey(value)方法:

复制代码

private V putForNullKey(V value) { for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) { //如果有key为null的对象存在,则覆盖掉 V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(0, null, value, 0); //如果键为null的话,则hash值为0 return null; }

复制代码

注意:如果key为null的话,hash值为0,对象存储在数组中索引为0的位置。即table[0]

我们再回去看看put方法中第4行,它是通过key的hashCode值计算hash码,下面是计算hash码的函数:

复制代码

//计算hash值的方法 通过键的hashCode来计算 static int hash(int h) { // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }

复制代码

得到hash码之后就会通过hash码去计算出应该存储在数组中的索引,计算索引的函数如下:

static int indexFor(int h, int length) { //根据hash值和数组长度算出索引值 return h & (length-1); //这里不能随便算取,用hash&(length-1)是有原因的,这样可以确保算出来的索引是在数组大小范围内,不会超出 }

这个我们要重点说下,我们一般对哈希表的散列很自然地会想到用hash值对length取模(即除法散列法),Hashtable中也是这样实现的,这种方法基本能保证元素在哈希表中散列的比较均匀,但取模会用到除法运算,效率很低,HashMap中则通过h&(length-1)的方法来代替取模,同样实现了均匀的散列,但效率要高很多,这也是HashMap对Hashtable的一个改进。

接下来,我们分析下为什么哈希表的容量一定要是2的整数次幂。首先,length为2的整数次幂的话,h&(length-1)就相当于对length取模,这样便保证了散列的均匀,同时也提升了效率;其次,length为2的整数次幂的话,为偶数,这样length-1为奇数,奇数的最后一位是1,这样便保证了h&(length-1)的最后一位可能为0,也可能为1(这取决于h的值),即与后的结果可能为偶数,也可能为奇数,这样便可以保证散列的均匀性,而如果length为奇数的话,很明显length-1为偶数,它的最后一位是0,这样h&(length-1)的最后一位肯定为0,即只能为偶数,这样任何hash值都只会被散列到数组的偶数下标位置上,这便浪费了近一半的空间,因此,length取2的整数次幂,是为了使不同hash值发生碰撞的概率较小,这样就能使元素在哈希表中均匀地散列。

这看上去很简单,其实比较有玄机的,我们举个例子来说明:

假设数组长度分别为15和16,优化后的hash码分别为8和9,那么&运算后的结果如下:

h & (table.length-1) hash table.length-1 8 & (15-1): 1000 & 1110 = 1000 9 & (15-1): 1001 & 1110 = 1000 ----------------------------------------------------------------------------------------------------------------------- 8 & (16-1): 1000 & 1111 = 1000 9 & (16-1): 1001 & 1111 = 1001

从上面的例子中可以看出:

当它们和15-1(1110)“与”的时候,产生了相同的结果,也就是说它们会定位到数组中的同一个位置上去,这就产生了碰撞,8和9会被放到数组中的同一个位置上形成链表,那么查询的时候就需要遍历这个链表,得到8或者9,这样就降低了查询的效率。同时,我们也可以发现,当数组长度为15的时候,hash值会与15-1(1110)进行“与”,那么 最后一位永远是0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!

而当数组长度为16时,即为2的n次方时,2n-1得到的二进制数的每个位上的值都为1,这使得在低位上&时,得到的和原hash的低位相同,加之hash(int h)方法对key的hashCode的进一步优化,加入了高位计算,就使得只有相同的hash值的两个值才会被放到数组中的同一个位置上形成链表。

所以说,当数组长度为2的n次幂的时候,不同的key算得得index相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。

根据上面 put 方法的源代码可以看出,当程序试图将一个key-value对放入HashMap中时,程序首先根据该 key 的 hashCode() 返回值决定该 Entry 的存储位置:  如果两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。  如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry 的 value,但key不会覆盖。    如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部——

具体说明继续看 addEntry() 方法的说明。

void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; //如果要加入的位置有值,将该位置原先的值设置为新entry的next,也就是新entry链表的下一个节点 table[bucketIndex] = new Entry<>(hash, key, value, e); if (size++ >= threshold) //如果大于临界值就扩容 resize(2 * table.length); //以2的倍数扩容 }

参数bucketIndex就是indexFor函数计算出来的索引值,第2行代码是取得数组中索引为bucketIndex的Entry对象,第3行就是用hash、key、value构建一个新的Entry对象放到索引为bucketIndex的位置,并且将该位置原先的对象设置为新对象的next构成链表。

第4行和第5行就是判断put后size是否达到了临界值threshold,如果达到了临界值就要进行扩容,HashMap扩容是扩为原来的两倍。

4、调整大小

resize()方法如下:

重新调整HashMap的大小,newCapacity是调整后的单位

复制代码

void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable);//用来将原先table的元素全部移到newTable里面 table = newTable; //再将newTable赋值给table threshold = (int)(newCapacity * loadFactor);//重新计算临界值 }

复制代码

新建了一个HashMap的底层数组,上面代码中第10行为调用transfer方法,将HashMap的全部元素添加到新的HashMap中,并重新计算元素在新的数组中的索引位置

当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,这是一个常用的操作,而在HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。

那么HashMap什么时候进行扩容呢?当HashMap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,扩容是需要进行数组复制的,复制数组是非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

5、数据读取

复制代码

public V get(Object key) { if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; }

复制代码

有了上面存储时的hash算法作为基础,理解起来这段代码就很容易了。从上面的源代码中可以看出:从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。

归纳起来简单地说,HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。

6、HashMap的性能参数:

HashMap 包含如下几个构造器:

HashMap():构建一个初始容量为 16,负载因子为 0.75 的 HashMap。

HashMap(int initialCapacity):构建一个初始容量为 initialCapacity,负载因子为 0.75 的 HashMap。

HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的负载因子创建一个 HashMap。

HashMap的基础构造器HashMap(int initialCapacity, float loadFactor)带有两个参数,它们是初始容量initialCapacity和加载因子loadFactor。

initialCapacity:HashMap的最大容量,即为底层数组的长度。

loadFactor:负载因子loadFactor定义为:散列表的实际元素数目(n)/ 散列表的容量(m)。

负载因子衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。对于使用链表法的散列表来说,查找一个元素的平均时间是O(1+a),因此如果负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。

HashMap的实现中,通过threshold字段来判断HashMap的最大容量:

threshold = (int)(capacity * loadFactor);

结合负载因子的定义公式可知,threshold就是在此loadFactor和capacity对应下允许的最大元素数目,超过这个数目就重新resize,以降低实际的负载因子。默认的的负载因子0.75是对空间和时间效率的一个平衡选择。当容量超出此最大容量时, resize后的HashMap容量是容量的两倍:

if (size++ >= threshold) resize(2 * table.length);

7、Fail-Fast机制:

我们知道java.util.HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。

这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。

复制代码

private abstract class HashIterator<E> implements Iterator<E> { Entry<K,V> next; // next entry to return int expectedModCount; // For fast-fail int index; // current slot Entry<K,V> current; // current entry HashIterator() { expectedModCount = modCount; if (size > 0) { // advance to first entry Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } } public final boolean hasNext() { return next != null; } final Entry<K,V> nextEntry() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); Entry<K,V> e = next; if (e == null) throw new NoSuchElementException(); if ((next = e.next) == null) { Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } current = e; return e; } public void remove() { if (current == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); Object k = current.key; current = null; HashMap.this.removeEntryForKey(k); expectedModCount = modCount; } }

复制代码

在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map:

注意到modCount声明为volatile,保证线程之间修改的可见性。

final Entry<K,V> nextEntry() { if (modCount != expectedModCount) throw new ConcurrentModificationException();

在HashMap的API中指出:

由所有HashMap类的“collection 视图方法”所返回的迭代器都是快速失败的:在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器本身的 remove 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒在将来不确定的时间发生任意不确定行为的风险。

注意,迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误

安装完成后,打开 WebStorm,

在打开的 License Activation 窗口中选择 License server。

在输入框输入网址即可:

http://hb5.s.osidea.cc:1017(网址在下面 更新 , 最新网址不能用请换以前的试试)

最后点击 Activate。

提示: jetbrains软件都可以用此方法激活

上面的不能用了请用下面的尝试 , 博主也会在第一时间更新.

原始网址列表:

http://idea.imsxm.com/

http://im.js.cn:8888

http://idea.codebeta.cn

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80034493

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80041521

复制代码

package IO;import java.util.ArrayList;import java.util.Random;public class TestRandom { public static void main(String[] args) { // 案例2 // 对于种子相同的Random对象,生成的随机数序列是一样的。 Random ran1 = new Random(10); System.out.println("使用种子为10的Random对象生成[0,10)内随机整数序列: "); for (int i = 0; i < 10; i++) { System.out.print(ran1.nextInt(10) + " "); } System.out.println(); Random ran2 = new Random(10); System.out.println("使用另一个种子为10的Random对象生成[0,10)内随机整数序列: "); for (int i = 0; i < 10; i++) { System.out.print(ran2.nextInt(10) + " "); } /** * 输出结果为: * * 使用种子为10的Random对象生成[0,10)内随机整数序列: * 3 0 3 0 6 6 7 8 1 4 * 使用另一个种子为10的Random对象生成[0,10)内随机整数序列: * 3 0 3 0 6 6 7 8 1 4 * */ // 案例3 // 在没带参数构造函数生成的Random对象的种子缺省是当前系统时间的毫秒数。 Random r3 = new Random(); System.out.println(); System.out.println("使用种子缺省是当前系统时间的毫秒数的Random对象生成[0,10)内随机整数序列"); for (int i = 0; i < 10; i++) { System.out.print(r3.nextInt(10)+" "); } /** * 输出结果为: * * 使用种子缺省是当前系统时间的毫秒数的Random对象生成[0,10)内随机整数序列 * 1 1 0 4 4 2 3 8 8 4 * */ // 另外,直接使用Random无法避免生成重复的数字,如果需要生成不重复的随机数序列,需要借助数组和集合类 ArrayList list=new TestRandom().getDiffNO(10); System.out.println(); System.out.println("产生的n个不同的随机数:"+list); } /** * 生成n个不同的随机数,且随机数区间为[0,10) * @param n * @return */ public ArrayList getDiffNO(int n){ // 生成 [0-n) 个不重复的随机数 // list 用来保存这些随机数 ArrayList list = new ArrayList(); Random rand = new Random(); boolean[] bool = new boolean[n]; int num = 0; for (int i = 0; i < n; i++) { do { // 如果产生的数相同继续循环 num = rand.nextInt(n); } while (bool[num]); bool[num] = true; list.add(num); } return list; } }

复制代码

fastJson对于json格式字符串的解析主要用到了一下三个类:

JSON:fastJson的解析器,用于JSON格式字符串与JSON对象及javaBean之间的转换。

JSONObject:fastJson提供的json对象。

JSONArray:fastJson提供json数组对象。

我们可以把JSONObject当成一个Map<String,Object>来看,只是JSONObject提供了更为丰富便捷的方法,方便我们对于对象属性的操作。我们看一下源码。

同样我们可以把JSONArray当做一个List<Object>,可以把JSONArray看成JSONObject对象的一个集合。

此外,由于JSONObject和JSONArray继承了JSON,所以说也可以直接使用两者对JSON格式字符串与JSON对象及javaBean之间做转换,不过为了避免混淆我们还是使用JSON。

首先定义三个json格式的字符串,作为我们的数据源。

复制代码

//json字符串-简单对象型private static final String JSON_OBJ_STR = "{"studentName":"lily","studentAge":12}";//json字符串-数组类型private static final String JSON_ARRAY_STR = "[{"studentName":"lily","studentAge":12},{"studentName":"lucy","studentAge":15}]";//复杂格式json字符串private static final String COMPLEX_JSON_STR = "{"teacherName":"crystall","teacherAge":27,"course":{"courseName":"english","code":1270},"students":[{"studentName":"lily","studentAge":12},{"studentName":"lucy","studentAge":15}]}";

复制代码

示例1:JSON格式字符串与JSON对象之间的转换。

示例1.1-json字符串-简单对象型与JSONObject之间的转换

复制代码

/** * json字符串-简单对象型与JSONObject之间的转换 */ public static void testJSONStrToJSONObject(){ JSONObject jsonObject = JSON.parseObject(JSON_OBJ_STR); //JSONObject jsonObject1 = JSONObject.parseObject(JSON_OBJ_STR); //因为JSONObject继承了JSON,所以这样也是可以的 System.out.println(jsonObject.getString("studentName")+":"+jsonObject.getInteger("studentAge")); }

复制代码

示例1.2-json字符串-数组类型与JSONArray之间的转换

复制代码

/** * json字符串-数组类型与JSONArray之间的转换 */ public static void testJSONStrToJSONArray(){ JSONArray jsonArray = JSON.parseArray(JSON_ARRAY_STR); //JSONArray jsonArray1 = JSONArray.parseArray(JSON_ARRAY_STR);//因为JSONArray继承了JSON,所以这样也是可以的 //遍历方式1 int size = jsonArray.size(); for (int i = 0; i < size; i++){ JSONObject jsonObject = jsonArray.getJSONObject(i); System.out.println(jsonObject.getString("studentName")+":"+jsonObject.getInteger("studentAge")); } //遍历方式2 for (Object obj : jsonArray) { JSONObject jsonObject = (JSONObject) obj; System.out.println(jsonObject.getString("studentName")+":"+jsonObject.getInteger("studentAge")); } }

复制代码

示例1.3-复杂json格式字符串与JSONObject之间的转换

复制代码

/** * 复杂json格式字符串与JSONObject之间的转换 */ public static void testComplexJSONStrToJSONObject(){ JSONObject jsonObject = JSON.parseObject(COMPLEX_JSON_STR); //JSONObject jsonObject1 = JSONObject.parseObject(COMPLEX_JSON_STR);//因为JSONObject继承了JSON,所以这样也是可以的 String teacherName = jsonObject.getString("teacherName"); Integer teacherAge = jsonObject.getInteger("teacherAge"); JSONObject course = jsonObject.getJSONObject("course"); JSONArray students = jsonObject.getJSONArray("students"); }

复制代码

示例2:JSON格式字符串与javaBean之间的转换。

首先,我们针对数据源所示的字符串,提供三个javaBean。

复制代码

public class Student { private String studentName; private Integer studentAge; public String getStudentName() { return studentName; } public void setStudentName(String studentName) { this.studentName = studentName; } public Integer getStudentAge() { return studentAge; } public void setStudentAge(Integer studentAge) { this.studentAge = studentAge; }}

复制代码复制代码

public class Course { private String courseName; private Integer code; public String getCourseName() { return courseName; } public void setCourseName(String courseName) { this.courseName = courseName; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; }}

复制代码复制代码

public class Teacher { private String teacherName; private Integer teacherAge; private Course course; private List<Student> students; public String getTeacherName() { return teacherName; } public void setTeacherName(String teacherName) { this.teacherName = teacherName; } public Integer getTeacherAge() { return teacherAge; } public void setTeacherAge(Integer teacherAge) { this.teacherAge = teacherAge; } public Course getCourse() { return course; } public void setCourse(Course course) { this.course = course; } public List<Student> getStudents() { return students; } public void setStudents(List<Student> students) { this.students = students; }}

复制代码

json字符串与javaBean之间的转换推荐使用 TypeReference<T> 这个类,使用泛型可以更加清晰,当然也有其它的转换方式,这里就不做探讨了。

示例2.1-json字符串-简单对象型与javaBean之间的转换

复制代码

/** * json字符串-简单对象与JavaBean_obj之间的转换 */ public static void testJSONStrToJavaBeanObj(){ Student student = JSON.parseObject(JSON_OBJ_STR, new TypeReference<Student>() {}); //Student student1 = JSONObject.parseObject(JSON_OBJ_STR, new TypeReference<Student>() {});//因为JSONObject继承了JSON,所以这样也是可以的 System.out.println(student.getStudentName()+":"+student.getStudentAge()); }

复制代码

示例2.2-json字符串-数组类型与javaBean之间的转换

复制代码

/** * json字符串-数组类型与JavaBean_List之间的转换 */ public static void testJSONStrToJavaBeanList(){ ArrayList<Student> students = JSON.parseObject(JSON_ARRAY_STR, new TypeReference<ArrayList<Student>>() {}); //ArrayList<Student> students1 = JSONArray.parseObject(JSON_ARRAY_STR, new TypeReference<ArrayList<Student>>() {});//因为JSONArray继承了JSON,所以这样也是可以的 for (Student student : students) { System.out.println(student.getStudentName()+":"+student.getStudentAge()); } }

复制代码

示例2.3-复杂json格式字符串与与javaBean之间的转换

复制代码

/** * 复杂json格式字符串与JavaBean_obj之间的转换 */ public static void testComplexJSONStrToJavaBean(){ Teacher teacher = JSON.parseObject(COMPLEX_JSON_STR, new TypeReference<Teacher>() {}); //Teacher teacher1 = JSON.parseObject(COMPLEX_JSON_STR, new TypeReference<Teacher>() {});//因为JSONObject继承了JSON,所以这样也是可以的 String teacherName = teacher.getTeacherName(); Integer teacherAge = teacher.getTeacherAge(); Course course = teacher.getCourse(); List<Student> students = teacher.getStudents(); }

复制代码

对于TypeReference<T>,由于其构造方法使用 protected 进行修饰,所以在其他包下创建其对象的时候,要用其实现类的子类:new TypeReference<Teacher>() {}

此外的:

1,对于JSON对象与JSON格式字符串的转换可以直接用 toJSONString()这个方法。

2,javaBean与JSON格式字符串之间的转换要用到:JSON.toJSONString(obj);

3,javaBean与json对象间的转换使用:JSON.toJSON(obj),然后使用强制类型转换,JSONObject或者JSONArray。

最后说一点,我们作为程序员,研究问题还是要仔细深入一点的。当你对原理了解的有够透彻,开发起来也就得心应手了,很多开发中的问题和疑惑也就迎刃而解了,而且在面对其他问题的时候也可做到触类旁通。当然在开发中没有太多的时间让你去研究原理,开发中要以实现功能为前提,可等项目上线的后,你有大把的时间或者空余的时间,你大可去刨根问底,深入的去研究一项技术,为觉得这对一名程序员的成长是很重要的事情。

import java.util.ArrayList;import java.util.List;import org.apache.http.NameValuePair;import org.apache.http.client.entity.UrlEncodedFormEntity;import org.apache.http.client.methods.CloseableHttpResponse;import org.apache.http.client.methods.HttpPost;import org.apache.http.impl.client.CloseableHttpClient;import org.apache.http.impl.client.HttpClients;import org.apache.http.message.BasicNameValuePair;import org.apache.http.util.EntityUtils;public class DoPOSTParam { public static void main(String[] args) throws Exception { // 创建Httpclient对象 CloseableHttpClient httpclient = HttpClients.createDefault(); // 创建http POST请求 HttpPost httpPost = new HttpPost("http://www.oschina.net/search"); // 设置2个post参数,一个是scope、一个是q List<NameValuePair> parameters = new ArrayList<NameValuePair>(0); parameters.add(new BasicNameValuePair("scope", "project")); parameters.add(new BasicNameValuePair("q", "java")); // 构造一个form表单式的实体 UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters); // 将请求实体设置到httpPost对象中 httpPost.setEntity(formEntity); CloseableHttpResponse response = null; try { // 执行请求 response = httpclient.execute(httpPost); // 判断返回状态是否为200 if (response.getStatusLine().getStatusCode() == 200) { String content = EntityUtils.toString(response.getEntity(), "UTF-8"); System.out.println(content); } } finally { if (response != null) { response.close(); } httpclient.close(); } }}

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80074087

一、request请求参数出现的乱码问题

get请求:

get请求的参数是在url后面提交过来的,也就是在请求行中,

MyServlet是一个普通的Servlet,浏览器访问它时,使用get请求方式提交了一个name=小明的参数值,在doGet中获取该参数值,并且打印到控制台,发现出现乱码

出现乱码的原因:

前提知识:需要了解码表,编码,解码这三个名词的意思。我简单说一下常规的,

码表:是一种规则,用来让我们看得懂的语言转换为电脑能够认识的语言的一种规则,有很多中码表,IS0-8859-1,GBK,UTF-8,UTF-16等一系列码表,比如GBK,UTF-8,UTF-16都可以标识一个汉字,而如果要标识英文,就可以用IS0-8859-1等别的码表。

编码:将我们看得懂的语言转换为电脑能够认识的语言。这个过程就是编码的作用

解码:将电脑认识的语言转换为我们能看得懂得语言。这个过程就是解码的作用

详细请参考这篇博文。

这里只能够代表经过一次编码例子,有些程序中,会将一个汉字或者一个字母用不同的码表连续编码几次,那么第一次编码还是上面所说的作用,第二次编码的话,就是将电脑能够认识的语言转换为电脑能够认识的语言(转换规则不同),那么该解码过程,就必须要经过两次解码,也就是编码的逆过程,下面这个例子就很好的说明了这个问题。

浏览器使用的是UTF-8码表,通过http协议传输,http协议只支持IS0-8859-1,到了服务器,默认也是使用的是IS0-8859-1的码表,看图

也就是三个过程,经历了两次编码,所以就需要进行两次解码,

1、浏览器将"小明"使用UTF-8码表进行编码(因为小明这个是汉字,所以使用能标识中文的码表,这也是我们可以在浏览器上可以手动设置的,如果使用了不能标识中文的码表,那么就将会出现乱码,因为码表中找不到中文对应的计算机符号,就可能会用??等其他符号表示),编码后得到的为 1234 ,将其通过http协议传输。

2、在http协议传输,只能用ISO-8859-1码表中所代表的符号,所以会将我们原先的1234再次进行一次编码,这次使用的是ISO-8859-1,得到的为 ???? ,然后传输到服务器

3、服务器获取到该数据是经过了两次编码后得到的数据,所以必须跟原先编码的过程逆过来解码,先是UTF-8编码,然后在ISO-8859-1编码,那么解码的过程,就必须是先ISO-8859-1解码,然后在用UTF-8解码,这样就能够得到正确的数据。????.getBytes("ISO-8859-1");//第一次解码,转换为电脑能够识别的语言, new String(1234,"UTF-8");//第二次解码,转换为我们认识的语言

解决代码

Post请求:

post请求方式的参数是在请求体中,相对于get请求简单很多,没有经过http协议这一步的编码过程,所以只需要在服务器端,设置服务器解码的码表跟浏览器编码的码表是一样的就行了,在这里浏览器使用的是UTF-8码表编码,那么服务器端就设置解码所用码表也为UTF-8就OK了

设置服务器端使用UTF-8码表解码

request.setCharacterEncoding("UTF-8");  //命令Tomcat使用UTF-8码表解码,而不用默认的ISO-8859-1了。

所以在很多时候,在doPost方法的第一句,就是这句代码,防止获取请求参数时乱码。

总结请求参数乱码问题

get请求和post请求方式的中文乱码问题处理方式不同

get:请求参数在请求行中,涉及了http协议,手动解决乱码问题,知道出现乱码的根本原因,对症下药,其原理就是进行两次编码,两次解码的过程

new String(xxx.getBytes("ISO-8859-1"),"UTF-8");

post:请求参数在请求体中,使用servlet API解决乱码问题,其原理就是一次编码一次解码,命令tomcat使用特定的码表解码。

request.setCharaterEncoding("UTF-8");

二、response响应回浏览器出现的中文乱码。

首先介绍一下,response对象是如何向浏览器发送数据的。两种方法,一种getOutputStream,一种getWrite。

ServletOutputStream getOutputStream();  //获取输出字节流。提供write() 和 print() 两个输出方法

PrintWriter getWrite();  //获取输出字符流  提供write() 和 print()两个输出方法

print()方法底层都是使用write()方法的,相当于print()方法就是将write()方法进行了封装,使开发者更方便快捷的使用,想输出什么,就直接选择合适的print()方法,而不用考虑如何转换字节。

1、ServeltOutputStream getOutputStream();

不能直接输出中文,直接输出中文会报异常,

报异常的源代码

解决:

resp.getoutputStream().write("哈哈哈,我要输出到浏览器".getBytes("UTF-8"));

将要输出的汉字先用UTF-8进行编码,而不用让tomcat来进行编码,这样如果浏览器用的是UTF-8码表进行解码的话,那么就会正确输出,如果浏览器用的不是UTF-8,那么还是会出现乱码,所以说这个关键要看浏览器用的什么码表,这个就不太好,这里还要注意一点,就是使用的是write(byte)方法,因为print()方法没有输出byte类型的方法。

2、PrintWriter getWrite();

直接输出中文,不会报异常,但是肯定会报异常,因为用ISO-8859-1的码表不能标识中文,一开始就是错的,怎么解码编码读没用了

有三种方法来让其正确输出中文

1、使用Servlet API  response.setCharacterEncoding()

response.setCharacterEncoding("UTF-8");  //让tomcat将我们要响应到浏览器的中文用UTF-8进行编码,而不使用默认的ISO-8859-1了,这个还是要取决于浏览器是不是用的UTF-8的码表,跟上面的一样有缺陷

2、通知tomcat和浏览器都使用同一张码表

response.setHeader("content-type","text/html;charset=uft-8");  //手动设置响应内容,通知tomcat和浏览器使用utf-8来进行编码和解码。

charset=uft-8就相当于response.setCharacterEncoding("UTF-8");//通知tomcat使用utf-8进行编码

response.setHeader("content-type","text/html;charset=uft-8");//合起来,就是既通知tomcat用utf-8编码,又通知浏览器用UTF-8进行解码。

response.setContentType("text/html;charset=uft-8");  //使用Servlet API 来通知tomcaat和强制浏览器使用UTF-8来进行编码解码,这个的底层代码就是上一行的代码,进行了简单的封装而已。

3、通知tomcat,在使用html<meta>通知浏览器 (html源码),注意:<meta>建议浏览器应该使用编码,不能强制要求

进行两步

所以response在响应时,只要通知tomcat和浏览器使用同一张码表,一般使用第二种方法,那么就可以解决响应的乱码问题了

三、总结

在上面讲解的时候总是看起来很繁琐,其实知道了其中的原理,很简单,现在来总结一下,

请求乱码

get请求:

经过了两次编码,所以就要两次解码

第一次解码:xxx.getBytes("ISO-8859-1");得到yyy

第二次解码:new String(yyy,"utf-8");

连续写:new String(xxx.getBytes("ISO-8859-1"),"UTF-8");

post请求:

只经过一次编码,所以也就只要一次解码,使用Servlet API request.setCharacterEncoding();

request.setCharacterEncoding("UTF-8");  //不一定解决,取决于浏览器是用什么码表来编码,浏览器用UTF-8,那么这里就写UTF-8。

响应乱码

getOutputStream();

使用该字节输出流,不能直接输出中文,会出异常,要想输出中文,解决方法如下

解决:getOutputStream().write(xxx.getBytes("UTF-8"));  //手动将中文用UTF-8码表编码,变成字节传输,变成字节后,就不会报异常,并且tomcat也不会在编码,因为已经编码过了,所以到浏览器后,如果浏览器使用的是UTF-8码表解码,那么就不会出现中文乱码,反之则出现中文乱码,所以这个方法,不能完全保证中文不乱码

getWrite();

使用字符输出流,能直接输出中文,不会出异常,但是会出现乱码。能用三种方法解决,一直使用第二种方法

解决:通知tomcat和浏览器使用同一张码表。

response.setContentType("text/html;charset=utf-8");  //通知浏览器使用UTF-8解码

通知tomcat和浏览器使用UTF-8编码和解码。这个方法的底层原理是这句话:response.setHeader("contentType","text/html;charset=utf-8");

注意:getOutputStream()和getWrite() 这两个方法不能够同时使用,一次只能使用一个,否则报异常

事件驱动和异步IO

通常,我们写服务器处理模型的程序时,有以下几种模型:(1)每收到一个请求,创建一个新的进程,来处理该请求;(2)每收到一个请求,创建一个新的线程,来处理该请求;(3)每收到一个请求,放入一个事件列表,让主进程通过非阻塞I/O方式来处理请求上面的几种方式,各有千秋,第(1)中方法,由于创建新的进程的开销比较大,所以,会导致服务器性能比较差,但实现比较简单。第(2)种方式,由于要涉及到线程的同步,有可能会面临死锁等问题。第(3)种方式,在写应用程序代码时,逻辑比前面两种都复杂。综合考虑各方面因素,一般普遍认为第(3)种方式是大多数网络服务器采用的方式 看图说话讲事件驱动模型

在UI编程中,常常要对鼠标点击进行相应,首先如何获得鼠标点击呢?

方式一:创建一个线程,该线程一直循环检测是否有鼠标点击,那么这个方式有以下几个缺点:

1. CPU资源浪费,可能鼠标点击的频率非常小,但是扫描线程还是会一直循环检测,这会造成很多的CPU资源浪费;如果扫描鼠标点击的接口是阻塞的呢?

2. 如果是堵塞的,又会出现下面这样的问题,如果我们不但要扫描鼠标点击,还要扫描键盘是否按下,由于扫描鼠标时被堵塞了,那么可能永远不会去扫描键盘;

3. 如果一个循环需要扫描的设备非常多,这又会引来响应时间的问题;

所以,该方式是非常不好的。

方式二:就是事件驱动模型

目前大部分的UI编程都是事件驱动模型,如很多UI平台都会提供onClick()事件,这个事件就代表鼠标按下事件。事件驱动模型大体思路如下:

1. 有一个事件(消息)队列;

2. 鼠标按下时,往这个队列中增加一个点击事件(消息);

3. 有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如onClick()、onKeyDown()等;

4. 事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数;

事件驱动编程是一种编程范式,这里程序的执行流由外部事件来决定。它的特点是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。另外两种常见的编程范式是(单线程)同步以及多线程编程。

让我们用例子来比较和对比一下单线程、多线程以及事件驱动编程模型。下图展示了随着时间的推移,这三种模式下程序所做的工作。这个程序有3个任务需要完成,每个任务都在等待I/O操作时阻塞自身。阻塞在I/O操作上所花费的时间已经用灰色框标示出来了。

在单线程同步模型中,任务按照顺序执行。如果某个任务因为I/O而阻塞,其他所有的任务都必须等待,直到它完成之后它们才能依次执行。这种明确的执行顺序和串行化处理的行为是很容易推断得出的。如果任务之间并没有互相依赖的关系,但仍然需要互相等待的话这就使得程序不必要的降低了运行速度。

在多线程版本中,这3个任务分别在独立的线程中执行。这些线程由操作系统来管理,在多处理器系统上可以并行处理,或者在单处理器系统上交错执行。这使得当某个线程阻塞在某个资源的同时其他线程得以继续执行。与完成类似功能的同步程序相比,这种方式更有效率,但程序员必须写代码来保护共享资源,防止其被多个线程同时访问。多线程程序更加难以推断,因为这类程序不得不通过线程同步机制如锁、可重入函数、线程局部存储或者其他机制来处理线程安全问题,如果实现不当就会导致出现微妙且令人痛不欲生的bug。

在事件驱动版本的程序中,3个任务交错执行,但仍然在一个单独的线程控制中。当处理I/O或者其他昂贵的操作时,注册一个回调到事件循环中,然后当I/O操作完成时继续执行。回调描述了该如何处理某个事件。事件循环轮询所有的事件,当事件到来时将它们分配给等待处理事件的回调函数。这种方式让程序尽可能的得以执行而不需要用到额外的线程。事件驱动型程序比多线程程序更容易推断出行为,因为程序员不需要关心线程安全问题。

当我们面对如下的环境时,事件驱动模型通常是一个好的选择:程序中有许多任务,而且…任务之间高度独立(因此它们不需要互相通信,或者等待彼此)而且…在等待事件到来时,某些任务会阻塞。

当应用程序需要在任务间共享可变的数据时,这也是一个不错的选择,因为这里不需要采用同步处理。

网络应用程序通常都有上述这些特点,这使得它们能够很好的契合事件驱动编程模型。

编辑代码的时候一些模板不尽人意,设置一下类生成模板

File -- Settings -- Editor -- Code Style -- File and Code Templates

主要是修改了注释

/*** ${DESCRIPTION}* @author * @create ${YEAR}-${MONTH}-${DAY} ${TIME}**/

序号方法描述1int capacity()

返回当前容量。2char charAt(int index)

返回此序列中指定索引处的  值。3void ensureCapacity(int minimumCapacity)

确保容量至少等于指定的最小值。4void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)

将字符从此序列复制到目标字符数组 。5int indexOf(String str)

返回第一次出现的指定子字符串在该字符串中的索引。6int indexOf(String str, int fromIndex)

从指定的索引处开始,返回第一次出现的指定子字符串在该字符串中的索引。7int lastIndexOf(String str)

返回最右边出现的指定子字符串在此字符串中的索引。8int lastIndexOf(String str, int fromIndex)

返回 String 对象中子字符串最后出现的位置。9int length()

返回长度(字符数)。10void setCharAt(int index, char ch)

将给定索引处的字符设置为 。11void setLength(int newLength)

设置字符序列的长度。12CharSequence subSequence(int start, int end)

返回一个新的字符序列,该字符序列是此序列的子序列。13String substring(int start)

返回一个新的 ,它包含此字符序列当前所包含的字符子序列。14String substring(int start, int end)

返回一个新的 ,它包含此序列当前所包含的字符子序列。15String toString()

返回此序列中数据的字符串表示形式。

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80215947

完成上面几个步骤后就完成了从服务器数据初始化的所有操作,从服务器此时可以接收来自用户的读请求。2 增量同步  Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。 增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。 3 Redis主从同步策略  主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。 4 注意点如果多个Slave断线了,需要重启的时候,因为只要Slave启动,就会发送sync请求和主机全量同步,当多个同时出现的时候,可能会导致Master IO剧增宕机。

1. 什么是『线程安全』?

如果一个对象构造完成后,调用者无需额外的操作,就可以在多线程环境下随意地使用,并且不发生错误,那么这个对象就是线程安全的。

2. 线程安全的几种程度

线程安全性的前提:对『线程安全性』的讨论必须建立在对象内部存在共享变量这一前提,若对象在多条线程间没有共享数据,那这个对象一定是线程安全的!

2.1. 绝对的线程安全

上述线程安全性的定义即为绝对线程安全的情况,即:一个对象在构造完之后,调用者无需任何额外的操作,就可以在多线程环境下随意使用。

绝对的线程安全是一种理想的状态,若要达到这一状态,往往需要付出巨大的代价。

通常并不需要达到绝对的线程安全。

2.2. 相对的线程安全

我们通常所说的『线程安全』即为『相对的线程安全』,JDK中标注为线程安全的类通常就是『相对的线程安全』,如:Vector、HashTable、Collections.synchronizedXXX。

对于相对线程安全的类,使用它们时一般不需要使用额外的保障措施,但对于一些特定的使用场景,仍然需要额外的操作来保证线程安全,如:

1234567891011121314151617

vector是一个线程安全的容器,它所提供的方法均为同步方法,但上述代码仍然会出现线程安全性问题:

若线程1读了一半的元素后暂停,线程2开始执行,并删除了所有的元素,然后线程1继续执行,此时发生角标越界异常!

修改方案:加上额外的同步

123456789101112131415161718192021

2.3. 线程对立

线程对立指的是:不论调用者采用何种同步措施,都无法达到线程安全的目的。

如Thread类的suspend、resume方法就是线程对立的方法。

suspend方法会暂停线程,但它不会释放资源,若resume需要请求到该资源才会被运行的话,系统就会进入死锁状态。

3. 实现线程安全的方法

3.1. 互斥同步

同步指的是同一时刻,只有一条线程操作『共享变量』。

实现同步的方式有很多:互斥访问、CAS操作。

互斥会引起阻塞,当一条线程请求一个已经被另一线程使用的锁时,就会进入阻塞态;而进入阻塞态会涉及上下文切换。因此,使用互斥来实现同步的开销是很大的。

互斥同步(阻塞式同步)是一种『悲观锁』,即它认为总是存在多条线程竞争资源的情况,因此它不管当前是不是真的有多条线程在竞争共享资源,它总是先上锁,然后再处理。

Java中有两种实现互斥同步的方式:synchronized和ReentrantLock。synchronized

编译器会在synchronized同步块的开始和结束位置加上monitorenter和monitorexit指令;这两个指令需要一个reference类型的参数来指名要锁定和解锁的对象;若同步块没有明确指定锁对象,那么就使用当前对象或当前类的Class对象;它是一把可重入的锁,即:当前线程在已经获得锁的情况下,可以再次获取该锁,因此不会出现当前线程把自己锁死的情况;ReentrantLock

它也是一把可重入的锁,但比synchronized多如下功能:

等待可中断:若一条线程长时间占用锁不释放,那被阻塞的线程可以选择放弃等待,而去做别的事;这对于要处理长时间的同步块时是很有帮助的。可实现公平锁:synchronized是一种非公平锁,即:被阻塞的线程竞争锁是随机的;而公平锁是根据被阻塞线程先来后到的顺序给予锁。ReentrantLock默认是非公平锁,可以通过构造函数构造公平锁。可以绑定多个条件:synchronized可使用wait/notify来实现等待/通知机制,但一个synchronized同步块只能使用一次,若要使用多次,就需要嵌套同步块;但ReentrantLock可以通过newCondition创建多个条件。

synchronized和ReentrantLock如何选择?

优先选择synchronized!

JDK1.6已经对synchronized做了很多优化,性能与ReentrantLock相差不大。在条件允许的请况下应优先选择synchronized。

3.2. 非阻塞同步

它是一种『乐观锁』,即它总是认为当前没有线程使用共享资源,因此它不管当前的状态,直接操作共享资源,若发现产生了冲突,那么再采取补偿措施(如:CAS的补偿措施就是不断尝试,直到不发生冲突为止),这种方式线程无需进入阻塞态(挂起态),因此称为『非阻塞同步』。

JUC中各种整形原子类的自增、自减等操作就使用了CAS。

CAS操作过程:CAS操作存在3个值:共享变量V、预期的旧值A、新值B,若V与A相同,则将V更新成B,否则就不更新,继续循环比较,直到更新完成为止。

CAS操作可能引发的问题:ABA问题。

若V一开始的值为A,但在准备赋新值的过程中A变成了B,又变成了A,而CAS操作误认为V没有被改过。

无同步方案

『阻塞式同步』和『非阻塞式同步』都是同一时刻只让一条线程处理共享数据,而下面的方案使得多条线程之间不存在共享数据,从而无需同步。

可重入代码

如果一块代码段只要输入的值一样其结果就一样的话,这段代码就叫『可重入代码』。

这一类代码天生具有线程安全性,线程随意切换结果都一样。

线程封闭

线程封闭:把所有涉及共享变量操作的任务都放在一个线程中运行。

这样就不存在多条线程同时处理共享变量了,从而达到了线程安全目的。

WEB服务器采用的就是这种方式,它把每个请求封装在一条线程中处理,从而不存在线程安全性问题。不可变对象

如果是共享的基本数据类型变量,只要被final修饰,它就是不可变的;

如果是共享的对象,那就要确保它内部的共享成员变量不会被它的行为所改变。

PS:保证对象内部共享变量不会被改变的方法有很多,最简单粗暴的方式就是将所有共享变量用final修饰。

不可变对象一定是线程安全的。

概述        java.util包中的大部分容器都是非线程安全的,若要在多线程中使用容器,你可以使用Collections提供的包装函数:synchronizedXXX,将普通容器变成线程安全的容器。但该方法仅仅是简单地给容器使用同步,效率很低。因此并发大师Doug Lea提供了java.util.concurrent包,提供高效的并发容器。并且为了保持与普通的容器的接口一致性,仍然使用util包的接口,从而易于使用、易于理解。PS:问题:synchronizedXXX究竟对容器做了什么从而能达到线程安全的目的?

类图

List和Set

JUC包中List接口的实现类:CopyOnWriteArrayListCopyOnWriteArrayList是线程安全的ArrayList

JUC包中Set接口的实现类:CopyOnWriteArraySet、ConcurrentSkipListSetCopyOnWriteArraySet是线程安全的Set,它内部包含了一个CopyOnWriteArrayList,因此本质上是由CopyOnWriteArrayList实现的。

ConcurrentSkipListSet相当于线程安全的TreeSet。它是有序的Set。它由ConcurrentSkipListMap实现。

Map

ConcurrentHashMap:线程安全的HashMap。采用分段锁实现高效并发。ConcurrentSkipListMap:线程安全的有序Map。使用跳表实现高效并发。

Queue

ConcurrentLinkedQueue:线程安全的无界队列。底层采用单链表。支持FIFO。ConcurrentLinkedDeque:线程安全的无界双端队列。底层采用双向链表。支持FIFO和FILO。ArrayBlockingQueue:数组实现的阻塞队列。LinkedBlockingQueue:链表实现的阻塞队列。LinkedBlockingDeque:双向链表实现的双端阻塞队列。

CopyOnWrite容器(写时复制容器)

CopyOnWrite容器包括:CopyOnWriteArrayList和CopyOnWriteArraySet。PS:CopyOnWriteArraySet有CopyOnWriteArrayList实现。

特性适用于读操作远远多于写操作,并且数据量较小的情况。修改容器的代价是昂贵的,因此建议批量增加addAll、批量删除removeAll。

CopyOnWrite容器是如何实现线程安全的?使用volatile修饰数组引用:确保数组引用的内存可见性。对容器修改操作进行同步:从而确保同一时刻只能有一条线程修改容器(因为修改容器都会产生一个新的容器,增加同步可避免同一时刻复制生成多个容器,从而无法保证数组数据的一致性)修改时复制容器:确保所有修改操作都作用在新数组上,原本的数组在创建过后就用不变化,从而其他线程可以放心地读。

新增方法CopyOnWriteArrayList:

// 添加集合中不存在的元素

int addAllAbsent(Collection<? extends E> c)

// 该元素若不存在则添加

boolean addIfAbsent(E e)CopyOnWriteArraySet:木有新增!

迭代CopyOnWriteArrayList拥有内部类:COWIterator,它是ListIterator的子类。

当调用iterator函数时返回的是COWIterator对象。

COWIterator不允许修改容器,你若调用则会抛出UnsupportedOperationException。

优点读操作无需加锁,从而高效。

缺点数据一致性问题由于迭代的是容器当前的快照,因此在迭代过程中容器发生的修改并不能实时被当前正在迭代的线程感知。内存占用问题由于修改容器都会复制数组,从而当数组超大时修改容器效率很低。PS:因此写时复制容器适合存储小容量数据。

ConcurrentHashMap

java.util包中提供了线程安全的HashTable,但这家伙只是通过简单的同步来实现线程安全,因此效率低。只要有一条线程获取了容器的锁之后,其他所有的线程访问同步函数都会被阻塞。因此同一时刻只能有一条线程访问同步函数。而ConcurrentHashMap采用了分段锁机制实现高效的并发访问。

分段锁原理ConcurrentHashMap由多个Segment构成,每个Segment都包含一张哈希表。每次操作只将操作数据所属的Segment锁起来,从而避免将整个锁住。

数据结构

ConcurrentHashMap内部包含了Segment数组,而每个Segment又继承自ReentrantLock,因此它是一把可重入的锁。

Segment内部拥有一个HashEntry数组,它就是一张哈希表。HashEntry是单链表的一个节点,HashEntry数组存储单链表的表头节点。

新增API

V putIfAbsent(K key, V value)

ConcurrentSkipListMap

它是一个有序的Map,相当于TreeMap。TreeMap采用红黑树实现排序,而ConcurrentHashMap采用跳表实现有序。

跳表的由来作用:存储有序序列,并且实现高效的查找与插入删除。存储有序序列最简单的办法就是使用数组,从而查找可以采用二分搜索,但插入删除需要移动元素较为低效。因此出现了二叉搜索树,用来解决插入删除移动元素的问题。但二叉搜索树在最坏情况下会退化成一条单链表,搜索的效率降为O(n)。为了避免二叉搜索树的退化,出现了二叉平衡树,它在每次插入删除节点后都会重新调整树形,使得它仍然保持平衡,从而保证了搜索效率,也保证了插入删除的效率。此外,根据平衡算法的不同,二叉平衡树又分为:B+树、B-树、红黑树。但平衡算法过于复杂,因此出现跳表。

跳表介绍跳表是条有序的单链表,它的每个节点都有多个指向后继节点的引用。它有多个层次,上层都是下层的子集,从而能跳过不必要的节点,提升搜索速度。它通过空间来换取时间。如查找19的过程:

ConcurrentSkipListSet

它是一个有序的、线程安全的Set,相当于线程安全的TreeSet。它内部拥有ConcurrentSkipListMap实例,本质上就是一个ConcurrentSkipListMap,只不过仅使用了Map中的key。

ArrayBlockingQueue

概要ArrayBlockingQueue是一个 数组实现的 线程安全的 有限 阻塞队列。

数据结构

ArrayBlockingQueue继承自AbstractQueue,并实现了BlockingQueue接口。ArrayBlockingQueue内部由Object数组存储元素,构造时必须要指定队列容量。ArrayBlockingQueue由ReentrantLock实现队列的互斥访问,并由notEmpty、notFull这两个Condition分别实现队空、队满的阻塞。ReentrantLock分为公平锁和非公平锁,可以在构造ArrayBlockingQueue时指定。默认为非公平锁。

新增API

// 在队尾添加指定元素,若队已满则等待指定时间

boolean offer(E e, long timeout, TimeUnit unit)

// 获取并删除队首元素,若队为空则阻塞等待

E take()

// 添加指定元素,若队已满则一直等待

void put(E e)

// 获取队首元素,若队为空,则等待指定时间

E poll(long timeout, TimeUnit unit)

队满、队空阻塞唤醒的原理队满阻塞:当添加元素时,若队满,则调用notFull.await()阻塞当前线程;当移除一个元素时调用notFull.signal()唤醒在notFull上等待的线程。队空阻塞:当删除元素时,若队为空,则调用notEmpty.await()阻塞当前线程;当队首添加元素时,调用notEmpty.signal()唤醒在notEmpty上等待的线程。

LinkedBlockingQueue

概要LinkedBlockingQueue是一个 单链表实现的、线程安全的、无限 阻塞队列。

数据结构

LinkedBlockingQueue继承自AbstractQueue,实现了BlockingQueue接口。

LinkedBlockingQueue由单链表实现,因此是个无限队列。但为了方式无限膨胀,构造时可以加上容量加以限制。

LinkedBlockingQueue分别采用读取锁和插入锁控制读取/删除 和 插入过程的并发访问,并采用notEmpty和notFull两个Condition实现队满队空的阻塞与唤醒。

队满队空阻塞唤醒的原理队满阻塞:若要插入元素,首先需要获取putLock;在此基础上,若此时队满,则调用notFull.await(),阻塞当前线程;当移除一个元素后调用notFull.signal()唤醒在notFull上等待的线程;最后,当插入操作完成后释放putLock。队空阻塞:若要删除/获取元素,首先要获取takeLock;在此基础上,若队为空,则调用notEmpty.await(),阻塞当前线程;当插入一个元素后调用notEmpty.signal()唤醒在notEmpty上等待的线程;最后,当删除操作完成后释放takeLock。

PS:API和ArrayBlockingQueue一样。

LinkedBlockingDeque

概要它是一个 由双向链表实现的、线程安全的、 双端 无限 阻塞队列。

数据结构

ConcurrentLinkedQueue

概述它是一个由单链表实现的、线程安全的、无限 队列。

数据结构

它仅仅继承了AbstractQueue,并未实现BlockingQueue接口,因此它不是阻塞队列,仅仅是个线程安全的普通队列。

特性head、tail、next、item均使用volatile修饰,保证其内存可见性,并未使用锁,从而提高并发效率。PS:它究竟是怎样在不使用锁的情况下实现线程安全的?

什么是死锁

死锁是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。例如,在某一个计算机系统中只有一台打印机和一台输入 设备,进程P1正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程P2 所占用,而P2在未释放打印机之前,又提出请求使用正被P1占用着的输入设备。这样两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。

死锁产生的原因

1. 系统资源的竞争系统资源的竞争导致系统资源不足,以及资源分配不当,导致死锁。2. 进程运行推进顺序不合适进程在运行过程中,请求和释放资源的顺序不当,会导致死锁。

产生死锁的四个必要条件:

互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。循环等待条件: 若干进程间形成首尾相接循环等待资源的关系这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

死锁的避免与预防:

死锁避免的基本思想:系统对进程发出每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,如果分配后系统可能发生死锁,则不予分配,否则予以分配。这是一种保证系统不进入死锁状态的动态策略。理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。所以,在系统设计、进程调度等方面注意如何让这四个必要条件不成立,如何确定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态的情况下占用资源。因此,对资源的分配要给予合理的规划。死锁避免和死锁预防的区别:死锁预防是设法至少破坏产生死锁的四个必要条件之一,严格的防止死锁的出现,而死锁避免则不那么严格的限制产生死锁的必要条件的存在,因为即使死锁的必要条件存在,也不一定发生死锁。死锁避免是在系统运行过程中注意避免死锁的最终发生。

Spring的两种IoC容器

BeanFactory

基础类型的IoC容器;采用延迟初始化策略(容器初始化完成后并不会创建bean的对象,只有当收到初始化请求时才进行初始化);由于延迟初始化,因此启动速度较快,占用资源较少;ApplicationContext

在BeanFactory的基础上,增加了更为高级的特定:事件发布、国际化等;在容器启动时便完成所有bean的创建;启动时间较长,占用资源更多;

IoC容器的主要类/接口介绍

title

BeanFactory

它是一个接口,提供了获取容器中Bean的相关方法。

BeanDefinitionRegistry

它才是IoC的容器,用于存储、管理所有的Bean对象。

DefaultListableBeanFactory

它是IoC容器的一个具体实现,实现了BeanFactory和BeanDefinitionRegistry接口,因此既拥有管理Bean的容器,又拥有访问Bean的方法。

BeanDefinition

每一个Bean都有一个BeanDefinition与之对应,用于存储Bean的相关信息:对象的class类型、是否是抽象类、构造方法参数等。

RootBeanDefinition和ChildBeanDefinition是BeanDefinition的两个主要的实现类。

BeanDefinitionReader

在Spring中,标注Bean的依赖关系有四中方式:直接在代码中声明通过XML文件声明通过Properties文件声明通过注解声明

BeanDefinitionReader接口的作用就是读取配置文件中的bean信息,把它们解析成BeanDefinition对象,然后注册到BeanDefinitionRegistry中去。

PropertiesBeanDefinitionReader和XmlBeanDefinitionReader是该接口的两个实现类,分别用于解析properties和xml格式的配置文件。

XmlBeanFactory

它是一个集成了XmlBeanDefinitionReader功能的BeanFactory,用于简化初始化操作。

BeanFactory的两个重要阶段

容器启动阶段

该阶段Spring会使用BeanDefinitionReader加载配置文件,并把所有的bean解析成BeanDefinition对象,并注册到BeanDefinitionRegistry。

Bean实例化阶段

对于BeanFactory容器,当调用者主动调用getBean方法或者因存在依赖关系容器隐式调用getBean时,如果当前Bean尚未初始化,或者bean配置成prototype,就会触发Bean实例的初始化。

BeanFactoryPostProcessor:一种容器扩展机制

Spring提供了BeanFactoryPostProcessor这种容器扩展机制,它允许我们在容器启动完成后、Bean实例化前插入额外的操作。

BeanFactoryPostProcessor提供了三个实现类:

1.PropertyPlaceholderConfigurer

一般情况下,我们并不会将数据库连接信息直接写死在dataSource这个bean中,而是将它们单独写在一个properties文件中,这样易于修改与阅读。而bean中使用占位符代替这些属性值,当容器启动完成后,在Bean初始化前用properties文件中的值替换占位符,再创建对象。

PropertyPlaceholderConfigurer就能实现这样的功能。xml中作如下配置:

12345678使用properties文件存储属性值:

12

当容器启动完成后dataSource的BeanDefinition对象将会被注册进BeanDefinitionRegistry中,此时BeanDefinition中的属性值仍然是占位符的形式;接下俩,PropertyPlaceholderConfigurer就会发挥作用,它会将占位符用properties文件中的属性值替换掉。接下来bean就可以被正确地创建。

2.PropertyOverrideConfigurer

它的功能与PropertyPlaceholderConfigurer类似,也需要指定一个properties文件,只不过它会用配置文件中设置的那些bean的属性值替换指定bean的属性值。xml中作如下配置:

12345678使用properties文件存储属性值:

12

PropertyOverrideConfigurer会在容器启动完毕后、Bean对象创建之前,通过修改BeanDefinition对象,替换指定的属性值。

properties文件的内容必须遵循如下格式:

1

3.CustomEditorConfigurer

该类用于向Spring容器增添自定义的PropertyEditor对象。

容器启动结束后bean创建之前,配置文件中所有的bean都被解析成BeanDefinition对象,该对象中关于bean所有的信息都是String类型的,若要创建bean对象,就需要将这些String类型的信息解析成它们原本的类型。在Spring中,每种类型都有对应一个PropertyEditor类,该类中封装了String与该类型的转换方法。当然,对于某些类型Spring并未提供相应的PropertyEditor时,我们可以自定义PropertyEditor,并使用CustomEditorConfigurer将其告诉Spring容器,让它在遇到该类型的时候采用我们自定义的PropertyEditor去解析。

Spring提供的部分PropertyEditor:StringArrayPropertyEditor

将字符串转换成String[],默认以,分割。ClassEditor

类似于Class.forname(String),将字符串转换成class对象。FileEditor

将字符串转换成File对象。URLEditor

将字符串转换成URL对象。InputStreamEditor

将字符串转换成InputStream对象。LocaleEditor

将字符串转换成Locale对象。PatternEditor

将字符串转换成Pattern对象。

以上类型的字符串,Spring会自动将它们转换成原本的类型。而我们自定义的PropertyEditor必须要通过CustomEditorConfigurer将其加入容器。

如何开启BeanFactoryPostProcessor功能?

1.BeanFactory

12345678

2.ApplicationContext

ApplicationContext会自动检测配置文件中出现的BeanFactoryPostProcessor,因此只需要在配置文件中声明所使用的BeanFactoryPostProcessor即可。

1234567

docker虚拟机未启动问题

Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

解决

service docker start

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80400443

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80400454

运行命令:

echo '{ "insecure-registries":["xxx.xxx.xxx.xxx:5000"] }' > /etc/docker/daemon.json

systemctl restart docker

上面xxx为ip地址 5000为端口 如果你docker registry启动端口改了就把5000也改了

1、下载私有仓库镜像

docker pull registry

docker registry 启动

docker run -d -p 5001:5000 registry  默认端口5000我虚拟机占用5000所以改5001

上传

docker push localhost:5001/test/es:v1

另一台机器下载

docker pull docker pull 47.106.154.105:5001/test/e:v1  ip为机器1公网ip 后面仓库位置要一样

docker pull 报错:Get https://registry:5000/v1/_ping: http: server gave HTTP response to HTTPS client

下载成功

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80416511

sshd

docker inspect --format "{{.State.Pid}}" 4779583e9bcd

nsenter --target 18613 --mount --uts --ipc --net --pid

运行在root权限下

已经运行的容器里

执行里面的命令

netstat -tlnp

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80417198

docker create --name mysqlsrvl -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 mysql

docker start mysqlsrvl

docker exec mysqlsrvl env

运行完就删掉

docker exec -it mysqlsrvl /bin/bash

进入容器

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80464652

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80464707

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80489933

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80543871

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80546119

import java.io.UnsupportedEncodingException;import java.net.URLDecoder;import java.net.URLEncoder;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * * Cookie 工具类 * */public final class CookieUtils { protected static final Logger logger = LoggerFactory.getLogger(CookieUtils.class); /** * 得到Cookie的值, 不编码 * * @param request * @param cookieName * @return */ public static String getCookieValue(HttpServletRequest request, String cookieName) { return getCookieValue(request, cookieName, false); } /** * 得到Cookie的值, * * @param request * @param cookieName * @return */ public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) { Cookie[] cookieList = request.getCookies(); if (cookieList == null || cookieName == null){ return null; } String retValue = null; try { for (int i = 0; i < cookieList.length; i++) { if (cookieList[i].getName().equals(cookieName)) { if (isDecoder) { retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8"); } else { retValue = cookieList[i].getValue(); } break; } } } catch (UnsupportedEncodingException e) { logger.error("Cookie Decode Error.", e); } return retValue; } /** * 得到Cookie的值, * * @param request * @param cookieName * @return */ public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) { Cookie[] cookieList = request.getCookies(); if (cookieList == null || cookieName == null){ return null; } String retValue = null; try { for (int i = 0; i < cookieList.length; i++) { if (cookieList[i].getName().equals(cookieName)) { retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString); break; } } } catch (UnsupportedEncodingException e) { logger.error("Cookie Decode Error.", e); } return retValue; } /** * 设置Cookie的值 不设置生效时间默认浏览器关闭即失效,也不编码 */ public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue) { setCookie(request, response, cookieName, cookieValue, -1); } /** * 设置Cookie的值 在指定时间内生效,但不编码 */ public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage) { setCookie(request, response, cookieName, cookieValue, cookieMaxage, false); } /** * 设置Cookie的值 不设置生效时间,但编码 */ public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, boolean isEncode) { setCookie(request, response, cookieName, cookieValue, -1, isEncode); } /** * 设置Cookie的值 在指定时间内生效, 编码参数 */ public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) { doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode); } /** * 设置Cookie的值 在指定时间内生效, 编码参数(指定编码) */ public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, String encodeString) { doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString); } /** * 删除Cookie带cookie域名 */ public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String cookieName) { doSetCookie(request, response, cookieName, "", -1, false); } /** * 设置Cookie的值,并使其在指定时间内生效 * * @param cookieMaxage * cookie生效的最大秒数 */ private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) { try { if (cookieValue == null) { cookieValue = ""; } else if (isEncode) { cookieValue = URLEncoder.encode(cookieValue, "utf-8"); } Cookie cookie = new Cookie(cookieName, cookieValue); if (cookieMaxage > 0) cookie.setMaxAge(cookieMaxage); if (null != request)// 设置域名的cookie cookie.setDomain(getDomainName(request)); cookie.setPath("/"); response.addCookie(cookie); } catch (Exception e) { logger.error("Cookie Encode Error.", e); } } /** * 设置Cookie的值,并使其在指定时间内生效 * * @param cookieMaxage * cookie生效的最大秒数 */ private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, String encodeString) { try { if (cookieValue == null) { cookieValue = ""; } else { cookieValue = URLEncoder.encode(cookieValue, encodeString); } Cookie cookie = new Cookie(cookieName, cookieValue); if (cookieMaxage > 0) cookie.setMaxAge(cookieMaxage); if (null != request)// 设置域名的cookie cookie.setDomain(getDomainName(request)); cookie.setPath("/"); response.addCookie(cookie); } catch (Exception e) { logger.error("Cookie Encode Error.", e); } } /** * 得到cookie的域名 */ private static final String getDomainName(HttpServletRequest request) { String domainName = null; String serverName = request.getRequestURL().toString(); if (serverName == null || serverName.equals("")) { domainName = ""; } else { serverName = serverName.toLowerCase(); serverName = serverName.substring(7); final int end = serverName.indexOf("/"); serverName = serverName.substring(0, end); final String[] domains = serverName.split("."); int len = domains.length; if (len > 3) { // www.xxx.com.cn domainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1]; } else if (len <= 3 && len > 1) { // xxx.com or xxx.cn domainName = "." + domains[len - 2] + "." + domains[len - 1]; } else { domainName = serverName; } } if (domainName != null && domainName.indexOf(":") > 0) { String[] ary = domainName.split(":"); domainName = ary[0]; } return domainName; }}

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80547783

1、在github上的仓库建立一个存放图片的文件夹,文件夹名字随意。如:img-folder

2、将需要在READNE.md中显示的图片,push到img-folder文件夹中。

3、然后打开github官网,进入仓库的img-folder文件夹中,打开图片

点击红框所示的按钮,copy地址。

4、在README.md中填入:

![Image text](https://raw.github.com/yourName/repositpry/master/yourprojectName/img-folder/test.jpg)

保存即可。

注:![Image text]这个标识不可缺少,不然就显示文字了。

Image text:指的是如果图片不存在了,要显示的文字说明。

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80554866

我们这边隔离掉dog这个bean

启动类

import org.springframework.context.annotation.AnnotationConfigApplicationContext;/** * @author rainyday * @createTime 2018/6/3. */public class AnnotationClient2 { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationScan.class); System.out.println(context.getBean(Dog.class)); context.close(); }}

包扫描类

import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.FilterType;/** * 扫包隔离一个bean * @author rainyday * @createTime 2018/6/3. */@ComponentScan(basePackages = "spring4",excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = DogConfig.class ))@Configurationpublic class AnnotationScan {}

隔离配置类

import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/** * @author rainyday * @createTime 2018/6/3. */@Configurationpublic class DogConfig {  // init 和 destory是bean加载的初始化和销毁方法 @Bean(initMethod = "init", destroyMethod = "destory") public Dog createDog() { return new Dog(); }}

bean对象

/** * @author rainyday * @createTime 2018/6/3. */public class Dog { public void init() { System.out.println("init ============= "); } public void destory() { System.out.println("destory ==================="); }}

想换字体颜色等各种...百度了一波,然后发现换主题最方便。

直接去下面这个网址下载主题jar包..

http://www.riaway.com/theme.php

然后打开idea

file --  import  setting-- 导入你下载的jar包

自动提示重启IDEA

然后我自己设置了一下字体

file -- setting  -- font & color

换个主题换个心情

<dependency> <groupId>io.protostuff</groupId> <artifactId>protostuff-core</artifactId> <version>1.4.0</version></dependency><dependency> <groupId>io.protostuff</groupId> <artifactId>protostuff-runtime</artifactId> <version>1.4.0</version></dependency>

import java.util.Map;import io.protostuff.LinkedBuffer;import io.protostuff.ProtostuffIOUtil;import io.protostuff.Schema;import io.protostuff.runtime.RuntimeSchema;/** * @author rainyday * @createTime 2018/6/5. */public class SerializationUtil { private static Map<Class<?>, Schema<?>> cachedSchema = new ConcurrentHashMap<>(); private static <T> Schema<T> getSchema(Class<T> cls) { Schema<T> schema = (Schema<T>) cachedSchema.get(cls); if (schema == null) { schema = RuntimeSchema.createFrom(cls); if (schema != null) { cachedSchema.put(cls, schema); } } return schema; } @SuppressWarnings("unchecked") public static <T> byte[] serialize(T obj) { Class<T> cls = (Class<T>) obj.getClass(); LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); try { Schema<T> schema = getSchema(cls); return ProtostuffIOUtil.toByteArray(obj, schema, buffer); } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); } finally { buffer.clear(); } } public static <T> T deserialize(byte[] data, Class<T> cls) { try { T obj = cls.newInstance(); Schema<T> schema = getSchema(cls); ProtostuffIOUtil.mergeFrom(data, obj, schema); return obj; } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); } }}

--name 名称 -it -i 容器输入终端保持打开, -t开一个伪终端

ip ad li 查看网段

docker attach id 获取docker

有些容器进入不成功

后台运行 ctrl p + ctrl q

获取容器pid

nsenter

nsenter --target 4497 --mount --uts --ipc --net --pid

root /usr/share/nginx/html

91 映像端口 80 容器被映像的端口

-h 指定主机名 -v 数据卷

docker run -it --name volume-test1 -h centos -v /datacentos

docker inspect -f "{{.Volumes}}"volume-test1

docker日志如何存储

只读格式

useradd -s /sbin/nologin -M www

前台运行

docker commit -m "my nginx" c85374823499rainyday/my-ngnix:v1

#This is My first Dockerfile

# Version 1.0

#Author : Rainyday

From centos

#MAINTAINER

MAINTAINER Rainyday

#ADD

ADD pcre-8.42.tar.gz /usr/local/src

ADD nginx-1.13.12.tar.gz /usr/local/src

#RUN

RUN yum install -y wget gcc gcc-c++ make openssl-devel

RUN useradd -s /sbin/nologin -M www

#WORKDIR

WORKDIR /usr/local/src/nginx-1.13.12

RUN http://hzhcontrols.com/configure --prefix=/usr/local/nginx --user=www--with-http_ssl_module --with-http_stub_status_module--with-pcre=/usr/local/src/pcre-8.42 && make &&make install

RUN echo "daemon off;" >>/usr/local/nginx/conf/nginx.conf

ENV PATH /usr/local/nginx/sbin:$PATH

EXPOSE 80

CMD ["nginx"]

资源限制 cgroup

http://mirrors.aliyun.com/repo/

wget http://mirrors.aliyun.com/repo/epel-6.repo

docker build -t stress .

docker images

docker run -it --rm -c 512 stress --cpu 1 指定cpu大小

docker run -it --rm stress --cpu 1 指定cpu

docker run -it --rm --cpuset-cpus=0 stress --cpu 1

指定占用几个cpu

docker exec id

docker run -it --rm -m 128m stress --v

m 1 --vm-bytes 120m --vm-hang 0

测压大于两倍关掉

cpu -c --cpu-set

内存 -m

docker 网络模式

docker run -d -p 5001:5000 registry

docker push localhost:5001/test/e:v1

docker tag d1fd7d86a825 47.106.154.105:5001/test/es:v1

echo '{"insecure-registries":["47.106.154.105:5001"] }' >/etc/docker/daemon.json

docker pull 47.106.154.105:5001/test/e:v1

shipyard

-H tcp:0.0.0.0:235 -H unix:///var:/run/docker.sock

docker run -it -d --name shipyard-rethinkdb-data--entrypoint /bin/bash shipyard/rethinkdb -l

在使用Docker创建了容器之后,大家比较关心的就是如何进入该容器了,其实进入Docker容器有好几多种方式,这里我们就讲一下常用的几种进入Docker容器的方法。

进入Docker容器比较常见的几种做法如下:使用docker attach使用SSH使用nsenter使用exec

一、使用docker attach进入Docker容器

Docker提供了attach命令来进入Docker容器。

接下来我们创建一个守护态的Docker容器,然后使用docker attach命令进入该容器。$ sudo docker run -itd ubuntu:14.04 /bin/bash

然后我们使用docker ps查看到该容器信息,接下来就使用docker attach进入该容器$ sudo docker attach 44fc0f0582d9

可以看到我们已经进入到该容器中了。

但在,使用该命令有一个问题。当多个窗口同时使用该命令进入该容器时,所有的窗口都会同步显示。如果有一个窗口阻塞了,那么其他窗口也无法再进行操作。

因为这个原因,所以docker attach命令不太适合于生产环境,平时自己开发应用时可以使用该命令。

二、使用SSH进入Docker容器

在生产环境中排除了使用docker attach命令进入容器之后,相信大家第一个想到的就是ssh。在镜像(或容器)中安装SSH Server,这样就能保证多人进入

容器且相互之间不受干扰了,相信大家在当前的生产环境中(没有使用Docker的情况)也是这样做的。但是使用了Docker容器之后不建议使用ssh进入到Docker容

器内。关于为什么不建议使用,请参考如下文章:

为什么不需要在 Docker 容器中运行 sshd

三、使用nsenter进入Docker容器

在上面两种方式都不适合的情况下,还有一种比较方便的方法,即使用nsenter进入Docker容器。关于什么是nsenter请参考如下文章:

https://github.com/jpetazzo/nsenter

在了解了什么是nsenter之后,系统默认将我们需要的nsenter安装到主机中

如果没有安装的话,按下面步骤安装即可(注意是主机而非容器或镜像)

具体的安装命令如下:$ wget https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz  $ tar -xzvf util-linux-2.24.tar.gz  $ cd util-linux-2.24/  $ http://hzhcontrols.com/configure --without-ncurses  $ make nsenter  $ sudo cp nsenter /usr/local/bin

安装好nsenter之后可以查看一下该命令的使用。

nsenter可以访问另一个进程的名称空间。所以为了连接到某个容器我们还需要获取该容器的第一个进程的PID。可以使用docker inspect命令来拿到该PID。

docker inspect命令使用如下:$ sudo docker inspect --help

inspect命令可以分层级显示一个镜像或容器的信息。比如我们当前有一个正在运行的容器

可以使用docker inspect来查看该容器的详细信息。$ sudo docker inspect 44fc0f0582d9

由其该信息非常多,此处只截取了其中一部分进行展示。如果要显示该容器第一个进行的PID可以使用如下方式$ sudo docker inspect -f {{.State.Pid}} 44fc0f0582d9

在拿到该进程PID之后我们就可以使用nsenter命令访问该容器了。$ sudo nsenter --target 3326 --mount --uts --ipc --net --pid  $ sudo nsenter --target 3326 --mount --uts --ipc --net --pid

其中的3326即刚才拿到的进程的PID

当然,如果你认为每次都输入那么多参数太麻烦的话,网上也有许多做好的脚本供大家使用。

地址如下:

http://yeasy.gitbooks.io/docker_practice/content/container/enter.html

http://www.tuicool.com/articles/eYnUBrR

四、使用docker exec进入Docker容器

除了上面几种做法之外,docker在1.3.X版本之后还提供了一个新的命令exec用于进入容器,这种方式相对更简单一些,下面我们来看一下该命令的使用:$ sudo docker exec --help

接下来我们使用该命令进入一个已经在运行的容器$ sudo docker ps  $ sudo docker exec -it 775c7c9ee1e1 /bin/bash

zookeeper 的监控工具

公司很多产品会使用zookeeper,比如Meta消息中间件,在测试的过程中,我们经常需要查询zookeeper里面的信息来精确定位问题。目前项目中有开发团队自己写的浏览器node-zk-browser,是基于node.js的express.js框架和node-zookeeper客户端实现的,具体可参考https://github.com/killme2008/node-zk-browser. 但node-zk-browser对于不太熟悉node.js的同学部署起来会比较困难,因此跟大家分享一个使用起来非常简单的zk浏览器工具和Eclipse 插件. 该工具除了能展示树形结构外,也能展示每个path的属性和数据,而且如果数据是文本的也可以进行编辑.

一、ZooInspector

下载:https://issues.apache.org/jira/secure/attachment/12436620/ZooInspector.zip

运行: 解压缩后点击ZooInspectoruildzookeeper-dev-ZooInspector.jar后会出现以下界面

在dos窗口中输入java -jar zookeeper-dev-ZooInspector.jar,会弹出一个GUI窗口如下:

连接ZK     点击左上角的绿色按钮,输入ZK Server的地址和端口

连接成功后就能看到ZK的节点数据信息.

二、zk浏览器Eclipse插件

安装Eclipse插件

Step 1. 在 Eclipse  菜单打开Help -> Install New Software...

Step 2. 添加 url http://www.massedynamic.org/eclipse/updates/ .

Step 3. 选择插件并安装

运行

Step 1. 在 Eclipse  菜单打开Window->Show View->Other...->ZooKeeper 3.2.2

Step 2. 连接ZK

输入正在运行的ZK server 地址和端口

连接成功后就就可以在Eclipse里查看ZK Server里的节点信息.

二、zookeeper-monitor

http://jm-blog.aliapp.com/?p=1450

我这段时间在用Redis,感觉挺方便的,但比较疑惑在选择内存数据库的时候到底什么时候选择redis,什么时候选择memcache,然后就查到下面对应的资料,是来自redis作者的说法(stackoverflow上面)。

You should not care too much about performances. Redis is faster per core with small values, but memcached is able to use multiple cores with a single executable and TCP port without help from the client. Also memcached is faster with big values in the order of 100k. Redis recently improved a lot about big values (unstable branch) but still memcached is faster in this use case. The point here is: nor one or the other will likely going to be your bottleneck for the query-per-second they can deliver.

You should care about memory usage. For simple key-value pairs memcached is more memory efficient. If you use Redis hashes, Redis is more memory efficient. Depends on the use case.

You should care about persistence and replication, two features only available in Redis. Even if your goal is to build a cache it helps that after an upgrade or a reboot your data are still there.

You should care about the kind of operations you need. In Redis there are a lot of complex operations, even just considering the caching use case, you often can do a lot more in a single operation, without requiring data to be processed client side (a lot of I/O is sometimes needed). This operations are often as fast as plain GET and SET. So if you don’t need just GEt/SET but more complex things Redis can help a lot (think at timeline caching).

有网友翻译如下[1]:

没有必要过多的关注性能。由于Redis只使用单核,而Memcached可以使用多核,所以在比较上,平均每一个核上Redis在存储小数据时比Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis,虽然Redis最近也在存储大数据的性能上进行优化,但是比起Memcached,还是稍有逊色。说了这么多,结论是,无论你使用哪一个,每秒处理请求的次数都不会成为瓶颈。

你需要关注内存使用率。对于key-value这样简单的数据储存,memcache的内存使用率更高。如果采用hash结构,redis的内存使用率会更高。当然,这些都依赖于具体的应用场景。

你需要关注关注数据持久化和主从复制时,只有redis拥有这两个特性。如果你的目标是构建一个缓存在升级或者重启后之前的数据不会丢失的话,那也只能选择redis。

你应该关心你需要的操作。redis支持很多复杂的操作,甚至只考虑内存的使用情况,在一个单一操作里你常常可以做很多,而不需要将数据读取到客户端中(这样会需要很多的IO操作)。这些复杂的操作基本上和纯GET和POST操作一样快,所以你不只是需要GET/SET而是更多的操作时,redis会起很大的作用。

对于两者的选择还是要看具体的应用场景,如果需要缓存的数据只是key-value这样简单的结构时,我在项目里还是采用memcache,它也足够的稳定可靠。如果涉及到存储,排序等一系列复杂的操作时,毫无疑问选择redis。

关于redis和memcache的不同,下面罗列了一些相关说法,供记录:

redis和memecache的不同在于[2]:

1、存储方式:

memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小

redis有部份存在硬盘上,这样能保证数据的持久性,支持数据的持久化(笔者注:有快照和AOF日志两种持久化方式,在实际应用的时候,要特别注意配置文件快照参数,要不就很有可能服务器频繁满载做dump)。

2、数据支持类型:

redis在数据支持上要比memecache多的多。

3、使用底层模型不同:

新版本的redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。

4、运行环境不同:

redis目前官方只支持Linux 上去行,从而省去了对于其它系统的支持,这样的话可以更好的把精力用于本系统 环境上的优化,虽然后来微软有一个小组为其写了补丁。但是没有放到主干上

memcach简介

Memcache时一个内存对象缓存系统,用于加速动态web应用程序,减轻数据库负载。它可以应对任意多个连接,使用非阻塞的网络I/O,

工作机制:

在内存中开辟一块空间,然后建立一个hash表,memcached自管理这些hash表

工作原理

Memcached基于健值对存储,key会通过hash算法转化成hash-key,便于查找。

Memcached有两个核心组件组成:服务端(server)和客户端,在一个memcached的查询中,客户端会先计算key的hash值来确定所出的server位置。当server确定以后,客户端对就会发送一个查询请求给对应的server,让它查找确切的数据。

内存管理机制

emcached会预先分配内存,

Memcached使用预分配的内存池的方式,使用slab和大小不同的chunk来管理内存,ltem根据大小选择合适的chunk存储,内存池的方式可以省去申请/释放内存的开销,并且减少内存碎片的产生,但这种方式也会带来一定程度上的空间浪费

memcache与redis区别

1)redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储

2)内存使用使用效率对比

使用简单的key-value存储的话,memcached的内存利用率会更高一点,如果redis采用hash结构来做key-value存储,由于其组合式的压缩,内存的利用率更高。

3)性能对比:由于redis只使用单核,而memcached使用多核,所以平均在每一个核上redis在存储小数据时比memcached性能更高,而在100Ks=以上的时候memcached性能要高于redis

4)内存管理机制的不同

在redis中,并不是所有的数据都一一直存储在内存中的,这是和memcached相比最大的一个区别

Redis只会缓存所有的key端的信息,如果redis发现内存的使用量超过某一个值,将触发swap的操作,redis根据相应的表达式计算出那些key对应value需要swap到磁盘,然后再将这些这些key对应的value持久化到磁盘中,同时再内存清除。同时由于redis将内存中的数据swap到磁盘的时候,提供服务的主线程和进行swap操作的子进程会共享这部分内存,所以如果更新需要swap的数据,redis将阻塞这个操作,直到子线程完成swap操作后才可以进行修改

5)数据持久化的支持

虽然redis是基于内存的存储系统,但是他本身是支持内存数据的持久化,而且主要提供两种主要的持久化策略,RDB快照和AOF日志,而memcached是不支持数据持久化的操作的。

RDB持久化通过保存了数据库的健值对来记录数据库状态的不同,AOF持久化是通过保存reds服务器所执行的命令来保存记录数据库的状态的,

RDB持久化保存数据库状态的方法是将msg,fruits,numbers三个健的健值对保存到RDB文件中,而AOF持久化保存数据库的状态则是将服务器执行的SET,SADD,RPUSH三个命令保存到AOF文件中的,RDB快照

redis支持将当前的数据快照存放成一个数据文件的持久化机制,即RDB快照,但是一个持续写入的数据库是如何生成快照的,

在生成快照的时候,将当前的进程fork出一个子进程,然后在子进程中循环所有的数据,将数据写成RDB文件。我们可以通过redis的slave指令来配置RDB快照生成的时机。RDB文件不会坏掉,因为其写操作是在一个新进程中进行的,当生成一个新的RDB文件时redis生成的子进程会先将数据写到一个临时文件中,然后通过原子性rename系统调用将临时文件重命名为RDB文件。

AOF持久化的实现

OF持久化的实现可以分为命令追击(append),文件写入,文件同步(sync)

1)命令追加

当AOF持久化功能打开的时候,服务器在执行完一个写命令的时候,会以协议的格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾

2)AOF文件的写入与同步

服务器在处理文件事件时可能会执行写命令,使得一些内容被追加到aof_buf缓冲区里面,所以服务器在每次结束一个事件循环之前,它都会调用flushAppendonlyFIle函数考虑是否需要将将缓冲区的内容写入和保存到AOF文件里面。如果函数被调用,并且距离上次同步AOF文件已经超过了一秒钟,那么服务器会先将aof_buf中的内容写入到AOF文件中,然后再对AOF文件进行同步

3)文件的载入与数据还原

4)AOF重写

AOF持久化保存的命令越来越多,文件里的内容也月来越多,会对计算机造成影响。为了解决AOF文件体积膨胀问题,REDIS提供了AOF重写功能。Redis服务器可以创建一个新的AOF文件,新旧两个AOF文件保存的数据状态相同,但新AOF文件不会包含任何浪费空间的冗余命令,所以新的AOF会比旧的体积要小

1. Docker 简介

Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。Docker image 是用于运行容器化进程的方案,在本文中,我们将构建一个简单的 Spring Boot 应用程序。

2.环境搭建

JDK 1.8+Maven 3.0+Docker 最新版。

3.用 Maven 构建项目

3.1 创建目录结构

在linux或者mac系统中。

这里写图片描述

3.2 创建 pom.xml 文件

注意:

Spring Boot Maven plugin 提供了很多方便的功能:

1)它收集的类路径上所有 jar 文件,并构建成一个单一的、可运行的jar,这使得它更方便地执行和传输服务。

2)它搜索的 public static void main() 方法来标记为可运行的类。

3)它提供了一个内置的依赖解析器,用于设置版本号以匹配 Spring Boot 的依赖。您可以覆盖任何你想要的版本,但它会默认选择的 Boot 的版本集。

Spotify 的 docker-maven-plugin 插件是用于构建 Maven 的 Docker Image

1)imageName指定了镜像的名字,本例为 springio/lidong-spring-boot-demo

2)dockerDirectory指定 Dockerfile 的位置

3)resources是指那些需要和 Dockerfile 放在一起,在构建镜像时使用的文件,一般应用 jar 包需要纳入。4.编写 第一个Spring Boot 应用

编写一个简单的 Spring Boot 应用 :

src/main/java/com/lidong/demo/SampleController.java:

类用 @SpringBootApplication @RestController 标识,可用 Spring MVC 来处理 Web 请求。@RequestMapping 将 / 映射到 home() ,并将”Hello Docker World” 文本作为响应。main() 方法使用 Spring Boot 的 SpringApplication.run() 方法来启动应用。

5.运行程序

5.1使用Maven命令

1

运行:

1

访问项目

如果程序正确运行,浏览器访问 http://localhost:8081/,可以看到页面 “Hello Docker World.” 字样。5.2 使用IDEA 插件

这里写图片描述

6.将项目容器化

Docker 使用 Dockerfile 文件格式来指定 image 层,

创建文件 src/main/docker/Dockerfile:

解释下这个配置文件:

VOLUME 指定了临时文件目录为/tmp。其效果是在主机 /var/lib/docker 目录下创建了一个临时文件,并链接到容器的/tmp。改步骤是可选的,如果涉及到文件系统的应用就很有必要了。/tmp目录用来持久化到 Docker 数据文件夹,因为 Spring Boot 使用的内嵌 Tomcat 容器默认使用/tmp作为工作目录

项目的 jar 文件作为 “app.jar” 添加到容器的

ENTRYPOINT 执行项目 app.jar。为了缩短 Tomcat 启动时间,添加一个系统属性指向 “/dev/urandom” 作为 Entropy Source

构建 Docker Image

执行构建成为 docker image:

如果是在linux 可以建立一个dockerfile文件

然后 docker build -t [你要起的镜像名称]  .(这个点表示当前目录查找Dockerfile)

运行

运行 Docker Image

这里写图片描述

看到这个Spring的图标。就以为这我们在docker 上发布Spring boot 程序已经完成。

接下来去访问在浏览器访问 http://localhost:8081/,可以看到页面 “Hello Docker World.” 字样。

1、安装wget命令

如果需要通过使用wget命令,直接通过网络下载maven安装包时,需要在linux系统中安装wget命令。

yum -y install wget

2、下载maven安装包

wget http://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/3.5.3/binaries/apache-maven-3.5.3-bin.tar.gz

如果地址不可用 可到maven官网找到下载页面右键选择下载地址

maven官网

3、解压缩maven

tar -zxvf apache-maven-3.5.2-bin.tar.gz

我这里将maven解压缩之后的路径为:/var/local

4、配置maven环境变量

vi /etc/profile

添加环境变量

export MAVEN_HOME=/var/local/apache-maven-3.5.2

export MAVEN_HOME

export PATH=$PATH:$MAVEN_HOME/bin

编辑之后记得使用source /etc/profile命令是改动生效。

5、验证结果

在任意路径下执行mvn -version验证命令是否有效。

正常结果如下,能够看到当前maven及jdk版本。

由于公司要做微服务所以我对jenkins docker springcloud如何集成起来做自动部署做了一些研究,这里写出来也算是结自己一个总结,同时也希望能帮助到其他人。

我这里环境如下:

Contos7 64jdk-8u144-linux-x64.tar.gz

jenkins.war 2.78dockergitlabmavengit因为jenkins是java 编写的所以需要安装jdk安装步骤很简单这大概也就是一步:

进入jdk的目录  cd /usr/local 我这里是/usr/local这个目录查看目录下文件 ls -il解压jdk tar zxvf jdk-8u144-linux-x64.tar.gz配置jdk 编辑profile 文件加入jdk 环境变量 vim /etc/profileexport JAVA_HOME=/usr/local/jdk1.8.0_144

export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

export PATH=$PATH:$JAVA_HOME/bin

使用source命令使配置生效 source /etc/profile这时我们来执行 java -version 出现下面这个就说明配置成功了。

接下来我们就可以安装jenkins了首选需要下载jenkins 下载地址 http://updates.jenkins-ci.org/download/war/下载后将jenkins.war上传到 /usr/local到jenkins.war 目录下 执行 java -jar jenkins.war 或者是 java -jar httpPort=8080(这里可以指定怎么想指定的端口) jenkins.war jenkins 默认是8080因为liunx 系统没有开放8080端口所以要开放8080端口 firewall-cmd --add-port=8080/tcp --permanent重新加载防火墙配置 firewall-cmd --reload这时在浏览器访问 http://ip:8080 就可以看到这是jenkins为我们自动生成的一个密钥 这个密钥可以在控制台找到 当然也可以去 这个目录下找到/root/.jenkins/secrets/initialAdminPassword这里我们到控制台去找将这个密钥复制到文本框 点击Continue 就可以看到这个页面这里这们选择推荐(install suggested plugins)这时可以看到已经在安装插件安装完成后需要创建一个管理员账户 填写完成后点击 Save and finish当进入下面这个页面后jenkins就算是安装完成了

docker 的安装我这里是安官网的安装这里就不做请说明。请参考官网 https://docs.docker.com/engine/installation/linux/docker-ce/centos/#install-using-the-repositorymaven 安装 首先要下载一上maven 的安装文件 使用wget下载 wget http://mirror.bit.edu.cn/apache/maven/maven-3/3.5.0/binaries/apache-maven-3.5.0-bin.tar.gz下载完成后解压 maven :tar zxvf apache-maven-3.5.0-bin.tar.gz配置MAVEN_HONE 环境变量编辑 vim /etc/profile 加入下面这两句export  MAVEN_HOME=/usr/local/apache-maven-3.5.0

export PATH=$PATH:$MAVEN_HOME/bin

配置生效 source /etc/profile执行mvn -version 看到如果图说明配置成功

配置阿里云的maven repository 进入 maven conf目录下 cd /usr/local/apache-maven-3.5.0/conf编辑settings.xml加入阿里云的maven仓库 vimsettings.xml <mirror>

<id>nexus-aliyun</id>

<mirrorOf>*</mirrorOf>

<name>Nexus aliyun</name>

<url>http://maven.aliyun.com/nexus/content/groups/public</url>

</mirror>

安装git yum install -y git安装完成执行 git --version

接下来进入主题来构建我们的第一个项目1.在jenkins中创建一个新任务

2.构建一个自由风格的软件项目

3.配置项目构建信息

1)配置git

2)配置git web hook 安装 gitlab hook plugin 系统管理 -> 插件管理 -> 可选插件 -> 搜索 gitlab hook plugin -> 选中安装

首先说明我这里用的是私有的gitlab 如果你的机器可以公网访问那么你可以使用github 或 码云 。搭建私有gitlab请看 http://www.imooc.com/article/17128?block_id=tuijian_wz

进入点击generate生成一个token 复制 http://192.168.189.139:8080/project/microservice-discovery 进入 gitlab Settings -> Integrations -> 添加url和 token

3)配置构建环境

maven构建 增加构建步骤 -> invoker top-level-Maven targets

构建命令 clean package

shell 脚本 增加构建步骤 -> Execute shell

构建前我们需要设置一下docker 的regitry通过不安全的http访问 地址以及阿里云加速器 vim /usr/lib/systemd/system/docker.service 加入如下--registry-mirror=https://aj2rgad5.mirror.aliyuncs.com --insecure-registry 192.168.189.133:5000

完成已上步骤我们点构建就可以构建我们的项目了 从下图中可以看到已经开始在构建

构建完成后我们访问http://192.168.189.139:8761就可以看到springcloud eureak的管理页了

构建脚本如下:#!/bin/bashREGISTRY_URL=192.168.189.133:5000WORK_DIR=/root/work_buildPROJECT_NAME=microservice-providerPROJECT_VERSION=0.0.1if [ ! -e ${WORK_DIR}/${PROJECT_NAME} ] && [ ! -d ${WORK_DIR}/${PROJECT_NAME} ]; thenmkdir -p ${WORK_DIR}/${PROJECT_NAME}echo "Create Dir: ${WORK_DIR}/${PROJECT_NAME}"fiif [ -e ${WORK_DIR}/${PROJECT_NAME}/Dockerfile ]; thenrm -rf ${WORK_DIR}/${PROJECT_NAME}/Dockerfileecho "Remove File: ${WORK_DIR}/${PROJECT_NAME}/Dockerfile"ficp http://hzhcontrols.com/Dockerfile ${WORK_DIR}/${PROJECT_NAME}/cp http://hzhcontrols.com/target/*.jar ${WORK_DIR}/${PROJECT_NAME}/cd ${WORK_DIR}/${PROJECT_NAME}/docker build -t ${REGISTRY_URL}/microservice/${PROJECT_NAME}:${PROJECT_VERSION} .docker push ${REGISTRY_URL}/microservice/${PROJECT_NAME}:${PROJECT_VERSION}if docker ps -a | grep ${PROJECT_NAME}; thendocker rm -f ${PROJECT_NAME}echo "Remove Docker Container: ${PROJECT_NAME}"fidocker run -d -p 8761:8761 --name ${PROJECT_NAME} ${REGISTRY_URL}/microservice/${PROJECT_NAME}:${PROJECT_VERSION}

众所周知,spring cloud eureka是使用hostname进行注册的,如果想使用IP进行注册那应该如何处理呢。只需要在eureka服务端增加如下配置

eureka.instance.perferIpAddress=true

可参见spring cloud的官方手册,也可以参考提问信息。

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80643775

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80644651

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80868196

MySql中添加用户,新建数据库,用户授权,删除用户,修改密码(注意每行后边都跟个;表示一个命令语句结束):

1.新建用户

1.1 登录MYSQL:

@>mysql -u root -p

@>密码

1.2 创建用户:

mysql> insert into mysql.user(Host,User,Password) values("localhost","test",password("1234"));

这样就创建了一个名为:test 密码为:1234 的用户。

注意:此处的"localhost",是指该用户只能在本地登录,不能在另外一台机器上远程登录。如果想远程登录的话,将"localhost"改为"%",表示在任何一台电脑上都可以登录。也可以指定某台机器可以远程登录。

1.3 然后登录一下:

mysql>exit;

@>mysql -u test -p

@>输入密码

mysql>登录成功

2.为用户授权

授权格式:grant 权限 on 数据库.* to 用户名@登录主机 identified by "密码";

2.1 登录MYSQL(有ROOT权限),这里以ROOT身份登录:

@>mysql -u root -p

@>密码

2.2 首先为用户创建一个数据库(testDB):

mysql>create database testDB;

2.3 授权test用户拥有testDB数据库的所有权限(某个数据库的所有权限):

mysql>grant all privileges on testDB.* to test@localhost identified by '1234';

mysql>flush privileges;//刷新系统权限表

格式:grant 权限 on 数据库.* to 用户名@登录主机 identified by "密码";

2.4 如果想指定部分权限给一用户,可以这样来写:

mysql>grant select,update on testDB.* to test@localhost identified by '1234';

mysql>flush privileges; //刷新系统权限表

2.5 授权test用户拥有所有数据库的某些权限:

mysql>grant select,delete,update,create,drop on *.* to test@"%" identified by "1234";

//test用户对所有数据库都有select,delete,update,create,drop 权限。

//@"%" 表示对所有非本地主机授权,不包括localhost。(localhost地址设为127.0.0.1,如果设为真实的本地地址,不知道是否可以,没有验证。)

//对localhost授权:加上一句grant all privileges on testDB.* to test@localhost identified by '1234';即可。

3. 删除用户

@>mysql -u root -p

@>密码

mysql>Delete FROM user Where User='test' and Host='localhost';

mysql>flush privileges;

mysql>drop database testDB; //删除用户的数据库

删除账户及权限:>drop user 用户名@'%';

>drop user 用户名@ localhost;

4. 修改指定用户密码

@>mysql -u root -p

@>密码

mysql>update mysql.user set password=password('新密码') where User="test" and Host="localhost";

mysql>flush privileges;

5. 列出所有数据库

mysql>show database;

6. 切换数据库

mysql>use '数据库名';

7. 列出所有表

mysql>show tables;

8. 显示数据表结构

mysql>describe 表名;

9. 删除数据库和数据表

mysql>drop database 数据库名;

mysql>drop table 数据表名;

1:show databases;

查看所有的数据库,等同于select schema_name from information_schema.schemataG。G 替换;,以纵向报表的形式输出结果,有利于阅读。

2. status 查看mysql数据库的运行状态

3. use 命令选择数据库 例如 use information_schema,当使用此命令后

select schema_name from information_schema.schemataG,可以为

select schema_name from schemataG

4. 查看数据库中的表

show tables

同样也可以在information_schema中查看,show命令是方便使用的简短模式。

select table_name from tables where table_schema='jblog';

5. 查看表结构

desc  table_name;

6.查看表状态 show table status from db like 条件

可以查看engine数据库引擎,version,row、index等信息

7.小技巧,当多行命令输入,发现错误后,用c结束。

-------------------------------------------------------------

另,查询数据库运行状态的基本命令:

jpa: hibernate: ddl-auto: create

ddl-auto:create----每次运行该程序,没有表格会新建表格,表内有数据会清空

ddl-auto:create-drop----每次程序结束的时候会清空表

ddl-auto:update----每次运行程序,没有表格会新建表格,表内有数据不会清空,只会更新

ddl-auto:validate----运行程序会校验数据与数据库的字段类型是否相同,不同会报错

<table tableName="user_info_t" domainObjectName="User" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table>

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80880433

1.登录阿里云

2.登录后找到右上角的“管理中心”,点击进入后》点击“镜像加速器”;剩下的安装文档配置就好

问题1:配置完后还是提示:Tag latest not found in repository 【本人也是在这里被困了好久,尝试了各种方法】

解决方案:最后发现这里有个镜像搜索

这里以busybox为例,点击“详情”

是不是突然感觉很坑的感觉,在网上找了那么多的文档,都是docker pull busybox

测试拉取的镜像:docker run registry.cn-hangzhou.aliyuncs.com/busybox echo "hello world"

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80884381

代码

Springboot + jpa  上面是代码

配置

spring: datasource: url: jdbc:mysql://localhost:3306/demo username: root password: root driver-class-name: com.mysql.jdbc.Driver jpa: # jpa 配置 database: mysql show-sql: true hibernate: ddl-auto: update devtools: # 热部署配置 restart: enabled: true

实体类

mport javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;/** * * @author rainyday * @createTime 2018/7/2. */@Entitypublic class Cat { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; private String catName; private int catAge; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getCatName() { return catName; } public void setCatName(String catName) { this.catName = catName; } public int getCatAge() { return catAge; } public void setCatAge(int catAge) { this.catAge = catAge; }}

继承

CrudRepository

可以直接调用 jpa的方法

import org.springframework.data.repository.CrudRepository;import springboot.bean.Cat;public interface CatRepository extends CrudRepository<Cat, Integer>{}

Service层

import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import springboot.bean.Cat;import springboot.repository.CatRepository;/** * @author rainyday * @createTime 2018/7/2. */@Servicepublic class CatService { @Autowired private CatRepository catRepository; @Transactional public void save(Cat cat) { catRepository.save(cat); } @Transactional public void delete(int id) { catRepository.deleteById(id); } @Cacheable("getAll") public Iterable<Cat> getAll() { System.out.println("no use cache"); return catRepository.findAll(); }}

控制层

import org.springframework.beans.factory.annotation.Autowired;import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.*;import springboot.bean.Cat;import springboot.service.CatService;/** * @author rainyday * @createTime 2018/7/2. */@RestController@RequestMapping("/cat")public class CatController { @Autowired private CatService catService; @PostMapping("/save") public void save(@RequestParam(required = false) Cat cat) { if (StringUtils.isEmpty(cat)) { cat = new Cat(); cat.setCatName("jack"); } cat.setCatAge(3); catService.save(cat); } @RequestMapping("delete") public String delete() { catService.delete(1); return "delete ok!"; } public Iterable<Cat> getAll() { return catService.getAll(); }}

代码地址

一、实现的方式概述

注意以下的热部署方式在IDEA是默认没有打开自动编译的,手动编译需要快捷键(Ctrl+Shift+F9),自动编译的修改配置如下:(注意刷新不要太快,会有1-2秒延迟)

File-Settings-Compiler-Build Project automatically

二、spring-boot-devtools

在pom中直接引入依赖

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional></dependency>

设置以下两项(第一项如已设置直接设置第二项)

1) “File” -> “Settings” -> “Build,Execution,Deplyment” -> “Compiler”,选中打勾 “Build project automatically” 。

2) 组合键:“Shift+Ctrl+Alt+/” ,选择 “Registry” ,选中打勾 “compiler.automake.allow.when.app.running” 。

之后直接正常run即可!

三、spring-loaded

在Plugins中添加依赖

复制代码

<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <dependencies> <!-- spring热部署 --> <dependency> <groupId>org.springframework</groupId> <artifactId>springloaded</artifactId> <version>1.2.6.RELEASE</version> </dependency> </dependencies> </plugin> </plugins> </build>

复制代码

// 如果依赖提示not found,先在上面写让maven下载下来,再移到Plugin里面来

命令行窗口启动

找到pom.xml的路径,IDEA的话打开Pom后在标签页鼠标悬停即可看到,或者在pom上右击->file Path

在这个路径下打开cmd窗口(win下可以通过shift快速在对应路径打开),输入启动命令

mvn spring-boot:run

这样就可以在IDE里修改代码实现热加载了!

还有一种复杂的方式是通过启动参数指定jar包的位置,感觉更加复杂,暂不赘述,可以参考:这里

四、发布spring-boot程序

1.jar形式

通过maven直接INSTALL,把项目打包并且相关的依赖也打到同一个jar里面(推荐shade插件),maven的生命周期,参考这里

启动:

java -jar Demo-0.0.1-SNAPSHOT.jar

2.war形式

参考:https://www.cnblogs.com/gdpuzxs/p/7224959.html?utm_source=itdadao&utm_medium=referral

:https://www.cnblogs.com/coder-wzr/p/7860778.html

注意:context-path与warName一致

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80887625

刚刚学习使用idea中,想要把自己的项目上传到github,遇到这样一个问题,先记录下来,到时候解决了在把方法贴出来。

--------------------------------------------------------------这是分割线 --------------------------------------------------------------

我那个错误的原因是那个地方要的是git.exe,我一开始以为是github的客户端。

最后去下载了git的客户端。Git下载: http://git-scm.com/downloads 。

AudioUploadRequest 封装了音频和图片 属相 无法swagger 框架无法直接载入 需要在 controller 层调用的方法中加入

@ApiParam(value=  ...) MultipartFile cover 即可 通过对象去调用 视频和图片属性

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80935055

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80959719

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/81021384

本文主要关注Java编程中涉及到的各种集合类,以及它们的使用场景

相关学习资料

http://files.cnblogs.com/LittleHann/java%E9%9B%86%E5%90%88%E6%8E%92%E5%BA%8F%E5%8F%8Ajava%E9%9B%86%E5%90%88%E7%B1%BB%E8%AF%A6%E8%A7%A3%28collection%E3%80%81list%E3%80%81map%E3%80%81set%29.rarhttp://blog.sina.com.cn/s/blog_a345a8960101k9vx.htmlhttp://f51889920.iteye.com/blog/1884810

目录

1. Java集合类基本概念2. Java集合类架构层次关系3. Java集合类的应用场景代码

1. Java集合类基本概念

在编程中,常常需要集中存放多个数据。从传统意义上讲,数组是我们的一个很好的选择,前提是我们事先已经明确知道我们将要保存的对象的数量。一旦在数组初始化时指定了这个数组长度,这个数组长度就是不可变的,如果我们需要保存一个可以动态增长的数据(在编译时无法确定具体的数量),java的集合类就是一个很好的设计方案了。

集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类。所以的集合类都位于java.util包下,后来为了处理多线程环境下的并发安全问题,java5还在java.util.concurrent包下提供了一些多线程支持的集合类。

在学习Java中的集合类的API、编程原理的时候,我们一定要明白,"集合"是一个很古老的数学概念,它远远早于Java的出现。从数学概念的角度来理解集合能帮助我们更好的理解编程中什么时候该使用什么类型的集合类。

Java容器类类库的用途是"保存对象",并将其划分为两个不同的概念:

复制代码

1) Collection一组"对立"的元素,通常这些元素都服从某种规则   1.1) List必须保持元素特定的顺序   1.2) Set不能有重复元素   1.3) Queue保持一个队列(先进先出)的顺序2) Map一组成对的"键值对"对象

复制代码

Collection和Map的区别在于容器中每个位置保存的元素个数:

1) Collection 每个位置只能保存一个元素(对象)2) Map保存的是"键值对",就像一个小型数据库。我们可以通过"键"找到该键对应的"值"

2. Java集合类架构层次关系

复制代码

1. Interface Iterable迭代器接口,这是Collection类的父接口。实现这个Iterable接口的对象允许使用foreach进行遍历,也就是说,所有的Collection集合对象都具有"foreach可遍历性"。这个Iterable接口只

有一个方法: iterator()。它返回一个代表当前集合对象的泛型<T>迭代器,用于之后的遍历操作1.1 CollectionCollection是最基本的集合接口,一个Collection代表一组Object的集合,这些Object被称作Collection的元素。Collection是一个接口,用以提供规范定义,不能被实例化使用 1) Set Set集合类似于一个罐子,"丢进"Set集合里的多个对象之间没有明显的顺序。Set继承自Collection接口,不能包含有重复元素(记住,这是整个Set类层次的共有属性)。 Set判断两个对象相同不是使用"=="运算符,而是根据equals方法。也就是说,我们在加入一个新元素的时候,如果这个新元素对象和Set中已有对象进行注意equals比较都返回false,

则Set就会接受这个新元素对象,否则拒绝。 因为Set的这个制约,在使用Set集合的时候,应该注意两点:1) 为Set集合里的元素的实现类实现一个有效的equals(Object)方法、2) 对Set的构造函数,传入的Collection参数不能包

含重复的元素 1.1) HashSet HashSet是Set接口的典型实现,HashSet使用HASH算法来存储集合中的元素,因此具有良好的存取和查找性能。当向HashSet集合中存入一个元素时,HashSet会调用该对象的

hashCode()方法来得到该对象的hashCode值,然后根据该HashCode值决定该对象在HashSet中的存储位置。 值得主要的是,HashSet集合判断两个元素相等的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法的返回值相等 1.1.1) LinkedHashSet LinkedHashSet集合也是根据元素的hashCode值来决定元素的存储位置,但和HashSet不同的是,它同时使用链表维护元素的次序,这样使得元素看起来是以插入的顺序保存的。

当遍历LinkedHashSet集合里的元素时,LinkedHashSet将会按元素的添加顺序来访问集合里的元素。 LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet的性能,但在迭代访问Set里的全部元素时(遍历)将有很好的性能(链表很适合进行遍历) 1.2) SortedSet 此接口主要用于排序操作,即实现此接口的子类都属于排序的子类 1.2.1) TreeSet TreeSet是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态 1.3) EnumSet EnumSet是一个专门为枚举类设计的集合类,EnumSet中所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet时显式、或隐式地指定。EnumSet的集合元素也是有序的,

它们以枚举值在Enum类内的定义顺序来决定集合元素的顺序 2) List List集合代表一个元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引。List集合允许加入重复元素,因为它可以通过索引来访问指定位置的集合元素。List集合默认按元素

的添加顺序设置元素的索引 2.1) ArrayList ArrayList是基于数组实现的List类,它封装了一个动态的增长的、允许再分配的Object[]数组。 2.2) Vector Vector和ArrayList在用法上几乎完全相同,但由于Vector是一个古老的集合,所以Vector提供了一些方法名很长的方法,但随着JDK1.2以后,java提供了系统的集合框架,就将

Vector改为实现List接口,统一归入集合框架体系中 2.2.1) Stack Stack是Vector提供的一个子类,用于模拟"栈"这种数据结构(LIFO后进先出) 2.3) LinkedList implements List<E>, Deque<E>。实现List接口,能对它进行队列操作,即可以根据索引来随机访问集合中的元素。同时它还实现Deque接口,即能将LinkedList当作双端队列

使用。自然也可以被当作"栈来使用" 3) Queue Queue用于模拟"队列"这种数据结构(先进先出 FIFO)。队列的头部保存着队列中存放时间最长的元素,队列的尾部保存着队列中存放时间最短的元素。新元素插入(offer)到队列的尾部,

访问元素(poll)操作会返回队列头部的元素,队列不允许随机访问队列中的元素。结合生活中常见的排队就会很好理解这个概念 3.1) PriorityQueue PriorityQueue并不是一个比较标准的队列实现,PriorityQueue保存队列元素的顺序并不是按照加入队列的顺序,而是按照队列元素的大小进行重新排序,这点从它的类名也可以

看出来 3.2) Deque Deque接口代表一个"双端队列",双端队列可以同时从两端来添加、删除元素,因此Deque的实现类既可以当成队列使用、也可以当成栈使用 3.2.1) ArrayDeque 是一个基于数组的双端队列,和ArrayList类似,它们的底层都采用一个动态的、可重分配的Object[]数组来存储集合元素,当集合元素超出该数组的容量时,系统会在底层重

新分配一个Object[]数组来存储集合元素 3.2.2) LinkedList1.2 MapMap用于保存具有"映射关系"的数据,因此Map集合里保存着两组值,一组值用于保存Map里的key,另外一组值用于保存Map里的value。key和value都可以是任何引用类型的数据。Map的key不允

许重复,即同一个Map对象的任何两个key通过equals方法比较结果总是返回false。关于Map,我们要从代码复用的角度去理解,java是先实现了Map,然后通过包装了一个所有value都为null的Map就实现了Set集合Map的这些实现类和子接口中key集的存储形式和Set集合完全相同(即key不能重复)Map的这些实现类和子接口中value集的存储形式和List非常类似(即value可以重复、根据索引来查找) 1) HashMap 和HashSet集合不能保证元素的顺序一样,HashMap也不能保证key-value对的顺序。并且类似于HashSet判断两个key是否相等的标准也是: 两个key通过equals()方法比较返回true、

同时两个key的hashCode值也必须相等 1.1) LinkedHashMap LinkedHashMap也使用双向链表来维护key-value对的次序,该链表负责维护Map的迭代顺序,与key-value对的插入顺序一致(注意和TreeMap对所有的key-value进行排序进行区

分) 2) Hashtable 是一个古老的Map实现类 2.1) Properties Properties对象在处理属性文件时特别方便(windows平台上的.ini文件),Properties类可以把Map对象和属性文件关联起来,从而可以把Map对象中的key-value对写入到属性文

件中,也可以把属性文件中的"属性名-属性值"加载到Map对象中 3) SortedMap 正如Set接口派生出SortedSet子接口,SortedSet接口有一个TreeSet实现类一样,Map接口也派生出一个SortedMap子接口,SortedMap接口也有一个TreeMap实现类 3.1) TreeMap TreeMap就是一个红黑树数据结构,每个key-value对即作为红黑树的一个节点。TreeMap存储key-value对(节点)时,需要根据key对节点进行排序。TreeMap可以保证所有的

key-value对处于有序状态。同样,TreeMap也有两种排序方式: 自然排序、定制排序 4) WeakHashMap WeakHashMap与HashMap的用法基本相似。区别在于,HashMap的key保留了对实际对象的"强引用",这意味着只要该HashMap对象不被销毁,该HashMap所引用的对象就不会被垃圾回收。

但WeakHashMap的key只保留了对实际对象的弱引用,这意味着如果WeakHashMap对象的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象可能被垃圾回收,当垃

圾回收了该key所对应的实际对象之后,WeakHashMap也可能自动删除这些key所对应的key-value对 5) IdentityHashMap IdentityHashMap的实现机制与HashMap基本相似,在IdentityHashMap中,当且仅当两个key严格相等(key1 == key2)时,IdentityHashMap才认为两个key相等 6) EnumMap EnumMap是一个与枚举类一起使用的Map实现,EnumMap中的所有key都必须是单个枚举类的枚举值。创建EnumMap时必须显式或隐式指定它对应的枚举类。EnumMap根据key的自然顺序

(即枚举值在枚举类中的定义顺序)

复制代码

3. Java集合类的应用场景代码

学习了集合类的基本架构框架之后,我们接着来学习它们各自的应用场景、以及细节处的注意事项

0x1: Set

HashSet

复制代码

import java.util.*; //类A的equals方法总是返回true,但没有重写其hashCode()方法。不能保证当前对象是HashSet中的唯一对象class A{ public boolean equals(Object obj) { return true; }}//类B的hashCode()方法总是返回1,但没有重写其equals()方法。不能保证当前对象是HashSet中的唯一对象class B{ public int hashCode() { return 1; }}//类C的hashCode()方法总是返回2,且有重写其equals()方法class C{ public int hashCode() { return 2; } public boolean equals(Object obj) { return true; }}public class HashSetTest{ public static void main(String[] args) { HashSet books = new HashSet(); //分别向books集合中添加两个A对象,两个B对象,两个C对象 books.add(new A()); books.add(new A()); books.add(new B()); books.add(new B()); books.add(new C()); books.add(new C()); System.out.println(books); }}

复制代码

result:

[B@1, B@1, C@2, A@3bc257, A@785d65]

可以看到,如果两个对象通过equals()方法比较返回true,但这两个对象的hashCode()方法返回不同的hashCode值时,这将导致HashSet会把这两个对象保存在Hash表的不同位置,从而使对象可以添加成功,这就与Set集合的规则有些出入了。所以,我们要明确的是: equals()决定是否可以加入HashSet、而hashCode()决定存放的位置,它们两者必须同时满足才能允许一个新元素加入HashSet

但是要注意的是: 如果两个对象的hashCode相同,但是它们的equlas返回值不同,HashSet会在这个位置用链式结构来保存多个对象。而HashSet访问集合元素时也是根据元素的HashCode值来快速定位的,这种链式结构会导致性能下降。

所以如果需要把某个类的对象保存到HashSet集合中,我们在重写这个类的equlas()方法和hashCode()方法时,应该尽量保证两个对象通过equals()方法比较返回true时,它们的hashCode()方法返回值也相等

LinkedHashSet

复制代码

import java.util.*; public class LinkedHashSetTest{ public static void main(String[] args) { LinkedHashSet books = new LinkedHashSet(); books.add("Java"); books.add("LittleHann"); System.out.println(books); //删除 Java books.remove("Java"); //重新添加 Java books.add("Java"); System.out.println(books); }}

复制代码

元素的顺序总是与添加顺序一致,同时要明白的是,LinkedHashSetTest是HashSet的子类,因此它不允许集合元素重复

TreeSet

复制代码

import java.util.*;public class TreeSetTest{ public static void main(String[] args) { TreeSet nums = new TreeSet(); //向TreeSet中添加四个Integer对象 nums.add(5); nums.add(2); nums.add(10); nums.add(-9); //输出集合元素,看到集合元素已经处于排序状态 System.out.println(nums); //输出集合里的第一个元素 System.out.println(nums.first()); //输出集合里的最后一个元素 System.out.println(nums.last()); //返回小于4的子集,不包含4 System.out.println(nums.headSet(4)); //返回大于5的子集,如果Set中包含5,子集中还包含5 System.out.println(nums.tailSet(5)); //返回大于等于-3,小于4的子集。 System.out.println(nums.subSet(-3 , 4)); }}

复制代码

与HashSet集合采用hash算法来决定元素的存储位置不同,TreeSet采用红黑树的数据结构来存储集合元素。TreeSet支持两种排序方式: 自然排序、定制排序

1. 自然排序:

TreeSet会调用集合元素的compareTo(Object obj)方法来比较元素之间的大小关系,然后将集合元素按升序排序,即自然排序。如果试图把一个对象添加到TreeSet时,则该对象的类必须实现Comparable接口,否则程序会抛出异常。

当把一个对象加入TreeSet集合中时,TreeSet会调用该对象的compareTo(Object obj)方法与容器中的其他对象比较大小,然后根据红黑树结构找到它的存储位置。如果两个对象通过compareTo(Object obj)方法比较相等,新对象将无法添加到TreeSet集合中(牢记Set是不允许重复的概念)。

注意: 当需要把一个对象放入TreeSet中,重写该对象对应类的equals()方法时,应该保证该方法与compareTo(Object obj)方法有一致的结果,即如果两个对象通过equals()方法比较返回true时,这两个对象通过compareTo(Object obj)方法比较结果应该也为0(即相等)

看到这里,我们应该明白:

1) 对与Set来说,它定义了equals()为唯一性判断的标准,而对于到了具体的实现,HashSet、TreeSet来说,它们又会有自己特有的唯一性判断标准,只有同时满足了才能判定为唯一性2) 我们在操作这些集合类的时候,对和唯一性判断有关的函数重写要重点关注

2. 定制排序

TreeSet的自然排序是根据集合元素的大小,TreeSet将它们以升序排序。如果我们需要实现定制排序,则可以通过Comparator接口的帮助(类似PHP中的array_map回调处理函数的思想)。该接口里包含一个int compare(T o1, T o2)方法,该方法用于比较大小

复制代码

import java.util.*;class M{ int age; public M(int age) { this.age = age; } public String toString() { return "M[age:" + age + "]"; }}public class TreeSetTest4{ public static void main(String[] args) { TreeSet ts = new TreeSet(new Comparator() { //根据M对象的age属性来决定大小 public int compare(Object o1, Object o2) { M m1 = (M)o1; M m2 = (M)o2; return m1.age > m2.age ? -1 : m1.age < m2.age ? 1 : 0; } }); ts.add(new M(5)); ts.add(new M(-3)); ts.add(new M(9)); System.out.println(ts); }}

复制代码

看到这里,我们需要梳理一下关于排序的概念

1) equals、compareTo决定的是怎么比的问题,即用什么field进行大小比较2) 自然排序、定制排序、Comparator决定的是谁大的问题,即按什么顺序(升序、降序)进行排序它们的关注点是不同的,一定要注意区分

EnumSet

复制代码

import java.util.*;enum Season{ SPRING,SUMMER,FALL,WINTER}public class EnumSetTest{ public static void main(String[] args) { //创建一个EnumSet集合,集合元素就是Season枚举类的全部枚举值 EnumSet es1 = EnumSet.allOf(Season.class); //输出[SPRING,SUMMER,FALL,WINTER] System.out.println(es1); //创建一个EnumSet空集合,指定其集合元素是Season类的枚举值。 EnumSet es2 = EnumSet.noneOf(Season.class); //输出[] System.out.println(es2); //手动添加两个元素 es2.add(Season.WINTER); es2.add(Season.SPRING); //输出[SPRING,WINTER] System.out.println(es2); //以指定枚举值创建EnumSet集合 EnumSet es3 = EnumSet.of(Season.SUMMER , Season.WINTER); //输出[SUMMER,WINTER] System.out.println(es3); EnumSet es4 = EnumSet.range(Season.SUMMER , Season.WINTER); //输出[SUMMER,FALL,WINTER] System.out.println(es4); //新创建的EnumSet集合的元素和es4集合的元素有相同类型, //es5的集合元素 + es4集合元素 = Season枚举类的全部枚举值 EnumSet es5 = EnumSet.complementOf(es4); //输出[SPRING] System.out.println(es5); }}

复制代码

以上就是Set集合类的编程应用场景。那么应该怎样选择何时使用这些集合类呢?

1) HashSet的性能总是比TreeSet好(特别是最常用的添加、查询元素等操作),因为TreeSet需要额外的红黑树算法来维护集合元素的次序。只有当需要一个保持排序的Set时,才应该使用TreeSet,否则都应该使用HashSet2) 对于普通的插入、删除操作,LinkedHashSet比HashSet要略慢一点,这是由维护链表所带来的开销造成的。不过,因为有了链表的存在,遍历LinkedHashSet会更快3) EnumSet是所有Set实现类中性能最好的,但它只能保存同一个枚举类的枚举值作为集合元素4) HashSet、TreeSet、EnumSet都是"线程不安全"的,通常可以通过Collections工具类的synchronizedSortedSet方法来"包装"该Set集合。SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...));

0x2: List

ArrayList

如果一开始就知道ArrayList集合需要保存多少元素,则可以在创建它们时就指定initialCapacity大小,这样可以减少重新分配的次数,提供性能,ArrayList还提供了如下方法来重新分配Object[]数组

1) ensureCapacity(int minCapacity): 将ArrayList集合的Object[]数组长度增加minCapacity2) trimToSize(): 调整ArrayList集合的Object[]数组长度为当前元素的个数。程序可以通过此方法来减少ArrayList集合对象占用的内存空间

复制代码

import java.util.*;public class ListTest{ public static void main(String[] args) { List books = new ArrayList(); //向books集合中添加三个元素 books.add(new String("轻量级Java EE企业应用实战")); books.add(new String("疯狂Java讲义")); books.add(new String("疯狂Android讲义")); System.out.println(books); //将新字符串对象插入在第二个位置 books.add(1 , new String("疯狂Ajax讲义")); for (int i = 0 ; i < books.size() ; i++ ) { System.out.println(books.get(i)); } //删除第三个元素 books.remove(2); System.out.println(books); //判断指定元素在List集合中位置:输出1,表明位于第二位 System.out.println(books.indexOf(new String("疯狂Ajax讲义"))); //① //将第二个元素替换成新的字符串对象 books.set(1, new String("LittleHann")); System.out.println(books); //将books集合的第二个元素(包括) //到第三个元素(不包括)截取成子集合 System.out.println(books.subList(1 , 2)); }

复制代码

Stack

注意Stack的后进先出的特点

复制代码

import java.util.*;public class VectorTest{ public static void main(String[] args) { Stack v = new Stack(); //依次将三个元素push入"栈" v.push("疯狂Java讲义"); v.push("轻量级Java EE企业应用实战"); v.push("疯狂Android讲义"); //输出:[疯狂Java讲义, 轻量级Java EE企业应用实战 , 疯狂Android讲义] System.out.println(v); //访问第一个元素,但并不将其pop出"栈",输出:疯狂Android讲义 System.out.println(v.peek()); //依然输出:[疯狂Java讲义, 轻量级Java EE企业应用实战 , 疯狂Android讲义] System.out.println(v); //pop出第一个元素,输出:疯狂Android讲义 System.out.println(v.pop()); //输出:[疯狂Java讲义, 轻量级Java EE企业应用实战] System.out.println(v); }}

复制代码

LinkedList

复制代码

import java.util.*;public class LinkedListTest{ public static void main(String[] args) { LinkedList books = new LinkedList(); //将字符串元素加入队列的尾部(双端队列) books.offer("疯狂Java讲义"); //将一个字符串元素加入栈的顶部(双端队列) books.push("轻量级Java EE企业应用实战"); //将字符串元素添加到队列的头(相当于栈的顶部) books.offerFirst("疯狂Android讲义"); for (int i = 0; i < books.size() ; i++ ) { System.out.println(books.get(i)); } //访问、并不删除栈顶的元素 System.out.println(books.peekFirst()); //访问、并不删除队列的最后一个元素 System.out.println(books.peekLast()); //将栈顶的元素弹出"栈" System.out.println(books.pop()); //下面输出将看到队列中第一个元素被删除 System.out.println(books); //访问、并删除队列的最后一个元素 System.out.println(books.pollLast()); //下面输出将看到队列中只剩下中间一个元素: //轻量级Java EE企业应用实战 System.out.println(books); }}

复制代码

从代码中我们可以看到,LinkedList同时表现出了双端队列、栈的用法。功能非常强大

0x3: Queue

PriorityQueue

复制代码

import java.util.*;public class PriorityQueueTest{ public static void main(String[] args) { PriorityQueue pq = new PriorityQueue(); //下面代码依次向pq中加入四个元素 pq.offer(6); pq.offer(-3); pq.offer(9); pq.offer(0); //输出pq队列,并不是按元素的加入顺序排列, //而是按元素的大小顺序排列,输出[-3, 0, 9, 6] System.out.println(pq); //访问队列第一个元素,其实就是队列中最小的元素:-3 System.out.println(pq.poll()); }}

复制代码

PriorityQueue不允许插入null元素,它还需要对队列元素进行排序,PriorityQueue的元素有两种排序方式

1) 自然排序:采用自然顺序的PriorityQueue集合中的元素对象都必须实现了Comparable接口,而且应该是同一个类的多个实例,否则可能导致ClassCastException异常2) 定制排序创建PriorityQueue队列时,传入一个Comparator对象,该对象负责对队列中的所有元素进行排序关于自然排序、定制排序的原理和之前说的TreeSet类似

ArrayDeque

复制代码

import java.util.*;public class ArrayDequeTest{ public static void main(String[] args) { ArrayDeque stack = new ArrayDeque(); //依次将三个元素push入"栈" stack.push("疯狂Java讲义"); stack.push("轻量级Java EE企业应用实战"); stack.push("疯狂Android讲义"); //输出:[疯狂Java讲义, 轻量级Java EE企业应用实战 , 疯狂Android讲义] System.out.println(stack); //访问第一个元素,但并不将其pop出"栈",输出:疯狂Android讲义 System.out.println(stack.peek()); //依然输出:[疯狂Java讲义, 轻量级Java EE企业应用实战 , 疯狂Android讲义] System.out.println(stack); //pop出第一个元素,输出:疯狂Android讲义 System.out.println(stack.pop()); //输出:[疯狂Java讲义, 轻量级Java EE企业应用实战] System.out.println(stack); }}

复制代码

以上就是List集合类的编程应用场景。我们来梳理一下思路

1. java提供的List就是一个"线性表接口",ArrayList(基于数组的线性表)、LinkedList(基于链的线性表)是线性表的两种典型实现2. Queue代表了队列,Deque代表了双端队列(既可以作为队列使用、也可以作为栈使用)3. 因为数组以一块连续内存来保存所有的数组元素,所以数组在随机访问时性能最好。所以的内部以数组作为底层实现的集合在随机访问时性能最好。4. 内部以链表作为底层实现的集合在执行插入、删除操作时有很好的性能5. 进行迭代操作时,以链表作为底层实现的集合比以数组作为底层实现的集合性能好

我们之前说过,Collection接口继承了Iterable接口,也就是说,我们以上学习到的所有的Collection集合类都具有"可遍历性"

Iterable接口也是java集合框架的成员,它隐藏了各种Collection实现类的底层细节,向应用程序提供了遍历Collection集合元素的统一编程接口:

1) boolean hasNext(): 是否还有下一个未遍历过的元素2) Object next(): 返回集合里的下一个元素3) void remove(): 删除集合里上一次next方法返回的元素

iterator实现遍历:

复制代码

import java.util.*;public class IteratorTest{ public static void main(String[] args) { //创建一个集合 Collection books = new HashSet(); books.add("轻量级Java EE企业应用实战"); books.add("疯狂Java讲义"); books.add("疯狂Android讲义"); //获取books集合对应的迭代器 Iterator it = books.iterator(); while(it.hasNext()) { //it.next()方法返回的数据类型是Object类型, //需要强制类型转换 String book = (String)it.next(); System.out.println(book); if (book.equals("疯狂Java讲义")) { //从集合中删除上一次next方法返回的元素 it.remove(); } //对book变量赋值,不会改变集合元素本身 book = "测试字符串"; } System.out.println(books); }}

复制代码

从代码可以看出,iterator必须依附于Collection对象,若有一个iterator对象,必然有一个与之关联的Collection对象。

除了可以使用iterator接口迭代访问Collection集合里的元素之外,使用java5提供的foreach循环迭代访问集合元素更加便捷

foreach实现遍历:

复制代码

import java.util.*;public class ForeachTest{ public static void main(String[] args) { //创建一个集合 Collection books = new HashSet(); books.add(new String("轻量级Java EE企业应用实战")); books.add(new String("疯狂Java讲义")); books.add(new String("疯狂Android讲义")); for (Object obj : books) { //此处的book变量也不是集合元素本身 String book = (String)obj; System.out.println(book); if (book.equals("疯狂Android讲义")) { //下面代码会引发ConcurrentModificationException异常 //books.remove(book); } } System.out.println(books); }}

复制代码

除了Collection固有的iterator()方法,List还额外提供了一个listIterator()方法,该方法返回一个ListIterator对象,ListIterator接口继承了Iterator接口,提供了专门操作List的方法。ListIterator接口在Iterator接口的继承上增加了如下方法:

1) boolean hasPrevious(): 返回该迭代器关联的集合是否还有上一个元素2) Object previous(): 返回该迭代器的上一个元素(向前迭代)3) void add(): 在指定位置插入一个元素

ListIterator实现遍历:

复制代码

import java.util.*;public class ListIteratorTest{ public static void main(String[] args) { String[] books = { "疯狂Java讲义", "轻量级Java EE企业应用实战" }; List bookList = new ArrayList(); for (int i = 0; i < books.length ; i++ ) { bookList.add(books[i]); } ListIterator lit = bookList.listIterator(); while (lit.hasNext()) { System.out.println(lit.next()); lit.add("-------分隔符-------"); } System.out.println("=======下面开始反向迭代======="); while(lit.hasPrevious()) { System.out.println(lit.previous()); } }}

复制代码

0x4: Map

HashMap、Hashtable

复制代码

import java.util.*;class A{ int count; public A(int count) { this.count = count; } //根据count的值来判断两个对象是否相等。 public boolean equals(Object obj) { if (obj == this) return true; if (obj!=null && obj.getClass()==A.class) { A a = (A)obj; return this.count == a.count; } return false; } //根据count来计算hashCode值。 public int hashCode() { return this.count; }}class B{ //重写equals()方法,B对象与任何对象通过equals()方法比较都相等 public boolean equals(Object obj) { return true; }}public class HashtableTest{ public static void main(String[] args) { Hashtable ht = new Hashtable(); ht.put(new A(60000) , "疯狂Java讲义"); ht.put(new A(87563) , "轻量级Java EE企业应用实战"); ht.put(new A(1232) , new B()); System.out.println(ht); //只要两个对象通过equals比较返回true, //Hashtable就认为它们是相等的value。 //由于Hashtable中有一个B对象, //它与任何对象通过equals比较都相等,所以下面输出true。 System.out.println(ht.containsValue("测试字符串")); //① //只要两个A对象的count相等,它们通过equals比较返回true,且hashCode相等 //Hashtable即认为它们是相同的key,所以下面输出true。 System.out.println(ht.containsKey(new A(87563))); //② //下面语句可以删除最后一个key-value对 ht.remove(new A(1232)); //③ //通过返回Hashtable的所有key组成的Set集合, //从而遍历Hashtable每个key-value对 for (Object key : ht.keySet()) { System.out.print(key + "---->"); System.out.print(ht.get(key) + "

"); } }}

复制代码

当使用自定义类作为HashMap、Hashtable的key时,如果重写该类的equals(Object obj)和hashCode()方法,则应该保证两个方法的判断标准一致--当两个key通过equals()方法比较返回true时,两个key的hashCode()的返回值也应该相同

LinkedHashMap

复制代码

import java.util.*;public class LinkedHashMapTest{ public static void main(String[] args) { LinkedHashMap scores = new LinkedHashMap(); scores.put("语文" , 80); scores.put("英文" , 82); scores.put("数学" , 76); //遍历scores里的所有的key-value对 for (Object key : scores.keySet()) { System.out.println(key + "------>" + scores.get(key)); } }}

复制代码

Properties

复制代码

import java.util.*;import java.io.*;public class PropertiesTest{ public static void main(String[] args) throws Exception { Properties props = new Properties(); //向Properties中增加属性 props.setProperty("username" , "yeeku"); props.setProperty("password" , "123456"); //将Properties中的key-value对保存到a.ini文件中 props.store(new FileOutputStream("a.ini"), "comment line"); //① //新建一个Properties对象 Properties props2 = new Properties(); //向Properties中增加属性 props2.setProperty("gender" , "male"); //将a.ini文件中的key-value对追加到props2中 props2.load(new FileInputStream("a.ini") ); //② System.out.println(props2); }}

复制代码

Properties还可以把key-value对以XML文件的形式保存起来,也可以从XML文件中加载key-value对

TreeMap

复制代码

import java.util.*;class R implements Comparable{ int count; public R(int count) { this.count = count; } public String toString() { return "R[count:" + count + "]"; } //根据count来判断两个对象是否相等。 public boolean equals(Object obj) { if (this == obj) return true; if (obj!=null && obj.getClass()==R.class) { R r = (R)obj; return r.count == this.count; } return false; } //根据count属性值来判断两个对象的大小。 public int compareTo(Object obj) { R r = (R)obj; return count > r.count ? 1 : count < r.count ? -1 : 0; }}public class TreeMapTest{ public static void main(String[] args) { TreeMap tm = new TreeMap(); tm.put(new R(3) , "轻量级Java EE企业应用实战"); tm.put(new R(-5) , "疯狂Java讲义"); tm.put(new R(9) , "疯狂Android讲义"); System.out.println(tm); //返回该TreeMap的第一个Entry对象 System.out.println(tm.firstEntry()); //返回该TreeMap的最后一个key值 System.out.println(tm.lastKey()); //返回该TreeMap的比new R(2)大的最小key值。 System.out.println(tm.higherKey(new R(2))); //返回该TreeMap的比new R(2)小的最大的key-value对。 System.out.println(tm.lowerEntry(new R(2))); //返回该TreeMap的子TreeMap System.out.println(tm.subMap(new R(-1) , new R(4))); }}

复制代码

从代码中可以看出,类似于TreeSet中判断两个元素是否相等的标准,TreeMap中判断两个key相等的标准是:

1) 两个key通过compareTo()方法返回02) equals()放回true

我们在重写这两个方法的时候一定要保证它们的逻辑关系一致。

再次强调一下:

Set和Map的关系十分密切,java源码就是先实现了HashMap、TreeMap等集合,然后通过包装一个所有的value都为null的Map集合实现了Set集合类

WeakHashMap

复制代码

import java.util.*;public class WeakHashMapTest{ public static void main(String[] args) { WeakHashMap whm = new WeakHashMap(); //将WeakHashMap中添加三个key-value对, //三个key都是匿名字符串对象(没有其他引用) whm.put(new String("语文") , new String("良好")); whm.put(new String("数学") , new String("及格")); whm.put(new String("英文") , new String("中等")); //将WeakHashMap中添加一个key-value对, //该key是一个系统缓存的字符串对象。"java"是一个常量字符串强引用 whm.put("java" , new String("中等")); //输出whm对象,将看到4个key-value对。 System.out.println(whm); //通知系统立即进行垃圾回收 System.gc(); System.runFinalization(); //通常情况下,将只看到一个key-value对。 System.out.println(whm); }}

复制代码

如果需要使用WeakHashMap的key来保留对象的弱引用,则不要让key所引用的对象具有任何强引用,否则将失去使用WeakHashMap的意义

IdentityHashMap

复制代码

import java.util.*;public class IdentityHashMapTest{ public static void main(String[] args) { IdentityHashMap ihm = new IdentityHashMap(); //下面两行代码将会向IdentityHashMap对象中添加两个key-value对 ihm.put(new String("语文") , 89); ihm.put(new String("语文") , 78); //下面两行代码只会向IdentityHashMap对象中添加一个key-value对 ihm.put("java" , 93); ihm.put("java" , 98); System.out.println(ihm); }}

复制代码

EnumMap

复制代码

import java.util.*;enum Season{ SPRING,SUMMER,FALL,WINTER}public class EnumMapTest{ public static void main(String[] args) { //创建一个EnumMap对象,该EnumMap的所有key //必须是Season枚举类的枚举值 EnumMap enumMap = new EnumMap(Season.class); enumMap.put(Season.SUMMER , "夏日炎炎"); enumMap.put(Season.SPRING , "春暖花开"); System.out.println(enumMap); }}

复制代码

与创建普通Map有所区别的是,创建EnumMap是必须指定一个枚举类,从而将该EnumMap和指定枚举类关联起来

以上就是Map集合类的编程应用场景。我们来梳理一下思路

1) HashMap和Hashtable的效率大致相同,因为它们的实现机制几乎完全一样。但HashMap通常比Hashtable要快一点,因为Hashtable需要额外的线程同步控制2) TreeMap通常比HashMap、Hashtable要慢(尤其是在插入、删除key-value对时更慢),因为TreeMap底层采用红黑树来管理key-value对3) 使用TreeMap的一个好处就是: TreeMap中的key-value对总是处于有序状态,无须专门进行排序操作

POSIX threads(简称Pthreads)是在多核平台上进行并行编程的一套常用的API。线程同步(Thread Synchronization)是并行编程中非常重要的通讯手段,其中最典型的应用就是用Pthreads提供的锁机制(lock)来对多个线程之间共 享的临界区(Critical Section)进行保护(另一种常用的同步机制是barrier)。

Pthreads提供了多种锁机制:

(1) Mutex(互斥量):pthread_mutex_***

(2) Spin lock(自旋锁):pthread_spin_***

(3) Condition Variable(条件变量):pthread_con_***

(4) Read/Write lock(读写锁):pthread_rwlock_***

Pthreads提供的Mutex锁操作相关的API主要有:

pthread_mutex_lock (pthread_mutex_t *mutex);

pthread_mutex_trylock (pthread_mutex_t *mutex);

pthread_mutex_unlock (pthread_mutex_t *mutex);

Pthreads提供的与Spin Lock锁操作相关的API主要有:

pthread_spin_lock (pthread_spinlock_t *lock);

pthread_spin_trylock (pthread_spinlock_t *lock);

pthread_spin_unlock (pthread_spinlock_t *lock);

从 实现原理上来讲,Mutex属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和 Core1上。假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞 (blocking),Core0 会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其他的任务(例如另一个线程C)而不必进行忙等待。而Spin lock则不然,它属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,那么线程A就会一直在 Core0上进行忙等待并不停的进行锁请求,直到得到这个锁为止。

所以,自旋锁一般用用多核的服务器。

自旋锁(Spin lock)

自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是 否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。其作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远 高于互斥锁。虽然它的效率比互斥锁高,但是它也有些不足之处:

1、自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低。

2、在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁,调用有些其他函数也可能造成死锁,如 copy_to_user()、copy_from_user()、kmalloc()等。

因此我们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP的情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空操作。自旋锁适用于锁使用者保持锁时间比较短的情况下。

自旋锁的用法如下:

首先定义:spinlock_t x;

然后初始化:spin_lock_init(spinlock_t *x);   //自旋锁在真正使用前必须先初始化

在2.6.11内核中将定义和初始化合并为一个宏:DEFINE_SPINLOCK(x)

获得自旋锁:spin_lock(x);   //只有在获得锁的情况下才返回,否则一直“自旋”

spin_trylock(x);  //如立即获得锁则返回真,否则立即返回假

释放锁:spin_unlock(x);

结合以上有以下代码段:

spinlock_t lock;        //定义一个自旋锁

spin_lock_init(&lock);

spin_lock(&lock);

.......        //临界区

spin_unlock(&lock);   //释放锁

还有一些其他用法:

spin_is_locked(x)

//  该宏用于判断自旋锁x是否已经被某执行单元保持(即被锁),如果是,   返回真,否则返回假。

spin_unlock_wait(x)

//  该宏用于等待自旋锁x变得没有被任何执行单元保持,如果没有任何执行单元保持该自旋锁,该宏立即返回,否

//将循环    在那里,直到该自旋锁被保持者释放。

spin_lock_irqsave(lock, flags)

//  该宏获得自旋锁的同时把标志寄存器的值保存到变量flags中并失效本地中//断。相当于:spin_lock()+local_irq_save()

spin_unlock_irqrestore(lock, flags)

//  该宏释放自旋锁lock的同时,也恢复标志寄存器的值为变量flags保存的//值。它与spin_lock_irqsave配对使用。

//相当于:spin_unlock()+local_irq_restore()

spin_lock_irq(lock)

//该宏类似于spin_lock_irqsave,只是该宏不保存标志寄存器的值。相当         //于:spin_lock()+local_irq_disable()

spin_unlock_irq(lock)

//该宏释放自旋锁lock的同时,也使能本地中断。它与spin_lock_irq配对应用。相当于: spin_unlock()+local_irq+enable()

spin_lock_bh(lock)

//  该宏在得到自旋锁的同时失效本地软中断。相当于:  //spin_lock()+local_bh_disable()

spin_unlock_bh(lock)

//该宏释放自旋锁lock的同时,也使能本地的软中断。它与spin_lock_bh配对//使用。相当于:spin_unlock()+local_bh_enable()

spin_trylock_irqsave(lock, flags)

//该宏如果获得自旋锁lock,它也将保存标志寄存器的值到变量flags中,并且失//效本地中断,如果没有获得锁,它什么也不做。因此如果能够立即 获得锁,它等//同于spin_lock_irqsave,如果不能获得锁,它等同于spin_trylock。如果该宏//获得自旋锁lock,那需要 使用spin_unlock_irqrestore来释放。

spin_trylock_irq(lock)

//该宏类似于spin_trylock_irqsave,只是该宏不保存标志寄存器。如果该宏获得自旋锁lock,需要使用spin_unlock_irq来释放。

spin_trylock_bh(lock)

//  该宏如果获得了自旋锁,它也将失效本地软中断。如果得不到锁,它什么//也不做。因此,如果得到了锁,它等同于spin_lock_bh,如果得 不到锁,它等同//于spin_trylock。如果该宏得到了自旋锁,需要使用spin_unlock_bh来释放。

spin_can_lock(lock)

//  该宏用于判断自旋锁lock是否能够被锁,它实际是spin_is_locked取反。//如果lock没有被锁,它返回真,否则,返回 假。该宏在2.6.11中第一次被定义,在//先前的内核中并没有该宏。

Jconsole,jProfile,VisualVM

Jconsole : jdk自带,功能简单,但是可以在系统有一定负荷的情况下使用。对垃圾回收算法有很详细的跟踪。详细说明参考这里

JProfiler:商业软件,需要付费。功能强大。详细说明参考这里

VisualVM:JDK自带,功能强大,与JProfiler类似。推荐。

如何调优

观察内存释放情况、集合类检查、对象树

上面这些调优工具都提供了强大的功能,但是总的来说一般分为以下几类功能

堆信息查看

可查看堆空间大小分配(年轻代、年老代、持久代分配)

提供即时的垃圾回收功能

垃圾监控(长时间监控回收情况)

查看堆内类、对象信息查看:数量、类型等

对象引用情况查看

有了堆信息查看方面的功能,我们一般可以顺利解决以下问题:

–年老代年轻代大小划分是否合理

–内存泄漏

–垃圾回收算法设置是否合理

线程监控

线程信息监控:系统线程数量。

线程状态监控:各个线程都处在什么样的状态下

Dump线程详细信息:查看线程内部运行情况

死锁检查

热点分析

CPU热点:检查系统哪些方法占用的大量CPU时间

内存热点:检查哪些对象在系统中数量最大(一定时间内存活对象和销毁对象一起统计)

这两个东西对于系统优化很有帮助。我们可以根据找到的热点,有针对性的进行系统的瓶颈查找和进行系统优化,而不是漫无目的的进行所有代码的优化。

快照

快照是系统运行到某一时刻的一个定格。在我们进行调优的时候,不可能用眼睛去跟踪所有系统变化,依赖快照功能,我们就可以进行系统两个不同运行时刻,对象(或类、线程等)的不同,以便快速找到问题

举例说,我要检查系统进行垃圾回收以后,是否还有该收回的对象被遗漏下来的了。那么,我可以在进行垃圾回收前后,分别进行一次堆情况的快照,然后对比两次快照的对象情况。

内存泄漏检查

内存泄漏是比较常见的问题,而且解决方法也比较通用,这里可以重点说一下,而线程、热点方面的问题则是具体问题具体分析了。

内存泄漏一般可以理解为系统资源(各方面的资源,堆、栈、线程等)在错误使用的情况下,导致使用完毕的资源无法回收(或没有回收),从而导致新的资源分配请求无法完成,引起系统错误。

内存泄漏对系统危害比较大,因为他可以直接导致系统的崩溃。

需要区别一下,内存泄漏和系统超负荷两者是有区别的,虽然可能导致的最终结果是一样的。内存泄漏是用完的资源没有回收引起错误,而系统超负荷则是系统确实没有那么多资源可以分配了(其他的资源都在使用)。

年老代堆空间被占满

异常: java.lang.OutOfMemoryError: Java heap space

说明:

这是最典型的内存泄漏方式,简单说就是所有堆空间都被无法回收的垃圾对象占满,虚拟机无法再在分配新空间。

如上图所示,这是非常典型的内存泄漏的垃圾回收情况图。所有峰值部分都是一次垃圾回收点,所有谷底部分表示是一次垃圾回收后剩余的内存。连接所有谷底的点,可以发现一条由底到高的线,这说明,随时间的推移,系统的堆空间被不断占满,最终会占满整个堆空间。因此可以初步认为系统内部可能有内存泄漏。(上面的图仅供示例,在实际情况下收集数据的时间需要更长,比如几个小时或者几天)

解决:

这种方式解决起来也比较容易,一般就是根据垃圾回收前后情况对比,同时根据对象引用情况(常见的集合对象引用)分析,基本都可以找到泄漏点。

持久代被占满

异常:java.lang.OutOfMemoryError: PermGen space

说明:

Perm空间被占满。无法为新的class分配存储空间而引发的异常。这个异常以前是没有的,但是在Java反射大量使用的今天这个异常比较常见了。主要原因就是大量动态反射生成的类不断被加载,最终导致Perm区被占满。

更可怕的是,不同的classLoader即便使用了相同的类,但是都会对其进行加载,相当于同一个东西,如果有N个classLoader那么他将会被加载N次。因此,某些情况下,这个问题基本视为无解。当然,存在大量classLoader和大量反射类的情况其实也不多。

解决:

1. -XX:MaxPermSize=16m

2. 换用JDK。比如JRocket。

堆栈溢出

异常:java.lang.StackOverflowError

说明:这个就不多说了,一般就是递归没返回,或者循环调用造成

线程堆栈满

异常:Fatal: Stack size too small

说明:java中一个线程的空间大小是有限制的。JDK5.0以后这个值是1M。与这个线程相关的数据将会保存在其中。但是当线程空间满了以后,将会出现上面异常。

解决:增加线程栈大小。-Xss2m。但这个配置无法解决根本问题,还要看代码部分是否有造成泄漏的部分。

系统内存被占满

异常:java.lang.OutOfMemoryError: unable to create new native thread

说明:

这个异常是由于操作系统没有足够的资源来产生这个线程造成的。系统创建线程时,除了要在Java堆中分配内存外,操作系统本身也需要分配资源来创建线程。因此,当线程数量大到一定程度以后,堆中或许还有空间,但是操作系统分配不出资源来了,就出现这个异常了。

分配给Java虚拟机的内存愈多,系统剩余的资源就越少,因此,当系统内存固定时,分配给Java虚拟机的内存越多,那么,系统总共能够产生的线程也就越少,两者成反比的关系。同时,可以通过修改-Xss来减少分配给单个线程的空间,也可以增加系统总共内生产的线程数。

解决:

1. 重新设计系统减少线程数量。

2. 线程数量不能减少的情况下,通过-Xss减小单个线程大小。以便能生产更多的线程。

Java内存泄漏引起的原因:

内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。

长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景。

造成内存泄漏的几种情况:

1、静态集合类引起内存泄漏

像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。

2、当集合里面的对象属性被修改后,再调用remove()方法时不起作用。

3、监听器

在释放对象的时候却没有去删除这些监听器,增加了内存泄漏的机会。

4、各种连接

比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。

5、内部类和外部模块的引用

内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。此外程序员还要小心外部模块不经意的引用,例如程序员A 负责A 模块,调用了B 模块的一个方法如: public void registerMsg(Object b); 这种调用就要非常小心了,传入了一个对象,很可能模块B就保持了对该对象的引用,这时候就需要注意模块B 是否提供相应的操作去除引用。

6、单例模式

不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被JVM正常回收,导致内存泄漏。

1、term 过滤

term主要用于精确匹配哪些值,比如数字,日期,布尔值或 not_analyzed 的字符串(未经切词的文本数据类型):

{ "term": { "date":   "2017-07-01" }}

{ "term": { "title":    "内蒙古"  }}

完整的例子, hostname 字段完全匹配成 saaap.wangpos.com 的数据:

{

"query": {

"term": {

"title": "内蒙古"

}

}

}

2、terms 过滤

terms 跟 term 有点类似,但 terms 允许指定多个匹配条件。 如果某个字段指定了多个值,那么文档需要一起去做匹配:

{

"terms": {"title": [  "内蒙古",  "黑龙江"  ] }

}

完整的例子,所有文章标题是 内蒙古 或黑龙江的 的,

{

"query": {

"terms": {

"title": [

"内蒙古",

"黑龙江"

]

}

}

}

3、range 过滤

range过滤允许我们按照指定范围查找一批数据:

{

"range": {

"pubTime": {

"gt": "2017-06-25",

"lt": "2017-07-01"

}

}

}

范围操作符包含:gt :: 大于 gte:: 大于等于 lt :: 小于 lte:: 小于等于

一个完整的例子, 查询发表时间在2017-06-25和2017-07-01之间的数据

{

"query": {

"range": {

"pubTime": {

"gt": "2017-06-25",

"lt": "2017-07-01"

}

}

}

}

4、exists 和 missing 过滤

exists 和 missing 过滤可以用于查找文档中是否包含指定字段或没有某个字段,类似于SQL语句中的IS_NULL条件.

{

"exists":   {

"field":    "title"

}

}

这两个过滤只是针对已经查出一批数据来,但是想区分出某个字段是否存在的时候使用。

5、bool 过滤

bool 过滤可以用来合并多个过滤条件查询结果的布尔逻辑,它包含一下操作符:must :: 多个查询条件的完全匹配,相当于 and。 must_not :: 多个查询条件的相反匹配,相当于 not。 should :: 至少有一个查询条件匹配, 相当于 or。

这些参数可以分别继承一个过滤条件或者一个过滤条件的数组:

{

"bool": {

"must":     { "term": { "folder": "inbox" }},

"must_not": { "term": { "tag":    "spam"  }},

"should": [

{ "term": { "starred": true   }},

{ "term": { "unread":  true   }}

]

}

}

6、match_all 查询

可以查询到所有文档,是没有查询条件下的默认语句。

{

"match_all": {}

}

此查询常用于合并过滤条件。 比如说你需要检索所有的邮箱,所有的文档相关性都是相同的,所以得到的_score为1.

7、match 查询

match查询是一个标准查询,不管你需要全文本查询还是精确查询基本上都要用到它。

如果你使用 match 查询一个全文本字段,它会在真正查询之前用分析器先分析match一下查询字符:

{

"query": {

"match": {

"content": "韩国 上海 北京"

}

}

}

如果用match下指定了一个确切值,在遇到数字,日期,布尔值或者not_analyzed 的字符串时,它将为你搜索你给定的值:

{ "match": { "age": 12}}

{ "match": { "pubTime":   "2017-07-01" }}

{ "match": { "title":    "韩国"  }}

提示: 做精确匹配搜索时,你最好用过滤语句,因为过滤语句可以缓存数据。

match查询只能就指定某个确切字段某个确切的值进行搜索,而你要做的就是为它指定正确的字段名以避免语法错误。

8、multi_match 查询

multi_match查询允许你做match查询的基础上同时搜索多个字段,在多个字段中同时查一个:

{

"query": {

"multi_match": {

"query": "乌鲁木齐",

"fields": [

"title",

"content"

]

}

}

}

查询文章标题和内容包含乌鲁木齐的数据

9、bool 查询

bool 查询与 bool 过滤相似,用于合并多个查询子句。不同的是,bool 过滤可以直接给出是否匹配成功, 而bool 查询要计算每一个查询子句的 _score (相关性分值)。must:: 查询指定文档一定要被包含。 must_not:: 查询指定文档一定不要被包含。 should:: 查询指定文档,有则可以为文档相关性加分。

以下查询将会找到 title 字段中包含 "how to make millions",并且 "tag" 字段没有被标为 spam。 如果有标识为 "starred" 或者发布日期为2014年之前,

{

"bool": {

"must":     { "match": { "title": "how to make millions" }},

"must_not": { "match": { "tag":   "spam" }},

"should": [

{ "match": { "tag": "starred" }},

{ "range": { "date": { "gte": "2014-01-01" }}}

]

}

}

提示: 如果bool 查询下没有must子句,那至少应该有一个should子句。但是 如果有must子句,那么没有should子句也可以进行查询。

10、wildcards 查询

使用标准的shell通配符查询

以下查询能够匹配包含W1F 7HW和W2F 8HW的文档:

{

"query": {

"wildcard": {

"postcode": "W?F*HW"

}

}

}

又比如下面查询 hostname 匹配下面shell通配符的:

{

"query": {

"wildcard": {

"title": "乌鲁*"

}

}

}

11、regexp 查询

假设您只想匹配以W开头,紧跟着数字的邮政编码。使用regexp查询能够让你写下更复杂的模式:

GET /my_index/address/_search

{

"query": {

"regexp": {

"postcode": "W[0-9].+"

}

}

}

这个正则表达式的规定了词条需要以W开头,紧跟着一个0到9的数字,然后是一个或者多个其它字符。

下面例子是所有以 wxopen 开头的正则

{

"query": {

"regexp": {

"hostname": "wxopen.*"

}

}

}

12、prefix 查询

以什么字符开头的,可以更简单地用 prefix,如下面的例子:

{

"query": {

"prefix": {

"title": "屠杀"

}

}

}

13、短语匹配(Phrase Matching)

当你需要寻找邻近的几个单词时,你会使用match_phrase查询:

{

"query": {

"match_phrase": {

"content": "端午 旅游 云南"

}

}

}

和match查询类似,match_phrase查询首先解析查询字符串来产生一个词条列表。然后会搜索所有的词条,

但只保留含有了所有搜索词条的文档,并且词条的位置要邻接。

match_phrase查询也可以写成类型为phrase的match查询:

{

"query": {

"match": {

"content": {

"query": "端午 旅游 云南",

"type": "phrase"

}

}

}

}

搜索结果如

先上一个场景:假如你突然想做饭,但是没有厨具,也没有食材。网上购买厨具比较方便,食材去超市买更放心。

实现分析:在快递员送厨具的期间,我们肯定不会闲着,可以去超市买食材。所以,在主线程里面另起一个子线程去网购厨具。

但是,子线程执行的结果是要返回厨具的,而run方法是没有返回值的。所以,这才是难点,需要好好考虑一下。

模拟代码1:

复制代码

package test;public class CommonCook { public static void main(String[] args) throws InterruptedException { long startTime = System.currentTimeMillis(); // 第一步 网购厨具 OnlineShopping thread = new OnlineShopping(); thread.start(); thread.join(); // 保证厨具送到 // 第二步 去超市购买食材 Thread.sleep(2000); // 模拟购买食材时间 Shicai shicai = new Shicai(); System.out.println("第二步:食材到位"); // 第三步 用厨具烹饪食材 System.out.println("第三步:开始展现厨艺"); cook(thread.chuju, shicai); System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms"); } // 网购厨具线程 static class OnlineShopping extends Thread { private Chuju chuju; @Override public void run() { System.out.println("第一步:下单"); System.out.println("第一步:等待送货"); try { Thread.sleep(5000); // 模拟送货时间 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("第一步:快递送到"); chuju = new Chuju(); } } // 用厨具烹饪食材 static void cook(Chuju chuju, Shicai shicai) {} // 厨具类 static class Chuju {} // 食材类 static class Shicai {}}

复制代码

运行结果:

第一步:下单第一步:等待送货第一步:快递送到第二步:食材到位第三步:开始展现厨艺总共用时7013ms

可以看到,多线程已经失去了意义。在厨具送到期间,我们不能干任何事。对应代码,就是调用join方法阻塞主线程。

有人问了,不阻塞主线程行不行???

不行!!!

从代码来看的话,run方法不执行完,属性chuju就没有被赋值,还是null。换句话说,没有厨具,怎么做饭。

Java现在的多线程机制,核心方法run是没有返回值的;如果要保存run方法里面的计算结果,必须等待run方法计算完,无论计算过程多么耗时。

面对这种尴尬的处境,程序员就会想:在子线程run方法计算的期间,能不能在主线程里面继续异步执行???

Where there is a will,there is a way!!!

这种想法的核心就是Future模式,下面先应用一下Java自己实现的Future模式。

模拟代码2:

复制代码

package test;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;public class FutureCook { public static void main(String[] args) throws InterruptedException, ExecutionException { long startTime = System.currentTimeMillis(); // 第一步 网购厨具 Callable<Chuju> onlineShopping = new Callable<Chuju>() { @Override public Chuju call() throws Exception { System.out.println("第一步:下单"); System.out.println("第一步:等待送货"); Thread.sleep(5000); // 模拟送货时间 System.out.println("第一步:快递送到"); return new Chuju(); } }; FutureTask<Chuju> task = new FutureTask<Chuju>(onlineShopping); new Thread(task).start(); // 第二步 去超市购买食材 Thread.sleep(2000); // 模拟购买食材时间 Shicai shicai = new Shicai(); System.out.println("第二步:食材到位"); // 第三步 用厨具烹饪食材 if (!task.isDone()) { // 联系快递员,询问是否到货 System.out.println("第三步:厨具还没到,心情好就等着(心情不好就调用cancel方法取消订单)"); } Chuju chuju = task.get(); System.out.println("第三步:厨具到位,开始展现厨艺"); cook(chuju, shicai); System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms"); } // 用厨具烹饪食材 static void cook(Chuju chuju, Shicai shicai) {} // 厨具类 static class Chuju {} // 食材类 static class Shicai {}}

复制代码

运行结果:

复制代码

第一步:下单第一步:等待送货第二步:食材到位第三步:厨具还没到,心情好就等着(心情不好就调用cancel方法取消订单)第一步:快递送到第三步:厨具到位,开始展现厨艺总共用时5005ms

复制代码

可以看见,在快递员送厨具的期间,我们没有闲着,可以去买食材;而且我们知道厨具到没到,甚至可以在厨具没到的时候,取消订单不要了。

好神奇,有没有。

下面具体分析一下第二段代码:

1)把耗时的网购厨具逻辑,封装到了一个Callable的call方法里面。

复制代码

public interface Callable<V> { /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception;}

复制代码

Callable接口可以看作是Runnable接口的补充,call方法带有返回值,并且可以抛出异常。

2)把Callable实例当作参数,生成一个FutureTask的对象,然后把这个对象当作一个Runnable,作为参数另起线程。

public class FutureTask<V> implements RunnableFuture<V>

public interface RunnableFuture<V> extends Runnable, Future<V>

复制代码

public interface Future<V> { boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;}

复制代码

这个继承体系中的核心接口是Future。Future的核心思想是:一个方法f,计算过程可能非常耗时,等待f返回,显然不明智。可以在调用f的时候,立马返回一个Future,可以通过Future这个数据结构去控制方法f的计算过程。

这里的控制包括:

get方法:获取计算结果(如果还没计算完,也是必须等待的)

cancel方法:还没计算完,可以取消计算过程

isDone方法:判断是否计算完

isCancelled方法:判断计算是否被取消

这些接口的设计很完美,FutureTask的实现注定不会简单,后面再说。

3)在第三步里面,调用了isDone方法查看状态,然后直接调用task.get方法获取厨具,不过这时还没送到,所以还是会等待3秒。对比第一段代码的执行结果,这里我们节省了2秒。这是因为在快递员送货期间,我们去超市购买食材,这两件事在同一时间段内异步执行。

通过以上3步,我们就完成了对Java原生Future模式最基本的应用。下面具体分析下FutureTask的实现,先看JDK8的,再比较一下JDK6的实现。

既然FutureTask也是一个Runnable,那就看看它的run方法

复制代码

public void run() { if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return; try { Callable<V> c = callable; // 这里的callable是从构造方法里面传人的 if (c != null && state == NEW) { V result; boolean ran; try { result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; setException(ex); // 保存call方法抛出的异常 } if (ran) set(result); // 保存call方法的执行结果 } } finally { // runner must be non-null until state is settled to // prevent concurrent calls to run() runner = null; // state must be re-read after nulling runner to prevent // leaked interrupts int s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } }

复制代码

先看try语句块里面的逻辑,发现run方法的主要逻辑就是运行Callable的call方法,然后将保存结果或者异常(用的一个属性result)。这里比较难想到的是,将call方法抛出的异常也保存起来了。

这里表示状态的属性state是个什么鬼

复制代码

* Possible state transitions: * NEW -> COMPLETING -> NORMAL * NEW -> COMPLETING -> EXCEPTIONAL * NEW -> CANCELLED * NEW -> INTERRUPTING -> INTERRUPTED */ private volatile int state; private static final int NEW = 0; private static final int COMPLETING = 1; private static final int NORMAL = 2; private static final int EXCEPTIONAL = 3; private static final int CANCELLED = 4; private static final int INTERRUPTING = 5; private static final int INTERRUPTED = 6;

复制代码

把FutureTask看作一个Future,那么它的作用就是控制Callable的call方法的执行过程,在执行的过程中自然会有状态的转换:

1)一个FutureTask新建出来,state就是NEW状态;COMPETING和INTERRUPTING用的进行时,表示瞬时状态,存在时间极短(为什么要设立这种状态???不解);NORMAL代表顺利完成;EXCEPTIONAL代表执行过程出现异常;CANCELED代表执行过程被取消;INTERRUPTED被中断

2)执行过程顺利完成:NEW -> COMPLETING -> NORMAL

3)执行过程出现异常:NEW -> COMPLETING -> EXCEPTIONAL

4)执行过程被取消:NEW -> CANCELLED

5)执行过程中,线程中断:NEW -> INTERRUPTING -> INTERRUPTED

代码中状态判断、CAS操作等细节,请读者自己阅读。

再看看get方法的实现:

public V get() throws InterruptedException, ExecutionException { int s = state; if (s <= COMPLETING) s = awaitDone(false, 0L); return report(s); }

复制代码

private int awaitDone(boolean timed, long nanos) throws InterruptedException { final long deadline = timed ? System.nanoTime() + nanos : 0L; WaitNode q = null; boolean queued = false; for (;;) { if (Thread.interrupted()) { removeWaiter(q); throw new InterruptedException(); } int s = state; if (s > COMPLETING) { if (q != null) q.thread = null; return s; } else if (s == COMPLETING) // cannot time out yet Thread.yield(); else if (q == null) q = new WaitNode(); else if (!queued) queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q); else if (timed) { nanos = deadline - System.nanoTime(); if (nanos <= 0L) { removeWaiter(q); return state; } LockSupport.parkNanos(this, nanos); } else LockSupport.park(this); } }

复制代码

get方法的逻辑很简单,如果call方法的执行过程已完成,就把结果给出去;如果未完成,就将当前线程挂起等待。awaitDone方法里面死循环的逻辑,推演几遍就能弄懂;它里面挂起线程的主要创新是定义了WaitNode类,来将多个等待线程组织成队列,这是与JDK6的实现最大的不同。

挂起的线程何时被唤醒:

复制代码

private void finishCompletion() { // assert state > COMPLETING; for (WaitNode q; (q = waiters) != null;) { if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) { for (;;) { Thread t = q.thread; if (t != null) { q.thread = null; LockSupport.unpark(t); // 唤醒线程 } WaitNode next = q.next; if (next == null) break; q.next = null; // unlink to help gc q = next; } break; } } done(); callable = null; // to reduce footprint }

复制代码

以上就是JDK8的大体实现逻辑,像cancel、set等方法,也请读者自己阅读。

再来看看JDK6的实现。

JDK6的FutureTask的基本操作都是通过自己的内部类Sync来实现的,而Sync继承自AbstractQueuedSynchronizer这个出镜率极高的并发工具类

复制代码

/** State value representing that task is running */ private static final int RUNNING = 1; /** State value representing that task ran */ private static final int RAN = 2; /** State value representing that task was cancelled */ private static final int CANCELLED = 4; /** The underlying callable */ private final Callable<V> callable; /** The result to return from get() */ private V result; /** The exception to throw from get() */ private Throwable exception;

复制代码

里面的状态只有基本的几个,而且计算结果和异常是分开保存的。

复制代码

V innerGet() throws InterruptedException, ExecutionException { acquireSharedInterruptibly(0); if (getState() == CANCELLED) throw new CancellationException(); if (exception != null) throw new ExecutionException(exception); return result; }

复制代码

这个get方法里面处理等待线程队列的方式是调用了acquireSharedInterruptibly方法,看过我之前几篇博客文章的读者应该非常熟悉了。其中的等待线程队列、线程挂起和唤醒等逻辑,这里不再赘述,如果不明白,请出门左转。

最后来看看,Future模式衍生出来的更高级的应用。

再上一个场景:我们自己写一个简单的数据库连接池,能够复用数据库连接,并且能在高并发情况下正常工作。

实现代码1:

复制代码

package test;import java.util.concurrent.ConcurrentHashMap;public class ConnectionPool { private ConcurrentHashMap<String, Connection> pool = new ConcurrentHashMap<String, Connection>(); public Connection getConnection(String key) { Connection conn = null; if (pool.containsKey(key)) { conn = pool.get(key); } else { conn = createConnection(); pool.putIfAbsent(key, conn); } return conn; } public Connection createConnection() { return new Connection(); } class Connection {}}

复制代码

我们用了ConcurrentHashMap,这样就不必把getConnection方法置为synchronized(当然也可以用Lock),当多个线程同时调用getConnection方法时,性能大幅提升。

貌似很完美了,但是有可能导致多余连接的创建,推演一遍:

某一时刻,同时有3个线程进入getConnection方法,调用pool.containsKey(key)都返回false,然后3个线程各自都创建了连接。虽然ConcurrentHashMap的put方法只会加入其中一个,但还是生成了2个多余的连接。如果是真正的数据库连接,那会造成极大的资源浪费。

所以,我们现在的难点是:如何在多线程访问getConnection方法时,只执行一次createConnection。

结合之前Future模式的实现分析:当3个线程都要创建连接的时候,如果只有一个线程执行createConnection方法创建一个连接,其它2个线程只需要用这个连接就行了。再延伸,把createConnection方法放到一个Callable的call方法里面,然后生成FutureTask。我们只需要让一个线程执行FutureTask的run方法,其它的线程只执行get方法就好了。

上代码:

复制代码

package test;import java.util.concurrent.Callable;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;public class ConnectionPool { private ConcurrentHashMap<String, FutureTask<Connection>> pool = new ConcurrentHashMap<String, FutureTask<Connection>>(); public Connection getConnection(String key) throws InterruptedException, ExecutionException { FutureTask<Connection> connectionTask = pool.get(key); if (connectionTask != null) { return connectionTask.get(); } else { Callable<Connection> callable = new Callable<Connection>() { @Override public Connection call() throws Exception { return createConnection(); } }; FutureTask<Connection> newTask = new FutureTask<Connection>(callable); connectionTask = pool.putIfAbsent(key, newTask); if (connectionTask == null) { connectionTask = newTask; connectionTask.run(); } return connectionTask.get(); } } public Connection createConnection() { return new Connection(); } class Connection { }}

复制代码

推演一遍:当3个线程同时进入else语句块时,各自都创建了一个FutureTask,但是ConcurrentHashMap只会加入其中一个。第一个线程执行pool.putIfAbsent方法后返回null,然后connectionTask被赋值,接着就执行run方法去创建连接,最后get。后面的线程执行pool.putIfAbsent方法不会返回null,就只会执行get方法。

在并发的环境下,通过FutureTask作为中间转换,成功实现了让某个方法只被一个线程执行。

就这么多吧,真是呕心沥血啊!!!哈哈

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/81124590

一. 什么是死锁?

如果一个进程集合里面的每个进程都在等待这个集合中的其他一个进程(包括自身)才能继续往下执行,若无外力他们将无法推进,这种情况就是死锁,处于死锁状态的进程称为死锁进程

二. 死锁产生的原因?

1.因竞争资源发生死锁 现象:系统中供多个进程共享的资源的数目不足以满足全部进程的需要时,就会引起对诸资源的竞争而发生死锁现象

(1)可剥夺资源和不可剥夺资源:可剥夺资源是指某进程在获得该类资源时,该资源同样可以被其他进程或系统剥夺,不可剥夺资源是指当系统把该类资源分配给某个进程时,不能强制收回,只能在该进程使用完成后自动释放

(2)竞争不可剥夺资源:系统中不可剥夺资源的数目不足以满足诸进程运行的要求,则发生在运行进程中,不同的进程因争夺这些资源陷入僵局。

举例说明:  资源A,B; 进程C,D

资源A,B都是不可剥夺资源:一个进程申请了之后,不能强制收回,只能进程结束之后自动释放。内存就是可剥夺资源

进程C申请了资源A,进程D申请了资源B。

接下来C的操作用到资源B,D的资源用到资源A。但是C,D都得不到接下来的资源,那么就引发了死锁。

(3)竞争临时资源

2.进程推进顺序不当发生死锁

三. 产生死锁的四个必要条件?

(1)互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源

(2)请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放

(3)不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放

(4)环路等待条件:是指进程发生死锁后,必然存在一个进程--资源之间的环形链

四. 处理死锁的基本方法

1.预防死锁:通过设置一些限制条件,去破坏产生死锁的必要条件

2.避免死锁:在资源分配过程中,使用某种方法避免系统进入不安全的状态,从而避免发生死锁

3.检测死锁:允许死锁的发生,但是通过系统的检测之后,采取一些措施,将死锁清除掉

4.解除死锁:该方法与检测死锁配合使用

1、CMS收集器

CMS收集器是一种以获取最短回收停顿时间为目标的收集器。基于“标记-清除”算法实现,它的运作过程如下:

1)初始标记

2)并发标记

3)重新标记

4)并发清除

初始标记、从新标记这两个步骤仍然需要“stop the world”,初始标记仅仅只是标记一下GC Roots能直接关联到的对象,熟读很快,并发标记阶段就是进行GC Roots Tracing,而重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生表动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长点,但远比并发标记的时间短。

CMS是一款优秀的收集器,主要优点:并发收集、低停顿。

缺点:

1)CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。

2)CMS收集器无法处理浮动垃圾,可能会出现“Concurrent Mode Failure(并发模式故障)”失败而导致Full GC产生。

浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,伴随着程序运行自然就会有新的垃圾不断产生,这部分垃圾出现的标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC中再清理。这些垃圾就是“浮动垃圾”。

3)CMS是一款“标记--清除”算法实现的收集器,容易出现大量空间碎片。当空间碎片过多,将会给大对象分配带来很大的麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。

2、G1收集器

G1是一款面向服务端应用的垃圾收集器。G1具备如下特点:

1、并行于并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。

2、分代收集:虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。它能够采用不同的方式去处理新创建的对象和已经存活了一段时间,熬过多次GC的旧对象以获取更好的收集效果。

3、空间整合:与CMS的“标记--清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。

4、可预测的停顿:这是G1相对于CMS的另一个大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,

5、G1运作步骤:

1、初始标记;2、并发标记;3、最终标记;4、筛选回收

上面几个步骤的运作过程和CMS有很多相似之处。初始标记阶段仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS的值,让下一个阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这一阶段需要停顿线程,但是耗时很短,并发标记阶段是从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段时耗时较长,但可与用户程序并发执行。而最终标记阶段则是为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remenbered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,这一阶段需要停顿线程,但是可并行执行。最后在筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。

二,了解G1

G1的第一篇paper(附录1)发表于2004年,在2012年才在jdk1.7u4中可用。oracle官方计划在jdk9中将G1变成默认的垃圾收集器,以替代CMS。为何oracle要极力推荐G1呢,G1有哪些优点?

首先,G1的设计原则就是简单可行的性能调优

开发人员仅仅需要声明以下参数即可:

-XX:+UseG1GC -Xmx32g -XX:MaxGCPauseMillis=200

其中-XX:+UseG1GC为开启G1垃圾收集器,-Xmx32g 设计堆内存的最大内存为32G,-XX:MaxGCPauseMillis=200设置GC的最大暂停时间为200ms。如果我们需要调优,在内存大小一定的情况下,我们只需要修改最大暂停时间即可。

其次,G1将新生代,老年代的物理空间划分取消了。

这样我们再也不用单独的空间对每个代进行设置了,不用担心每个代内存是否足够。

1

取而代之的是,G1算法将堆划分为若干个区域(Region),它仍然属于分代收集器。不过,这些区域的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。老年代也分成很多区域,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了。

2

在G1中,还有一种特殊的区域,叫Humongous区域。 如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。

PS:在java 8中,持久代也移动到了普通的堆内存空间中,改为元空间。

对象分配策略

说起大对象的分配,我们不得不谈谈对象的分配策略。它分为3个阶段:TLAB(Thread Local Allocation Buffer)线程本地分配缓冲区 Eden区中分配 Humongous区分配

TLAB为线程本地分配缓冲区,它的目的为了使对象尽可能快的分配出来。如果对象在一个共享的空间中分配,我们需要采用一些同步机制来管理这些空间内的空闲空间指针。在Eden空间中,每一个线程都有一个固定的分区用于分配对象,即一个TLAB。分配对象时,线程之间不再需要进行任何的同步。

对TLAB空间中无法分配的对象,JVM会尝试在Eden空间中进行分配。如果Eden空间无法容纳该对象,就只能在老年代中进行分配空间。

最后,G1提供了两种GC模式,Young GC和Mixed GC,两种都是Stop The World(STW)的。下面我们将分别介绍一下这2种模式。

三,G1 Young GC

Young GC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。在这种情况下,Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间。Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。最终Eden空间的数据为空,GC停止工作,应用线程继续执行。

3

4

这时,我们需要考虑一个问题,如果仅仅GC 新生代对象,我们如何找到所有的根对象呢? 老年代的所有对象都是根么?那这样扫描下来会耗费大量的时间。于是,G1引进了RSet的概念。它的全称是Remembered Set,作用是跟踪指向某个heap区内的对象引用。

5

在CMS中,也有RSet的概念,在老年代中有一块区域用来记录指向新生代的引用。这是一种point-out,在进行Young GC时,扫描根时,仅仅需要扫描这一块区域,而不需要扫描整个老年代。

但在G1中,并没有使用point-out,这是由于一个分区太小,分区数量太多,如果是用point-out的话,会造成大量的扫描浪费,有些根本不需要GC的分区引用也扫描了。于是G1中使用point-in来解决。point-in的意思是哪些分区引用了当前分区中的对象。这样,仅仅将这些对象当做根来扫描就避免了无效的扫描。由于新生代有多个,那么我们需要在新生代之间记录引用吗?这是不必要的,原因在于每次GC时,所有新生代都会被扫描,所以只需要记录老年代到新生代之间的引用即可。

需要注意的是,如果引用的对象很多,赋值器需要对每个引用做处理,赋值器开销会很大,为了解决赋值器开销这个问题,在G1 中又引入了另外一个概念,卡表(Card Table)。一个Card Table将一个分区在逻辑上划分为固定大小的连续区域,每个区域称之为卡。卡通常较小,介于128到512字节之间。Card Table通常为字节数组,由Card的索引(即数组下标)来标识每个分区的空间地址。默认情况下,每个卡都未被引用。当一个地址空间被引用时,这个地址空间对应的数组索引的值被标记为”0″,即标记为脏被引用,此外RSet也将这个数组下标记录下来。一般情况下,这个RSet其实是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。

Young GC 阶段:阶段1:根扫描

静态和本地对象被扫描 阶段2:更新RS

处理dirty card队列更新RS 阶段3:处理RS

检测从年轻代指向年老代的对象 阶段4:对象拷贝

拷贝存活的对象到survivor/old区域 阶段5:处理引用队列

软引用,弱引用,虚引用处理

四,G1 Mix GC

Mix GC不仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的老年代分区。

它的GC步骤分2步:全局并发标记(global concurrent marking) 拷贝存活对象(evacuation)

在进行Mix GC之前,会先进行global concurrent marking(全局并发标记)。 global concurrent marking的执行过程是怎样的呢?

在G1 GC中,它主要是为Mixed GC提供标记服务的,并不是一次GC过程的一个必须环节。global concurrent marking的执行过程分为五个步骤:初始标记(initial mark,STW)

在此阶段,G1 GC 对根进行标记。该阶段与常规的 (STW) 年轻代垃圾回收密切相关。 根区域扫描(root region scan)

G1 GC 在初始标记的存活区扫描对老年代的引用,并标记被引用的对象。该阶段与应用程序(非 STW)同时运行,并且只有完成该阶段后,才能开始下一次 STW 年轻代垃圾回收。 并发标记(Concurrent Marking)

G1 GC 在整个堆中查找可访问的(存活的)对象。该阶段与应用程序同时运行,可以被 STW 年轻代垃圾回收中断 最终标记(Remark,STW)

该阶段是 STW 回收,帮助完成标记周期。G1 GC 清空 SATB 缓冲区,跟踪未被访问的存活对象,并执行引用处理。 清除垃圾(Cleanup,STW)

在这个最后阶段,G1 GC 执行统计和 RSet 净化的 STW 操作。在统计期间,G1 GC 会识别完全空闲的区域和可供进行混合垃圾回收的区域。清理阶段在将空白区域重置并返回到空闲列表时为部分并发。

三色标记算法

提到并发标记,我们不得不了解并发标记的三色标记算法。它是描述追踪式回收器的一种有用的方法,利用它可以推演回收器的正确性。 首先,我们将对象分成三种类型的。黑色:根对象,或者该对象与它的子对象都被扫描 灰色:对象本身被扫描,但还没扫描完该对象中的子对象 白色:未被扫描对象,扫描完成所有对象之后,最终为白色的为不可达对象,即垃圾对象

当GC开始扫描对象时,按照如下图步骤进行对象的扫描:

根对象被置为黑色,子对象被置为灰色。

6

继续由灰色遍历,将已扫描了子对象的对象置为黑色。

7

遍历了所有可达的对象后,所有可达的对象都变成了黑色。不可达的对象即为白色,需要被清理。

8

这看起来很美好,但是如果在标记过程中,应用程序也在运行,那么对象的指针就有可能改变。这样的话,我们就会遇到一个问题:对象丢失问题

我们看下面一种情况,当垃圾收集器扫描到下面情况时:

9

这时候应用程序执行了以下操作:

A.c=C

B.c=null

这样,对象的状态图变成如下情形:

10

这时候垃圾收集器再标记扫描的时候就会下图成这样:

11

很显然,此时C是白色,被认为是垃圾需要清理掉,显然这是不合理的。那么我们如何保证应用程序在运行的时候,GC标记的对象不丢失呢?有如下2中可行的方式:在插入的时候记录对象 在删除的时候记录对象

刚好这对应CMS和G1的2种不同实现方式:

在CMS采用的是增量更新(Incremental update),只要在写屏障(write barrier)里发现要有一个白对象的引用被赋值到一个黑对象 的字段里,那就把这个白对象变成灰色的。即插入的时候记录下来。

在G1中,使用的是STAB(snapshot-at-the-beginning)的方式,删除的时候记录所有的对象,它有3个步骤:

1,在开始标记的时候生成一个快照图标记存活对象

2,在并发标记的时候所有被改变的对象入队(在write barrier里把所有旧的引用所指向的对象都变成非白的)

3,可能存在游离的垃圾,将在下次被收集

这样,G1到现在可以知道哪些老的分区可回收垃圾最多。 当全局并发标记完成后,在某个时刻,就开始了Mix GC。这些垃圾回收被称作“混合式”是因为他们不仅仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的分区。混合式垃圾收集如下图:

12

混合式GC也是采用的复制的清理策略,当GC完成后,会重新释放空间。

13

至此,混合式GC告一段落了。下一小节我们讲进入调优实践。

五,调优实践

MaxGCPauseMillis调优

前面介绍过使用GC的最基本的参数:

-XX:+UseG1GC -Xmx32g -XX:MaxGCPauseMillis=200

前面2个参数都好理解,后面这个MaxGCPauseMillis参数该怎么配置呢?这个参数从字面的意思上看,就是允许的GC最大的暂停时间。G1尽量确保每次GC暂停的时间都在设置的MaxGCPauseMillis范围内。 那G1是如何做到最大暂停时间的呢?这涉及到另一个概念,CSet(collection set)。它的意思是在一次垃圾收集器中被收集的区域集合。Young GC:选定所有新生代里的region。通过控制新生代的region个数来控制young GC的开销。 Mixed GC:选定所有新生代里的region,外加根据global concurrent marking统计得出收集收益高的若干老年代region。在用户指定的开销目标范围内尽可能选择收益高的老年代region。

在理解了这些后,我们再设置最大暂停时间就好办了。 首先,我们能容忍的最大暂停时间是有一个限度的,我们需要在这个限度范围内设置。但是应该设置的值是多少呢?我们需要在吞吐量跟MaxGCPauseMillis之间做一个平衡。如果MaxGCPauseMillis设置的过小,那么GC就会频繁,吞吐量就会下降。如果MaxGCPauseMillis设置的过大,应用程序暂停时间就会变长。G1的默认暂停时间是200毫秒,我们可以从这里入手,调整合适的时间。

其他调优参数

-XX:G1HeapRegionSize=n

设置的 G1 区域的大小。值是 2 的幂,范围是 1 MB 到 32 MB 之间。目标是根据最小的 Java 堆大小划分出约 2048 个区域。

-XX:ParallelGCThreads=n

设置 STW 工作线程数的值。将 n 的值设置为逻辑处理器的数量。n 的值与逻辑处理器的数量相同,最多为 8。

如果逻辑处理器不止八个,则将 n 的值设置为逻辑处理器数的 5/8 左右。这适用于大多数情况,除非是较大的 SPARC 系统,其中 n 的值可以是逻辑处理器数的 5/16 左右。

-XX:ConcGCThreads=n

设置并行标记的线程数。将 n 设置为并行垃圾回收线程数 (ParallelGCThreads) 的 1/4 左右。

-XX:InitiatingHeapOccupancyPercent=45

设置触发标记周期的 Java 堆占用率阈值。默认占用率是整个 Java 堆的 45%。

避免使用以下参数:

避免使用 -Xmn 选项或 -XX:NewRatio 等其他相关选项显式设置年轻代大小。固定年轻代的大小会覆盖暂停时间目标。

触发Full GC

在某些情况下,G1触发了Full GC,这时G1会退化使用Serial收集器来完成垃圾的清理工作,它仅仅使用单线程来完成GC工作,GC暂停时间将达到秒级别的。整个应用处于假死状态,不能处理任何请求,我们的程序当然不希望看到这些。那么发生Full GC的情况有哪些呢?并发模式失败

G1启动标记周期,但在Mix GC之前,老年代就被填满,这时候G1会放弃标记周期。这种情形下,需要增加堆大小,或者调整周期(例如增加线程数-XX:ConcGCThreads等)。晋升失败或者疏散失败

G1在进行GC的时候没有足够的内存供存活对象或晋升对象使用,由此触发了Full GC。可以在日志中看到(to-space exhausted)或者(to-space overflow)。解决这种问题的方式是:

a,增加 -XX:G1ReservePercent 选项的值(并相应增加总的堆大小),为“目标空间”增加预留内存量。

b,通过减少 -XX:InitiatingHeapOccupancyPercent 提前启动标记周期。

c,也可以通过增加 -XX:ConcGCThreads 选项的值来增加并行标记线程的数目。巨型对象分配失败

当巨型对象找不到合适的空间进行分配时,就会启动Full GC,来释放空间。这种情况下,应该避免分配大量的巨型对象,增加内存或者增大-XX:G1HeapRegionSize,使巨型对象不再是巨型对象。

由于篇幅有限,G1还有很多调优实践,在此就不一一列出了,大家在平常的实践中可以慢慢探索。最后,期待java 9能正式发布,默认使用G1为垃圾收集器的java性能会不会又提高呢?

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/81158566