Spring的Bean与IoC实现

Author Avatar
stormjie 8月 18, 2018
  • 在其它设备中阅读本文章

一、传统JavaBean与Spring的Bean的区别

JavaBean现在很少人使用,Spring的Bean可以说是JavaBean的发展,但已经完全不是一样了。

规范:Spring容器对Bean 没有特殊要求,不像JavaBean 一样遵循一些规范(为每个属性提供相应的setter 和 getter 方法),不过对于设值注入的Bean,一定要提供setter 方法。

作用:Spring中的Bean是Java 实例、Java组件,它的作用几乎无所不包,任何应用组件都被称为Bean,而传统的Java应用中的JavaBean通常作为DTO(数据传输对象),来封装值对象,在各层之间传递数据。

生命周期:传统的JavaBean作为值对象传递,不接受任何容器管理其生命周期,Spring中的Bean有Spring管理其生命周期行为。

二、Bean的实例化

在Spring中实例化Bean有三种方式,分别为构造器实例化、静态工厂方式实例化和实例工厂方式实例化,其中最常用的是构造器实例化,下面我就对构造器实例化做详解。

首先写一个普通的Bean,我们不写它的构造函数,使用它默认的无参构造函数

package com.spring.demo;

public class myBean {
    public void print(){
        System.out.println("myBean......");
    }
}

接着在相同目录下配置Bean的xml文件bean.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"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 关键部分 -->
    <bean id="bean1" class="com.spring.demo.myBean"></bean>

</beans>

<bean id = “bean1” class=”com.spring.demo.myBean”></bean>:

id为在xml文件里的这个bean的标识,class为xml文件里的这个bean绑定的Java类(bean)的全路径(包名+类名)。

这个标签会自动寻找myBean类中的无参数构造函数来创建对象。

最后我们测试一下,在相同目录下编写一个测试类

package com.spring.demo;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Text {

    @Test
    public void textUser() {

        //Spring容器ApplicationContext在加载配置文件时对Bean进行实例化
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

         //由配置文件返回对象
         myBean b = (myBean)context.getBean("bean1");
         System.out.println(b);
         b.print();
    }
}

好了,上面就是Bean实例化中最常用的构造器实例化的一个例子,关于Bean配置中常用的标签和属性,不熟的话可以看看这篇Spring中Bean的配置,还想深入了解其他两种实例化方式的话可以看看这篇Spring学习之实例化bean的三种方式

三、Bean的作用域

当通过Spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。Spring支持如下5种作用域:

  • singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例。

  • prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例。

  • request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效。

  • session:对于每次HTTP Session,使用session定义的Bean都将产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效。

  • globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效。

用法举例:<bean id="bean1" class="com.spring.demo" scope="singleton"/>

其中比较常用的是singleton和prototype两种作用域。对于singleton作用域的Bean,每次请求该Bean都将获得相同的实例。容器负责跟踪Bean实例的状态,负责维护Bean实例的生命周期行为;如果一个Bean被设置成prototype作用域,程序每次请求该id的Bean,Spring都会新建一个Bean实例,然后返回给程序。在这种情况下,Spring容器仅仅使用new 关键字创建Bean实例,一旦创建成功,容器不在跟踪实例,也不会维护Bean实例的状态。也就是说,初始化生命周期回调方法在所有作用域的Bean是都会调用的,但是销毁生命周期回调方法在prototype作用域的Bean是不会调用的。

如果不指定Bean的作用域,Spring默认使用singleton作用域。Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此,prototype作用域Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用。因此,除非必要,否则尽量避免将Bean被设置成prototype作用域。

这是基于线程安全性的考虑,如果使用有状态的Bean对象用singleton作用域,而无状态的Bean对象用prototype作用域。

四、Bean的生命周期

1.Spring对Bean进行实例化(相当于程序中的new Xx())

2.Spring将值和Bean的引用注入进Bean对应的属性中

3.如果Bean实现了BeanNameAware接口,Spring将Bean的ID传递给setBeanName()方法(实现BeanNameAware清主要是为了通过Bean的引用来获得Bean的ID,一般业务中是很少有用到Bean的ID的)

4.如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanDactory(BeanFactory bf)方法并把BeanFactory容器实例作为参数传入。(实现BeanFactoryAware 主要目的是为了获取Spring容器,如Bean通过Spring容器发布事件等)

5.如果Bean实现了ApplicationContextAwaer接口,Spring容器将调用setApplicationContext(ApplicationContext ctx)方法,把y应用上下文作为参数传入
(作用与BeanFactory类似都是为了获取Spring容器,不同的是Spring容器在调用setApplicationContext方法时会把它自己作为setApplicationContext 的参数传入,而Spring容器在调用setBeanDactory前需要程序员自己指定(注入)setBeanDactory里的参数BeanFactory )

6.如果Bean实现了BeanPostProcess接口,Spring将调用它们的postProcessBeforeInitialization(预初始化)方法(作用是在Bean实例创建成功后对进行增强处理,如对Bean进行修改,增加某个功能)

7.如果Bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet方法,作用与在配置文件中对Bean使用init-method声明初始化的作用一样,都是在Bean的全部属性设置成功后执行的初始化方法。

8.如果Bean实现了BeanPostProcess接口,Spring将调用它们的postProcessAfterInitialization(后初始化)方法(作用与6的一样,只不过6是在Bean初始化前执行的,而这个是在Bean初始化后执行的,时机不同 )

9.经过以上的工作后,Bean如果定义作用范围为prototype,则该Bean交给调用者,调用者管理Bean的生命周期,Spring不再管理该Bean,否则Bean交给Spring IoC的缓冲池,将触发Spring对该Bean的生命周期管理。

10.如果Bean实现了DispostbleBean接口,Spring将调用它的destory方法,作用与在配置文件中对Bean使用destory-method属性的作用一样,都是在Bean实例销毁前执行的方法。

五、IoC实现

IoC的实现即Bean依赖注入的方式,也就是Bean的装配方式。Spring容器支持多种形式的Bean的装配方式,我想讲三种主要的装配方式。

1.基于XML的装配

Spring提供了两种基于XML的装配方式:设值注入和构造注入。下面就讲解下如何在XML配置文件中使用这两种注入方式来实现基于XML的装配。

在Spring实例化Bean的过程中,Spring首先会调用Bean的默认构造方法来实例化Bean对象,然后通过反射的方式调用setter方法来注入属性值。因此,设值注入一个Bean必须满足以下两点要求。

  • Bean类必须提供一个默认的无参构造方法。

  • Bean类必须为需要注入的属性提供对应的setter方法。

使用设值注入时,在Spring配置文件中,需要使用<bean>元素的子元素<property>来为每个属性注入值;而使用构造注入时,在配置文件里,需要使用<bean>元素的子元素<constructor-arg>来定义构造方法的参数,可以使用其value属性(或子元素)来设置该参数的值。下面通过一个案例来演示基于XML方式的Bean的装配。

//接口就不写了,下同
package dao;

public class UserDaoImpl implements UserDao {

    private String username;
    private String password;

    /**
     *1.使用构造注入
     *提供带所有参数的有参构造方法
     */
    public UserDaoImpl(String username,String password) {
        super();
        this.username = username;
        this.password = password;
    }

    /**
     *2.使用设值注入
     *提供默认无参构造方法
     *为所有属性提供setter方法
     */
    public UserDaoImpl() {    
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public void save() {
        System.out.println("hello,User Dao...My name is "+username+" password is "+password);        
    } 
}

UserDaoImpl的对象作为UserServiceImpl成员变量

package service;

import dao.UserDao;

public class UserServiceImpl implements UserService {

    private UserDao userDao;

    /**
     *1.使用构造注入
     *提供带所有参数的有参构造方法
     */
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    /**
     *2.使用设值注入
     *提供默认无参构造方法
     *为所有属性提供setter方法
     */
    public UserServiceImpl() {    
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    //实现了UserService中的方法
    @Override
    public void addUser() {
        userDao.save();
        System.out.println("hello,User Service...");
    } 
}

bean.xml文件中使用set方式装配普通成员变量与对象成员变量

<!-- 将指定对象配置给spring,让spring创建实例 -->
<bean id="userDao" class="dao.UserDaoImpl"> 
    <!-- 通过设值注入装配Bean --> 
    <property name="username" value="zhangsan"></property>
    <property name="password" value="123456"></property> 
    <!-- 通过构造注入装配Bean --> 
    <constructor-arg index="0" value="jack"></constructor-arg> 
    <constructor-arg index="1" value="1234"></constructor-arg>
</bean> 
<bean id="userService" class="service.UserServiceImpl"> 
    <!-- 设值注入方式 将userDao实例注入到userService实例中(使用setter方法) 与userDao实例装配Bean方式一样不过这里成员变量是对象 value改为ref -->
    <property name="userDao" ref="userDao"></property>
    <!-- 使用构造方法注入 -->
    <constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>

测试方法

@Test 
public void testUserDao() { 
    String xmlPath = "beans.xml"; 
    //初始化spring容器,加载配置文件 
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath); 
    //通过容器获取userDao实例 
    UserDao userDao = (UserDao)applicationContext.getBean("userDao"); 
    userDao.save(); 
}
@Test 
public void testService() { 
    String xmlPath="beans.xml"; 
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath); 
    UserService userService = (UserServiceImpl)applicationContext.getBean("userService");                     userService.addUser(); 
}
2.基于注解的装配

为了防止有过多的Bean导致配置文件繁琐,可使用注解的功能进行Bean的装配,提供3个@Component注解衍生注解(功能一样)
@Repository :Dao层 (数据访问层)
@Service:Service层 (业务层)
@Controller:Constroller层 (控制层)
这三个注解和@Component一样,但能使标注类的本身用途更加清晰。

除了上面四个类注解,Spring还提供了@Value取代xml中的普通字段值,提供@Resource取代xml中的字段引用值(实现相同功能的注解还有@Autowired和@Autowired与@Qualifier的配合使用)。

上面的例子使用注解方式配置如下:

xml配置 context:component-scan标签

<?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:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans.xsd
             http://www.springframework.org/schema/context
             http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 使用注解的方式装配Bean -->
    <!-- 通知Spring扫描指定包下的所有Bean类,进行注解解析 -->
    <context:component-scan base-package="annotation"/>
</beans>

UserDaoimpl(数据访问层)类中使用@Repository注解

@Repository("userDao")
public class UserDaoImpl implements UserDao {

    @Override
    public void save() {
        // TODO Auto-generated method stub
        System.out.println("userDao...save...");
    }
}

UserServiceImpl(业务逻辑层)使用@Service注解,有引用字段userDao

@Service("userService")
public class UserServiceImpl implements UserService{
    @Resource(name="userDao")
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    @Override
    public void save() {
        // TODO Auto-generated method stub
        userDao.save();
        System.out.println("userService...save...");
    }
}

UserAction(业务流程控制层)使用@Controller注解,有引用字段userService

@Controller("userAction")
public class UserAction {
    @Resource(name="userService")
    private UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void save() {
        userService.save();
        System.out.println("userAction...save...");
    }
}

测试方法

    @Test
    public void testAnnotation() {
        //文件配置路径
        String xmlPath="annotation/beans2.xml";
        //加载配置文件,获得应用上下文实例
        ApplicationContext context = 
                new ClassPathXmlApplicationContext(xmlPath);
        UserAction userAction = (UserAction)context.getBean("userAction");

        System.out.println(userAction);
        userAction.save();
    }
3.自动装配

需配置bean元素的autowire属性,该属性有五个值:byName、byType、constructor、autodetect、no 。

可将上例中beans.xml配置文件改成如下(类不需使用注解):

    <!-- 使用自动装配方式,类中不需使用注解,装配引用字段直接通过id自动装配 -->
    <bean id="userDao" class="annotation.UserDaoImpl"></bean>
    <bean id="userService" class="annotation.UserServiceImpl" autowire="byName"></bean>
    <bean id="userAction" class="annotation.UserAction" autowire="byName"></bean>

输出结果与使用注解一致。

这种方式不是很常用,感兴趣的自行了解Spring的自动装配Bean的三种方式spring实战二之Bean的自动装配(非注解方式)

好了,这就是今天我要说的全部内容,主要对Spring中Bean和依赖注入的实现进行了详细讲解,内容是经过我自己理解及网上资料的总结,如果有什么疑惑或者错误欢迎联系我。

参考资料:《JavaEE企业级应用开发教程》