天天看点

Spring 装配Bean

引言

Spring装配Bean的方式有3种:

  • 通过XML装配Bean
  • 通过注解装配Bean @Component
  • 自动装配

一、通过XML装配Bean

    通过XML装配Bean其实就是通过setter方法注入,通过在XML文件中设置Property值,使得对象在创建的时候就调用setter方法将初始值注入给对象。这里不再赘述。

二、通过注解装配Bean

    除了通过xml文件,Spring IoC还可以通过组件扫描的方式来发现Bean 。具体做法如下:

package package1;
    @Component(value = "role")
    public class Role{
    	@value("1")//赋初始值,装配
    	private Long id;
    	
    	@value("role_name_1")
    	private String roleName;
    	
    	@value("role_note_1")
    	private String note;
    	/***************setter and getter*******/
    
    }
    
    @ComponentScan(basePackages = {"package1"})
    public class Test {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(Test.class);
            Person role = cxt.getBean(Person.class);
            System.err.println(role.getName());
            cxt.close();
        }
    }
           

    @Component 代表Spring IoC会把该类扫描生成Bean,@Value 就是给Bean赋初值。

    给Test类加上 @ComponentScan 注解之后,代表该类会去package1包里面去扫描component,上面表示在package1中扫描,如果Role类所在包不是package1,就会导致扫描不到。

    @ComponentScan的 basePackages 属性规定了扫描的包名,其实是规定了一个包数组,可以写多个包在里面,扫描的时候只会去这些包中扫描。

三、自动装配

    如果一个对象Book是另一个对象Student的Field,那么要想将这种依赖关系注入进去,就需要用到 @AutoWired注解,用法如下:

package package3;

public interface Person {
    void say();
}

package package3;

import ...;
@Component
public class Student implements Person {
    @Autowired
    private Book book;//将Book对象注入Student

    @Override
    public void say() {
        System.out.println("a book named " + book.getContent());
    }
}


package package3;
@Component
public class Book {
    @Value("Float")
    private String content;
	/*********setter and getter*******/
}

package package3;
@ComponentScan
public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Test.class);
        Student s = context.getBean(Student.class);
        s.say();
    }
}
           

Student中的@AutoWired注解会实例化一个Book对象并注入给他。

歧义性

    现在来了一个Worker类,它也是Person接口的子类:

package package3;
@Component
public class Worker implements Person {
    @Override
    public void say() {
        System.out.println("I am a worker");
    }
}
           

School类持有一个Person:

package package3;
@Component
public class School {
    @Autowired
    private Person person;//将Person对象注入School

    public void makePersonSay(){
        person.say();
    }
}
           

下面测试:

School school = context.getBean(School.class);
school.makePersonSay();
           

这时候就会出错,因为School持有的是Person,Spring不知道是Worker还是Student

解决方法一:利用@Primary

这个情况就需要 @Primary注解,在Student或者Worker的@Component注解下面加上 @Primary,就表示将哪个对象注入到School中。

解决方法二:利用@Qualifier

在School的@AutoWired注解下面加上@Qualifier(“Student”),就表示将Student对象注入到School中。

四、使用@Bean装配Bean

    @Component注解只能加在类名上方,不能注解到方法上,但是有时候用到第三方Jar包的时候就无法使用@Component了,这时候就需要用 @Bean注解。

    例如,下面以一个DBCP的例子说明:

DataSourceService.java

package package4;
@Component
public class DataSourceService {
    @Autowired
    private DataSource dataSource = null; //Spring会在运行时将DataSource对象注入
    public void getMessage(){
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = dataSource.getConnection();//从DataSource获取数据库连接
            ps = conn.prepareStatement("select * from mytable");
            rs = ps.executeQuery();
            while (rs.next()){
                int id = rs.getInt("id");
                String note = rs.getString("note");
                System.out.print("name: " + id);
                System.out.println("age: " + note);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        //System.out.println("method run");
    }
}
           

ServiceConfig.java

package package4;
@ComponentScan
public class MyBean {
	//在方法中定义Bean,这样Spring会扫描到该Bean,并和其他Bean一样处理
	//前提是这个方法必须有返回值,返回值的类型就是这个Bean的类型
    @Bean(name = "dataSource")
    public DataSource createDataSource(){
        Properties props = new Properties();
        props.setProperty("driver","com.mysql.jdbc.Driver");
        props.setProperty("url","jdbc:mysql://localhost:3306/test");
        props.setProperty("username","root");
        props.setProperty("password","123456");
        DataSource dataSource = null;
        try {
            dataSource = BasicDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dataSource;
    }
}
           

Test.java

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ServiceConfig.class);
 DataSourceService service = context.getBean(DataSourceService.class);
 service.getMessage();
           

五、其他设置

1、使用Profile

    敏捷开发中,测试通常都是小阶段的,可能是开发人员使用一套环境,而测试人员使用另外一套,这两套环境的数据库环境通常是不同的。这时就需要给Bean定义Profile。

    假设现在配置两个数据库连接池,一个用于开发(dev),一个用于测试(test)。

(1)通过注解@Profile定义Profile

    给用于测试的Bean加上@Profile(“test”),给用于开发的Bean加上@Profile(“dev”),如果想测试的话,就加上@ActiveProfiles(“test”),如果想开发的话,就加上@ActiveProfiles(“dev”)

(2)通过XML定义Profile

    在Beans.xml文件中的< beans>< /beans>标签中加上profile属性,如果为"dev",那么这个标签中的所有bean都会用于开发。

2、加载属性文件(.properties)

    上文中的数据库信息其实可以通过创建一个properties文件来加载,如创建一个database-config.properties文件,内容如下:

jdbc.database.driver=com.mysql.jdbc.Driver
jdbc.database.url=jdbc:mysql://localhost:3306/test
jdbc.database.username=root
jdbc.database.password=123456
           

这样就不用跑到java代码中去修改数据库信息了,直接修改properties文件即可。

那么如何加载.properties文件呢,也有两种方式:

(1)通过注解加载

    给使用了数据库信息的Bean添加 @PropertySource注解,并且设置相关field,并给field加上@Value注解,通过 ${jdbc.database.driver} 等属性占位符来给field赋值:

@ComponentScan

//通过@PropertySource注解加载properties文件信息
@PropertySource(value={"classpath:database-config.properties"}, ignoreResourceNotFound=true)
public class MyBean {
    @Value("${jdbc.database.driver}")//直接从属性占位符获取数据
    private String driver = null;
    @Value("${jdbc.database.url}")
    private String url = null;
    @Value("${jdbc.database.username}")
    private String username = null;
    @Value("${jdbc.database.password}")
    private String password = null;
    
    @Bean(name = "dataSource")
    public DataSource createDataSource(){
        Properties props = new Properties();
        props.setProperty("driver",driver);//driver、url等field会被Spring注入
        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方式加载属性文件

在beans的xml文件中,对< context:property-placeholder>元素加载一些配置项即可,

如:

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

3、条件化装配@Conditional

    如果一个Bean加上了@Conditional这个注解,那么,在创建这个Bean的时候会先判断条件是否满足,不满足条件就不去创建这个bean。用法如下:

    给一个Bean加上 @Conditional({MyCondition.class})

其中MyCondition是自己实现的一个类,这个类实现了Conditional接口,会重写matches方法,可以在该方法中加上一定条件,如果返回true才会去创建该Bean。

六、Bean的作用域

    默认情况下,Spring IoC只会为每个Bean创建一个实例。Spring提供了4种作用域:

  • 单例(singleton)    默认情况,只会生成一个Bean的实例
  • 原型(prototype)    每次注入或者从IoC中获取都会创建一个新的实例
  • 会话(session)     在Web应用中使用,每次会话只创建一个实例
  • 请求(request)     在Web应用中使用,一次请求中产生一个实例,不同的请求会产生不同的实例

可以通过 @Scope 注解来规定一个Bean的作用域:

@Scope(ConfigureableBeanFactory.SCOPE_PROTOTYPE)

就能将一个bean的作用域变为prototype。

七、Spring表达式(Spring EL)

    Spring使用了Spring EL来提供更灵活的注入方式,他的功能如下:

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

举例如下:

ElBean.java

@Component("elBean")
public class ElBean {
	
    //通过beanName获取bean,然后注入
	@Value("#{role}")
	private Role role;
	
    //获取bean的属性id
	@Value("#{role.id}")
	private Long id;
	
    //调用bean的getNote方法,获取角色名称
	@Value("#{role.getNote().toString()}")
	private String note;
	/***********setter and getter**********/
}
           

Role.java

@Component("role")
public class Role {
	// 赋值long型
	@Value("#{1}")
	private Long id;
	// 字符串赋值
	@Value("#{'role_name_1'}")
	private String roleName;
	// 字符串赋值
	@Value("#{'note_1'}")
	private String note;
	/***********setter and getter**********/
}