Spring高级配置之运行时注入

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring高级配置之运行时注入相关的知识,希望对你有一定的参考价值。

参考技术A

之前,我们设置Bean的属性值时,采用的都是硬编码的形式。比如,在定义BlankDisc时:

与之类似,采用XML的方式也是硬编码:

硬编码的坏处想必大家都知道,所以我们就想能不能让这些值在程序运行时再确定。其实Spring已经提供了两种方式实现运行时注入的功能:

Environment的getProperty()方法有四个重载方法:

前两种形式的getProperty()方法都是返回String类型的值。后两种重载方法不是直接返回字符串,而是将获取到的值转化为指定类型的值,比如获取连接池中维持连接的总数量:

在使用getProperty()获取属性时,如果这个属性没有定义,那他获取的是null,如果我们不想让这样的情况发生,可以使用getRequiredProperty()方法:

如果disc.title或disc.arist没有定义的话,将会抛出IllegalStateException异常。
如果你想检查某个属性是否存在,那么可以调用containsProperty()方法:

如果你想将属性解析为类的话,可以使用getPropertyAsClass()方法:

除了属性相关的功能外,Environment还提供了一些方法来检查哪些profile处于激活状态:

在Spring装配中,占位符的形式为“$...”包装的属性名称。下面我们使用属性占位符在XML中解析tomcat连接池bean的属性:

要使用占位符,需要配置PropertySourcesPlaceholderConfigurer bean:

在Xml配置要使用到命名空间中的<context:property-placeholder>元素,它可以为我们生成PropertySourcesPlaceholderConfigurer bean:

Spring 3引入了Spring表达式(Spring Excpression Language,SpEL),他能够以一种强大和简洁的方式将值装配到bean属性和构造器参数中,这个过程中所使用的表达式会在运行时计算得到值。SpEL的特性有以下几点:

上面几个只是几个基本的SpEl样例,后面我们会着重介绍,下面我们先看看使用SpEL表达式装配bean的属性:

上例中使用到@Value注解,在注解中设置SpEL表达式。上面我们学习了几个简单的样例,也学习了如何将SpEL表达式解析得到的值注入到bean中,那么我们来继续学习一下SpEl表达式支持的基础表达式吧。

使用SpEL字面量样式可以表示整数、浮点数、String以及Boolean类型的值:

SpEL能够通过ID引入其他的bean、bean的属性和bean的方法:

如果还可以对方法的返回值做处理,直接调用返回值类型的属性或方法,假设sayHello()返回值是String类型: #blankDisc.sayHello().toUpperCase() ,这样就可以获取返回值的大写字母形式。
但如果返回值是null,调用它的方法就会报空指针异常,为了预防这种情况,可以使用 ? 判断获得返回值是否为null: #blankDisc.sayHello()?.toUpperCase() 如果sayHello()方法返回值是null,SpEl将不会调用toUpperCase()方法,SpEL的返回值是null。

如果SpEL表达式访问类作用域的方法或常量时,要使用到T()这个关键的运算符: #T(java.lang.Math) 运算符的结果是一个Class对象,它代表了java.lang.Math。通过它我们可以访问Math的静态方法和常量。

SpEL提供了多个运算符,这些运算符可以使用到SpEL表达式上:

SpEL通过matches运算符支持表达式中的模式匹配,匹配成功返回true,否则否则返回false,下面我们判断邮箱是否符合正则表达式:

使用SpEL引用列表中的一个元素: #jubox.songs[4].title ,获取列表的第5个元素(基于0开始),[] 是从集合或数组中按照索引取元素。
SpEL提供了查询运算符(.?[]),他会用来对集合进行过滤,得到集合的一个子集。下面我们将Aerosmith的歌曲过滤出来:

.^[]查询第一个匹配项
.$[]查询最后一个匹配项

投影运算符(.![]),将集合对象特定的字段投影到另一个集合中,下面我们将歌曲的名称投影到另一个String类型的集合中:

我们还可以过滤要投影的歌曲,下面我们获取Aerosmith歌曲的title:

自此运行时注入的全部内容已经全部介绍完啦。终于完了啦,下个月我们开始学习Spring的Aop机制,很期待吧,come on!

Spring之Bean的注入

Bean的配置中介绍的是Bean声明问题,在哪声明怎么声明的问题。Bean的注入是怎么实例化,怎么注入的问题。Bean注入的方式有两种,一种是在XML中配置,另一种则是使用注解的方式注入。

一、XML方式注入

XML方式注入一般有三种方式:属性注入、构造函数注入和工厂方法注入。

一、属性注入
在传统的对象实例化时可以通过new class(),然后通过setXXX()方法设置对象的属性值或依赖对象,属性注入也是采用这种方式,只是Spring框架会在内部完成这些操作,它会先调用Bean的默认构造函数实例化Bean对象,然后通过反射的方式调用Setter方法注入属性值。它会使用默认的构造函数(无参数构造函数),只需为注入的属性设置set方法,可选择性和灵活性比较高,所以也是比较常用的一种注入方式。这里示例还是在IOC章节使用人和空气的基础上稍作修改来演示。IAir接口和CleanAir、DirtyAir类不变,这里就不贴了。

1.新建XMLInstance类

技术分享图片
package com.demo.model;

public class XMLInstance {
    
    
    private String name;
    
    public void setName(String name) {
        this.name = name;
    }
    private IAir air;
    
    public void setAir(IAir air) {
        this.air = air;
    }
    public void Breath()
    {
        System.out.println("Name:"+this.name+";Air:"+this.air.toString());
    }
    
    public XMLInstance(String name, IAir air) {
        super();
        this.name = name;
        this.air = air;
    }
    public XMLInstance() {
        
    }
    public void DestoryMethod()
    {
        System.out.println("DestoryMethod");
    }
    public void InitMethod()
    {
        System.out.println("InitMethod");
    }
}
View Code

在XMLInstance类中并未声明构造函数,对于air属性只设置了set方法,get方法也没设置。

  <bean id="CleanAir" class="com.demo.model.CleanAir">
        <qualifier value="cleanair"/> 
  </bean> 
  <bean id="xmlinstance" class="com.demo.model.XMLInstance">
      <property name="air" ref="CleanAir"></property>
      <property name="name" value="abc"></property>
  </bean>

Xml中使用property类配置属性,name是属性名,value用来设置基本数据类型的属性值。Spring配置文件中bean之间可以相互引用,引用时可以用<ref>标签配置bean的id属性使用。<ref>可以用在<property>属性中,也可以用在<construct-arg>构造函数的参数值,还可以用在其他地方,通过引用能减少bean的声明。

二、构造函数注入
在属性注入时先使用默认的构造函数(无参数构造函数)实例化,然后通过set方法注入属性,在传统实例化对象时可以自定义构造函数进行实例化,构造函数注入就是通过自定义构造函数来进行对象的实例化。这里在XMLInstance类的基础上增加了一个构造函数,第一个参数是String类型的name,第二个参数是IAir类型的air。

    public XMLInstance(String name, IAir air) {
        super();
        this.name = name;
        this.air = air;
    }

Xml中使用使用<construect-arg>来设置构造函数的参数,index属性设置参数的顺序,参数顺序应该与构造函数的一致,ref设置引用bean的id,value设置构造函数参数的值。

  <bean id="xmlcontructinstance" class="com.demo.model.XMLInstance">
      <constructor-arg index="1" ref="CleanAir"></constructor-arg>
      <constructor-arg index="0" value="abc"></constructor-arg>
  </bean>

三、工厂方法注入
工厂方法注入参考的是工厂设计模式,通过在工厂类中实现对象的实例化。工厂类负责创建一个或多个目标类实例,工厂类方法一般以接口或抽象类变量的形式返回目标类实例,工厂类对外屏蔽了目标类的实例化步骤,调用者甚至不用知道具体的目标类是什么。工厂方法也分静态工厂方法和非静态工厂方法,静态工厂方式不用实例化工厂类,直接通过类名调用,非静态工厂方法需要先实例化工厂类,然后通过工厂类对象调用获取对象。这里创建了一个工厂类XMLFactory,在类中定义了一个静态方法,和一个实例方法用来实例化bean对象。

package com.demo.model;

public class XMLFactory {
    
    public XMLInstance CreateInstance()
    {
        return new XMLInstance("instance",new CleanAir());
    }
    
    public static XMLInstance CreateStaticInstance()
    {
        return new XMLInstance("static instance",new CleanAir());
    }
}

1.静态工厂方法
只需设置工厂方法对应的类,以及对应的工厂方法。

<bean id="xmlfactorystaticinstance" class="com.demo.model.XMLFactory" factory-method="CreateStaticInstance"></bean>

2.实例工厂方法
需要先实例化工厂类,再通过工厂类对象调用实例方法获取bean对象。

 <bean id="xmlfactoryinstance" factory-bean="xmlfactory" factory-method="CreateInstance" destroy-method="DestoryMethod" init-method="InitMethod"></bean>

四、常见数据类型注入

(1)List属性注入
使用<list>配置java.util.List类型的属性。List属性中元素可以是任何数据类型的值,如果是Java对象可以使用ref指定,或使用<bean>定义新实例。如果是基础数据类型可直接用字符串。<list>中的元素会按配置的先后顺序排序。

      <property name="lists">
         <list>
            <value>1</value>
            <ref bean="CleanAir" />
             <bean class="com.demo.model.CleanAir"/>
        </list>
    </property>

(2)Set属性注入
使用<set>配置java.util.Set类型的属性。Set属性中元素可以是任何数据类型的值,如果是Java对象可以使用ref指定,或使用<bean>定义新实例。如果是基础数据类型可直接用字符串。<set>中的元素没有先后顺序。

      <property name="sets">
        <set>
            <value>1</value>
            <ref bean="CleanAir" />
             <bean class="com.demo.model.CleanAir"/>
        </set>
    </property>

(3)Map属性注入
使用<map>配置java.util.Map类型的属性。<entry>配置Map里的元素,Key指定索引,value指定值。如果是Java对象可以使用ref指定,或使用<bean>定义新实例。

    <property name="maps">
        <map>
           <entry key="key1" value="1"></entry>
            <entry key="key2" value-ref="CleanAir"></entry>
            <entry key="key3" >
                 <bean class="com.demo.model.CleanAir"/>
            </entry>
        </map>
    </property>

(4)Properties属性注入
使用<props>配置java.util.Properties类型的属性。<props>配置一个Properties对象,<prop>配置一条属性,属性Key配置索引。

    <property name="pros">
        <props>
            <prop key="prokey1">prokeyA</prop>
            <prop key="prokey2">prokeyB</prop>
        </props>
    </property>

(5)自定义属性编辑器

对于有一些属性是没法注入的,此时就需要自定义,比如日期类型。可以通过继承PropertyEditorSupport的类,重写setAsText方法来实现注入。这里定义了CustomerProperty继承PropertyEditorSupport,重写了setAsText方法,并将该bean配置到xml中。

package com.demo.model;

import java.beans.PropertyEditorSupport;
import java.text.ParseException;
import java.text.SimpleDateFormat;

public class CustomerProperty extends PropertyEditorSupport {
    private String format="yyyy-MM-dd";
    public String getFormat() {
        return format;
    }
    public void setFormat(String format) {
        this.format = format;
    }
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        
        SimpleDateFormat sdf=new SimpleDateFormat(format);
        //super.setAsText(text);
        try {
            //转换对象,能过setValue方法重新赋值
            this.setValue(sdf.parse(text));
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}
 <bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer">
     <property name="customEditors">
          <map>
               <entry key="java.util.Date" value="com.demo.model.CustomerProperty"/>
           </map>
     </property>
</bean>

配置之后就可以注入Date类型的属性了。

    <property name="date" value="2018-8-20"/>

这里新建了XmlCollectionsDemo类,配置了上面的几个类型的属性来演示。

技术分享图片
package com.demo.model;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class XmlCollectionsDemo {
    
    private List<Object> list;
    
    private Properties pros;
    
    private Set<Object> sets;
    
    private Map<String,Object> maps;
    
    private Date date;
    
    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public List<Object> list() {
        return list;
    }

    public void setLists(List<Object> list) {
        this.list = list;
    }

    public Properties getPros() {
        return pros;
    }

    public void setPros(Properties pros) {
        this.pros = pros;
    }

    public Set<Object> getSets() {
        return sets;
    }

    public void setSets(Set<Object> sets) {
        this.sets = sets;
    }

    public Map<String, Object> getMaps() {
        return maps;
    }

    public void setMaps(Map<String, Object> maps) {
        this.maps = maps;
    }
    
    public static void main( String[] args ) throws Exception
    {

        ApplicationContext context=new ClassPathXmlApplicationContext(new String[]{"ApplicationContext.xml"});
        BeanFactory factory=context;
        XmlCollectionsDemo annontationInstance=(XmlCollectionsDemo)factory.getBean("xmlCollectionsDemo");
        System.out.println(annontationInstance.list);
        System.out.println(annontationInstance.pros);
        System.out.println(annontationInstance.sets);
        System.out.println(annontationInstance.maps);
        System.out.println(annontationInstance.date);
    }
}
View Code
技术分享图片
  <bean id="xmlCollectionsDemo" class="com.demo.model.XmlCollectionsDemo">
      <property name="lists">
         <list>
            <value>1</value>
            <ref bean="CleanAir" />
             <bean class="com.demo.model.CleanAir"/>
        </list>
    </property>
      <property name="sets">
        <set>
            <value>1</value>
            <ref bean="CleanAir" />
             <bean class="com.demo.model.CleanAir"/>
        </set>
    </property>
    <property name="maps">
        <map>
           <entry key="key1" value="1"></entry>
            <entry key="key2" value-ref="CleanAir"></entry>
            <entry key="key3" >
                 <bean class="com.demo.model.CleanAir"/>
            </entry>
        </map>
    </property>
    <property name="pros">
        <props>
            <prop key="prokey1">prokeyA</prop>
            <prop key="prokey2">prokeyB</prop>
        </props>
    </property>
    <property name="date" value="2018-8-20"/>
  </bean>
View Code

 通过运行main方法,打印出属性值。
[1, CleanAir, CleanAir]
{prokey2=prokeyB, prokey1=prokeyA}
[1, CleanAir, CleanAir]
{key1=1, key2=CleanAir, key3=CleanAir}
Mon Aug 20 00:00:00 CST 2018

五、初始化函数、销毁函数
通过上面3种注入方式的学习也对通过xml对bean实例化有的了解,有的对象在实例化之后还需要执行某些初始化代码,但这些初始化代码还不能写在构造函数中,此时可以将初始化代码写到某个方法中,将init-method属性值设置为该方法,Spring会强制执行该方法进行初始化。而又的对象在使用完毕之后需要释放,可以使用destroy-method来进行销毁。

    public void DestoryMethod()
    {
        System.out.println("DestoryMethod");
    }
    public void InitMethod()
    {
        System.out.println("InitMethod");
    }

这里先在XMLInstance类中增加了上面两个方法来模拟销毁和初始化方法。然后在xml配置bean时就可以设置destroy-method、init-method属性的值对应两个方法的方法名。注解中@PostConstruct对应init-method,@PreDestory对应destroy-method。

<bean id="xmlfactoryinstance" factory-bean="xmlfactory" factory-method="CreateInstance" destroy-method="DestoryMethod" init-method="InitMethod"></bean>

二、注解注入方式

一、常用注解介绍
学习完XML注入之后再学习注解方式注入就容易的多,注解方式注入主要涉及到@Autowired,@Resource,@Required,@Qualifier,@Value这几个注解。在第2章节的2.2.4IOC实例中定义Person时就使用过@Autowired、@Qualifier。下面来了解下它们具体用法。
@Autowired:默认是按类型匹配注入bean,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。在使用@Autowired时,首先在容器中查询对应类型的bean,如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据,如果查询的结果不止一个,那么@Autowired会根据名称来查找。如果查询的结果为空,那么会抛出异常。解决方法时,使用required=false。
@Required:适用于bean属性setter方法,并表示受影响的bean属性必须在XML配置文件在配置时进行填充。否则,容器会抛出一个BeanInitializationException异常。
@Qualifier:@Autowired默认是单实例的,但是在面向接口编程中,如果把一个属性设置为接口类型,一个接口可能有多个实现,那到底是注入哪一个呢?为了解决这个问题,就有了@Qualifier。
@Value:在xml配置属性时可以通过property的value设置默认值,@Value也可以为属性设置默认值。
@Resource:默认按名称匹配注入bean。要求提供一个bean名称的属性,如果属性为空,则自动采用标注处的变量名或方法名作为bean的名称。如果我们没有在使用@Resource时指定bean的名字,同时Spring容器中又没有该名字的bean,这时候@Resource就会退化为@Autowired即按照类型注入。

package com.demo.model;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class AnnontationInstance {
    
    @Value("abc")
    private String name;
    
    
    public void setName(String name) {
        this.name = name;
    }
    
    //@Resource与@Autowired两者选其一
    //    @Autowired
    //    @Qualifier(value="cleanair")
    private IAir air;
    @Resource(name="CleanAir")
    public void setAir(IAir air) {
        this.air = air;
    }
    
    public void Breath()
    {
        System.out.println("Name:"+this.name+";Air:"+this.air.toString());
    }
}

上面代码使用@Value注解为name设置了默认值,使用@Resources设置bean的名称为IAir属性注入bean,也可以使用@[email protected]为IAir注入bean。

二、开启注解
上面配置完注解之后,还要告诉Spring开启注解,这样@Autowired、@Resources这些注解才起作用。开启有两种比较简单的方式。
1.在xml配置文件中使用context:annotation-config

  <context:annotation-config />

2.在xml配置文件中使用context:component-scan

<context:component-scan base-package="com.demo.model"/>

 

























以上是关于Spring高级配置之运行时注入的主要内容,如果未能解决你的问题,请参考以下文章

Spring_总结_04_高级配置_Bean的初始化和销毁

Spring_总结_04_高级配置之处理歧义

spring-高级依赖关系配置

第3章—高级装配—运行时注入

Spring_总结_04_高级配置之条件注解@Conditional

Spring Boot实战笔记-- Spring高级话题(组合注解与元注解)