Spring MVC学习指南:Beans

前面提到,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-sessionportlet上下文的会话级别
applicationservlet 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>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值