Spring IoC 之Spring Bean的装配

一、装配Bean概述

  • Spring提供了三种方法进行Bean的配置:

  • 在XML中进行配置

  • 在java接口和类中用注解进行配置

  • 隐式Bean的发现机制和自动装配原则

在现实工作中,这三种方式都会被用到,并常常混合使用。基于“约定优于配置”的原则,最优先的是隐式Bean的发现机制和自动装配原则。这样的好处是减少开发者的决定权,简单而灵活。其次是通过注解的方式进行配置。它的好处是避免XML配置的泛滥,且更为简单方便。而如果你配置的类不是自己工程中开发的建议使用XML方式。本文主要介绍注解方式装配Bean。

二、通过注解装配Bean

在Spring中,提供两种方式来让Spring IoC容器发现Bean:

  • 组件扫描:通过定义资源的方式,让Spring IoC扫描对应的包,从而把Bean装配起来。
  • 自动装配:通过注解定义,使得一些依赖关系可以通过注解来完成。

1、使用@Component装配Bean

首先定义POJO类:

package com.yozzs.ioc.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component(value = "role")//Value不写默认简单类名首字母小写
public class Role {
    @Value("1")//值的注入,Spring会将1转化为long型
    private Long id;
    @Value("张三")
    private String name;
    @Value("北京")
    private String location;

    /*****setter and getter*****/
}

再定义一个Service接口:

package com.yozzs.ioc.service;

import com.yozzs.ioc.pojo.Role;

public interface IRoleService {
    public void printRoleInfo(Role role);
}

以及Service实现类:

package com.yozzs.ioc.service.impl;

import org.springframework.stereotype.Component;

import com.yozzs.ioc.pojo.Role;
import com.yozzs.ioc.service.IRoleService;

@Component
public class RoleServiceImpl implements IRoleService{

    @Override
    public void printRoleInfo(Role role) {
        System.out.println("id="+role.getId());
        System.out.println("name="+role.getName());
        System.out.println("location="+role.getLocation());
    }
}

此时Spring IoC还不知道去哪里扫描对象,需要定义一个java config来告诉它,它是一个没有逻辑的配置类,代码如下:

package com.yozzs.ioc.config;

import org.springframework.context.annotation.ComponentScan;

import com.yozzs.ioc.pojo.Role;
import com.yozzs.ioc.service.impl.RoleServiceImpl;

@ComponentScan(basePackages = {"com.yozzs.ioc.pojo","com.yozzs.ioc.service"})
/*@ComponentScan(basePackages = {"com.yozzs.ioc.pojo","com.yozzs.ioc.service"},
basePackageClasses = {Role.class,RoleServiceImpl.class})*/
/*@ComponentScan(basePackageClasses = {Role.class,RoleServiceImpl.class})*/
public class ApplicationConfig {

}

注意:两处注释是两种另外的写法,同时定义basePackages和basePackageClasses不会对同一个Bean生成多个对象。

测试类:

package com.yozzs.ioc.test;

import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.yozzs.ioc.config.ApplicationConfig;
import com.yozzs.ioc.pojo.Role;
import com.yozzs.ioc.service.IRoleService;

public class MyTest {
    @Test
    public void test01() {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ApplicationConfig.class);
        Role role = (Role) ctx.getBean("role");
        IRoleService roleService = ctx.getBean(IRoleService.class);
        roleService.printRoleInfo(role);
        ctx.close();
    }
}

测试结果:

id=1
name=张三
location=北京

2、自动装配——@Autowired

前面的方法是一些简单的值的注入,对于注入对象我们采用自动装配。所谓自动装配技术是一种Spring发现对应的Bean,自动完成装配工作的方式,这是应用到一个十分常用的注解@Autowired。下面改写以上代码中接口及其实现来举例:

Service类:

package com.yozzs.ioc.service;

public interface IRoleService2 {
    public void printRoleInfo();
}

Service实现类:

package com.yozzs.ioc.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.yozzs.ioc.pojo.Role;
import com.yozzs.ioc.service.IRoleService2;

@Component
public class RoleServiceImpl2 implements IRoleService2{
    @Autowired
    private Role role ;

    /****setter and getter****/

    @Override
    public void printRoleInfo() {
        System.out.println("id="+role.getId());
        System.out.println("name="+role.getName());
        System.out.println("location="+role.getLocation());
    }
}

当IoC容器里找不到注入需要的Bean时,默认情况下会抛出异常。此时可以通过@Autowired的配置项required来改变它,如@Autowired(required=false),required配置成false表示找不到Bean时不抛出异常,允许字段为空。required默认为true。@Autowired还可应用于方法和参数。

3、自动装配的歧义性(@Primary和@Qualifier)

@Autowired是按类型装配,当需要自动注入的是一个接口类型,并且IoC容器中存在多个该接口的不同实现类Bean时,IoC容器无法判断把哪个对象注入进来,于是就会抛出异常。这就是自动装配的歧义性。为了消除歧义性,Spring提供了两个注解,@Primary和@Qualifier。

  • @Primary 用在需要注入的接口类型的实现类上,表示优先使用该类注入
package com.yozzs.ioc.service.impl;

import org.springframework.context.annotation.Primary;

@Component("roleService3")
@Primary
public class RoleServiceImpl3 implements IRoleService2{
/*......*/
}
  • @Qualifier 用在需要注入的字段上,表示按Bean名称注
package com.yozzs.ioc.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import com.yozzs.ioc.service.IRoleService;

public class RoleController {
    @Autowired
    @Qualifier("roleService3")
    private IRoleService roleService ;

    /****setter and getter****/

    public void printRole() {
        roleService.printRoleInfo();
    }
}

4、使用@Bean装配Bean

@Component装配Bean只能注解在类上,@Bean可以注解在方法上,并且将返回的对象作为Spring Bean,存放在IoC容器中。例:

 @Bean(name = "dataSource")
 public DataSource getDataSource() {
     DataSource dataSource = new DataSource();
     return dataSource;
 }

@Bean包含四个配置项:

  • name:是一个字符串数组,允许配置多个BeanName
  • autowire:标志是否是一个引用的Bean对象,默认值是Autowire.NO
  • initMethod:自定义初始化方法
  • destroyMethod:自定义销毁方法

三、使用Profile

Spring中的Profile配置可以定义不同的环境,比如配置两个数据库连接池,一个用于开发(dev),一个用于测试(test)。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<beans profile="test">   <!-- 测试环境用的Bean -->
    <bean id="devDataSource" class="org.apache.commons.dbcp.BasicDataSource" >
        <property name="driver" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test01"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
</beans>
<beans profile="dev">   <!-- 开发环境用的Bean -->
    <bean id="devDataSource" class="org.apache.commons.dbcp.BasicDataSource" >
        <property name="driver" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test02"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
</beans>
</beans>

启动Profile的方法有多种:

  • 在使用Spring MVC的情况下可以配置Web上下文参数,或者DispatchServlet参数
  • 作为JNDI条目
  • 配置环境变量
  • 配置JVM启动参数
  • 在测试类上使用注解@ActiveProfiles

下面对几种方式进行介绍:

在eclipse的IDE中开发,可以给运行的类加入虚拟机参数,如图:
这里写图片描述
在web.xml中使用环境参数:

  <context-param>
      <param-name>spring.profiles.active</param-name>
      <param-value>test</param-value>
  </context-param>

使用Spring MVC的DispatcherServlet环境参数:

<servlet>
      <servlet-name>dispatcher</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <init-param>
         <param-name>spring.profiles.active</param-name>
         <param-value>test</param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
      <servlet-name>dispatcher</servlet-name>
      <url-pattern>/</url-pattern>
  </servlet-mapping>

在测试类上使用注解@ActiveProfiles:

/*......*/
@ActiveRrofiles("dev")
public class ProfileTest {
    @Autowired
    private DataSource dataSource;
    @Test
    public void test01() {
        /*......*/
    }
}

四、加载属性(properties)文件

1、使用注解方式加载属性文件

Spring提供了注解@PropertySource来加载属性文件,其配置项如下:

  • name:字符串,配置这次属性配置的名称
  • value:字符串数组,可以配置多个属性文件
  • ignoreResourceNotFound:boolean值,默认为false,默认情况下找不到对应的配置文件会抛出异常
  • encoding:编码,默认为“”

用法代码示例:
database-config.properties:

mysqlDriver=org.gjt.mm.mysql.Driver
mysqlURL=jdbc:mysql://localhost:3306/testjdbc
mysqlUser=root
mysqlPwd=123456

配置类:

package com.yozzs.ioc.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;

@Configuration
@ComponentScan(basePackages= {"com.yozzs.ioc"})
@PropertySource(value= {"classpath:database-config.properties"},ignoreResourceNotFound=true)
public class ApplicationConfig {
    //用于解析占位符
    @Bean
    public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

使用引入的配置文件:

package com.yozzs.ioc.pojo;

import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class DataSourceBean {
    @Value("${mysqlDriver}")
    private String driver = null;

    @Value("${mysqlURL}")
    private String url = null;

    @Value("${mysqlUser}")
    private String username = null;

    @Value("${mysqlPwd}")
    private String password = null;

    @Bean(name = "dataSource")
    public DataSource getDataSource() {
        Properties props = new Properties();
        props.setProperty("driver", driver);
        props.setProperty("url", url);
        props.setProperty("username", username);
        props.setProperty("password", password);
        DataSource dataSource = null;
        try {
            dataSource = BasicDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dataSource;
    }
}

2、使用XML方式加载属性配置文件

使用XML方式加载属性配置文件,只需使用< context:property-placeholder>元素加载一些配置项即可。

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

location是一个配置文件路径的选项,它可以配置单个或者多个文件,多个文件之间用逗号分隔。

五、Bean的作用域

默认情况下,Spring IoC容器创建的Bean是单例的,有时我们需要多个实例在不同的线程运行,这些由Spring的作用域决定。Spring提供了4中作用域:

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

用注解方式声明作用域用@Scope(ConfigurableBeanFactory.SCOPE_xxx)注解Bean类。

六、使用Spring表达式(Spring EL)

Spring EL一种非常灵活的注入方式,它有许多功能:

  • 使用Bean的id来引用Bean
  • 调用指定对象的方法和访问对象的属性
  • 进行运算
  • 提供正则表达式进行匹配
  • 集合配置

1、Spring EL解析器设计
这里写图片描述
2、Bean的属性和方法

用@Value给Bean的属性注入值,在属性文件中读取是用“$”,而在Spring EL中是使用“#”。下面一角色为例用代码演示:

初始角色类:

package com.yozzs.ioc.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("role")
public class Role {
    @Value("#{1}")
    private Long id;
    @Value("#{'张三'}")
    private String name;
    @Value("#{'北京'}")
    private String location;
    /****setter and getter****/
}

通过Spring EL引用role的属性,调用其方法

package com.yozzs.ioc.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("elBean")
public class ElBean {
    //通过beanName获取Bean,然后注入
    @Value("#{role}")
    private Role role;

    //获取Bean的属性id
    @Value("#{role.id}")
    private Long id;

    //调用bean的getName方法,获取角色名称
    @Value("#{role.getName().toString()}")
    private String name;

    /****setter and getter****/    
}

注意表达式”#{role.getName().toString()}”的注入,因为getName()可能返回null,这样toString()方法就会抛出异常了。为了解决这个问题,可以这样写:”#{role.getName()?.toString()}”,问号的含义是先判断返回是否为非null,如果不是则不再调用toString方法。
3、使用类的静态常量和方法

使用静态方法和常量,比如圆周率π:

    @Value("#{T(Math).PI}")
    private double pi;

对于不是java.lang.*包下的类要写类的全限定名,类似:

    @Value("#{T(java.lang.Math).PI}")
    private double pi;

还可以使用静态方法:

    @Value("#{T(Math).random()}")
    private double random;

4、Spring EL运算

角色编号加1注入:

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

连接字符串:

    @Value("#{role.name+role.location}")
    private String str;

判断:

    @Value("#{role.id==1}")
    private boolean equalNum;

    @Value("#{role.name eq '张三'}")
    private boolean equalString;

    @Value("#{role.id<2}")
    private boolean less;

三目运算:

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

    @Value("#{role.location?:'北京'}")
    private String defaultStr;

猜你喜欢

转载自blog.csdn.net/weixin_41172473/article/details/81911168