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

posted @ 2021-02-08 12:55  梦不是很远  阅读(67)  评论(0)    收藏  举报