二、Spring与IOC
1. 控制反转IOC的概述
控制反转(IOC:Inversion of Control)是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。
IOC是一个概念,是一种思想,其实现方式多种多样。当前比较流行的实现方式有两种:依赖注入和依赖查找。依赖注入的方式应用更加广泛。
- 依赖注入【DI:Dependency Injection,DI】:是指在程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。
- 依赖查找【DL:Dependency Lookup,DL】:容器提供回调接口和上下文环境给组件,程序代码则需要提供具体的查找方式。比较典型的是依赖与JNDI服务接口(Java Naming and Directory Interface)的查找。
依赖注入是目前最优秀的解耦方式。依赖注入让Spring的bean之间以配置文件的方式组织在一起,而不是以硬编码的方式耦合在一起的。
总结:
IOC控制反转就是将对象的创建全给了Spring容器。
DI依赖注入的前提是有IOC的环境,Spring创建这个类的过程中,Spring将类依赖的属性设置进去。
2. Spring的工厂类
Bean组件主要解决:Bean的定义,Bean的创建以及对Bean的解析。
开发者关心Bean的创建,其他由Spring内部帮你完成。
(1)Bean的创建是典型的工厂模式,它的顶级接口是BeanFactory,
ApplicationContext工厂:
ApplicationContext用于加载Spring的配置文件,在程序中充当“容器”的角色,其实现类有两个,通过ctrl+t查看。
A:配置文件在类路径下:使用ClassPathXmlApplicationContext实现类进行加载。
B:配置文件在本地目录中或者在项目根路径下:使用FileSystemXmlApplicationContext实现类进行加载。
@Test
public void classPathXmlTest() {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
IStudentService studentService = (IStudentService) ac.getBean("studentService");
studentService.some();
}
@Test
public void FileSystemXmlTest() {
ApplicationContext ac = new FileSystemXmlApplicationContext("d:/applicationContext.xml");
IStudentService studentService = (IStudentService) ac.getBean("studentService");
studentService.some();
}
BeanFactory工厂:
BeanFactory接口对象也可作为Spring容器出现。BeanFactory接口是ApplicationContext接口的父类。
若要创建BeanFactory容器,需要使用其实现类XmlBeanFactory,该类可以加载Spring配置文件。而Spring配置文件以资源Resource的形式出现在XmlBeanFactory类的构造器参数中。Resource是一个接口,其具体有两个实现类:
A:ClassPathResource:指定类路径下的资源文件
B:FileSystemResource:指定项目根路径或者本地磁盘路径下的资源文件。
@Test
public void BeanFactoryTest() {
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
IStudentService studentService = (IStudentService) factory.getBean("studentService");
studentService.some();
}
BeanFactory和ApplicationContext的区别:
虽然这两个接口容器所要加载的Spring配置文件是同一个文件,但在代码中的这两个容器对象却不是同一个对象,即不是同一个容器:它们对于容器内对象的装配(创建)时机是不同的。
ApplicationContext容器中对象的装配时机:
ApplicationContext容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好。以后代码中若要使用到这些对象,只需从内存中直接获取即可,执行效率比较高,但占用内存。
ApplicationContext容器中对象的装配时机:
BeanFactory容器,对容器中对象的装配与加载采用延迟加载策略,即在第一次调用getBean()时,才真正装配该对象。
@Test
public void BeanFactoryTest() {
// 获取容器:此时容器中的对象还未进行装配
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
// 执行下面语句时,会对studentService对象进行装配
IStudentService studentService = (IStudentService) factory.getBean("studentService");
studentService.some();
}
3. Bean的装配
Bean的装配,即Bean对象的创建。容器根据代码要求创建Bean对象后再传递给代码的过程,称为Bean的装配。
装配的方式:默认形式、
- 默认装配方式
代码通过getBean()方法从容器中获取指定的Bean实例,容器首先会调用Bean类的无参构造器,利用反射,创建空值的实例对象。 - 动态工厂Bean
有些时候,项目中需要通过工厂类来创建Bean实例,而不能像前面例子中似的,直接由Spring容器来装配Bean实例。使用工厂模式创建Bean实例,就会使工厂类与要创建的Bean类耦合到一起。
(1)动态工厂Bean作为普通Bean使用
将动态工厂Bean作为普通Bean来使用是指,在配置文件中注册动态工厂Bean后,测试类直接通过getBean()获取到工厂对象,再由工厂对象调用其相应方法创建相应的目标对象。配置文件中无需注册目标对象的Bean。因为目标对象的创建不由Spring容器来管理。
但这样做的缺点是:不仅工厂类与目标类耦合到一起了,测试类与工厂类也耦合到一起了。
public class ServiceFactory{
public IUserService getUserService(){
return new UserServiceImpl();
}
}
<bean id="factory" class="com.zhy.service.ServiceFactory" />
@Test
public void factoryTest(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从Spring容器中获取factory
ServiceFactory factroy = ac.getBean("factory");
IUserService userService = factory.getUserService();
userService.doSome();
}
(2)使用Spring的动态工厂Bean
Spring对于使用动态工厂来创建的Bean,由专门的属性定义。
factory-bean指定相应的工厂Bean,由factory-method指定创建所用方法。此时配置文件中至少会有两个bean的定义:工厂类的bean,与工厂类所要创建的目标类Bean。而测试类中不再需要获取工厂Bean对象了,可以直接获取目标Bean对象,实现测试类与工厂类间的解耦。
<bean id="factory" class="com.zhy.service.ServiceFactory" />
<bean id="userService" factory-bean="factory" factory-method="getUserService" />
@Test
public void springFactoryTest(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserService userService = ac.getBean("userService");
userService.doSome();
}
(3)静态工厂Bean
使用工厂模式中的静态工厂来创建实例Bean。
此时需要注意:静态工厂无需工厂实例,所以不再需要定义静态工厂
而对于工厂所要创建的Bean,其实不是由自己的类创建的,所以无需指定自己的类。但其实是由工厂类创建的,所以需要指定所用工厂类,故class属性指定的是工厂类而非自己的类。当然,还需要通过factory-method属性指定工厂方法。
public class ServiceFactory{
public static IUserService getUserService(){
return new UserServiceImpl();
}
}
<bean id="userService" class="com.zhy.service.ServiceFactory" factory-method="getUserService" />
4. 容器中Bean的作用域
当通过Spring容器创建一个Bean实例时,不仅可以完成Bean的实例化,还可以通过scope属性,为Bean指定特定的作用域。Spring支持五种作用域。
(1)singleton:单例模式。即在整个Spring容器中,使用Singleton定义的Bean将是单例的,只有一个实例,默认为单例的。
(2)prototype:多例模式。即每次使用getBean()方法获取
(3)request:对于每次HTTP请求,都将会产生一个不同的Bean实例。
(4)session:对于每个不同的HTTP session,都将会产生一个不同的Bean实例。
(5)global-session:
注意:
1.对于scope的值为request、session、global-session,只有在Web应用是使用Spring时,该作用于才有效。
2.对于scope的值为singleton的单例模式,该bean是在容器被创建时就被装配好了。
3.对于scope的值为prototype的原型模式,Bean实例是在代码中使用该Bean实例时才进行装配的。
5. Bean的后处理器
Bean后处理器是一种特殊的Bean,容器中所有的Bean在初始化时,均会自动执行该类的两个方法。由于该Bean是由其他Bean自动调用执行,不是程序员手工调用,故此Bean无需id属性。
需要做的是,在Bean后处理器类方法中,只要对Bean类与Bean类中的方法进行判断,就可实现对指定的Bean的指定方法进行功能扩展与增强。方法返回地Bean对象,即是增强过的对象。
代码中需要自定义Bean后处理器类。该类是实现了接口BeanPostProcessor的类。该接口中包含两个方法,分别在目标Bean初始化完毕之前与之后执行。它们的返回值为:功能被扩展或增强后的Bean对象。
Bean初始化完毕有一个标志,一个方法将被执行。即当该方法被执行时,表示该Bean被初始化完毕。所以,Bean后处理器中两个方法的执行,是在这个方法之前之后执行。这个方法在后面将会讲到。
public Object postProcessBeforeInitialzation(Object bean,String beanId) throws BeansException
该方法会在目标Bean初始化完毕之前由容器自动调用。
public Object postProcessAfterInitialzation(Object bean,String beanId) throws BeansException
该方法会在目标Bean初始化完毕之后由容器自动调用。
它们的参数是:第一个参数是系统即将初始化的Bean实例,第二个参数是该Bean实例的id属性值。若Bean没有id就是name属性值。
举例:
程序中有一个业务接口IService,其中两个业务方法some()和other()。有两个Bean:StudentServiceImpl和TeacherServiceImpl(),均实现了IService接口。
要求:对StudentServiceImpl的some()方法进行增强,输出其开始执行时间与执行结束时间。
public interface IService{
void some();
void other();
}
public class StudentServiceImpl implements IService{
@Override
public void some(){
System.out.println(this.getClass().getSimpleName()+",执行some()方法");
}
@Override
public void other(){
System.out.println(this.getClass().getSimpleName()+",执行other()方法");
}
}
public class MyBeanPostProcessor implements BeanPostProcessor{
@Override
public Object postProcessBeforeInitialization(Object bean,String beanName){
System.out.println("执行postProcessBefreInitialization()");
// 即使不对bean进行增强,也要使用方法返回bean,不能为默认的null,否则将抛出空指针异常。
return bean;
}
@Override
public Object postProcessAfterInitialization(final Object bean,String beanName){
if("studentService".equals(beanName)){
Proxu.newProxyInstance(bean.getClass().getClassLoader(),bean.getClass().getInterfaces(),new InvocationHandler(){
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
if("some".equals(method.getName())){
System.out.println("目标方法执行开始时间:"+System.currentTimeMillis());
// 执行目标方法
Object result = method.invoke(bean,args);
System.out.println("目标方法执行结束时间:"+System.currentTimeMillis());
return result;
}
return method.invoke(bean,args);
}
});
return proxy;
}
return bean;
}
}
<bean id="studentService" class="com.zhy.service.impl.StudentServiceImpl"/>
<bean id="teacherService" class="com.zhy.service.impl.TeacherServiceImpl"/>
<bean class="com.zhy.MyBeanPostProcessor"/>
@Test
public void factoryTest(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从Spring容器中获取factory
IService studentService= (IService)ac.getBean("studentService");
studentService.some();
studentService.other();
IService teacherService= (IService)ac.getBean("teacherService");
teacherService.some();
teacherService.other();
}
6. 定制Bean的生命始末
可以为Bean定制初始化后的生命行为,也可以为Bean定制销毁前的生命行为。
步骤:
- 这些方法需要在Bean类中实现定义好:方法名是随意的public void方法。
- 其次在配置文件的
标签中添加如下属性 - init-method:指定初始化方法的方法名
- destory-method:指定销毁方法的方法名
注意:如果希望看到Bean的destory-method的执行结果,需要满足两个条件:
(1)Bean为singleton,即单例的。
(2)要确保容器关闭,接口ApplicationContext没有close()方法,但其实现类有。所以,可以将ApplicationContext强转为其实现类对象,或者直接创建的就是实现类对象。
<bean id="userService" class="com.zhy.service.impl.UserServiceImpl"
init-method="setUp" destory-method="tearDown"/>
@Test
public void destoryMethodTest(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserService userService = (IUserService) ac.getBean("userService");
userService.doSome();
userService.doOther();
// 关闭容器对象
((ClassPathXmlApplicationContext)ac).close();
}
7. Bean的生命周期
Bean实例从创建到最后销毁,需要经过很多过程,执行很多生命周期方法。
- 调用无参构造器,创建实例对象。
- 调用参数的setter,为属性注入值。
- 若Bean实现了BeanNameAware接口,则会执行接口方法setBeanName(String beanId),使bean类可以获取其在容器中的id名称。
- 若Bean实现了BeanFactoryAware接口,则执行接口方法setBeanFactory(BeanFactory factory),使Bean类可以获取到BeanFactory对象。
- 若定义并注册了Bean后处理器BeanPostProcessor,则执行接口方法postProcessBeforeInitialization()。
- 若Bean实现了InitializingBean接口,则执行接口afterPropertiesSet()。该方法在Bean的所有属性的set方法执行完毕后执行,是Bean初始化结束的标志,即Bean实例化结束。
- 若设置了init-method方法,则执行。
- 若定义并注册了Bean后处理器BeanPostProcssor,则执行接口方法postProcessAfterInitialization()。
- 执行业务方法。
- 若Bean实现了DisposableBean接口,则执行接口方法destory()。
- 若设置了destory-method方法,则执行。