高级装配
一、环境与Profile
一)配置profile bean
环境的改变导致配置改变(需求:通过环境决定使用哪个bean),可以通过Spring的Profile解决。
Profile可以在程序运行时根据环境的改变决定使用哪个bean。所以一个部署单元能适应所有环境。
1.在Java文件中配置
@Configuration public class UserConfig { @Bean @Profile("dev") //dev激活时才创建使用该bean public User devUser() { return new User("devUser", 110); } @Bean @Profile("pro") public User proUser(){ return new User("proUser", 110); } }
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = UserConfig.class) @ActiveProfiles("dev") //指定测试时使用的bean public class TestAll { @Autowired private User user; @Test public void doSth() { System.out.println(user.getName() + " : " + user.getPhone()); //devUser : 110 } }
2.在xml中配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" profile="dev"> <bean class="entity.User"> <constructor-arg name="name" value="devUserXml"/> <constructor-arg name="phone" value="111"/> </bean> </beans>
方式二:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <beans profile="dev"> <bean class="entity.User"> <constructor-arg name="name" value="devUserXml2"/> <constructor-arg name="phone" value="11111"/> </bean> </beans> <beans profile="pro"> <bean class="entity.User"> <constructor-arg name="name" value="proUserXml2"/> <constructor-arg name="phone" value="222"/> </bean> </beans> </beans>
二)激活Profile
首先要配置两个独立的属性:
1.spring.profiles.active
2.Spring.profiles.default
有多种方式设置这两种属性,具体设置方式请查阅相关文档,注意:
如果设置成了spring.profiles.active后,spring.profiles.default设置成什么值都无所谓,系统优先使用前者的设置。
二、条件化的bean
如果我们要求:一个或者多个bean
1.在类路径下包含特定的的库时
2.在某个特定的bean声明之后
3.特定的环境变量设置之后
才创建。显然profile不能满足要求。
我们可以使用@Condtional注解。
@Configuration public class UserConfigWithCondition { @Bean //如果给定类中的matches方法返回true就创建此bean @Conditional(MyCondition.class) public User conditionUser() { return new User("conditionUser", 22); } }
/* * 必须实现Condition接口 * */ public class MyCondition implements Condition{ public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { return true; } }
public interface ConditionContext { //返回的类可检查bean的定义 BeanDefinitionRegistry getRegistry(); //返回类可检查bean是否存在,甚至探测其属性 @Nullable ConfigurableListableBeanFactory getBeanFactory(); //检查环境变量 Environment getEnvironment(); //探查ResourceLoader所加载的资源 ResourceLoader getResourceLoader(); @Nullable ClassLoader getClassLoader(); }
public interface AnnotatedTypeMetadata { //是否有给定名字的注解 boolean isAnnotated(String var1); @Nullable Map<String, Object> getAnnotationAttributes(String var1); @Nullable Map<String, Object> getAnnotationAttributes(String var1, boolean var2); @Nullable MultiValueMap<String, Object> getAllAnnotationAttributes(String var1); @Nullable MultiValueMap<String, Object> getAllAnnotationAttributes(String var1, boolean var2); }
三)处理自动装配的歧义性
如果有多个bean能够匹配结果的话,这种歧义性会阻碍Springle的自动装配。
解决方案:
1.指定首选的bean:
多个bean情况
@Configuration public class UserConfig { @Bean public User user() { return new User("firstUser", 1); } @Bean @Primary public User user2() { return new User("secondUser", 2); } @Bean public User user3() { return new User("thirdUser", 3); } }
@Autowired private User user; @Test public void doSth() { System.out.println(user.getName() + " : " + user.getPhone()); //secondUser : 2 }
多个实现类的情况:
public interface Schooler { void introduce(); } @Component @Primary public class Teacher implements Schooler{ public void introduce() { System.out.println("I'm a teacher."); } } @Component public class Student implements Schooler{ public void introduce() { System.out.println("I'm a student."); } } @Component public class Worker implements Schooler{ public void introduce() { System.out.println("I'm a worker." ); } }
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = AutoConfig.class) public class TestAll { @Autowired private Schooler schooler; @Test public void t() { schooler.introduce(); //I'm a teacher. } }
2.限定自动装配的Bean
1)根据bean的id
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = AutoConfig.class) public class TestAll { @Autowired @Qualifier("student") //根据bean的id来注入,这里使用的是默认id private Schooler schooler; @Test public void t() { schooler.introduce(); //I'm a student. } }
创建自定义限定符:
@Configuration public class UserConfig { @Bean @Qualifier("First") public User user() { return new User("firstUser", 1); } @Bean @Qualifier("Second") public User user2() { return new User("secondUser", 2); } @Bean @Qualifier("Third") public User user3() { return new User("thirdUser", 3); } }
@Autowired @Qualifier("First") private User user; @Test public void test() { System.out.println(user.getName()); //firstUser }
三)Bean的作用域
默认情况下,Spring应用中所有bean都是作为单例形式创建的。
Spring创建了多种作用域,可以基于这些作用域创建bean。
单例(Singleton):
原型(Prototype):每次注入或者通过上下文获取的时候,都会创建一个新的实例。
会话(Session):在Web应用中,为每个会话创建一个bean实例。
请求(request):在Web应用中,为每个请求创建一个bean实例。
其他略
四)运行时注入
为了注入字面量,且要避免硬编码(让这些值在运行时确定),Spring提供了两种运行时求值得方法:
1)属性占位符
2)Spring表达式语言(SpEl)
1.处理外部值
处理外部值最简单的方式是声明属性源并通过Spring的Environment来检索属性。
app.properties:
name = tang
@Configuration @PropertySource("classpath:/app.properties") public class UserConfig { @Autowired private Environment e; @Bean public User user() { return new User(e.getProperty("name"), 22); } }
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = UserConfig.class) public class TestAll { @Autowired User user; @Test public void t() { System.out.println(user.getName()); //tang } }
xml中配置:
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>app.properties</value> </list> </property> </bean> <bean class="entity.User" c:name="${aName}" c:phone="22"/>
如果我们使用组件扫描和和自动装配的话,可以使用@Value来实现:
@Component @PropertySource("classpath:/app.properties") public class Worker implements Schooler{ private String name; private int phone; public Worker(@Value("${name}") String name, @Value("${phone}") int phone) { this.name = name; this.phone = phone; } public void introduce() { System.out.println("I'm a worker. And my name is " + name); } }
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = AutoConfig.class) public class TestAll { @Autowired Worker worker; @Test public void t() { worker.introduce(); //I'm a worker. And my name is tang } }
2.使用Spring表达式语言进行装配
Spring Expression Language(SqEl)能将值装配到bean属性和构造器参数中,表达式会在运行时得到值。
在装配bean的时候如何使用这些表达式:
1)通过XML使用:
<bean id="aUser" class="entity.User"> <constructor-arg name="name" value="Peter"/> <constructor-arg name="id" value="222"/> </bean> <bean id="sys" class="entity.System"> <property name="name" value="aSystem"/> <property name="user" value="#{aUser}"/> </bean>
2)通过自动扫描:
@Component("userBean") public class User { @Value("tom") private String name; @Value("111") private int id; public User(String name, int id) { this.name = name; this.id = id; } }
@Component("sys") public class System { @Value("#{userBean}") private User user; @Value("TarSys") private String name; @Override public String toString() { return "System " + name + " have a user named " + user.getName(); } }
基础表达式:
1)表示字面量
#{3.14159}
#{9.18E4}
#{‘Hello’}
#{true}
2)引用bean的属性方法
#{user.name}
#{user.getName()}
#{user.getName().toUpperCase()}
#{user.getName().?toUpperCase()} //保证左边的值不为null,如果为null不调用右边的方法,直接返回null
3)表达式中使用类型
#{T(java.lang.Math).random()}
4)spEl运算符
略