《Spring实战》笔记(二):装配
1 装配机制
Spring具有非常大的灵活性,它提供了三种主要的装配机制:
- 在XML中进行显式配置。
- 在Java中进行显式配置。
- 隐式的bean发现机制和自动装配。
1.1 自动化装配
Spring从两个角度来实现自动化装配:
- 组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean。
- 自动装配(autowiring):Spring自动满足bean之间的依赖。
具体步骤如下:
- 用@Component注解来标记组件类。
@Component可以设置value属性为bean的id,如果不设置,默认id为类名的首字母缩写。大多数情况下,@Named同@Component - 在配置类中使用@ComponentScan注解来开启组件扫描(也可以使用xml配置 <context:component-scan>)。
@ComponentScan默认会扫描所在类的包及其所有子包,若果要指定特定的包,可以通过value属性设置,如果要指定一个或多个包,可以使用basePackage类指定或者使用basePackageClassess来指定(指定基础包中的任一个类即可) - 在要使用组件的类中使用@AutoWired注入组件。
@AutoWired可以声明在类的属性上,也可以声明在方法上。如果没有配置的bean,Spring会抛出异常,使用@AutoWired的required属性设置为false可以避免该情况,但是被尝试注入的参数将会为null。在大多数情况下,@Inject同@AutoWired
1.2 使用Java代码进行装配
你想要将第三方库中的组件装配到你的应用中,在这种情况下,是没有办法在它的类上添加@Component和@Autowired注解的,因此就不能使用自动化装配的方案了。
通过代码声明Bean,首先要创建一个方法用以返回一个实例,然后使用@Bean注解来声明。方法名为该Bean的id,也可以使用@Bean的value属性来设定Bean的id。
@Bean
public OneBean oneBean() {
return new OneBean();
}
1.3 使用XML进行装配:
- 使用<bean>元素声明bean
<bean id="myBean" class="myClass"/>
- 使用构造器注入初始化bean
2.1 注入其他bean
<bean id="myBean" class="myClass">
<constructor-arg ref="anotherBean">
</bean>
也可以使用c命名空间,但在使用之前要在XML顶部声明其模式。
<bean id="myBean" class="myClass" c:arg-ref="anotherBean"/>
在这里,arg表示构造器参数名,有三种写法:
- 直接写参数名,同上
- 使用参数序号
<bean id="myBean" class="myClass" c:_0-ref="anotherBean"/>
- 如果只有一个参数,可以不标示参数
<bean id="myBean" class="myClass" c:_-ref="anotherBean"/>
2.2 注入字面量
<bean id="myBean" class="myClass">
<constructor-arg value="anotherBean">
</bean>
使用c命名空间:
<bean id="myBean" class="myClass" c:arg="anotherBean"/>
其他两种方式同理,和注入bean相比只是去掉了-ref
2.3 注入集合(c命名空间不能注入集合)
<bean id="myBean" class="myClass">
<constructor-arg></null></constructor-arg>
</bean>
<bean id="myBean" class="myClass">
<constructor-arg>
<list>
<value>xxx</value>
<value>yyy</value>
</list>
</constructor-arg>
</bean>
<bean id="myBean" class="myClass">
<constructor-arg>
<list>
<ref bean=bean1/>
<ref bean=bean2/>
</list>
</constructor-arg>
</bean>
<bean id="myBean" class="myClass">
<constructor-arg>
<set>
<value>xxx</value>
<value>yyy</value>
</set>
</constructor-arg>
</bean>
- 使用setter注入初始化bean
使用setter注入与用构造器注入基本类似,只不过换成了<property>和p命名空间
<bean id="myBean" class="myClass" p:literal="literal" p:pBean-ref="pBean">
<property name="arg" ref="anotherBean"/>
<property name="literalValue" value="value"/>
<property name="collection">
<list>
<value>xxx</value>
<value>yyy</value>
</list>
</property>
</bean>
- 对于集合类,可以使用util命名空间创建对应的集合bean,再被引用。
<bean id="myBean" class="myClass">
<property name="arg" ref="testList"/>
</bean>
<util:list name="testList">
<value>xxx</value>
<value>yyy</value>
</util:list>
1.4 混合配置
- JavaConfig引入JavaConfig,使用@Import注解即可
- JavaConfig引入XML配置,使用@ImportResource注解
- XML配置引入JavaConfig和XML配置
<bean class="JavaConfig"/>
<import resource="abc.xml"/>
2 高级装配
2.1 profile
@Profile
可以用来修饰配置类和声明Bean的方法,用value属性来指定profile。在XML配置中,使用<beans>元素的profile属性来设置profile。激活profile是通过spring.profiles.active
和spring.profiles.default
来确定,如果前者没有设置的话,将会查找后者,如果两者都没有设置的话,则没有激活的profile,只有没有指定profile的bean可以被创建。
2.2 条件化配置
@Conditional
可以实现条件化地配置bean。@Conditional
的value属性为一个实现了Condition接口的类,需要实现mataches()
方法。@Profile
本身也使用了@Conditional
注解。
@Bean
@Conditional(DemoCondition.class)
public DemoBean demoBean() {
return new DemoBean();
}
public class DemoCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
if (conditionContext.getEnvironment().containsProperty("demo")) {
return true;
}
return false;
}
}
2.3 处理自动装配的歧义
@Primary
用来修饰bean,如果在装配时发生歧义将会优先选择该Bean。@Qualifier
用来指定Bean的限定符,与@AutoWired
一起使用的时候,将会选择对应限定符的Bean进行装配(默认的限定符为Bean的id)。@Qualifier
用来修饰Bean的时候,将会为Bean设置一个限定符。
2.4 bean的作用域
- Spring定义了多种作用域,可以基于这些作用域创建bean,包括:
- 单例(Singleton):在整个应用中,只创建bean的一个实例。在默认情况下,Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的。
- 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
- 会话(Session):在Web应用中,为每个会话创建一个bean实例。
- 请求(Rquest):在Web应用中,为每个请求创建一个bean实例。
-
使用@Scope或者在xml文件中<bean>的scope属性指定作用域。
-
使用会话和请求作用域(通过注解)
@Bean
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.INTERFACES)
public DemoBean demoBean() {
return new DemoBean();
}
要注意的是,@Scope
同时还有一个proxyMode属性,它被设置成了ScopedProxyMode.INTERFACES
。这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题。
如果DemoBean是一个类的话,就不能使用ScopedProxyMode.INTERFACES
,而要使用ScopedProxyMode.TARGET_CLASS
- 使用会话和请求作用域(通过XML)
<bean id="demo" class="DemoBean" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
<aop:scoped-proxy/>
表示使用作用域代理,默认创建目标类的代理,如果将proxy-target-class
设置为false
,将生成基于接口的代理
2.5 运行时注入
Spring提供了两种在运行时求值的方式:
- 属性占位符(Property placeholder)。
- Spring表达式语言(SpEL)。
2.5.1 属性占位符
@PropertySource
可以引入其他的属性文件,并将属性加载到Spring的Environment对象中属性占位符的格式为${xxx}
,可以在xml文件中引用,在Java文件中,通过@Value(${xxx})
注入到参数中
在使用属性占位符之前,需要先配置一个PropertySourcesPlaceholderConfigurer bean:
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
如果使用xml配置,则使用 <context:property-holder/>
2.5.2 Spring表达式语言(Spring Expression Language, SpEL)
SpEL使用 #{...}
的格式
- 表示字面量,直接写。String类型单双引号都可。
#{1}
#{3.14}
#{true}
#{'string'}
#{"string"}
- 引用bean,直接引用bean id即可。链式调用的时候使用
?.
用以避免空指针异常,如果前一个方法返回为null将不会再调用下一个方法。
#{bean}
#{bean.property1}
#{bean.method1()}
#{bean.method1().method2()}
#{bean.method1()?.method2()}
- 访问静态方法和常量,使用T()运算符
#{T(java.util.math).PI}
-
运算符
SpEL运算符.JPG
条件运算中,ternary表示三元运算符。Evlis表示简化的三元运算符,用以判断空值并用默认值代替空值,如:
#{user.name?:"no name"}
- 集合,用
[]
获取集合、数组或字符串中的元素。
-
.?[]
对集合进行查询,获取子集合 -
.^[]
获取符合条件的第一个元素 -
.$[]
获取或者条件的最后一个元素 -
.![]
对集合进行映射,如下面例子,从学生集合映射到学生名字的集合
#{list[0]}
#{studentList.?[age gt 16]}
#{studentList.^[age gt 16]}
#{studentList.$[age gt 16]}
#{studentList.![name]}