Wetts's blog

Stay Hungry, Stay Foolish.

0%

<bean>元素提供了一个指定自动装配类型的属性:autowire=”自动装配类型”
<beans>元素标签中的default-autowire属性可以匹配全局自动匹配,默认值是no

<beans>中定义的自动装配策略可以被<bean>的自动装配策略覆盖

Spring提供了4种自动装配类型

  • byName:根据名称进行自动匹配
  • byType:根据类型进行自动匹配
  • constructor:与byType类似,只不过它是针对构造函数注入而言的。如果容器中没有找到和构造函数入参匹配类型的Bean,Spring将抛出异常
  • autodetect:根据Bean的自省机制决定采用byType还是constructor进行自动装配:如果Bean提供了默认的构造函数,则采用byType,否则采用constructor

字面值

<value></value>

有5个特殊字符,分别是:&<>"'。转义和<![CDATA[]]>可以解决插入问题。

引用其他Bean

<ref bean=""></ref>

<ref>元素可以通过以下三个属性引用容器中其他Bean:

  • bean:通过该属性可以引用同一容器或父容器的Bean,这是最常见的形式
  • local:通过该属性只能应用同一配置文件中定义的Bean
  • parent:引用父容器中的Bean

内部Bean

null值

<null/>

级联属性

<property name="car.brand" value="" />

会调用getCar().setBrand(“”)

集合类型属性

List

1
2
3
<list>
<value></value>
</list>

Set

1
2
3
<set>
<value></value>
</set>

Map

1
2
3
4
5
6
7
8
<map>
<entry>
<key><value></value></key>
<value></value>
</entry>
<entry key="" value="" />
<entry key="" value-ref="" />
</map>

Properties

1
2
3
<props>
<prop key=""></prop>
</props>

集合合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<bean id="parent" abstract="true" class="">
<property name="favorites">
<set>
<value></value>
</set>
</property>
</bean>
<bean id="child" parent="parent">
<property name="favorites">
<set merge="true">
<value></value>
</set>
</property>
</bean>

merge=”true”属性只是子元素和父元素中同名的属性值进行合并。

通过util命名空间配置集合类型的Bean

1
2
3
4
5
6
7
8
9
<util:list id="" list-class="java.util.LinkedList">
<value></value>
</util:list>
<util:set id="">
<value></value>
</util:set>
<util:map id="">
<entry key="" value="" />
</util:map>

Spring支持两种依赖注入方式,分别是属性注入和构造函数注入。除此之外,Spring还支持工厂方法注入方式。

属性注入

通过set方法注入Bean的属性值或者依赖对象。是实际应用中最常采用的注入方式。

Spring只会检查Bean中是否有对应的Setter方法,至于Bean中是否有对应的属性变量则不做要求。

一般情况下,Java的属性变量名都以小写字母起头。但也存在特殊情况,考虑到一些特定意义的大写英文缩略词(USA、XML等),JavaBean也允许大写字母起头的属性变量名,不过必须满足“变量的前两个字母要么全部大写,要么全部小写”的要求。

构造函数注入

它保证一些必要的属性在Bean实例化时就得到设置。

按类型匹配入参

1
2
3
<constructor-arg type="">
<value></value>
</constructor-arg>

两个参数相同类型就会出问题

按索引匹配入参

1
<constructor-arg index="" value="" />

能解决按类型匹配入参会遇到的问题。但两个构造方法参数个数相同会出现问题

联合使用类型和索引匹配入参

1
<constructor-arg index="" type=""><value></value></constructor-arg>

能解决按索引匹配入参会遇到的问题。

工厂方法注入

非静态工厂方法

1
2
<bean id="carFactory" class="" />
<bean id="car" factory-bean="carFactory" factory-method="createCar" />

静态工厂方法

1
<bean id="car" class=""(工厂类) factory-method="createCar" />

id

一般情况下,在配置一个Bean时,需要为其指定一个id属性作为Bean的名称。id在IoC容器中必须是唯一的,此外id的命名需要满足XML对id的命名规范(id是XML规定的特殊属性):必须以字母开始,后面可以是字母、数字、连字符、下划线、句号、冒号等完整结束(full stops)的符号,逗号和空格这些非完整结束符是非法的。

name

但是如果用户希望用到一些特殊字符对进行Bean命名,可以使用的name属性进行命名,name属性没有字符上的限制,几乎可以用任何字符。

id和name都未指定

Spring自动将全限定类名作为Bean的名称。

如果存在多个实现类相同的匿名。第一个Bean通过getBean(“com.wetts.Car”)获取;第二个Bean通过getBean(“com.wetts.Car#1”)获得;第三个通过getBean(“com.wetts.Car#2”)获得。

相同id或者相同name

Spring配置文件不允许出现两个相同id的,但却可以出现两个相同name的

如果有多个name相同的,通过getBean(beanName)获取Bean时,将返回最后声明的那个Bean,原因是后面的Bean覆盖了前面同名的Bean。

在低版本的Spring中,由于只有两个Bean作用域,所以采用singleton="true|false"的配置方式,Spring为了向后兼容,依旧支持这种配置方式。不过,Spring推荐采用新的配置方式:scope=”作用域类型”。

scope分类

  • singleton
  • prototype
  • request
  • session
  • globalSession

Spring默认是singleton。在启动容器的时候默认实例化,并缓存于容器中。如果用户不希望在容器启动的时候提前实例化singleton的Bean,可以通过lazy-init=”true”来进行控制。

自定义作用域

可以通过org.springframework.beans.factory.config.Scope接口定义新的作用域,再通过org.springframework.beans.factory.config.CustomScopeConfigurer这个BeanFactoryPostProcessor注册自定义的Bean作用域。

Web相关作用域的Bean注入

将Web相关作用域的Bean注入到singleton或prototype的Bean中,我们需要在配置文件中添加<aop:scoped-proxy />,使引用者从指定的域中取得应用。

1
2
3
4
5
6
<bean id="car" class="" scope="request">
<aop:scoped-proxy />
</bean>
<bean id="boss" class="">
<property name="car" ref="car" />
</bean>

car Bean是request作用域,它被singleton作用域的boss Bean引用。为了boss能够从适当作用域中获取car Bean的引用,需要使用Spring AOP的语法为car Bean配置一个代理类。

在boss Bean在Web环境下,调用car Bean时,Spring AOP将启动动态代理只能判断boss Bean位于哪个HTTP请求线程中,并从对应的HTTP请求线程中获取对应的car Bean。

boss Bean的作用域是singleton,也就是说,在Spring容器中始终只有一个实例,而car Bean的作用域为request,所以每个调用到car Bean的那些HTTP请求都会创建一个car Bean。Spring通过动态代理技术,能够让boss Bean引用到对应HTTP请求的car Bean。

  • Spring1.0的配置文件采用DTD格式
  • Spring2.0以后的配置文件采用Schema格式

  • ApplicationEventPublisher:让容器拥有发布应用上下文事件的功能,包括容器启动事件、关闭事件等。实现了ApplicationListener事件监听接口的Bean可以接收到容器事件,并对事件进行相应处理。在ApplicationContext抽象实现类AbstractApplicationContext中,我们可以发现存在一个ApplicationEventMulticaster,它负责保存所有监听器,以便在容器产生上下文事件时通知这些事件监听者。

  • MessageSource:为应用提供i18n国际化消息访问的功能。

  • ResourcePatternResolver:所有ApplicationContext实现类都实现了类似于PathMatchingResourcePatternResolver的功能,可以通过带前缀的Ant风格的资源文件路径装载Spring的配置文件。

  • LifeCycle:该接口是Spring2.0加入的,该接口提供了start()和stop()两个方法,主要用于控制异步处理过程。在具体使用时,ApplicationContext及具体的Bean都必须同时实现该接口,ApplicationContext会将start/stop的信息传递给容器中所有实现了该接口的Bean,以达到管理和控制JMX、任务调度等目的。

  • ConfigurableApplicationContext:扩展于ApplicationContext,它新增加了两个主要的方法:refresh()和close(),让ApplicationContext具有启动、刷新和关闭应用上下文的能力。在应用上下文关闭的情况下调用refresh()即可启动应用上下文,在已经启动的状态下,调用refresh()则清除缓存并重新装载配置信息,而调用close()则可关闭应用上下文。

Class反射对象描述类语义结构,可以从Class对象中获取构造函数、成员变量、方法类等类元素的反射对象。

下面是三个最主要的反射类:

  • Constructor:类的构造函数反射类。
  • Method:类方法的反射类。
  • Field:类的成员变量的反射类。

通过类实例变量无法在外部访问私有变量、调用私有方法,但通过反射机制却可以绕过这个限制。

在访问private、protected成员变量和方法时必须通过setAccessible(boolean access)方法取消Java语言检查,否则将抛出IllegalAccessException。如果JVM的安全管理器设置了相应的安全机制,调用该方法将抛出SecurityException。

单例模式(Java)

  • 懒汉
    • 第一次调用 getInstance() 方法时初始化对象
    • 会有线程安全问题。两个线程同时获取对象,但是都还未初始化完成,则会返回两个不同的对象。虽然可以对方法用 synchronized,但是每次获取都需要加锁,降低效率。
  • 双重检查锁定
    • 需要用 volatile 修饰对象,以免对象初始化的时候指令重排。如下所示,如果 2 和 3 之间重排,则另一个线程可能会获得一个还未初始化的对象。
      1. 分配对象的内存空间
      2. 初始化对象
      3. 设置 uniqueInstance 指向刚分配的内存地址
  • 饿汉
    • 在类加载的时候直接创建这个对象,这样既能提高效率,又能保证线程安全。
  • 静态内部类
    • 饿汉式的方式只要 Singleton 类被装载了,那么 uniqueInstance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,uniqueInstance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有显示通过调用 getInstance 方法时,才会显示装载 SingletonHolder 类,从而实例化 uniqueInstance
  • 枚举
    • 用枚举实现单例模式可以避免如下 2 个问题,其他四种方式都不能避免
      1. 序列化造成单例模式不安全
      2. 反射造成单例模式不安全
  • 如何防止反射、克隆、序列化对单例模式的破环
    • 防止反射破环(虽然构造方法已私有化,但通过反射机制使用 newInstance() 方法构造方法也是可以被调用):
      • 首先定义一个全局变量开关 isFristCreate 默认为开启状态
      • 当第一次加载时将其状态更改为关闭状态
    • 防止克隆破环
      • 重写 clone(),直接返回单例对象
    • 防止序列化破环
      • 添加 readResolve(),返回 Object 对象

转自:http://blog.csdn.net/zhangerqing/article/details/8194653

工厂方法模式(Factory Method)

工厂方法模式分为三种:

  • 普通工厂方法模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。首先看下关系图:

    普通工厂模式

  • 多个工厂方法模式,是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式是提供多个工厂方法,分别创建对象。关系图:

    多个工厂方法模式

  • 静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不需要创建实例,直接调用即可。

总体来说,工厂模式适合:凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。在以上的三种模式中,第一种如果传入的字符串有误,不能正确创建对象,第三种相对于第二种,不需要实例化工厂类,所以,大多数情况下,我们会选用第三种——静态工厂方法模式。

抽象工厂模式(Abstract Factory)

工厂方法模式有一个问题就是,类的创建依赖工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则,所以,从设计角度考虑,有一定的问题,如何解决?就用到抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。因为抽象工厂不太好理解,我们先看看图,然后就和代码,就比较容易理解。

抽象工厂模式

单例模式(Singleton)

单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:

  1. 某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
  2. 省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
  3. 有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。

首先我们写一个简单的单例类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Singleton {  

/* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
private static Singleton instance = null;

/* 私有构造方法,防止被实例化 */
private Singleton() {
}

/* 静态工程方法,创建实例 */
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}

/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
public Object readResolve() {
return instance;
}
}

这个类可以满足基本要求,但是,像这样毫无线程安全保护的类,如果我们把它放入多线程的环境下,肯定就会出现问题了,如何解决?我们首先会想到对getInstance方法加synchronized关键字,如下:

1
2
3
4
5
6
public static synchronized Singleton getInstance() {  
if (instance == null) {
instance = new Singleton();
}
return instance;
}

但是,synchronized关键字锁住的是这个对象,这样的用法,在性能上会有所下降,因为每次调用getInstance(),都要对对象上锁,事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了,所以,这个地方需要改进。我们改成下面这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static Singleton getInstance() {  
if (instance == null) {
synchronized (instance) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
```
似乎解决了之前提到的问题,将synchronized关键字加在了内部,也就是说当调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要加锁,性能有一定的提升。但是,这样的情况,还是有可能有问题的,看下面的情况:在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就可能出错了,我们以A、B两个线程为例:
1. A、B线程同时进入了第一个if判断
2. A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
3. 由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
4. B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
5. 此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。

所以程序还是有可能发生错误,其实程序在运行过程是很复杂的,从这点我们就可以看出,尤其是在写多线程环境下的程序更有难度,有挑战性。我们对该程序做进一步优化:

private static class SingletonFactory{
private static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonFactory.instance;
}

1
2
实际情况是,单例模式使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。这样我们暂时总结一个完美的单例模式:

public class Singleton {

/* 私有构造方法,防止被实例化 */  
private Singleton() {  
}  

/* 此处使用一个内部类来维护单例 */  
private static class SingletonFactory {  
    private static Singleton instance = new Singleton();  
}  

/* 获取实例 */  
public static Singleton getInstance() {  
    return SingletonFactory.instance;  
}  

/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */  
public Object readResolve() {  
    return getInstance();  
}  

}

1
2
其实说它完美,也不一定,如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。所以说,十分完美的东西是没有的,我们只能根据实际情况,选择最适合自己应用场景的实现方法。也有人这样实现:因为我们只需要在创建类的时候进行同步,所以只要将创建和getInstance()分开,单独为创建加synchronized关键字,也是可以的:

public class SingletonTest {

private static SingletonTest instance = null;  

private SingletonTest() {  
}  

private static synchronized void syncInit() {  
    if (instance == null) {  
        instance = new SingletonTest();  
    }  
}  

public static SingletonTest getInstance() {  
    if (instance == null) {  
        syncInit();  
    }  
    return instance;  
}  

}

1
2
3
4
考虑性能的话,整个程序只需创建一次实例,所以性能也不会有什么影响。

补充:采用"影子实例"的办法为单例对象的属性同步更新

public class SingletonTest {

private static SingletonTest instance = null;  
private Vector properties = null;  

public Vector getProperties() {  
    return properties;  
}  

private SingletonTest() {  
}  

private static synchronized void syncInit() {  
    if (instance == null) {  
        instance = new SingletonTest();  
    }  
}  

public static SingletonTest getInstance() {  
    if (instance == null) {  
        syncInit();  
    }  
    return instance;  
}  

public void updateProperties() {  
    SingletonTest shadow = new SingletonTest();  
    properties = shadow.getProperties();  
}  

}

通过单例模式的学习告诉我们:
1. 单例模式理解起来简单,但是具体实现起来还是有一定的难度。
2. synchronized关键字锁定的是对象,在用的时候,一定要在恰当的地方使用(注意需要使用锁的对象和过程,可能有的时候并不是整个对象及整个过程都需要锁)。

到这儿,单例模式基本已经讲完了,结尾处,笔者突然想到另一个问题,就是采用类的静态方法,实现单例模式的效果,也是可行的,此处二者有什么不同?

首先,静态类不能实现接口。(从类的角度说是可以的,但是那样就破坏了静态了。因为接口中不允许有static修饰的方法,所以即使实现了也是非静态的)

其次,单例可以被延迟初始化,静态类一般在第一次加载是初始化。之所以延迟加载,是因为有些类比较庞大,所以延迟加载有助于提升性能。

再次,单例类可以被继承,他的方法可以被覆写。但是静态类内部方法都是static,无法被覆写。

最后一点,单例类比较灵活,毕竟从实现上只是一个普通的Java类,只要满足单例的基本需求,你可以在里面随心所欲的实现一些其它功能,但是静态类不行。从上面这些概括中,基本可以看出二者的区别,但是,从另一方面讲,我们上面最后实现的那个单例模式,内部就是用一个静态类来实现的,所以,二者有很大的关联,只是我们考虑问题的层面不同罢了。两种思想的结合,才能造就出完美的解决方案,就像HashMap采用数组+链表来实现一样,其实生活中很多事情都是这样,单用不同的方法来处理问题,总是有优点也有缺点,最完美的方法是,结合各个方法的优点,才能最好的解决问题!

### 建造者模式(Builder)
工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性,其实建造者模式就是前面抽象工厂模式和最后的Test结合起来得到的。

建造者模式将很多功能集成到一个类里,这个类可以创造出比较复杂的东西。所以与工程模式的区别就是:工厂模式关注的是创建单个产品,而建造者模式则关注创建符合对象,多个部分。因此,是选择工厂模式还是建造者模式,依实际情况而定。

### 原型模式(Prototype)
原型模式虽然是创建型的模式,但是与工程模式没有关系,从名字即可看出,该模式的思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。本小结会通过对象的复制,进行讲解。在Java中,复制对象是通过clone()实现的。

很简单,一个原型类,只需要实现Cloneable接口,覆写clone方法,此处clone方法可以改成任意的名称,因为Cloneable接口是个空接口,你可以任意定义实现类的方法名,如cloneA或者cloneB,因为此处的重点是super.clone()这句话,super.clone()调用的是Object的clone()方法,而在Object类中,clone()是native的,具体怎么实现,我会在另一篇文章中,关于解读Java中本地方法的调用,此处不再深究。在这儿,我将结合对象的浅复制和深复制来说一下,首先需要了解对象深、浅复制的概念:
- 浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。
- 深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。

要实现深复制,需要采用流的形式读入当前对象的二进制输入,再写出二进制数据对应的对象。