2.1 Spring配置的可选方案
1.Spring配置的可选方案
2.在Java中进行显式配置
3.隐式的bean发现机制和自动装配
2.2 自动化装配bean
Spring从两个角度来实现自动化装配
组件扫描(component scanning) : Spring会自动发现应用上下文中所创建的bean。
自动装配(autowiring) : Spring自动满足bean之间的依赖
2.2.1 @Component 创建可被发现的Bean
@Component
public class HelloWord {
public String sayHello(){
return "hello";
}
}
HelloWord 类上使用了@Component注解 这个注解表明该类会作为组件类 并告知Spring要为这个类创建bean 没有必要显式配置 HelloWord Bean 因为这个类使用了@Component注解, 所以Spring会为你把事情处理妥当
组件扫描默认是不启用的我们还需要显式配置一下Spring, 从而命令它去寻找带有@Component注解的类 并为其创建bean。
2.2.2 @ComponentScan 注解启动组件扫描
@ComponentScan
@Component
public class HelloWord {
public String sayHello(){
return "hello";
}
}
@ComponentScan注解能够在Spring中启用组件扫描 如果没有其他配置的话 @ComponentScan默认会扫描与配置类相同的包 因此Spring将会扫描这个包以及这个包下的所有子包, 查找带有@Component注解的类 自动为其创建一个bean
2.2.3 为组件扫描的bean命名
Spring应用上下文中所有的bean都会给定一个ID 类名的第一个字母变为小写 如果想将这个bean标识
为hello
@Component("hello")
public class HelloWord {
public String sayHello(){
return "hello";
}
}
2.2.4 设置组件扫描的基础包
如果你想扫描不同的包
@ComponentScan(basePackages = {"chat1","chat2"})
@Component
public class HelloWord {
public String sayHello(){
return "hello";
}
}
@ComponentScan还提供了另外一种方法, 那就是将其指定为包中所包含的类或接口
不是再使用String类型的名称来指定包 为basePackageClasses属性所设置的数组中包含了类 这些类所在的包将会作为组件扫描的基础包
@ComponentScan(basePackageClasses = {HelloWord.class})
@Component
public class HelloWord {
public String sayHello(){
return "hello";
}
}
2.2.5 @Autowired 自动装配Bean
@Autowired是Spring特有的注解 在Spring初始化bean之后, 它会尽可能得去满足bean的依赖
@Autowired
HelloWord helloWord;
2.2.6 @Configuration 创建配置类
@Configuration注解表明这个类是一个配置类 该类应该包含在Spring应用上下文中如何创建bean的细节
@Configuration
public class Demo {
}
2.2.7 @Bean 声明简单的Bean
@Bean注解会告诉Spring这个方法将会返回一个对象, 该对象要注册为Spring应用上下文中的bean。
@Configuration
public class Demo {
@Bean
public HelloWord helloWord(){
return new HelloWord();
}
}
可以通过name属性指定一个不同的名字
@Configuration
public class Demo {
@Bean(name = "hello")
public HelloWord helloWord(){
return new HelloWord();
}
}
3.1 @Profile 环境与profile
在开发软件的时候, 有一个很大的挑战就是将应用程序从一个环境迁移到另外一个环境考虑一下数据库配置 在开发环境中 我们可能会使用嵌入式数据库 并预先加载测试数据
3.1.1 配置profile bean
使用@Profile注解指定某个bean属于哪一个profile它会告诉Spring这个配置类中的bean只有在dev profile激活时才会创建。 如果dev profile没有激活的话, 那么带有@Bean注解的方法都会被忽略掉只有prod profile激活的时候, 才会创建对应的bean
@Configuration
@Profile("dev")
public class DevDemo {
}
从Spring 3.2开始, 可以在方法级别上使用@Profile注解 只有当规定的profile激活时 相应的bean才会被创建
@Configuration
public class DevAndProd {
@Bean
@Profile("dev")
public HelloWord helloWordDev(){
return new HelloWord("dev");
}
@Bean
@Profile("prod")
public HelloWord helloWordProd(){
return new HelloWord("prod");
}
}
3.1.2 激活profile
Spring在确定哪个profile处于激活状态时, 需要依赖两个独立的属性:
spring.profiles.active和spring.profiles.default
有多种方式来设置这两个属性:
作为DispatcherServlet的初始化参数;
作为Web应用的上下文参数
作为环境变量
在集成测试类上, 使用@ActiveProfiles注解设置。
使用profile进行测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DevAndProd.class)
@ActiveProfiles("prod")
public class DEMO {
@Autowired
HelloWord helloWord;
@Test
public void test(){
System.out.println();
}
}
3.2 @Conditional 条件化的bean
希望某个bean只有当另外某个特定的bean也声明了之才会创建 我们还可能要求只有某个特定的环境变量设置之后 才会创建某个bean
@Configuration
public class UserConfig {
@Bean
@Conditional(UserExists.class)
public User user(){
return new User("张三");
}
}
它通过给定的ConditionContext对象进而得到Environment对象, 并使用这个对象检查环境中是否存在名为name的环境属性 如果条件能够得到满足所有@Conditional注解上引用的UserExists的bean都会被创建 如果这个属性不存在的话 这些bean都不会被创建
public class UserExists implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment environment = conditionContext.getEnvironment();
String a = environment.getProperty("name"); // 从环境变量得到name的属性
if(StringUtils.hasText(a)){
if(a.equals("jack")){ // 如果当前属性值为jack 返回true
return true;
}
}
return false;
}
}
AnnotatedTypeMetadata 我们能够判断带有@Bean注解的方法是不是还有其他特定的注解。 借助其他的那些方法 我们能够检查@Bean注解的方法上其他注解的属性。
3.3 处理自动装配的歧义性
这三个实现都使用了@Component注解 在组件扫描的时候能够发现并将其创建为上下文里的bean 当spring自动装配setDessert方法中的参数的时候 它并没有唯一 无歧义的可选值 spring无法选择会抛出 NoUniqueBeanDefinitopnException
@Autowired
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
@Component
public class IceCream implements Dessert {
}
@Component
public class Cookies implements Dessert{
}
@Component
public class IceCream implements Dessert {
}
3.3.1 @Primary 表示首选的bean
在声明bean的时候 通过将其中一个可选的bean设置为首选的bean能够避免自动装配时的歧义性
@Component
@Primary
public class Cookies implements Dessert{
}
或者显示声明
@Bean
@Primary
public Dessert coke(){
return new Coke();
}
当时当有2个首选bean的时候 就带来了新的歧义性
3.3.2 @Qualifier 限定自动装配的bean
@Qualifier指向的是组件扫描时所创建的bean 并且这个bean是cookie的实例
@Autowired
@Qualifier("cookie")
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
注解化
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cookie {
}
@Autowired
@Cookie
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
3.4 bean的作用域
在默认情况下Spring应用上下文中所有bean都是作为以单例( singleton) 的形式创建的。不管给定的一个bean被注入到其他bean多少次 每次所注入的都是同一个实例
Spring定义了多种作用域 可以基于这些作用域创建bean包括
单例( Singleton) : 在整个应用中, 只创建bean的一个实例
原型( Prototype) : 每次注入或者通过Spring应用上下文获取的时候, 都会创建一个新的bean实例
会话( Session) : 在Web应用中, 为每个会话创建一个bean实例
请求( Rquest) : 在Web应用中, 为每个请求创建一个bean实例
单例是默认的作用域 如果选择其他的作用域, 要使用@Scope注解
@Autowired
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
3.4.1 使用会话和请求作用域
在Web应用中, 如果能够实例化在会话和请求范围内共享的bean, 那将是非常有价值的事情例如, 在典型的电子商务应用中, 可能会有一个bean代表用户的购物车。 如果购物车是单例的话, 那么将会导致所有的用户都会向同一个购物车中添加商品。
@Autowired
@Scope(value = WebApplicationContext.SCOPE_SESSION,proxyMode = ScopedProxyMode.INTERFACES)
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
将value设置成了WebApplicationContext中的SCOPE_SESSION常量(它的值是session) 这会告诉Spring为Web应用中的每个会话创建一个bean实例。 这会创建多个 bean的实例, 但是对于给定的会话只会创建一个实例, 在当前会话相关的操作中, 这个bean实际上相当于单例的
@Scope同时还有一个proxyMode属性 这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题 因为Dessert 是一个单例的bean 会在Spring应用上下文加载的时候创建。 当它创建的时候,Spring会试图将Dessert注入到setDessert方法中
但是Dessert bean是会话作用域的, 此时并不存在。 直到某个用户进入系统, 创建了会话之后, 才会出现Dessert实例。 系统中将会有多个Dessert实例 每个用户一个
我们并不想让Spring注入某个固定的Dessert实例到setDessert方法中 Spring并不会将实际的Dessertbean注入到setDessert中 Spring会注入一个到Dessertbean的代理 这个
代理会暴露与Dessertbean相同的方法,如果setDessert是接口而不是类的话, 这是可以的(也是最为理想的代理模式)如果bean类型是具体类的话我们必须要将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS, 以此来表明要以生成目标类扩展的方式创建代理。
@Autowired
@Scope(value = WebApplicationContext.SCOPE_SESSION,proxyMode = ScopedProxyMode.TARGET_CLASS)
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
3.5.1 @PropertySource 注入外部的值
PropertySource引用了类路径中一个名为app.properties的文件这个属性文件会加载到Spring的Environment中
@PropertySource("classpath:/com/app.properties")
public class Coke implements Dessert{
@Autowired
Environment environment;
@Autowired
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
}
public void setDessert(Dessert dessert){
environment.getProperty("name"); // 获取属性值
environment.getProperty("name","默认值"); // 获取属性值
}