Spring实战笔记——高级装配
环境与profile
- Dev: 开发环境
- QA:测试环境
- prod:生产环境
配置profile bean
软件开发过程中,将应用程序从一个环境迁移到另外一个环境中使一个很大的挑战,使用@profile可以根据环境来决定是否创建bean。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
public class DataSource {
@Bean
@Profile("dev")
public void embeddedDateBulider(){
//代码
}
@Bean
@Profile("prod")
public void B(){}
}
注:@Profile可以用在类级别上,表示该类上的bean只有在特定环境下激活,也可以与@Bean结合用在方法上,表示该方法返回的bean在特定环境下被激活
在xml中配置profile
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
profile="dev"
>
<!-- 该xml文件中的bean都在特定环境下被激活 -->
</beans>
将所有的profile bean定义在一个xml文件中
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<beans profile="dev">
<!-- 开发环境 -->
</beans>
<beans profile="qa">
<!-- 测试环境 -->
</beans>
<beans profile="prod">
<!-- 生产环境 -->
</beans>
</beans>
激活profile
利用spring.profiles.active和spring.profiles.default激活profile,spring会先查看spring.profiles.active的属性然后再查看spring.profiles.default属性,如果两个都没有设置则,没有激活的profile,设置以上两种属性的方式有:
- 作为DispatcherServlet的初始化参数
- 作为Web应用的上下文参数
- 作为JNDI(Java 命名与目录接口)条目
- 作为环境变量
- 作为JVM的系统属性
- 再集成测试类上,使用@ActiveProfiles注解设置
作为DispatcherServlet的初始化参数,主要实在Web应用中的web.xml文件中定义
<!-- 为上下文配置默认的profile -->
<context-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</context-param>
<servlet>
<!-- 为Servlet设置默认的profile-->
<init-param>
<param-name>spring.profiles.default</param-name>
<param-value>dev</param-value>
</init-param>
</servlet>
条件化的bean
使用@Conditional注解可以实现只有满足特定条件才创建bean
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
public class MagicBean {
@Bean
@Conditional(MagicExistsCondition.class)//只有当magic存在时创建
public MagicBean magicBean(){
return new MagicBean();
}
}
MagicExistsCondition是自定义的类,需要实现Condition接口
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import java.lang.annotation.Annotation;
public class MagicExistsCondition implements Condition {
//重写matches方法
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
return env.containsProperty("magic");
}
}
ConditionContext是一个接口其中包含的方法有:
package org.springframework.context.annotation;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
/**
* Context information for use by {@link Condition}s.
*
* @author Phillip Webb
* @since 4.0
*/
public interface ConditionContext {
/**
* Return the {@link BeanDefinitionRegistry} that will hold the bean definition
* should the condition match or {@code null} if the registry is not available.
* @return the registry or {@code null}
*/
//检查bean定义
BeanDefinitionRegistry getRegistry();
/**
* Return the {@link ConfigurableListableBeanFactory} that will hold the bean
* definition should the condition match or {@code null} if the bean factory
* is not available.
* @return the bean factory or {@code null}
*/
//检查bean是否存在
ConfigurableListableBeanFactory getBeanFactory();
/**
* Return the {@link Environment} for which the current application is running
* or {@code null} if no environment is available.
* @return the environment or {@code null}
*/
Environment getEnvironment();
/**
* Return the {@link ResourceLoader} currently being used or {@code null}
* if the resource loader cannot be obtained.
* @return a resource loader or {@code null}
*/
//返货ResourceLoader所加载的资源
ResourceLoader getResourceLoader();
/**
* Return the {@link ClassLoader} that should be used to load additional
* classes or {@code null} if the default classloader should be used.
* @return the class loader or {@code null}
*/
//检查类是否存在
ClassLoader getClassLoader();
}
AnnotatedTypeMetadata也是一个接口可以@Bean注解的方法上是否还有其他注解
package org.springframework.core.type;
import java.util.Map;
import org.springframework.util.MultiValueMap;
/**
* Defines access to the annotations of a specific type ({@link AnnotationMetadata class}
* or {@link MethodMetadata method}), in a form that does not necessarily require the
* class-loading.
*
* @author Juergen Hoeller
* @author Mark Fisher
* @author Mark Pollack
* @author Chris Beams
* @author Phillip Webb
* @author Sam Brannen
* @since 4.0
* @see AnnotationMetadata
* @see MethodMetadata
*/
public interface AnnotatedTypeMetadata {
/**
* Determine whether the underlying element has an annotation or meta-annotation
* of the given type defined.
* <p>If this method returns {@code true}, then
* {@link #getAnnotationAttributes} will return a non-null Map.
* @param annotationName the fully qualified class name of the annotation
* type to look for
* @return whether a matching annotation is defined
*/
//判断带有@Bean注解的方法是不是还有其他特定的注解
boolean isAnnotated(String annotationName);
/**
* Retrieve the attributes of the annotation of the given type, if any (i.e. if
* defined on the underlying element, as direct annotation or meta-annotation),
* also taking attribute overrides on composed annotations into account.
* @param annotationName the fully qualified class name of the annotation
* type to look for
* @return a Map of attributes, with the attribute name as key (e.g. "value")
* and the defined attribute value as Map value. This return value will be
* {@code null} if no matching annotation is defined.
*/
Map<String, Object> getAnnotationAttributes(String annotationName);
/**
* Retrieve the attributes of the annotation of the given type, if any (i.e. if
* defined on the underlying element, as direct annotation or meta-annotation),
* also taking attribute overrides on composed annotations into account.
* @param annotationName the fully qualified class name of the annotation
* type to look for
* @param classValuesAsString whether to convert class references to String
* class names for exposure as values in the returned Map, instead of Class
* references which might potentially have to be loaded first
* @return a Map of attributes, with the attribute name as key (e.g. "value")
* and the defined attribute value as Map value. This return value will be
* {@code null} if no matching annotation is defined.
*/
Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);
/**
* Retrieve all attributes of all annotations of the given type, if any (i.e. if
* defined on the underlying element, as direct annotation or meta-annotation).
* Note that this variant does <i>not</i> take attribute overrides into account.
* @param annotationName the fully qualified class name of the annotation
* type to look for
* @return a MultiMap of attributes, with the attribute name as key (e.g. "value")
* and a list of the defined attribute values as Map value. This return value will
* be {@code null} if no matching annotation is defined.
* @see #getAllAnnotationAttributes(String, boolean)
*/
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);
/**
* Retrieve all attributes of all annotations of the given type, if any (i.e. if
* defined on the underlying element, as direct annotation or meta-annotation).
* Note that this variant does <i>not</i> take attribute overrides into account.
* @param annotationName the fully qualified class name of the annotation
* type to look for
* @param classValuesAsString whether to convert class references to String
* @return a MultiMap of attributes, with the attribute name as key (e.g. "value")
* and a list of the defined attribute values as Map value. This return value will
* be {@code null} if no matching annotation is defined.
* @see #getAllAnnotationAttributes(String)
*/
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);
}
@Profile注解也是基于@Conditional和Condition实现的
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.ConfigurableEnvironment;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();
}
//ProfileCondition类的实现
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (context.getEnvironment() != null) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
return true;
}
}
return false;
}
}
return true;
}
}
处理自动装配的歧义性
假设有一个Dessert接口,并且有三实现类,这三个类都使用了@Component注解,当spring自动装配setDessert()中的Dessert时会报错,因为Dessert不是唯一的bean
@Component
public class Cake implements Dessert{...}
@Component
public class Cookies implements Dessert{...}
@Component
public class IceCream implements Dessert{...}
//报错
@Autowrited
public void setDessert(Dessert dessert){...}
标记首选bean
可以使用@Primary表示该bean为首选,只能存在一个,如果存在多个@Primary,这会带来新的歧义性
@Component
@Primary
public class Cake implements Dessert{...}
XML配置文件中定义primary
<bean id = "iceCream"
class = "com.desserteater.IceCream"
primary="true"/>
### 限定自动装配的bean
使用@Qualifier注解可以在所有可选的bean上进行缩小范围的操作,@Qualifier可以和@Autowired和@Inject协同使用,例如:
@Autowirde
@Qualifier("iceCream")//会注入bean ID为iceCream的bean
public void setDessert(Dessert dessert){
this.dessert = dessert
}
**注:**`@Qualifier("iceCream")`所引用的bean要具有String类型的`iceCream`作为限定符。如果没有指定其他的限定符的话,所有的bean都会给定一个默认的限定符,该限定符与bean ID相同。
当对代码重构时可能会修改类名,而setDessert()方法的限定符与要注入的bean的名称时高度耦合的,修改类名会导致错误。
利用@Qualifier和@Component相组合可以组合成类的限定符,此时可以任意修改类名,并且不会破坏自动装配
@Component
@Qualifier("soft")
public class Cake implements Dessert{...}
//自动注入
@Autowirde
@Qualifier("soft")
public void setDessert(Dessert dessert){
this.dessert = dessert
}
当两个限定符的内容一样时,即有两个`@Qualifier("soft")`。会出现新的歧义,因此可以利用自定义注解解决该问题。自定义注解的方法如下所示:
```java
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold{}
//代替@Qualifier("creamy")
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cream{}
//定义IceCream类
@Component
@Cold
@Cream
public class IceCream implements Dessert{...}
//注入bean
@Autowirde
@Cold
@Cream
public void setDessert(Dessert dessert){
this.dessert = dessert
}
bean的作用域
Spring定义的作用域:
- 单例(Singleton):在整个应用中知创建一个bean实例(默认的作用域)
- 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例
- 会话(Session):在Web应用中,为每个会话创建一个bean实例
- 请求(Request):在Web应用中,为每个请求创建一个bean实例
利用@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)或@Scope("prototype")注解可以更改作用域,可以域@Component或@Bean一起使用。
在XML配置文件中可以利用<bean id = "notepad" class = "com.myapp.Notepad" scope = "prototype">。
使用会话和请求作用域
WebApplicationContext.SCOPE_SESSION指定作用域为session,为每一个会话创建一个,ScopedProxyMode.INTERFACES用来解决将会话或者作用域的bean注入到单例bean所遇到的问题。
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION,
proxyMode = ScopedProxyMode.INTERFACES
)
public interface Dessert {
}
//将会话或者作用域的bean注入到单例bean
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CakeService {
@Autowired
public void setCake(Dessert dessert){};
}
单例的bean会在Spring应用上下文加载的时候创建,当它创建的时候,Spring会试图将Dessert bean注入到setCake中,但是Dessert bean只有创建会话之后才会创建,因此会将Dessert bean的代理注入到单例bean中。proxyMode = ScopedProxyMode.INTERFACES会实现Dessert接口,并将调用委托个实现bean。如果bean类不是接口而是实现类需要设置为ScopedProxyMode.TARGET_CLASS。
请求作用域的bean同样需要以作用代理的方式进行注入

在XML声明作用域代理
<bean id = "de" class="Dessert"
scope="session"><!-- 定义 scope="session"需要在web.xml中做一下设置打开session机制-->
<aop:scoped-proxy proxy-target-class="false"/><!-- 默认为true,即目标bean为具体类 -->
</bean>
运行时注入
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import ruian.BlankDisc;
@Configuration
@PropertySource("classpath:app.properties")//指定配置文件
public class ExpressiveConfig {
@Autowired
Environment env;
@Bean
public BlankDisc disc(){
return new BlankDisc(env.getProperty("disc.title"), env.getProperty("disc.artist"));
}
}
配置文件内容
disc.title=title
disc.artist=artist
也可以使用占位符注入
public BlankDisc(
@value("$(disc.title)") String title
){
this.title = title;
}
//配置占位符
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer(){
return new PropertySourcesPlaceholderConfigurer();
}
XML中配置占位符
<context:property-placeholder/>
Spring表达式语言进行装配
Spring表达式语言(SpEL)的特性:
- 使用bean ID来引用bean;
- 使用方法和访问对象的属性
- 对值进行算术、关系和逻辑运算
- 正则表达式匹配
- 集合操作
格式为#{}
#{T(System).currentTimeMillis()},输出当前时间,T()表达式,将java.lang.System视为Java中对应的类型。
引用bean、属性和方法
#{sgtPeppers.artist}sgtPeppers为bean id
浙公网安备 33010602011771号