第一部分 Spring核心

第一章 Spring之旅

Spring 来自于《Export One on One :J2EE Design and Development》这本书,初创者是Rod Johnson Spring 采取了以下4种策略

基于POJO的轻量级和最小入侵性编程;通过一来注入和面相接口实现松耦合通过切面和管理进行申明式编程;通过切面和模板减少样板式代码。

依赖注入

构造器注入, constructor injection 对依赖进行替换的一种最常用的方法就是使用mock对象

装配

创建应用组件之间协作的行为叫做装配(wiring) 有两种方式进行装配:

xmljava注解

@Configuration@Bean

加载装配的配置文件

Application Context ClassPathXmlApplicationContext

应用切面

apspect-oriented programming AOP 允许你把遍布应用各处的功能分离出来形成可重用的组件。 AOP的常见栗子为i日志、事务管理、安全等系统服务。这些一般被叫做横切关注点。 AOP把这些服务模块化,并以声明的方式应用到他们能需要影响的组件中。

AOP确保了POJO的简单性。

老式的、基于xml配置文件的AOP其实也挺烦的,因为他和代码分离,当这种代码很多的时候,这样调试起来其实很麻烦

Spring 容器

用来创建、装配并管理Bean的整个生命周期。 有两种容器:

bean工厂应用上下文 相对来说,应用上下文更加受欢迎,也用的更多。

应用上下文

有多种:

AnnotationConfigApplicationContextAnnnotationConfigWebApplicationContext;ClassPathXmlApplicationContext;FileSystemXmlApplicationContext;XmlWebApplicationContext;

Bean生命周期

主要是有一些勾子

BeanNameAwareBeanFactoryAwareApplicationContextAwareBeanPostProcessorInitializingBeanBeanPostProcessorDisposableBean 估计大部分时候,bean都不会去实现这些接口,即使有,也是偶尔实现个吧。

Spring体系结构

新的spring有变化了,老的就不看了。

第二章,装配Bean

Spring配置的可选方案

在xml中装配;在java中装配;自动装配; 尽可能的去使用自动装配》然后是基于java的装配》最后才是xml装配。

因为JAVA装配具备类型安全,所以优于xml装配

自动化装配Bean

通过两个角度来实现:

组件扫描,Spring会自动发现上下文中所创建的bean;自动装配,Spring自动满足bean之间的依赖;

创建可被发现的bean

@Component@Named,这个基本等效于 @Component,一般不用。@Configuration@ComponentScan

@Configuration

@ComponentScan

public class CDPlayerConfig{

}

自动创建的Bean的命令,就是把类的第一个字母小写,如果想要其他的名字,则只要传递给@Component注解即可。

设置组件扫描的基础包

@ComponentScan("soundsystem")

public class CDPlayerConfig{}

@ComponentScan(basePackages="soundsystem")

public class CDPlayerConfig{}

@ComponentScan(basePackages={"soundsystem", "video"})

public class CDPlayerConfig{}

@ComponentScan(basePackagesClasses={CDPlayer.class, DVDPlayer.class})

public class CDPlayerConfig{}

以上这些都是可以到,要注意其中的区别,其中最后一个应该相对好一点,具备类型安全。

而且你可以不用业务类,而是在每个package中专门增加一个类用于自动扫描用。

添加注解实现自动装配

@Autowired类似的还有一个 @Injected,一般也不用。 他可以用在构造器,或者setter上,其实他可以用在任何方法上。

如果设置了@Autowired,但是在装配的时候又找不到对应的bean,则Spring会抛出一个异常。

要避免的话,可以设置 @Autowired的required属性为false

@Autowired(required=false)

public void setCD(...){ ... }

如果有多个bean可以满足依赖关系,Spring也会抛出异常,此时要解决装配的歧义性。这点以后再说

通过java代码装备bean

自动装配有失效的时候,比如需要讲第三方的代码装配到你的应用中,此时@Component, @Autowired就无效了。

选择JavaConfig进行装配,有几个优点:

更加强大类型安全对重构友好

Javaconfig是配置代码,不应该包含任何业务逻辑。

通常应该将Javaconfig放到单独的包中,使他与其他应用程序逻辑分离,这样对于他的意图就不会产生困惑了

创建配置类

@Configuration @Bean ,会告诉Spring这个方法会返回一个对象,该对象要注册位Spring应用上下文中的bean,默认情况下,bean的id和这个方法的名称相同。如果想要不一样,则给@Bean指定name参数。

借助Javaconfig实现注入

@Bean

public CDPlayer cdPlayer(){

return new CDPlayer(sgtPepers())

}

看起来,cdPlayer()是通过调用sgtPepers()得到的,但情况并非完全如此,因为sgtPepers()方法上添加了@Bean注解,Spring将会拦截所有对他的调用,并确保直接返回该方法锁创建的bean,而不是每次都对其进行实际的调用。

就是说,实际上只创建了一个SgtPepers对象,然后保存在Spring容器中,以后每次都返回这个对象。

所以,默认情况下,Spring中的bean都是单例的。

上面的装配代码让人有点困惑,下面的更好理解点

@Bean

public CDPlayer cdPlayer(CompactDisc compactDisc){

return new CDPlayer(compactDisc)

}

然后Spring会自动传入compactDisc bean,不需要手动指定。这样就不要求 sgtPepers()方法和 cdPlayer()在一个配置类中了,甚至不需要在JavaConfig中声明,他可以是自动扫描发现或者xml文件中配置的。

通过xml装配bean

虽然xml配置已经部署主流,但是历史遗留代码中有大量的xml配置,所以理解xml配置还是必要的。

创建xml配置规范

可以使用Spring Tool Suite来创建spring的xml配置文件,会方便点。

根元素是

声明一个简单

元素类似于JavaConfig中的@bean注解。 Spring发现这个bean元素时,他将会调用他的默认构造函数来创建bean,所以相对来说没有JavaConfig灵活。

由于在xml中是以字符串的方式来指定类的名称,所以没有编译器的类型合法检查。 可以使用Spring Tools Suite IDE来检查xml配置的合法性。不然到时候有的问题不好排查。

借助构造器注入初始化bean

子元素

c-命名空间 例如:

这里:cd中的cd就是参数名称,在xml中指定参数名称不太好,万一重构时改了呢,所以有一种替代方案,用下划线占位,这种格式只适合有一个参数的情况。

将字面量注入到bean中

和之前传入bean时使用ref不同,这里使用了value

装配集合 这种时候只能使用,而不能使用c:命令空间

haha, where are you 1

haha, where are you 2

这里list的子元素是value,其实也可以是ref list元素也可以用set元素替代,只不过set不允许重复,赋值之后会过滤重复项

设置属性

一般对于强依赖使用构造器注入,对于可选依赖使用属性注入。

通过ref引用另外一个bean,如果是字面量,则使用value 与构造函数可以通过c-命名空间来简化传参类似,可以使用p-命名空间来简化属性的传参。

与构造函数中类似,给属性传参时,也可以传递数组,此时不能使用p-命名空间,而是使用property。

定义变量 可以使用util-命名空间的工具来定义变量,比如:

哈哈,你在哪?1

哈哈,你在哪?2

哈哈,你在哪?3

然后就可以通过p-命名空间在bean定义中引用他。

最后还有一点,p-命名空间可以和混用,也就是一部分属性使用p-,一部分使用property

util中可用的工具包括

util:constantutil:listutil:maputil:propertiesutil:property-pathutil:set

###导入与混合配置 首先我们需要明白,自动装配,javaconfig以及xml配置,可以混用。他们有各自的优缺点,我们可以把他们混合,发挥各自的优点。

自动装配时,Spring其实不在于bean来自于哪里,他会考虑所有的bean,在容器中就行。

在JavaConfig中引用xml配置

如果JavaConfig拆分为多个类,则使用 @Import注解。

如果要在JavaConfig中引用xml定义的配置,则使用 @ImportResource(“classpath:cd-config.xml”)

在xml中引入Javaconfig定义的bean

在xml配置中,可以使用来拆分配置,这里import引入了其他的xml配置文件。

如果要引入JavaConfig中的配置,则可以

惯例

一般会创建一个根配置,引入其他的所有子配置,并且在根配置中启动组件的自动扫描。如果在xml中,就是用 ,如果在Javaconfig中,则使用 @ComponentScan

第三章 高级装配

环境与profile

比如我们在开发、测试、QA、生产等不同环境下的数据源的不同,此时就可以用profile注解。

Spring引入了bean profile功能,于是有了 @Profile注解,这个注解可以用到类中,也可以用到方法中。 例如:

@Configuration

@Profile("dev")

public class DevelopmentProfileConfig{}

没有指定@Profile的bean始终都会创建。

在xml中配置profile

...

激活profile

依赖于两个独立的属性:

spring.profiles.activespring.profiles.default 其中active优先,如果两个都没设置,则所有Profile的bean都不会创建。

可以配置上面这两个属性的方式:

作为DispatcherServlet的初始化参数;作为Web应用上下文的参数;作为JNDI条目;作为环境变量;作为JVM的系统属性;在集成测试类上,使用 @ActiveProfiles注解设置。

可以同时激活多个profile,虽然一般没有必要。

集成测试的时候使用@ActiveProfile

@ActiveProfile("dev")

public class PersistenceTest{}

条件化的bean

@Conditional注解,他可以用到带有@Bean注解的方法上,如果给定的条件是真就创建bean,否则就不创建。 这种特殊的条件,可以是:

路径下包含特定的库;另外一个什么bean存在;设定了某个特定的环境变量;。。。

其实可以是任何可以通过编程判断的条件。 @Conditionnal注解接受的参数是一个实现了Condition接口的类,定义如下

public interface Condition{

boolean matches(ConditionContext ctxt, AnnotatedTypeMetadata metadata)

}

使用的时候是这样

@Bean

@Conditional(MagicExistsCondition.class)

public MagicBean magicBean(){

return new MagicBean()

}

再来说说这个Condition对接口 matches的参数 ConditionContext是一个接口,定义这些方法

public interface ConditionContext{

BeanDefinitionRegistry getRegistry();

ConfigurableListableBeanFactory getBeanFactory();

Enviroment getEnviroment();

ResourceLoader getResourceLoader();

ClassLoader getClassLoader();

}

而AnnotatedTypeMetadata也是一个接口,通过他可以拿到当前bean还有哪些注解,一些这些注解的属性,以便做进一步的判断。

处理自动装配的歧义性

仅有一个bean的时候,自动装配才会生效。 有几种方法可以解决:

标示首选的bean

使用@Primary注解,和@Component一起使用,也可以和@Bean注解一起使用。

@Component

@Primary

public class IceCream implements Dessert{}

//或者

@Bean

@Primary

public Dessert iceCream(){

return new IceCream()

}

使用限定符

使用 @Qualifier注解设置一个限定符,可以和@Autowired, @Injected一起使用。

@Autowired

@Qualifier("iceCream")

public void setDessert(Dessert dessert){}

这里的iceCream就是bean的id,其实这个iceCream是一个针对bean的限定符,只不过一般都不设置限定符,此时限定符的默认值就是id值

创建自定义的限定符 可以给bean创建自定义的限定符,这样在引用的时候就不会和类名强耦合了,例如

@Componet

@Qualifier("cold")

public class IceCream implements Dessert{}

这里是和bean类的定义写一起了,其实这个@Qualifier也可以和@Bean的方法写一起

@Bean

@Qualifier("cold")

public Dessert iceCream(){}

使用自定义的限定符注解 创建一个新的注解,只要该注解使用@Qualifier注解来限定,那么新的注解也就具有了 @Qualifier注解的特性了。

@Target(ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Qualifier

public @inerface Cold{}

这样,我们就定义了一个@Cold注解

其实作者写这个,只是为了绕开一个位置不能有多个@Qualifier注解的问题,感觉必要性不是很大。

bean的作用域

在有些任务中,让对象保持屋状态并且在应用中反复重复用这些对象是不合理的。有些类是易变的(mutable)。

Spring有多种作用域:

单利,singleton;这个是默认的。原型,prototype,每次都是新的;会话,session,在一个session内是新的;请求,request,在一个request内是新的; 这些都能理解,和web的一些对象的生命周期差不多。

可以使用@Scope注解来修改,比如:

@Component

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

//@Scope('prototype'),这样也是可以的,这是这种不具备类型安全

public class Notepad{}

这个scope也可以和@Bean一起使用。

使用会话和请求作用域

看个栗子

@Bean

@Scope(value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopeProxyMode.INTERFACE)

public ShoppingCart cart(){ ... }

要注意下这里的proxyMode,这是为了解决爸scope为session的bean装配被scope为singleton的类时可能会遇到的问题。 Spring会创建一个代理,给singleton的bean装配session的bean时,不会装配具体的bean,而是装配一个和bean实现了相同接口的代理,然后代理内部会根据具体的session去调用各自session内的bean。 有点复杂,看书上p87的那个图就明白了。

这个代理叫做作用域代理这个代理是通过CGLib来创建的。

在xml中声明作用域代理

其实就是使用了aop命名空间的一个新元素

运行时值注入

Spring提供了两种在运行时秋实的方式:

属性占位符Spring表达式语言, SpEL

注入外部的值

在Spring中,处理外部值的最简单的方式就是生命属性源并通过Spring的Environment来检索属性。 例如:

@Configuration

@PropertySource("classpath:/com/soundsystem/app.properties")

public class ExpresiveConfig{

@Autowired

Enviroment env;

@Bean

public BlankDisc disc(){

return new BlankDisc(env.getProperty("disc.title"), env.getProperty("disc.artist"))

}

}

disc.title=你好啊

disc.artist=许三多

看到这里,我就有点明白各种数据源的配置了

深入学习Spring的Enviroment getProperty的4个overload

String getProperty(String key);String getProperty(String key, String defaultValue)T getProperty(String key, Class type);T getProperty(Stirng key, Class type, T defaultValue)

其他几个需要了解的方法

String getRequiredProperty(String key),值不存在的时候抛出异常boolean containsProperty(String key),判断是否存在T getPropertyAsClass(String key, Class type),将获取的值转换为classString[] getActiveProfiles()String[] getDefaultProfiles()boolean acceptsProfiles(String … profiles)

后面三个不知道是静态方法还是普通方法?

解析属性占位符 占位符的格式: ${key} 这个占位符可以在xml,

也可以在Javaconfig中使用

public BlankDisc(@Value("${disc.title}") String title, @Value("${disc.artist}") String artist){

this.title = title

this.artist = artist

}

注意这里用到了 @Value注解

然后还需要一个placeholder来配合 javaconfig的配置

@Bean

public PropertySourcesPlaceHolderConfigurer placeholderConfigurer(){}

xml的方式

总的来说,解析外部属性,能够将值的处理推迟到运行时。

使用Spring表达式预言进行装配

Spring表达式要放在 #{…}内部,下面给一些栗子

#{1} // 常量

#{T(System).currentTimeMillis()} //调用对象方法

#{sgtPepers.artist} // 调用bean的属性

#{systemPropertis['disc.title']} //读取propertis

这些表达式可以在Javaconfig中使用,也可以在xml配置中使用。

标示字面值 #{3.1415926} #{false} #{‘Hello’}

引用bean、属性和方法

#{sgtPeppers}#{sgtPepers.artist}#{artistSelector.selectArtist()}#{artistSelector.selectArtist().toUpperCase()}#{artistSelector.selectArtist()?.toUpperCase()}

在表达式中使用类型 要使用T(…)的形式,比如

#{T(java.lang.Math).PI}#{T(java.lang.Math).random()}

SpEL运算符 基本上支持常用的算数、逻辑、比较、条件运算符,以及正则表达式 比如

#{2T(java.lang.Math).PIcircle.radius}#{disc.title + ‘by’ +disc.artist}#{counter.total == 100}#{counter.total eq 100}

计算正则表达式 通过matches对String类型的文本做运算,比如 #{admin.email matchs ‘[a-zA-Z0-9._%±]@[a-zA-Z0-9.-]+\.com’}

计算集合

#{jukebox.songs[4].title}#{jukebox.songs[T(java.lang.Math.random()*jukebox.songs.size())].title}

SpEL提供了查询运算符(.?[]),他会用来对集合进行过滤,得到集合的一个子集 比如,下面会得到所有artist为 Aerosmith的所有歌曲 #{jukebox.songs.?[artist eq ‘Aerosmith’]}

另外还有两个表达式, .1 和 .$[] 他们分别用来查找集合中第一个匹配和最后一个匹配项

#{jukebox.songs.2},这个是找到第一个匹配项

最后还有一个投影功能,就类似于JavaScript的map功能, .![],返回一个新的集合 #{jukebox.songs.![title]}

以上这些运算符可以组合起来一起用。

↩︎

artist eq ‘Aerosmith’ ↩︎