Spring系列(1)-装配Spring Bean

本章的学习目标看如下的思维导图:

思维导图

依赖注入

在实际环境中实现Ioc容器的方式主要分为:依赖查找和依赖注入
二者关系 : 我们知道Spring是先完成Bean的定义和生成,然后在寻找需要注入的资源,找到对应的类型然后将其注入,完成依赖注入.
依赖注入有三种方式:构造器注入,setter注入和接口注入.

构造器注入

构造器注入依赖于构造方法来实现,Spring也可以通过使用构造方法来完成注入,这是构造器注入原理

package pojo;

public class Role {
    private Long id;
    private String name;

    public Role() {
        super();
    }

    public Role(Long id, String name) {
        super();
        this.id = id;
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

配置构造器,其中constructor-arg 元素用于定义类构造方法的参数,index用于定义参数的位置,value是设置对应的值

    <bean id="role1" class="pojo.Role">
        <constructor-arg index="0" value="1" />
        <constructor-arg index="1" value="wt"/>
    </bean>

使用setter注入

setter是Spring中最主流的注入方式,它利用JAVA Bean规范定义的setter方法来完成注入,这里值得注意的是,这里的构造方法是无参构造方法,然后使用setter注入相对应的值,其实这也是通过JAVA发射技术得以实现的

    <!-- setter注入 -->
    <bean id="role2" class="pojo.Role">
         <property name="id" value="1"/>
         <property name="name" value="wt"/>
    </bean>

接口注入

来自外界的资源,我们一般使用接口来注入,虽然说,考虑使用注解的方式去装配Bean多一点,但是,对于外部的资源而言,很多情况之下是不能知晓其源码,更不可能一一为其加上注解,此时我们便可以使用接口注入.


装配Spring Bean

将以及开发好的Bean装配到Spring Ioc容器中,通常情况下,我们会使用ApplicationContext的具体实现类

通常情况下,在没有歧义的前提之下,优先使用注解来自动装配,这样可以减少大量的XML配置,如果所配置的类并非自己工程所开发的,那么建议使用XML的方式会更加方便一点

使用XML配置装配Bean

方法与依赖注入中使用setter注入的方式一样,常用属性有:
id :这个Bean的编号
class:类的全限定名
property:定义类的属性,其中name是属性的名称,value是其值
ref:可用于引用之前Bean的id . <ref bean="需要引入的Bean的ID"/>

下面介绍一下装配常用集合类的方法(直接上代码):

    <!-- 集合类注入 -->
    <bean id="role3" class="">
        <!-- List -->
        <property name="list">
            <list>
                <value>one</value>
                <value>two</value>
            </list>
        </property>
        <!-- Map -->
        <property name="map">
            <map>
                <entry key="key1" value="value1" />
                <entry key="key2" value="value2" />
            </map>
        </property>
        <!-- Properties(该元素有一个必填属性key) -->
        <property name="pros">
            <props>
                <prop key="key1">value1</prop>
                <prop key="key2">value2</prop>
            </props>
        </property>
        <!-- set -->
        <property name="set">
            <set>
                <value>value1</value>
                <value>value2</value>
            </set>
        </property>
        <!-- array -->
        <property name="array">
            <array>
                <value>value1</value>
                <value>value2</value>
            </array>
        </property>
    </bean>

使用ApplicationContext来测试

    public static void main(String[] args) {
       ApplicationContext context = new ClassPathXmlApplicationContext("classpath:pojo/SpringBean-config.xml");
       ComplexRole role = (ComplexRole) context.getBean("role3");
       System.out.println(role.toString());
    }
输出:ComplexRole [list=[one, two], map={key1=value1, key2=value2}, props={key2=value2, key1=value1}, set=[value1, value2], array=[value1, value2]]

测试成功!

命名空间的装配

使用名字变量空间的时候需要引入对应的命名空间和XML模式(XSD)文件


通过注解来装配Bean

相比与XML,注解的功能更为强大,使用注解的方式可以减少XML的配置,它既能实现XML的功能,也提供了自动专装配的功能.在Spring中,它提供了两种方式让Spring IoC容器来发现Bean:
①组件扫描:通过定义资源的方式,让Spring IoC容器扫描对应的包,从而把Bean装配起来.
②自动装配:通过注解定义,使得一些依赖关系可以通过注解来完成.

使用@Component装配Bean

@Component(value="role1")
public class Role {
    @Value("1")
    private Long id;
    @Value("wt")
    private String name;
/***setter and getter ***/
}

解释一下,这里的注释Component 和 Value :
@Component : 代表Spring IoC会把这类扫描成Bean实例,而其中的Value代表这个类在Spring中的id,相当于XML中的id,对于没有表面的,容器就默认类名,以首字母为小写的形式作为id.
@Value : 代表的是值的注入

敲黑板的时候到了,虽然现在已经有了这个类,到时Spring Ioc并不知道去哪里扫描这个对象,这个时候我们则需要一个JAVA Config来告诉它(直接上代码)

package pojo;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class PojoConfig {
}

解释一下这里新增加的注释 @ComponentScan,扫描包,默认是当前包的路径.

       ApplicationContext context = new AnnotationConfigApplicationContext(PojoConfig.class);
       Role role = (Role) context.getBean(Role.class);
       System.out.println(role.toString());
输出:Role [id=1, name=wt]

测试成功!

下面,我们再来深入学习一下@ComponentScan这个注解,该注解存在两个配置项:basePackages(扫描指定包,可同时指定多个包) 和 basePackageClasses(扫描指定类,可以同时指定多个类)

import org.springframework.context.annotation.ComponentScan;

@ComponentScan(basePackageClasses= {Role.class, ComplexRole.class})
//扫描包的做法如下
//@ComponentScan(basePackages = {"pojo"})
public class PojoConfig {
}

自动装配@Autowired

在之前提到的@Value中,我们不难发现一个缺点,便是该注释只能注入简单的值而不能注入对象,此时,便有@Autowired注释的诞生,该注释可以注入对象,直接上代码

@Component("roleService")
public class RoleServiceImpl implements RoleService{

    @Autowired
    private Role role = null;
    
    public void printRoleInfo() {
        // TODO Auto-generated method stub
        System.out.println(role.toString());
    }

}

测试代码

       ApplicationContext context = new AnnotationConfigApplicationContext(PojoConfig.class);
       Role role = (Role) context.getBean(Role.class);
       System.out.println(role.toString());
输出:Role [id=1, name=wt]

测试成功!

解决自动装配的歧义性@Primary和@Qualifier

到了这里,相信大家都对自动装配注释@Autowired有了一定了解,此时,估计小伙伴们会有这样一个疑惑,在现实编程中,一个接口类往往会有多个实现类,若@Authowired的对象是一个接口类的话,如果这个接口类有多个实现类,那么此时,Spring IoC容器就会犯糊涂,它无法判断要把哪个对象注入进来,于是就会抛出异常,然后注入失败.
为了解决此类异常,
解释一下这两个注释:
@Primary : 顾名思义,该注释代表首要的,可告诉Spring IoC 容器优先注入该类

@Component(value="role1")
@Primary
public class Role {
    @Value("1")
    private Long id;
    @Value("wt")
    private String name;
   .....
}

@Qualifier : 按名称去查找Bean而不是按类型查找

@Component("roleService")    
public class RoleServiceImpl implements RoleService{

    @Autowired
    @Qualifier("role1")    #这里传入的参数是Bean的id
    private Role role = null;
    
    public void printRoleInfo() {
        // TODO Auto-generated method stub
        System.out.println(role.toString());
    }

}

装载带有参数的构造方法类

我们可以通过@Autowired 和@Qualifier来注入参数,例如

    public RoleServiceImpl(@Autowired Role role) {
        super();
        this.role = role;
    }

使用@Bean装配Bean

在以上的学习中,我们不难发现,我们基本都是通过@Component来装配Bean的,但是@Component只能注解在类上,而不能注解到方法上,这个时候Spring给予了一个注解@Bean,这个注解可以注解到方法智商,并且将方法返回来的对象作为Spring的Bean,存放在Spring IoC容器中.
@Bean的优势在于,可以快速引入第三方的包所返回的对象,而不是一个个地为别人的代码加上@Component,而且这样也是不可取的.
举个栗子:
例如,我想使用第三方jar包中的TestBean对象,但是我总不能去那个jar包里面为这个类加上@Component注释吧,况且,很多情况下,第三方包的源码是不公开的,这种情况下,我们想加注释也加不了,此时,@Bean的优势便能体现出来,在以下例子中,我们可以在getTestBean()方法下加上@Bean注释,使之成为成功Bean,那么我们就可以调用这个Bean来得到TestBean这个对象,除此之外,这个Bean也可以和其它Bean一样,通过@Autowired和@Qualifier来注入别的Bean中

public class PojoConfiguration {

    //@Bean注解注册bean,同时可以指定初始化和销毁方法
    //@Bean(name="testNean",initMethod="initMethod",destroyMethod="destroyMethod")
    @Bean
    @Scope("prototype")
    public TestBean getTestBean() {
        return new TestBean();
    }
}

解释一下这几个属性:
name : 配置BeanName,可配置多个
autowired : 标志是否是一个引用的Bean对象
initMethod : 自定义初始化方法
destroyMethod:自定义销毁方法

测试代码

public class TestMain {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
        //获取bean
        TestBean tb = context.getTestBean("testBean");
    }
}

再次强调,在现实生活中,使用XML或者注解各有道理,笔者建议在自己的工程所开发的类尽量使用注解方式,而对于引入第三方包的或者服务的类,尽量使用XML方式,这样的好处是可以尽量对第三方包或者服务的细节减少理解,也更加清晰和明朗,开发者在引入第三方包之前要了解第三方包的使用规则,这样对XML进行改写就简单许多了.

扫描XML文件

@ComponentScan(basePackageClasses= {Role.class, ComplexRole.class, RoleServiceImpl.class})
//扫描包的做法如下
//@ComponentScan(basePackages = {"service.Impl"})
@ImportResource({"classPath:SpringBean-config.xml"})
public class PojoConfig {
}

@ImportResource配置的内容是一个数组,可以配置多个XML 文件,这样就可以引入多个XML定义的Bean了
此外,在介绍一下@Import注解,有些时候,JAVAConfig类会不止一个,这个时候,就可以使用@Import来映入其它的JavaConfig类

@Import({PojoConfig2.class, PojoConfig3.class})

最后在补充一个知识点:目前,XML是无法加载JAVA配置类的,即(JAVAConfig),但是,支持通过XML的配置来扫描注解的包,代码如下

@ComponentScan(basePackages={"pojo"})

等同于XML中的

<context:component-scan base-package="pojo"/>

一般都以注解为主


Profile

该注解可以满足在不同环境下切换的需求,方便开发人员和测试人员使用不同的环境,此处便不做过多介绍.


加载属性(properties)文件
首先,Spring提供了注解@PropertySource来加载属性文件,其中,该注解包含几个属性,先来了解一下
name:配置这次属性配置的名称
value:配置文件的名称,可以配置多个属性文件
ignoreResourceNotFound:boolean值,其含义是如果找不到对应的属性文件是否进行忽略处理,默认值为false,即抛出异常
encoding:编码

首先,创建属性文件config.properties,其内容如下:

name = wt
id = 1

然后,在Spring环境中使用属性文件

@ComponentScan(basePackageClasses= {Role.class, ComplexRole.class, RoleServiceImpl.class})
//扫描包的做法如下
//@ComponentScan(basePackages = {"service.Impl"})

//扫描配置文件
//@ImportResource({"classPath:SpringBean-config.xml"})

//扫描属性文件
@PropertySource(value= {"classpath:config.properties"})
public class PojoConfig {

}

测试加载属性文件

public class test {
    public static void main(String[] args) {
       ApplicationContext context = new AnnotationConfigApplicationContext(PojoConfig.class);
       String name = context.getEnvironment().getProperty("name");
       System.out.println(name);
    }
}
输出:wt

测试成功!
此时,我们可以直接将属性文件中的数据,通过@Value注解注入到类的属性中,此时,Spring推荐使用文件解析类来处理,即PropertySourcesPlaceholderConfigurer,使用它便以为着允许Spring解析对应的文件文件,并且通过占位符来应用对应的文件
这样说可能会比较抽象,下面我们通过一个例子来解答:
首先,先配置好JAVA配置类

@ComponentScan(basePackageClasses = { Role.class, ComplexRole.class, RoleServiceImpl.class })
//扫描包的做法如下
//@ComponentScan(basePackages = {"service.Impl"})

//扫描配置文件
//@ImportResource({"classPath:SpringBean-config.xml"})

//扫描属性文件
@PropertySource(value = { "classpath:myConfig.properties" })
public class PojoConfig {
    @Bean
    public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

上述代码定义了一个PropertySourcesPlaceholderConfigurer类的Bean,它的作用是能够让Spring来解析属性占位符
引入属性文件的配置,通过占位符${name}来加载对应的属性

@Component(value="role1")
@Primary
public class Role {
    @Value("${id}")
    private Long id;
    @Value("${name}")
    private String name;

...
    @Override
    public String toString() {
        return "Role [id=" + id + ", name=" + name + "]";
    }

}

测试用例

public class test {
    public static void main(String[] args) {
       ApplicationContext context = new AnnotationConfigApplicationContext(PojoConfig.class);
       Role role = (Role) context.getBean(Role.class);
       System.out.println(role.toString());
    }
}
输出:Role [id=1, name=wt]

测试成功!

与此同时,我们也可以通过XML 方式来加载属性文件,代码如下

<context:property-placeholder ignore-resource-not-found="true" location="classpath:myConfig.properties"/>

条件化装配Bean

Spring提供了注解@Conditional来提供条件化装配Bean的功能,需要使用的读者可自行查阅,此处便不再累赘.

Bean的作用域

在默认的情况下,Spring IoC容器只会为配置的Bean生成一个实例,而不是多个
有时候,为了满足互联网并发的要求,有时候,我们需要创建多个实例,即每当我们请求的时候,就会产生一个新的独立对象,而不是默认的一个,这样多个实例就可以在不同的的线程运行,就不会存在并发问题了

Spring 提供了4种作用域,它会根据情况来决定是否生成新的对象:

单例singleton:默认选项,在整个应用中Spring 只为配置的Bean生成一个实例
原型prototype:当每次注入,或者通过Spring IoC容器获取Bean时,Spring都会为其创建一个新的实例
会话session:在Web应用中使用,就是在会话过程中Spring只创建一个新的实例
请求request:在Web应用中使用,根据不同的请求创建不同的实例

举个栗子:

@Component("roleService")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class RoleServiceImpl implements RoleService{

    @Autowired
    @Qualifier("role1")
    private Role role = null;
    
    
    public RoleServiceImpl(@Autowired Role role) {
        super();
        this.role = role;
    }


    public void printRoleInfo() {
        // TODO Auto-generated method stub
        System.out.println(role.toString());
    }

}

这里使用的注解@Scope,并且修改声明为原型


Spring EL

下面通过例子来直接说明,首先介绍ExpressionParser接口

public class test {
    public static void main(String[] args) {
        //表达式解析器
        ExpressionParser parser = new SpelExpressionParser();
        //设置表达式
        Expression exp = parser.parseExpression("'hello world'");
        String str = (String) exp.getValue();
        System.out.println(str);      #输出hello world
        //通过EL访问普通方法
        exp = parser.parseExpression("'hello world'.charAt(0)");
        char ch = (Character) exp.getValue();
        System.out.println(ch);  #调用charAt(0),输出h
    }
}

Spring EL 具有对表达式的解析功能,但是Spring EL最重要的功能是对Bean属性进行注入

下面,笔者将会以注解的方式来介绍它们:

Bean的属性和方法

前面我们介绍到@Value,在属性文件读取中使用的是"$",而在Spring EL中使用的是"#"
举个栗子:

public class Role {
    
    //获取mess类的属性id
    @Value("#{mess.id}")
    private Long id;
    
    //调用bean的getnote方法,获取角色名称,注意下面的方法,有一个问号?,此处判断是否返回非null,如果是null,调用toString方法
    @Value("${mess.getNote()?.toString}")
    private String name;
...
}

使用类的静态变量和方法

举几个简单的栗子:

//注入圆周率π,此处不需要使用import导入
@Value("#{T(Math).PI}")
private double Pl;

这样就可以通过调用类的静态方法加载对应的数据

Spring EL运算

举几个简单的栗子:

加1操作

@Value("#{role,id+1}")
private int id;

字符串拼接

@Value("#{role,str1 + role .str2}")
private int str;

三目运算

@Value("#{role,id>1?5:1}")
private int id;

实际上,Spring EL的功能远不止这些,需要深入了解的读者可自行查询.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容