Spring 的属性赋值与装配

一. 属性赋值

  1. 传统 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>
  1. 传统方式中,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:/"

  1. 创建配置类,@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();
    }

}
  1. 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 + '\'' +
                '}';
    }
}
  1. 运行测试,当配置文件加载到项目中后,通过 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

  1. @Autowired 可以用来修饰: 属性,以属性类型去容器中获取,修饰方法以方法形参类型去容器中获取,修饰参数, 修饰构造器,提供有参构造以构造器形参类型获取注入,并且当向容器中注入一个只有有参构造器的对象,在不使用@Autowired的情况下,默认会通过构造器的形参在容器中获取对应的bean赋值给注入的bean
  2. 当使用@Autowired 在容器中获取 bean,赋值给某个属性时,首先会按照类型去容器中获取,如果容器中有多个,会将属性名做为id 去容器中查找,
  3. 在使用@Autowired,如果想指定以属性名去为id去容器中查找,在使用@Autowired的同时,添加@Qualifier(“id名称”)注解,指定id获取
  4. @Autowired 修饰的属性,默认是容器中必须存在的,如果不存在则报错,在某些特定的场景,想要容器中有则进行装配,若没有则不装配,可以通过设置@Autowired注解的required属性值为false来设置
  5. @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 实现装配功能

  1. @Resource 是 java 提供的装配注解, 与 @Autowired 的不同是默认按照被该注解修饰的属性名称去容器中获取 bean, 并且不支持 @Qualifier(“id名称”) 与 @Primary
  2. @Inject 是 java 提供的需要导入依赖, 与 @Autowired 不同的是没有required属性设置
    在这里插入图片描述

自动装配注解实现原理

通过 AutowiredAnnotationBeanPostProcessor 后置处理器来设置 bean 在初始化前后对属性的赋值,具体查看 “Spring Bean 的注入方式” 中后置处理器,与初始化 bean 的操作

自定义组件实现获取 Spring 中底层提供的组件

只需要将自定义组件实现XxxxAware 接口即可 , 继承的每个XxxxAware 都有对应的后置处理器,通过初始化bean以后,自动调用后置处理器中的方法,通过后置处理器中的方法运行自定义组件继承 XxxAware 重写的方法,达到功能效果,例如 “Spring Bean 的生命周期” 中提到的获取IOC容器,ApplicationContextAware 对应 ApplicationContextAwareProcessor

三. @Profile

  1. @Profile 可以修饰类(对整个类中的所有方法生效),可以修饰向Spring注入bean的方法(只对当前被修饰的方法生效),默认的value值为"default"所有的都可以
  2. 作用: Spring 提供可以根据当前环境,动态的切换一系列bean组件的功能,例如: 在项目开发时,有开发环境,测试环境,生产环境,拿数据库连接来举例: 在开发时连接开发库,测试时连接测试库,发布生产则连接生产库,在不改动代码的情况下通过 @ProFile 实现动态的修改,指定组件在哪个环境下才可以激活将组件注册到容器中,设置激活环境的方式有两种,可以通过命令行的方式,也可通过代码的方式设置环境
  3. 示例: 创建配置类,配置向容器中注入数据源连接 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;
    }

}
  1. 设置激活环境
  • 命令行的方式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();

    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值