前面提到,Bean是可以通过xml(不推荐)、java配置类、注解,三种方式注册到容器中。
注意:默认的生命周期是Singleton。其含义是,在应用中,无论何时、何处,获取这个Bean的实例时,都是共享的同一个实例。因此,在使用Singleton的实例时,需要考虑线程安全的问题。因此,一般将无状态的类设计为Singleton类型。
在容器中,Bean都是通过id进行唯一标记的,在任何位置你都是可以通过指定的id或者Bean的名称获取这个Bean的实例的。但是id、名称并不是必须指定的,如果你不指定,Spring会自动为其生成一个名称。但是,一般为了便于对其进行引用,我们都会对其命名。
绑定的优先级:
1. 如果id或名称不存在,则优先根据类型进行绑定。
2. 如果你为其指定了@Primary,或者这个Bean是唯一的,则无需再指定名称。
Bean的定义
一般包含以下属性:
class | 全限定类名 |
id | 唯一标记 |
name | 为其指定的名称 |
parent | 父类 |
scope | 声明周期 |
constructorargs | 构造器参数 |
properties | 属性参数 |
autowire mode | 自动绑定 |
primary | 首选实现 |
depends-on | 依赖Bean的初始化 |
lazy-init | 懒加载。用到再初始化 |
init-method | 初始化方法 |
destory-method | 销毁方法 |
factory-method | 工厂方法 |
factory-bean | 与工厂方法配合使用,用于使用指定的工厂类生产Bean |
基于xml的示例:
<bean id="xmlTaskService" class="com...XmlDefinedTaskService"
init-method="init" destroy-method="cleanup">
<constructor-arg ref="userService"/>
<constructor-arg>
<bean class="com...TaskInMemoryDAO"></bean>
</constructor-arg>
</bean>
基于注解的示例:
@Service
public class AnnotatedTaskService implements TaskService {
...
@Autowired
private UserService userService;
@Autowired
private TaskDAO taskDAO;
@PostConstruct
public void init() {
logger.debug(this.getClass().getName() + " started!");
}
@PreDestroy
public void cleanup() {
logger.debug(this.getClass().getName() + " is about to destroy!");
}
public Task createTask(String name, int priority, int createdByuserId, int assigneeUserId) {
Task task = new Task(name, priority, "Open",
userService.findById(createdByuserId), null,
userService.findById(assigneeUserId));
taskDAO.createTask(task);
logger.info("Task created: " + task);
return task;
}
...
}
Bean的初始化
通过构造器创建Bean的实例:
<bean id="greeter" class="com...GreetingBean"></bean>
@Component("greeter")
public class GreetingService {
...
}
通过静态工厂方法实例化:
<bean id="Greeter" class="...GreetingBean" factory-method="newInstance"></bean>
@Configuration
@ComponentScan(basePackages = "com.springessentialsbook")
public class SpringJavaConfigurator {
...
@Bean
public BannerService createBanner() {
return new BannerServiceImpl();
}
...
}
xml通过factory-method指定,java配置类则直接通过@Bean注解的方法生产实例。
通过实例工厂方法实例化:
通过其他Bean的非静态方法生产实例。
<bean id="greeter" factory-bean="serviceFactory" factory-method="createGreeter"/>
<bean id="serviceFactory" class="...ServiceFactory">
<!— ... Dependencies ... -->
</bean>
Bean的注入
Spring支持构造器注入,set方法注入两种。
构造器注入:
我们SimpleTaskService需要用到UserService、TaskDAO的实例。
public class SimpleTaskService implements TaskService {
...
private UserService userService;
private TaskDAO taskDAO;
public SimpleTaskService(UserService userService, TaskDAO taskDAO) {
this.userService = userService;
this.taskDAO = taskDAO;
}
...
}
<bean id="taskService" class="com...SimpleTaskService"">
<constructor-arg ref="userService" />
<constructor-arg ref="taskDAO"/>
</bean>
对于简单类型的构造器参数,可以直接在xml中进行配置:
<bean id="systemSettings" class="com...SystemSettings">
<constructor-arg index="0" type="int" value="5"/>
<constructor-arg index="1" type="java.lang.String" value="dd/mm/yyyy"/>
<constructor-arg index="2" type="java.lang.String" value="Taskify!"/>
</bean>
起始的序号从0开始。
基于Setter方法的注入:
<bean id="systemSettings" class="com...SystemSettings">
<property name="openUserTasksMaxLimit" value="5"/>
<property name="systemDateFormat" value="dd/mm/yyyy"/>
<property name="appDisplayName" value="Taskify!"/>
</bean>
注入List
<bean id="systemSettings" class="com...SystemSettings">
. . .
<constructor-arg>
<list>
<value>admin@taskify.ae</value>
<value>it@taskify.ae</value>
<value>devops@taskify.ae</value>
</list>
</constructor-arg>
</bean>
注入对象时,可以使用<ref>替换<value>
注入Map
<bean id="systemSettings" class="com...SystemSettings">
. . .
<property name="emails">
<map>
<entry key="admin" value="admin@taskify.ae"></entry>
<entry key="it" value="it@taskify.ae"></entry>
<entry key="devops" value="devops@taskify.ae"></entry>
</map>
</property>
</bean>
注入对象时,可以使用<ref>或<bean>替换<value>
自动绑定注入
可以通过注解构造器方法:
@Service
public class AnnotatedTaskService implements TaskService {
...
@Autowired
public AnnotatedTaskService(UserService userService, TaskDAO taskDAO) {
this.userService = userService;
this.taskDAO = taskDAO;
}
...
}
可以注解成员变量:
@Service
public class AnnotatedTaskService implements TaskService {
...
@Autowired
private UserService userService;
@Autowired
private TaskDAO taskDAO;
...
}
区分不同的实现的注入:
对成员变量使用@Qualifier
@Autowired(required = true)
@Qualifier("taskDAO")
private UserService userService;
对构造器方法的参数:
@Autowired
public AnnotatedTaskService(@Qualifier("userService") UserService userService, @Qualifier("taskDAO") TaskDAO taskDAO) {
this.userService = userService;
this.taskDAO = taskDAO;
}
Bean的生命周期
默认生命周期是Singleton,下面是目前支持的生命周期:
Singleton | 单例 |
prototype | 每次都是新的 |
request | 每一次请求都是新的 |
session | 每个会话维护一个 |
global-session | portlet上下文的会话级别 |
application | servlet context级别的 |
XML通过scope属性设置生命周期:
<bean id="userPreferences" class="com...UserPreferences" scope="session">... </bean>
通过注解也是可以设置的:
@Component
@Scope("request")
public class TaskSearch {...}
一般来说Service层、DAO层的POJO都是单例的,因为他们一般不用来维护状态。
不同生命周期的Bean的注入问题
当一个session级别的Bean注入到一个Singleton的Bean,所有都用到的是相同的实例,存在问题,我们需要通过代理解决这个问题:
<bean id="userPreferences" class="com...UserPreferences" scope="session">
<aop:scoped-proxy />
</bean>
<bean id="taskService" class="com...TaskService">
<constructor-arg ref="userPreferences"/>
</bean>
也可以通过注解:
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserPreferences { ... }
public class AnnotatedTaskService implements TaskService {
...
@Autowired
private UserPreferences userPreferences;
...
}
指定procyMode熟悉即可。
跟踪Bean的生命周期
容器会回调两个方法:
org.springframework.beans.factory.InitializingBean.afterPropertiesSet()
org.springframework.beans.factory.DisposableBean.destroy()
可以实现InitializingBean和DisposableBean接口,重写这两个方法(不推荐):
public class UserServiceImpl implements UserService, InitializingBean, DisposableBean {
...
@Override
public void afterPropertiesSet() throws Exception {
logger.debug(this + ".afterPropertiesSet() invoked!");
// Your initialization code goes here..
}
@Override
public void destroy() throws Exception {
logger.debug(this + ".destroy() invoked!");
// Your cleanup code goes here..
}
...
}
也可以通过@PostConstruct和@PreDestory两个注解实现(推荐):
@Service
public class AnnotatedTaskService implements TaskService {
...
@PostConstruct
public void init() {
logger.debug(this.getClass().getName() + " started!");
}
@PreDestroy
public void cleanup() {
logger.debug(this.getClass().getName() + " is about to destroy!");
}
...
}
xml也可以通过init-method和destory-method实现:
<bean id="xmlTaskService" class="com...XmlDefinedTaskService" init-method="init" destroy-method="cleanup">
...
</bean>
容器级别的default-init-method和default-destory-method方法
<beans default-init-method="init" default-destroy-method="cleanup">
...
</beans>
通过Profile开启不同的环境
@Configuration
@ComponentScan(basePackages = "com.springessentialsbook")
public class ProfileConfigurator {@Bean
@Profile("dev")
public DataSource devDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL) .addScript("scripts/tasks-system-schema.sql") .addScript("scripts/tasks-master-data.sql") .build();
}
@Bean
@Profile("prod")
public DataSource productionDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource/tasks");
}
}
也可以通过xml配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<!-- other bean definitions -->
<beans profile="dev">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:scripts/tasks-system-schema.sql"/>
<jdbc:script location="classpath:scripts/tasks-master-data.sql"/>
</jdbc:embedded-database>
</beans><beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
怎么确定开启哪个Profile?
1. 代码实现
ctx.getEnvironment().setActiveProfiles("p1", "p2", ..)
2. spring.profile.active参数,设置在环境变量,或者jvm系统变量,或者web.xml的servlet context param
3. -Dspring.profile.active="p1,p2, .." 通过启动参数配置
注入配置文件
@PropertySource注解可以将指定的配置文件注入到Spring环境中
@Configuration
@PropertySource("classpath:application.properties")
@ComponentScan(basePackages = "com.springessentialsbook")
public class SpringJavaConfigurator {
...
@Autowired
@Lazy
private SystemSettings systemSettings;
@Autowired
private Environment env;
@Bean
public SystemSettings getSystemSettings() {
String dateFormat = env.getProperty("system.date-format");
String appDisplayName = env.getProperty("app.displayname");
return new SystemSettings(dateFormat, appDisplayName);
}
…
}
xml读取配置
通过property-placeholder读取配置信息,然后注入到bean中。
<context:property-placeholder location="classpath:datasource.properties"/>
<bean id="dataSource" destroy-method="close"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>