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