《Spring in action 4th》 学习笔记,第一部分

第一部分 Spring核心

第一章 Spring之旅

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

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

依赖注入

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

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

  • xml
  • java注解
    • @Configuration
    • @Bean
加载装配的配置文件

Application Context
ClassPathXmlApplicationContext

应用切面

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

AOP确保了POJO的简单性。

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

Spring 容器

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

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

有多种:

  • AnnotationConfigApplicationContext
  • AnnnotationConfigWebApplicationContext;
  • ClassPathXmlApplicationContext;
  • FileSystemXmlApplicationContext;
  • XmlWebApplicationContext;
Bean生命周期

主要是有一些勾子

  • BeanNameAware
  • BeanFactoryAware
  • ApplicationContextAware
  • BeanPostProcessor
  • InitializingBean
  • BeanPostProcessor
  • DisposableBean
    估计大部分时候,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配置文件,会方便点。

根元素是 <beans>

声明一个简单 <bean>

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

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

借助构造器注入初始化bean
  • 子元素 <constructor-arg>
<bean id="cdPlayer" class="soundsystem.CDPlayer">
	<constructor-arg ref="conpactDisc"/>
</bean>
  • c-命名空间
    例如:
<bean id="cdPlayer" class="soundsystem.CDPlayer"  c:cd-ref="compactDisc" >

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

<bean id="cdPlayer" class="soundsystem.CDPlayer"  c:_-ref="compactDisc" >

将字面量注入到bean中

<bean id="compactDisc" class="soundsystem.BlankDisc">
	<consturctor-arg value="三毛流浪记" />
	<consturctor-arg value="三毛" />
</bean>

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

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

<constructor-arg>
	<list>
		<value>haha, where are you 1</value>
		<value>haha, where are you 2</value>
	</list>
</constructor-arg>

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

设置属性

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

<bean id="cdPlayer" class="soundsystem.CDPlayer">
	<property name="compactDisc" ref="compactDisc"/>
</bean>

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

<bean id="cdPlayer" class="soundsystem.CDPlayer" p:compactDisc-ref="compactDisc"/>

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

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

<util:list id="trackList">
	<value>哈哈,你在哪?1</value>
	<value>哈哈,你在哪?2</value>
	<value>哈哈,你在哪?3</value>
</util:list>

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

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

util中可用的工具包括

  • util:constant
  • util:list
  • util:map
  • util:properties
  • util:property-path
  • util:set

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

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

在JavaConfig中引用xml配置

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

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

在xml中引入Javaconfig定义的bean

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

	<import resoune="cd-config.xml"/>

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

	<bean class="soundsystem.CDConfig"/>

惯例

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

第三章 高级装配

环境与profile

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

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

@Configuration
@Profile("dev")
public class DevelopmentProfileConfig{}

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

在xml中配置profile

<beans profile="dev">
	<jdbc:embedded-database id="datasource">
		...
	</jdbc:embedded-database>
</beans>

激活profile

依赖于两个独立的属性:

  • spring.profiles.active
  • spring.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命名空间的一个新元素

<aop:scoped-proxy/>

运行时值注入

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),将获取的值转换为class
  • String[] getActiveProfiles()
  • String[] getDefaultProfiles()
  • boolean acceptsProfiles(String … profiles)

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

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

<bean id="sgtPepers" class="soundsystem.BlankDisc" c:_title="${disc.title}" c:_artist="${disc.artist}"/>

也可以在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的方式

<context:property-placeholder/?

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

使用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]}

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


  1. ↩︎

  2. artist eq ‘Aerosmith’ ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值