IOC 控制反转
一. 属性赋值
- 传统 xml 方式赋值,通过 property 标签, name为属性名, value为属性值
<bean id="hh" class="com.ssm.entity.HelpCategory" lazy-init="false" >
<property name="name" value="aaaa"/>
<property name="url" value="bbbb"/>
</bean>
- 传统方式中,xml 读取 运行环境中, .properties 文件夹中定义的变量,需要先设置要读取变量的 .properties文件加载到项目中, 然后使用 “${读取变量在文件中的名字}”,读取
@Value() 注解赋值
使用 @Value 注解修饰向 向容器中注入的 bean 的属性,注解中传递该属性的数据,可以对属性赋值
@Value 注解赋值的几种方式
- 直接在括号中输入数据 : @Value(“变量值”)
- 在括号中输入 Spring 的 EL 表达式(EL 表达式是可以实现运算功能的) : @Value("#{11-1}")
- 在括号中使用
获
取
配
置
文
件
中
的
值
:
@
V
a
l
u
e
(
"
{} 获取配置文件中的值 : @Value("
获取配置文件中的值:@Value("{配置文件中需要获取的变量的变量名}");
注意点: 在使用@Value("${}") 读取配置文件中的变量时,需要先将properties 配置文件获取到项目中
@PropertySource 注解导入配置文件到项目中
该注解可以需要一个String 数组, 数组中的每个字符串变量代表一个配置文件,可以输入类路径"classpath:/",也可以输入文件路径"file:/"
- 创建配置类,@PropertySource 配置导入项目的配置文件,编写向容器中注入 Person 的方法
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
//通过 @PropertySource 配置 resources 文件夹中的 properties文件夹中
//下的"jdbc.properties"配置文件导入到项目
@PropertySource(value = {"classpath:/properties/jdbc.properties"})
@Configuration
public class MyConfiguration {
//Persion 注入到容器中
@Bean
public Persion configMethod1(){
return new Persion();
}
}
- Person 类中,通过 @Value 注解对属性赋值
import org.springframework.beans.factory.annotation.Value;
public class Person {
@Value("男") //直接赋值
private String sex;
@Value("${jdbc.username}") //读取配置文件
private String name;
@Value("#{20-2}") //el表达式
private String age;
@Override
public String toString() {
return "Persion{" +
"sex='" + sex + '\'' +
", name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
- 运行测试,当配置文件加载到项目中后,通过 Spring 容器对象,获取运行环境,也可以读取到配置文件中的数据
public static void main(String[] args) {
//1.获取ioc容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
//2.获取运行时环境
ConfigurableEnvironment environment = context.getEnvironment();
//3.获取导入到环境中.properties配置文件中指定名称的变量
String url = environment.getProperty("jdbc.url");
System.out.println(url);
//4.获取 bean
Person p = (Person) context.getBean("configMethod1");
System.out.println(p);
//5.关闭容器
context.close();
}
二. 自动装配
什么是自动装配: Spring 利用依赖注入,完成对 IOC 容器各个组件的依赖关系,以实际开发为例,Controller 中会用到 Service, Service 中会用到对应数据库的 Dao, 进而实现对数据的增删改查,将需被使用的类声明为使用该类的属性,通过一个注解将被使用的类,在容器中获取赋值给使用者的这个属性,例如 @Autowired
@Autowired
- @Autowired 可以用来修饰: 属性,以属性类型去容器中获取,修饰方法以方法形参类型去容器中获取,修饰参数, 修饰构造器,提供有参构造以构造器形参类型获取注入,并且当向容器中注入一个只有有参构造器的对象,在不使用@Autowired的情况下,默认会通过构造器的形参在容器中获取对应的bean赋值给注入的bean
- 当使用@Autowired 在容器中获取 bean,赋值给某个属性时,首先会按照类型去容器中获取,如果容器中有多个,会将属性名做为id 去容器中查找,
- 在使用@Autowired,如果想指定以属性名去为id去容器中查找,在使用@Autowired的同时,添加@Qualifier(“id名称”)注解,指定id获取
- @Autowired 修饰的属性,默认是容器中必须存在的,如果不存在则报错,在某些特定的场景,想要容器中有则进行装配,若没有则不装配,可以通过设置@Autowired注解的required属性值为false来设置
- @Autowired 实现原理: Spring在启动时会向容器中注入一个AutowiredAnnotationBeanPostProcessor实现了MergedBeanDefinitionPostProcessor 的bean,后续创建其它bean时,通过重写的该接口中的方法,最终会执行到一个buildAutowiringMetadata() 方法,通过该方法扫描,如果带有@Autowired注解,则将依赖注入信息封装到InjectionMetadata中
参考大神的
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
LinkedList<InjectionMetadata.InjectedElement> elements = new LinkedList<>();
Class<?> targetClass = clazz;//需要处理的目标类
do {
final LinkedList<InjectionMetadata.InjectedElement> currElements = new LinkedList<>();
/*通过反射获取该类所有的字段,并遍历每一个字段,并通过方法findAutowiredAnnotation遍历
每一个字段的所用注解,并如果用autowired修饰了,则返回auotowired相关属性*/
ReflectionUtils.doWithLocalFields(targetClass, field -> {
AnnotationAttributes ann = findAutowiredAnnotation(field);
if (ann != null) {//校验autowired注解是否用在了static方法上
if (Modifier.isStatic(field.getModifiers())) {
if (logger.isWarnEnabled()) {
logger.warn("Autowired annotation is not supported on static fields: " + field);
}
return;
}//判断是否指定了required
boolean required = determineRequiredStatus(ann);
currElements.add(new AutowiredFieldElement(field, required));
}
});
//和上面一样的逻辑,但是是通过反射处理类的method
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
if (logger.isWarnEnabled()) {
logger.warn("Autowired annotation is not supported on static methods: " + method);
}
return;
}
if (method.getParameterCount() == 0) {
if (logger.isWarnEnabled()) {
logger.warn("Autowired annotation should only be used on methods with parameters: " +
method);
}
}
boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new AutowiredMethodElement(method, required, pd));
}
});
//用@Autowired修饰的注解可能不止一个,因此都加在currElements这个容器里面,一起处理
elements.addAll(0, currElements);
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
return new InjectionMetadata(clazz, elements);
}
然后执行,进行注入
@Override
public PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
}
catch (BeanCreationException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
}
return pvs;
}
它调用的方法是InjectionMetadata中定义的inject方法
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Collection<InjectedElement> checkedElements = this.checkedElements;
Collection<InjectedElement> elementsToIterate =
(checkedElements != null ? checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) {
for (InjectedElement element : elementsToIterate) {
if (logger.isTraceEnabled()) {
logger.trace("Processing injected element of bean '" + beanName + "': " + element);
}
element.inject(target, beanName, pvs);
}
}
}
@Primary 设置装配优先级
假设,有两个相同类型的 bean 都要装配到容器中,但是有级别要求,可以通过 @Primary 修饰注入的 bean 或注入bean 的方法,首选将这个bean注入到容器中
@Resource 与 @Inject 实现装配功能
- @Resource 是 java 提供的装配注解, 与 @Autowired 的不同是默认按照被该注解修饰的属性名称去容器中获取 bean, 并且不支持 @Qualifier(“id名称”) 与 @Primary
- @Inject 是 java 提供的需要导入依赖, 与 @Autowired 不同的是没有required属性设置
自动装配注解实现原理
通过 AutowiredAnnotationBeanPostProcessor 后置处理器来设置 bean 在初始化前后对属性的赋值,具体查看 “Spring Bean 的注入方式” 中后置处理器,与初始化 bean 的操作
自定义组件实现获取 Spring 中底层提供的组件
只需要将自定义组件实现XxxxAware 接口即可 , 继承的每个XxxxAware 都有对应的后置处理器,通过初始化bean以后,自动调用后置处理器中的方法,通过后置处理器中的方法运行自定义组件继承 XxxAware 重写的方法,达到功能效果,例如 “Spring Bean 的生命周期” 中提到的获取IOC容器,ApplicationContextAware 对应 ApplicationContextAwareProcessor
三. @Profile
- @Profile 可以修饰类(对整个类中的所有方法生效),可以修饰向Spring注入bean的方法(只对当前被修饰的方法生效),默认的value值为"default"所有的都可以
- 作用: Spring 提供可以根据当前环境,动态的切换一系列bean组件的功能,例如: 在项目开发时,有开发环境,测试环境,生产环境,拿数据库连接来举例: 在开发时连接开发库,测试时连接测试库,发布生产则连接生产库,在不改动代码的情况下通过 @ProFile 实现动态的修改,指定组件在哪个环境下才可以激活将组件注册到容器中,设置激活环境的方式有两种,可以通过命令行的方式,也可通过代码的方式设置环境
- 示例: 创建配置类,配置向容器中注入数据源连接 bean,注入两个,一个test环境下的,一个dev环境下的,根据环境不同自动注入对应的连接 bean
@Configuration
public class MyConfiguration {
//指定环境为"dev"时才会将这个方法返回的 bean 注入到容器中
@Profile("dev")
@Bean
public DruidDataSource configMethod1(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl("jdbc:mysql://localhost:3306/mysql?serverTimezone=GMT%2B8");
druidDataSource.setName("root");
druidDataSource.setPassword("");
return druidDataSource;
}
//指定环境为"test"时才会将这个方法返回的 bean 注入到容器中
@Profile("test")
@Bean
public DruidDataSource configMethod2(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl("jdbc:mysql://localhost:3306/sys?serverTimezone=GMT%2B8");
druidDataSource.setName("root");
druidDataSource.setPassword("");
return druidDataSource;
}
}
- 设置激活环境
-
命令行的方式active后面的值就是指定激活的开发环境 : -Dspring.profiles.active=test
-
代码的方式 : 通过调用无参构造器创建 IOC 容器对象 AnnotationConfigApplicationContext,通过这个对象获取运行时环境对象 ConfigurableEnvironment, 通过运行时环境对象调用setActiveProfiles() 方法设置激活环境,然后再使用 AnnotationConfigApplicationContext 对象调用 register() 方法读取 Spring 框架相关的配置一些配置,手动调用 refresh() 方法进行刷新
public static void main(String[] args) {
//创建 Ioc 容器时,使用带参构造器,由于带参构造器,在构造器中会执行 refresh()方法,
//刷新开发环境所以需要通过无参构造,创建IOC容器,在设置环境成功后,手动调用 refresh()
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//2.通过容器获取运行时环境对象
ConfigurableEnvironment environment = context.getEnvironment();
//3.通过运行时环境对象设置激活环境,可以设置多个,使用逗号隔开
environment.setActiveProfiles("test");
//4.注册主配置类
context.register(MyConfiguration.class);
//5.手动启动刷新容器
context.refresh();
//2.获取 bean
//DruidDataSource d = (DruidDataSource) context.getBean("configMethod1");
Map<String, DruidDataSource> druidDataSourceMap = context.getBeansOfType(DruidDataSource.class);
druidDataSourceMap.entrySet().stream().forEach(m -> System.out.println(m.getValue().getUrl()));
//3.关闭容器
context.close();
}