Bean基础配置

Bean在Application.xml配置文件中书写并实现注入,那么它就需要一些属性去进行声明

一个Bean标签有

  • id:唯一的,可以理解为这个Bean对象的名字

  • name:多个,用空格隔开,也可以理解为Bean对象的名字,同样可以用来映射关系,也就是所谓的起别名

  • class:就是类、包的全类名,也就是这个Bean对象实现的是哪个类的意思

  • scope:定义Bean的作用范围,默认是single,即单例对象,也就是说一次创建,对象都是唯一一个

    像表现层、业务层、数据层还有工具对象我们只需要创建一次就好了,提高复用性优化性能,但是对于一些频繁变动数据的用来封装实体的JavaBean域对象来说它们不适合交给容器进行管理,故可以设置scope为prototype多例对象

在Bean标签里边还可以添加子标签

  • property标签:配置当前Bean的属性——也就是Bean对象实现的是哪个接口,又是通过哪个实现类实现的
    • name:表示配置哪一个具体的属性——对应接口,也就是Service代码中声明的接口
    • ref:表示参照哪一个bean——对应实现类,也就是容器里一个Bean的name

Bean的实例化

方法一:构造方法【常用】

我们过去创建对象是通过new出来的,其实Spring也是一样。Spring底层就是通过构造方法来完成Bean的实例化的。

例如:我们只要在配置文件中的Bean对象对应全类名的实现类中定义一个空参构造方法即可,Spring默认就是调用对应的空参构造方法完成Bean对象的实例化。(事实上我们可以不写,因为只要我们不写构造方法,默认就会自动创建一个空参构造),如果手动创建了带参的构造方法却没有书写空参的构造方法那么,Spring就会抛出异常BeanCreationException

OrderDaoImpl.java

package com.itheima.dao.impl;

import com.itheima.dao.OrderDao;

public class OrderDaoImpl implements OrderDao {

    public void save() {
        System.out.println("order dao save ...");
    }
}

applicationContext.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="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
</beans>

方法二:静态工厂【了解】

除了通过new实现类来得到对象外此前我们也学过使用工厂对象来创建对象来进行一定程度的解耦,这在Spring之前也很是流行,所以其实Spring里边也有兼容工厂创建对象的写法,了解即可。

首先提供一个可以获取实现类对象的工厂对象,然后在Spring配置文件中配置加上factory-method指明工厂获取对象的方法即可

OrderDaoFactory.java

package com.itheima.factory;

import com.itheima.dao.OrderDao;
import com.itheima.dao.impl.OrderDaoImpl;
//静态工厂创建对象
public class OrderDaoFactory {
    public static OrderDao getOrderDao(){
        System.out.println("factory setup....");
        return new OrderDaoImpl();
    }
}

applicationContext.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="orderDao" class="com.itheima.factory.OrderDaoFactory" factory-method="getOrderDao"/>
</beans>

方法三:实例工厂

UserDaoFactory.java

package com.itheima.factory;

import com.itheima.dao.UserDao;
import com.itheima.dao.impl.UserDaoImpl;
//实例工厂创建对象
public class UserDaoFactory {
    public UserDao getUserDao(){
        return new UserDaoImpl();
    }
}

applicationContext.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-->
    <bean id="userFactory" class="com.itheima.factory.UserDaoFactory"/>
    <bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
</beans>

小结

实例工厂和静态工厂的区别就是获取对象的方法是否是静态的。如果是static静态方法那么就不需要创建对象,直接类名点的形式就可以调用方法。如果获取对象的方法是非静态的就需要手动创建对象,这个过程称为实例化对象所以称为实例工厂,而这些代码都被Spring所封装,体现在配置文件上如何配置,实例化工厂的配置显然就比静态工厂要麻烦,需要先创建一个Bean来表示工厂对象,再创建一个Bean调用这个工厂Bean的工厂方法达成实例化对象代码的封装

演变-FactoryBean【实用】

FactoryBean其实是实例工厂的进一步演化,Spring提供一个接口来创建类的工厂对象,通过这个特殊的类工厂对象,配置文件可以简化成和通过构造方法一样的配置的形式,也是很实用的一种Bean的实例化方式

UserDaoFactoryBean.java

package com.itheima.factory;

import com.itheima.dao.UserDao;
import com.itheima.dao.impl.UserDaoImpl;
import org.springframework.beans.factory.FactoryBean;
//FactoryBean创建对象
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
    //代替原始实例工厂中创建对象的方法
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }

    public Class<?> getObjectType() {
        return UserDao.class;
    }

}

需要注意的就是泛型的设置(最终返回的对象类型要一一对应),接口除了这两个必须重写的抽象方法外还有一个isSingleleton()的抽象方法可以被重写用来设置对象是否是单例对象(默认return true,单例对象)

applicationContext.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">
    <!--方式四:使用FactoryBean实例化bean-->
    <bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>

</beans>

Bean生命周期控制

生命周期过程

Bean是对象,从创建到销毁的过程就是其生命周期,同样的我们需要在其创建之初执行一些初始化操作和销毁之前执行销毁操作。

首先来了解这个过程都发生了什么,方便后面的理解

  • 初始化容器
    1. 创建对象(内存分配)
    2. 执行构造方法
    3. 执行属性注入(set操作)
    4. 执行bean初始化方法
  • 使用bean
    1. 执行业务操作
  • 关闭/销毁容器
    1. 执行bean销毁方法

配置方法控制

最原始的方法就是在配置文件中通过init-method和destroy-method两个属性进行配置,当然相应的全类名包下对应的类也得有相应的指定方法才行,本质就是调用自己指定好的方法来进行逻辑处理

<?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">
    <!--init-method:设置bean初始化生命周期回调函数-->
    <!--destroy-method:设置bean销毁生命周期回调函数,仅适用于单例对象-->
    <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>

    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
    </bean>

</beans>

接口方法控制【了解】

使用接口方法来实现对Bean的生命周期控制。其实就是在创建实现类的时候除了实现对应接口的同时还实现InitializingBean,和DisposableBean这两个Spring提供的接口,然后重写destroy()afterPropertiesSet()方法,分别对应初始化和销毁方法

BookServiceImpl.java

package com.itheima.service.impl;

import com.itheima.dao.BookDao;
import com.itheima.service.BookService;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
    private BookDao bookDao;

    public void setBookDao(BookDao bookDao) {
        System.out.println("set .....");
        this.bookDao = bookDao;
    }

    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }

    public void destroy() throws Exception {
        System.out.println("service destroy");
    }

    public void afterPropertiesSet() throws Exception {
        System.out.println("service init");
    }
}

afterPropertiesSet()方法见名知意,就是在设置属性后执行初始化,参照生命周期过程流程不难理解

Tip:关闭容器

如果不做处理,执行main方法后我们会发现初始化方法确实执行成功,但销毁方法却没有被执行!

这是因为执行方法结束了Java虚拟机关闭但“容器对象”未来得及关闭,所以我们需要使用代码来关闭“容器”,方式有两种:

  • close()——暴力关闭
  • registerShutdownHook()——注册钩子

首先ApplicationContext这个顶级接口是没有close方法,close方法是他的第二个子类ClassPathXmlApplicationContext才有的,所以创建ctx对象的时候注意对象类型的声明(多态思想)。

暴力就是手动关闭,要在方法最后的位置结束之前执行——不能随意位置,而相对的通过注册关闭钩子的方式是可以随意位置的,相当于服务监听给系统注册了个服务,告知要在虚拟机关闭之前关闭容器。参考代码如下:

AppForLifeCycle.java

package com.itheima;

import com.itheima.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AppForLifeCycle {
    public static void main( String[] args ) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();
        //注册关闭钩子函数,在虚拟机退出之前回调此函数,关闭容器
        //ctx.registerShutdownHook();
        //关闭容器
        ctx.close();
    }
}

实际开发中我们其实都不用写关闭容器,因为Web开发中不需要控制,容器的关闭伴随着Tomcat的开启和关闭

小结

  • 配置

    • init-method
    • destroy-method
  • 接口(了解)

    • InitializingBean
    • DisposableBean
  • 关闭容器

    • ConfigurableApplicationContext
      • close()
      • registerShutdownHook()