引言
创建应用对象之间的协作关系的行为叫做装配,这也是依赖注入的本质
装配bean的三种可选方式
spring容器负责创建bean,通过DI来协调管理这些bean的关系,spring提供了三种方式来装配bean:
- 在XML中显式配置
- 在Java配置类中显式配置
- 隐式的bean方便机制和自动装配
这三种装配方式不是只能选择一种,可以选择多种搭配使用,以适应项目的需要。书籍中推荐的是自动装配,更加的便利。
自动化装配bean
spring如何实现自动化装配bean?
- 组件扫描:Spring会自动发现应用上下文中创建的bean。
- 自动装配: Spring会自动满足bean之间的依赖。
组件扫描
使用@ComponetScan注解,手动开启组件扫描,如果没有对@ComponetScan注解指定目录,默认扫描当前注解所在类的包中(包含子包)。在@ComponetScan注解中既可以指定目录,也可以指定类,指定类的话就是这个类所在的包作为基础包(包含子包)。
自动装配
简单来说自动装配就是让spring自动满足bean的依赖关系,也就是在一个bean中需要使用到另外的bean,spring会自动在spring应用上下文中找到满足要求的bean。为了声明要进行自动装配,可以使用@Autowired注解。
显示配置bean
准备使用第三方库的组件装配到自己的应用中的时候,就没办法使用@Autowired和@Component注解了,这个时候就需要使用XML或者使用java配置类的方式显示配置bean。
可以使用@Configuration注解和@Bean注解组合。spring会拦截所有对@Bean注解所修饰的方法的调用,并确保直接返回该方法所创建锅的bean,而不是每次都再走一遍创建的逻辑。
条件化的bean
如果希望一个bean在另外一个bean也声明了之后才创建,或者希望配置文件中有特定的配置属性才创建bean类似这种特定的要求,可以使用@Conditional注解,如果给定的条件为true,就会创建;如果为false,就不会创建。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
public interface ConditionContext {
BeanDefinitionRegistry getRegistry();
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
Environment getEnvironment();
ResourceLoader getResourceLoader();
@Nullable
ClassLoader getClassLoader();
}
@Conditional注解中接收的是一个数组,数组的元素类型是Condition接口的实现类。而Condition接口中只包含了一个返回布尔值的方法。
Conditional接口中提供了几个方法,可以获取到BeanDefinitionRegistry,ConfigurableListableBeanFactory,Environment,ResourceLoader,ClassLoader几个属性,借助于ConditionContext:
- 借助BeanDefinitionRegistry 可以检查bean定义
- 借助ConfigurableListableBeanFactory可以检查bean是否存在,甚至探查bean的属性
- 借助Environment可以检查环境变量是否存在以及它的值是什么
- 借助ResourceLoader可以读取加载的资源
- 借助ClassLoader可以加载并检查类是否存在
AnnotatedTypeMetadata 能够让我们检查带有@Bean注解的方法上还有什么其他的注解,AnnotatedTypeMetadata 也是一个接口,借助isAnnotated方法,我们能够判断带有@Bean注解的方法是不是还有其他特定的注解,借助AnnotatedTypeMetadata 接口的其他方法可以检查@Bean注解的方法上其他注解的属性。
从spring4开始,@Profile注解就是使用了@Conditional和Condition接口:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();
}
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
}
自动装配歧义
在使用@Autowired注解注入某一个bean的时候,如果有多个bean符合要求(同一个接口的多个实现类),这个时候spring包抛出NoUniqueBeanDefinitionException。这个时候可以使用@Primary注解来表示首选,或者使用@Qualifier注解来指定某一个。
bean的作用域
- singleton(单例):这是Spring的默认作用域。Spring容器在第一次获取Bean时创建实例,并在后续请求中返回同一个实例。这意味着对于所有的对象访问的都是同一个Bean实例。
- prototype(原型):对于每次请求都会创建一个新的Bean实例。这意味着每次通过Spring容器获取Bean时,都会得到一个新的实例。
- request:这个作用域仅在web应用程序中有效。对于每个HTTP请求,Spring都会创建一个新的Bean实例,并且这个Bean实例仅在当前HTTP请求内有效。当请求结束时,Bean实例会被销毁。
- session:这个作用域也仅在web应用程序中有效。对于每个HTTP Session,Spring都会创建一个新的Bean实例,并且这个Bean实例仅在当前HTTP Session内有效。当Session结束时,Bean实例会被销毁。
- application(全局作用域):这个作用域在web应用程序中表示在一个ServletContext内只有一个Bean实例。也就是说,这个Bean在整个web应用程序的生命周期内都是唯一的。
- websocket:这个作用域也在web应用程序中有效。Bean的生命周期绑定到WebSocket的生命周期上,这意味着WebSocket的每个连接都会有一个与之关联的Bean实例。
使用@Scope注解来为一个bean指定作用域,@Scope中有一个ScopedProxyMode属性,ScopedProxyMode是一个枚举,主要用于Spring框架中配置Bean的代理模式
public enum ScopedProxyMode {
DEFAULT,
NO,
INTERFACES,
TARGET_CLASS
}
NO 和 DEFAULT 的作用是一样的,都表示不使用代理,如果需要就直接创建Bean实例。
INTERFACES 表示为Bean的所有接口创建一个基于JDK的动态代理。
TARGET_CLASS 表示为Bean的类本身创建一个基于CGLIB的代理。
通过配置ScopedProxyMode,可以实现对Bean的访问控制和生命周期管理,以满足不同的业务需求。例如,在Web应用程序中,可能需要在不同的请求或会话中保持独立的Bean状态,这时就可以通过配置ScopedProxyMode来实现。
运行时值注入
使用Environment中配置的值
Environment中的方法:
// 获取配置值为string类型
String getProperty(String key);
// 为空则使用默认值
String getProperty(String key, String defaultValue);
// 转换为指定的类型
<T> T getProperty(String key, Class<T> targetType);
// 转换为指定的类型,并且有默认值
<T> T getProperty(String key, Class<T> targetType, T defaultValue);
// 不存在报错
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
// 是否包含配置key
boolean containsProperty(String key);
属性占位符
属性占位符号: ${},一般配合@Value注解使用
使用Spring表达式语言(SpEL)进行值装配
spring3引入了Spring表达式语言(SqEL),它能够以强大和简洁的方式将值装配到bean属性和构造器参数中,在这个过程中所使用的表达式会在进行时计算得到值。包括的特性有:
- 使用bean的ID来引用bean
- 调用方法和访问对象的属性
- 对值进行算数,关系和逻辑运算
- 正则表达式匹配
- 集合操作
SpEL表达式要放到 "#{…}"之中,类似的属性占位符要放到 "${…}"之中。
-
#{1}: 1
-
#{T(System).currentTimeMillis()}: 它的最终结果是表达式的那一刻的毫秒数。T()表达式会将java.lang.System视为Java中的类型,因此可调用其static修饰的currentTimeMillis方法。
-
#{beanID.field}: beanID通常就是类名全程,第一个字母小写,例如: #{dog.name},同样的,也可以调用bean的方法。
-
#{systemProperties[xxx.yy]} : 就是从配置文件中取出 xxx.yy的配置值