天天看點

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**********/
}