新闻资讯
Spring基础——控制反转(IOC)(二)
3.1.2 激活Profile
Spring 在确定哪个 Profile 处于激活状态时,需要依赖两个独立的属性:spring.profiles.active和spring.profiles.default。前者会根据指定值来确定哪个 Profile 是激活的;后者是当没有指定active属性的值时,默认激活的 Profile。Spring 中设置这两个属性的方式
- 作为 DispatcherServlet 的初始化参数;
- 作为 Web 应用的上下文参数;
- 作为 JNDI 条目;
- 作为环境变量;
- 作为 JVM 的系统属性;
- 在记成测试类上,使用@ActiveProfiles注解设置;
下面的例子中,使用 DispatcherServlet 的参数将spring.profiles.default设置 profile。在 Web 应用中,设置spring.profiles.default的 web.xml 文件如下
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--为上下文设置默认的 profile--> <context-param> <param-name>spring.profiles.default</param-name> <param-value>dev</param-value> </context-param> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class>cn.book.main.servlet.DispatcherServlet</servlet-class> <!--为Servlet设置默认的 profile--> <init-param> <param-name>spring.profiles.default</param-name> <param-value>dev</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/appServlet</url-pattern> </servlet-mapping> </web-app>
注意
spring.profiles.active和spring.profiles.default属性中,profile 使用的是复数形式,可以同时激活多个 Profile——通过列出多个 profile 名称,并以逗号分隔。
3.2 条件化 Bean
如果我们定义的 bean ,但不希望它们被 Spring 容器即刻被创建,而是希望当类路径下包含某个库,或者是创建了其它 Bean,亦或者要求设置了某个特定环境变量后,该 Bean 才被创建。此时,我们就需要使用条件化配置。
要实现一个条件化 Bean,在装配 Bean 的方法上( 使用@Bean),引用另一个注解@Conditional(*.class),注意:括号内给定的是一个类文件。该注解会根据括号内给定类的返回结果判断是否创建 Bean,如果为true,会创建 Bean,否则不创建。
但是,这只是定义了一个要条件化的 Bean,该 Bean 需要满足怎样的条件,需要自己实现。上面说到,@Conditional注解需要传入一个类文件,该类在创建时,要实现Condition接口,并重写matches()方法。下面是一个简单的例子
package cn.book.main.pojo; //Bean 类 public class TestCondition { public TestCondition() {
System.out.println("Bean 被创建了");
}
}
该类实现Condition接口,并重写matches()方法,在方法内可以编写判断代码,并返回 boolean 值。
package cn.book.main.condition; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; public class IfCreatCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { return false;
}
}
配置类,装配 Bean。
package cn.book.resource; import cn.book.main.condition.IfCreatCondition; import cn.book.main.pojo.TestCondition; import org.springframework.context.annotation.*; @Configuration public class HumanJobConfig { @Bean @Conditional(IfCreatCondition.class) public TestCondition getCondition(){ return new TestCondition();
}
}
测试类,如果 IfCreatCondition 类返回 true,则 Bean 被创建;否则不会被创建。
package cn.book.test; import cn.book.resource.HumanJobConfig; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=cn.book.resource.HumanJobConfig.class) public class HumanJobTest { @Test public void Test(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(HumanJobConfig.class);
}
}
上面只是演示了实现条件化 Bean 的流程,我们的条件可以再复杂。大家应该注意到了,matches()中有两个参数:ConditionContext和AnnotatedTypeMetadata。通过这两个对象,我们可以实现符合 IOC 和 DI 的条件。接下来,就来了解这两个对象:
ConditionContext是一个接口,它有以下方法
方法 | 描述 |
---|---|
getRegistry | 返回 BeanDefinitionRegistry 检查 bean 定义; |
getBeanFactory | 返回 ConfigurableListableBeanFactory 检查 bean 是否存在,甚至 检查 bean 的属性; |
getEnvironment | 返回 Environment 检查环境变量是否存在以及它的值是什么; |
getResourceLoader | 返回 ResourceLoader 所加载的资源; |
getClassLoader | 返回 ClassLoader 加载并检查类是否存在; |
AnnotatedTypeMetadata也是一个接口,能够检查带有@Bean注解的方法上还有什么注解。它有以下方法:
方法 | 描述 |
---|---|
boolean isAnnotated(String annotationType) | 检查带@Bean的方法上是否存在其它特定的注解 |
Map<String,Object> getAnnotationAttributes(String annotationType) | 获得指定注解的 Bean |
Map<String,Object> getAnnotationAttributes(String annotationType, boolean classValueAsString) | ==未了解== |
MultiValueMap<String,Object> getAllAnnotationAttributes(String annotationType) | 获得指定注解的所有 Bean |
MultiValueMap<String,Object> getAllAnnotationAttributes(String annotationType, boolean classValueAsString) | ==未了解== |
3.3 处理自动装配的歧义性
自动化装配中,仅当只有一个 Bean 满足时,才能装配成功。当多个 bean 满足装配时,Spring 会产生异常:NoUniqueBeanDefinitionException。最常见的情况是:==当一个接口有多个实现类,调用时使用接口对象引用子类==。
比如:Human接口有两个实现类:Man类和 Woman类
package cn.book.main.entity; public interface Human {
}
package cn.book.main.entity; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @Component public class Man implements Human { public Man() {
System.out.println("I am man");
}
}
package cn.book.main.entity; import org.springframework.stereotype.Component; @Component public class Woman implements Human { public Woman() {
System.out.println("I am woman");
}
}
配置类
package cn.book.resource; import cn.book.main.pojo.TestCondition; import org.springframework.context.annotation.*; @Configuration @ComponentScan("cn.book.main.entity") public class HumanConfig {
}
测试类,自动注入一个Human接口。此时,spring会产生:NoUniqueBeanDefinitionException。
package cn.book.test; import cn.book.main.entity.Human; import cn.book.resource.HumanJobConfig; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=cn.book.resource.HumanJobConfig.class) public class HumanJobTest { @Autowired private Human human; @Test public void Test(){
System.out.println(human.getClass());
}
}
当确实发生装配的歧义性时,Spring 提供了以下方案:
- 将可选 Bean 中的某一个设为首选(primary) 的 Bean;
- 使用限定符(qualifier)限定到符合的、唯一的 Bean;
3.3.1 标示首选 Bean
标示首选需要使用关键字 primary,它在 JavaConfig 中是注解@Primary,在 XML 是bean元素中的属性 primary。
JavaConfig 配置
@Primary注解配合@Component和@Bean注解组合使用,在需要设置为首选的组件类和 Bean 对象上。
与@Component注解配合使用
@Component @Primary public class Man{
}
或者,与@Bean注解配合使用
@Configuration public class JavaConfig{ @Bean @Primary public Human getMan(){ return new Man();
}
}
在 XML 中设置 Bean 为首选项的配置为:
<bean id="man" class="Man" primary="true"/>
缺点
- 不能设置多个首选 Bean;
- 不够灵活,存在歧义性时,只能装配使用设置首选的Bean;
3.3.2 限定符限定装配
限定符@qualifier注解,主要作用是在可选的 Bean 进行缩小范围选择,直到找到满足的 Bean。它的有两个作用:
-
与@Autowired和@Inject协同使用,在注入的时候指定想要注入的是哪个 Bean;
@qualifier("")括号内所设置的参数时要注入 Bean 的 ID 值。
-
与@Component和@Bean协同使用,为 Bean 设定限定符;
@qualifier("")括号内是为 Bean 设置的限定符,在注入时使用qualifier中引用。
3.3.3 限定符注解
如果使用注解@qualifier限定符依旧无法解决 bean 的装配歧义性问题时,而且,在 Spring 中无法重复使用相同的@qualiifer注解,在这种情况下,可以自定义注解来区分 bean。那么,如何自定义注解呢?
import org.springframework.beans.factory.annotation.Qualifier; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.CONSTRUCTOR,ElementType.FIELD,
ElementType.METHOD,ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface 注解名 {
}
注意:
- 自定义注解不能使用在类上;
- 使用自定义注解时,需要同时放在 声明Bean的地方 和 注入 Bean 的地方;
4、Bean 作用域
在默认情况下,Spring 应用上下文中所有的 Bean 都是以单例形式创建的。也就是,不管一个 Bean 被注入多少次,每次注入的 Bean 都是同一个实例。
如果一个实例需要保持无状态并在应用中重复使用,单例作用域是不可行且不安全的。在 Spring 定义了多种作用域,Spring 会基于这些作用域创建 Bean,这些作用域包括:
- 单例(Singleton):在整个应用,只会创建 Bean 的一个实例;
- 原型(Prototype):每次注入或通过 Spring 应用上下文获取时,都会创建一个新的 Bean 实例;
- 会话(Session):在 Web 应用中,为每个会话创建一个 Bean 实例;
- 请求(Request):在 Web 应用中,为每个请求创建一个 Bean 实例;
单例是默认的作用。如果想要选择其它作用域,要使用@Scope注解。注解内使用以下表示作用域的参数:
- ConfigurableBeanFactory.SCOPE_PROTUTYPE或者"prototype";
- ConfigurableBeanFactory.SCOPE_SESSION或者"session";
- ConfigurableBeanFactory.SCOPE_REQUEST或者"request";
如果使用 XML 配置,在<bean>元素中的属性scope设置 bean 的作用域。
4.1 会话和请求作用域
==学习到 Web 部分内容再深入学习==
5、运行时值注入
前面在装配 Bean,讲到在创建 Bean 时,将常量(比如int类型、String类型)直接给定,这是将值硬编码到 Bean 中。有时,为了避免硬编码值,想让这些值在运行时在确定,Spring 提供了两种在运行时求值的方式:
- 属性占位符
- Spring 表达式语言
5.1 注入外部值
回顾一下,在我们使用 JDBC 时,会创建一个属性文件*.properties文件放置连接数据库所需的配置参数。假设,在 Spring 中该文件依旧存在,我们如何在配置类或配置文件中解析并取值?
JavaConfig 配置类
- 通过注解@PropertySource中的value属性设置属性文件路径;
- 自动注入 Environment 对象;
- 通过 Environment 对象获取属性值;
@Configuration @PropertySource(value = "classpath:/JDBC.properties") public class JdbcConfig { @Autowired Environment env; @Bean public JdbcParams getJdbc(){ return new JdbcParams(
env.getProperty("jdbc.driver"),
env.getProperty("jdbc.url"),
env.getProperty("jdbc.username"),
env.getProperty("jdbc.password")
);
}
}
public class JdbcParams { private String driver; private String url; private String username; private String password; public JdbcParams() {
} public JdbcParams(String driver, String url, String username, String password) { this.driver = driver; this.url = url; this.username = username; this.password = password;
}
}
Environmen 接口的用法,通过 Environment 接口可以调用以下方法:
方法 | 描述 |
---|---|
String getProperty(String key) | 根据指定值获取属性,属性没有定义返回null; |
String getProperty(String key, String defaultValue) | 根据指定值获取属性,如果没有属性值,则返回defaultValue; |
T getProperty(String key, Class<T> type) | 返回指定类型的属性值;type为指定类型的.class |
T getProperty(String key, Class<T> type,T defaultValue) | 返回指定类型的属性值;type为指定类型的.class,如果没有属性值,则返回defaultValue; |
getRequiredProperty(String key) | 根据指定值获取属性,属性没有定义抛出异常; |
containProperty(String key) | 检查属性文件是否存在某个属性; |
T getPropertyAsClass(String key,Class<T> type) | 将属性文件解析为指定的类文件; |
String[] getActiveProfiles() | 返回激活 profile 名称的数组; |
String[] getDefaultProfiles() | 返回默认 profile 名称的数组; |
boolean acceptsProfiles(String... profiles) | 如果 environment 支持给定的 profile 的话,就返回 true; |
5.2 占位符注入值
Spring 支持将属性定义到外部的属性文件中,并使用占位符将值插入到 Bean 中。在 Spring 装配中,占位符的形式为使用${...}包装的属性名称。
为了使用占位符,需要配置一个PropertySourcePlaceholderConfigurerBean,它能够基于 Environment 及其属性源来解析占位符。下面来看看,JavaConfig 配置和 XMl 配置中使用占位符的用法
import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; @Configuration //声明属性源,并将属性文件加载到Spring @PropertySource(value = "classpath:/JDBC.properties") public class StudentCongif {
二、 //(1)使用占位符解析属性 @Bean public JdbcParams getJdbc(
@Value("${jdbc.driver}") String driver,
@Value("${jdbc.url}") String url,
@Value("${jdbc.username}") String username,
@Value("${jdbc.password}") String password){ return new JdbcParams(driver,url,username,password);
} //(2)还需要配置一个PropertySourcesPlaceholderConfigurer 的 bean @Bean public PropertySourcesPlaceholderConfigurer placeholderConfigurer(){ return new PropertySourcesPlaceholderConfigurer();
}
}
<!--创建 PropertySourceHolderConfigurer --> <context:property-placeholder location="classpath:/JDBC.properties"/> <!-- 使用占位符进行值注入--> <bean id="jdbc" class="cn.book.main.valueInject.JdbcParams" c:driver="${jdbc.driver}" c:url="${jdbc.url}" c:username="${jdbc.username" c:password="${jdbc.password}"/>
解析外部属性能够将值的处理推迟到运行时,但它的关注点在于根据名称解析来自 Spring Environment 和属性源的属性。
回复列表