1.目的
耦合具有两面性。一方面,紧密耦合的代码难以测试、难以复用、难以理解。另一方面,耦合又是必须的—-完全没有耦合的代码什么也做不了。为了完成有意义的功能,不同的类必须以适当的方式进行交互。总而言之,耦合式必须的,但应当小心谨慎地管理。
通过DI,对象的依赖关系将由系统中负责协调个对象的第三方组件在创建对象时进行设定。对象无需自行创建或管理他们的依赖关系,依赖关系将自动注入到需要他们的对象中去。
2.方法
(1).自动化装配Bean
Spring从两个角度实现自动化装配:
组件扫描:Spring会自动发现应用上下文中创建的bean
自动装配:Spring自动满足bean之间的依赖
- 1.
@Component
注解:这个注解表明该类会作为组件类,并告知Spring要为这个类创建bean,从而没有必要显式配置该类的bean。
package soundsystem;
import org.springframework.stereotype.Component;
@Component
public class SgtPeppers implements CompactDisc {
public String title = "sgt. Pepper's Lonely ~~~";
public String artist = "The Beatles";
public void play() {
System.out.println("playing "+ title +" by "+artist);
}
}
- 2.
@ComponentScan
注解:开启组件扫描,从而寻找带有@Component注解的类
package soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
- 3.
@Autowired
注解:实现自动装配。可以用于构造器,属性的Setter方法等等一些类的方法上。
@Autowired
public CDplayer(CompactDisc cd){
this.cd = cd;
}
注意:当Sping初始化bean时,会尽可能满足bean依赖。假如有且只有一个bean匹配的话就会被装配进来。如果没有匹配的bean,那么在应用上下文创建时,Spring会抛出异常。为了避免异常出现,可以将@Autowired的required属性设置为false:
@Autowired(required=false)
public CDplayer(CompactDisc cd){
this.cd = cd;
}
注意:如果有多个可匹配的bean的话,Spring会抛出异常,表明没有明确指出选择哪个bean进行装配。
(2).Java代码装配Bean
- 1.创建配置类
- 2.
@Bean
注解:会告诉spring这个方法会返回一个对象,该对象要注册为spring应用上下文的bean。方法体中包含了最终产生bean实例的逻辑 - 3.借助javaconfig实现注入
package soundsystem;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CDPlayerConfig {
@Bean
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
@Bean
public CDplayer cdplayer() {
return new CDplay(sgtPeppers());
}
}
(3).XML代码装配Bean
<bean id="自己命名的id号" class="所在类" >
<construct-arg ref="构造器参数的引用对象的id" />
<!--等价于上述构造器--->
<!--格式为 <c:要装配的参数名-ref=" " --->
<c:cd-ref="构造器参数的引用对象的id"/>
<!--中间替换为参数的索引值 --->
<c:_0-ref="构造器参数的引用对象的id"/>
<!--中间替换为空(如果构造器只有一个参数)--->
<c:_-ref="构造器参数的引用对象的id"/>
<!--字面量注入值--->
<construct-arg value=“字面量” />
<c:_属性名=“字面量”/>
<c:_索引=“字面量”/>
<!--中间替换为空(如果构造器只有一个参数)--->
<c:_=“字面量”/>
<!-- 装配聚合 (没有相应的c标签)-->
<construct-arg>
<list>
<value>值1</value>
<value>值2</value>
<value>值3</value>
</list>
</construct-arg>
<!--设置属性--->
<property name="属性名" ref="引用的bean的id"/>
<p:属性名-ref="注入bean的id" />
<!--字面量注入值--->
<property name="属性名" value=" "/>
<p:属性名=" "/>
<!-- 装配聚合与之前类似,不表(没有相应的p标签)--->
<!-- 可以使用util-命名空间对集合进行装配--->
<util:list id="id号">
<value>值1</value>
<value>值2</value>
<value>值3</value>
</util:list>
</bean
3.一些扩展
- 为组件扫描的bean命名、
@Component(命名)
或者@Named(命名)
- 设置组件扫描的一些基础包
@ComponentScan(基础包名)
@ComponentScan(basePackage=基础包名)
多个基础包
@ComponentScan(basePackage={基础包名1,基础包名2})
更加具体指定到类
@ComponentScan(basePackageClasses={类1.class,类2.class})
@Autowired
可以替换成@Inject
- 为bean命名
@Bean(name=命名)
- 在JavaConfig中引用JavaConfig配置
使用@Import(配置类.class)
- 在JavaConfig中引用XML配置
@ImportResource("路径(比如classpath):配置文件名.xml")
在XML中引用XML配置
<import resoune="配置文件名.xml"/>
在XML中引用JavaConfig配置
<bean class="配置包.配置类" />
- 最好的方法是另写一个配置文件或配置类,包含着所需的配置文件名或配置类名。
4.高级装配
(1)环境与profile
问题和需求:
在实际应用的过程中,某些bean会在不同的环境中有所不同。所以需要一种方法,使其在每种环境下都会选择最合适的配置。
简单的方法:
在单独的配置类或者XML文件中配置每个bean,然后在构建阶段确定要将哪一种配置编译到可部署的应用中。
又有问题:
要为每种环境都重新构建应用。
解决方法:profile bean
。
1.配置profile bean
使用@Profile(“命名”)注解。这样子配置类中的bean只有在 命名 profile激活时才会被创建。
技巧:在Spring3.1中只能用于类级别上。Spring3.2开始,可以在方法级别上与@Bean注解一起使用。这样的话,就能将这两个bean的声明放到同一个配置类中。使用XML配置profile,使用的profile属性。
<beans profile="命名"></beans>
技巧:在根 <beans>元素中嵌套定义<beans>,而不用为每个环境都创建一个profile XML文件。
2.激活profile
Spring在确定哪个profile处于激活状态时,需要依赖两个独立属性:spring.profile.active
和spring.profile.default
。如果设置了active属性,那么它的值就会用来确定哪个profile是激活的。否则查找default的值。如果都没有设置的话,那就没有激活的profile。
有以下方式设置这两个属性:- 作为
DispatcherServlet
的初始化参数 - 作为Web应用上下文参数
- 作为JDNI条目
- 作为环境变量
- 作为JVM的系统属性
- 在集成测试类上,使用
@ActiveProfiles
注解设置
以web.xml配置为例
<!-- 为上下文设置默认的profile---> <context-param> <param-name>spring.profiles.default</param-name> <param-value>dev</param-value> </context-param> <servlet> <!--省去了name,class配置代码 ---> <!--为Servlet设置默认的profile ---> <init-param> <param-name>spring.profiles.default</param-name> <param-value>dev</param-value> </init-param> <load-on-srartup>1</load-on-srartup> </servlet>
注意:spring提供了
@ActiveProfiles(环境名)
注解来指定激活环境- 作为
(2)条件化bean(限于spring4之后)
使用@Conditional(条件类.class)
注解。
如下:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ConditionConfig {
//条件化创建bean
@Bean
@Conditional(MagicExistsCondition.class)
public MagicBean magicBean(){
return new MagicBean();
}
}
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class MagicExistsCondition implements Condition {
@Override
public boolean matches(
ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment environment = conditionContext.getEnvironment();
return environment.containsProperty("magic");//检查环境中是否有magic属性
}
}
通过ConditionContext,我们可以做到:
- 借助
getRegistry()
返回的BeanDefinitionRegistry
检查bean定义 - 借助
getBeanFactory()
返回的ConfigurableListableBeanFactory
检查bean是否存在,甚至探查bean的属性 - 借助
getEnvironment()
返回的Environment
检查环境变量是否存在以及值 - 读取并探查
getResourceLoader()
返回的ResourceLoader
所加载的资源 - 借助
getClassLoader()
返回的ClassLoader
加载并检查类是否存在
通过借助AnnotatedTypeMetadata的isAnnotated()方法,可以判断带有@Bean注解的方法是不是还有其他特定的注解。
技巧:Spring4之后的@Profile属性基于@Condition和@Conditional实现
(3)处理自动装配的歧义性
问题与需求:
在之前的注意中我们指出若有多个可匹配的bean,spring会报异常。
解决方法:
标示与限定
- 标示首选的bean
法一:自动化装配下
@Component
@Primary
public class XX {...}
法二:java配置类下
@Bean
@Primary
public Dessert iceCream(){
return new IceCrean();
}
法三:XML配置下
<bean primary="true" />
限定自动装配的bean
背景:当有多个primary标签时依旧存在歧义性
方法@Qualifier(命名)//命名不限于bean id号
可以分别于@Component
@Autowired
@Bean
组合使用
另外@Qualifier
标签个数不定,可以用多个@Qualifier
标签进行限定
最佳实践:为bean选择特征性或描述性的术语,而不是随意的名字。- 更高级的自定义注解
@Target({ElementType.CONSTRUCTOR,ElementType.FIELD, ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Cold { }
(4)bean的作用域
- 单例 singleton
- 原型 prototype
- 会话 session
- 请求 request
@Scope(ConfigurableBeanFactory.作用域类型)
注解
或者
`
(5)运行时值注入
Spring提供了两种在运行时求值的方式:
- 属性占位符
- Spring表达式语言
属性占位符:
1.注入外部的值
@Configuration
//声明属性源
@PropertySource("classpath:/com/soundsystem/app.properties")
public class ExpressiveConfig {
@Autowired
Environment environment;
@Bean
public BlankDisc disc(){
return new BlankDisc(
//检索属性值(可以写上默认值)
environment.getProperty("disc.title","Rattle and Hum"),
environment.getProperty("disc.artist","U2")
);
}
在例子中,@PropertySource引用了类路径中一个名为app.properties的文件,该文件大致内容如下:
disc.title=某值
disc.artist=某值